React组件设计模式-Provider-Consumer

时间: 2019-06-08阅读: 172标签: 组件

我们都知道,基于props做组件的跨层级数据传递是非常困难并且麻烦的,中间层组件要为了传递数据添加一些无用的props。
而React自身早已提供了context API来解决这种问题,但是16.3.0之前官方都建议不要使用,认为会迟早会被废弃掉。说归说,很多库已经采用了context API。可见呼声由多么强烈。终于在16.3.0之后的版本,React正式提供了稳定的context API,本文中的示例基于v16.3.0之后的context API。


概念

首先要理解上下文(context)的作用以及提供者和消费者分别是什么,同时要思考这种模式解决的是什么问题(跨层级组件通信)。context做的事情就是创建一个上下文对象,并且对外暴露提供者(通常在组件树中上层的位置)和消费者,在上下文之内的所有子组件,都可以访问这个上下文环境之内的数据,并且不用通过props。可以理解为有一个集中管理state的对象,并限定了这个对象可访问的范围,在范围之内的子组件都能获取到它内部的值。提供者为消费者提供context之内的数据,消费者获取提供者为它提供的数据,自然就解决了上边的问题。


用法

这里要用到一个小例子,功能就是主题颜色的切换。效果如图:
图片描述

根据上边的概念和功能,分解一下要实现的步骤:

  • 创建一个上下文,来提供给我们提供者和消费者
  • 提供者提供数据
  • 消费者获取数据

这里的文件组织是这样的:

├─context.js    // 存放context的文件
│─index.js      // 根组件,Provider所在的层级
│─Page.js       // 为了体现跨层级通信的添加的一个中间层级组件,子组件为Title和Paragraph
│─Title.js      // 消费者所在的层级
│─Paragraph.js  // 消费者所在的层级


创建一个上下文

import React from 'react'

const ThemeContext = React.createContext()

export const ThemeProvider = ThemeContext.Provider
export const ThemeConsumer = ThemeContext.Consumer

这里,ThemeContext就是一个被创建出来的上下文,它内部包含了两个属性,看名字就可以知道,一个是提供者一个是消费者。Provider和Consumer是成对出现的,每一个Provider都会对应一个Consumer。而每一对都是由React.createContext()创建出来的。


page组件

没啥好说的,就是一个容器组件而已

const Page = () => <>
  <Title/>
  <Paragraph/>
</>


提供者提供数据

提供者一般位于比较上边的层级,ThemeProvider 接受的value就是它要提供的上下文对象。

// index.js
import { ThemeProvider } from './context'

render() {
  const { theme } = this.state
  return <ThemeProvider value={{ themeColor: theme }}>
    <Page/>
  </ThemeProvider>
}


消费者获取数据

在这里,消费者使用了renderProps模式,Consumer会将上下文的数据作为参数传入renderProps渲染的函数之内,所以这个函数内才可以访问上下文的数据。

// Title.js 和 Paragraph的功能是一样的,代码也差不多,所以单放了Title.js
import React from 'react'
import { ThemeConsumer } from './context'
class Title extends React.Component {
  render() {
    return <ThemeConsumer>
      {
        theme => <h1 style={{ color: theme.themeColor }}>
          title
        </h1>
      }
    </ThemeConsumer>
  }
}


关于嵌套上下文

此刻你可能会产生疑问,就是应用之内不可能只会有一个context。那多个context如果发生嵌套了怎么办?


v16.3.0之前的版本

其实v16.3.0之前版本的React的context的设计上考虑到了这种场景。只不过实现上麻烦点。来看一下具体用法:
和当前版本的用法不同的是,Provider和Consumer不是成对被创建的。Provider是一个普通的组件,当然,是需要位于Consumer组件的上层。要创建它,我们需要用到两个方法:

  • getChildContext: 提供自身范围上下文的数据
  • childContextTypes:声明自身范围的上下文的结构
class ThemeProvider extends React.Component {
  getChildContext() {
    return {
      theme: this.props.value
    };
  }
  render() {
    return (
      <React.Fragment>
        {this.props.children}
      </React.Fragment>
    );
  }
}
ThemeProvider.childContextTypes = {
  theme: PropTypes.object
};

再看消费者,需要用到contextTypes,来声明接收的上下文的结构。

const Title = (props, context) => {
  const {textColor} = context.theme;
  return (
    <p style={{color: color}}>
      我是标题
    </p>
  );
};

Title.contextTypes = {
  theme: PropTypes.object
};

最后的用法:

<ThemeProvider value={{color: 'green' }} >
   <Title />
</ThemeProvider>

回到嵌套的问题上,大家看出如何解决的了吗?

Provider做了两件事,提供context数据,然后。又声明了这个context范围的数据结构。而Consumer呢,通过contextTypes定义接收到的context数据结构。
也就相当于Consumer指定了要接收哪种结构的数据,而这种结构的数据又是由某个Provider提前定义好的。通过这种方式,再多的嵌套也不怕,Consumer只要定义接收谁声明的context的结构就好了。如果不定义的话,是接收不到context的数据的。


v16.3.0之后的版本

v16.3.0之后的版本使用起来比以前简单了很多。解决嵌套问题的方式也更优雅。由于Provider和Consumer是成对地被创建出来的。即使这一对的Provider于另一对的Consumer的数据结构和值的类型相同,这个Consumer也让能访问那个Provider的上下文。这便是解决方法。


总结

对于这个context这个东西。我感觉还是不要在应用里大量使用。就像React-Redux的Provider,或者antd的LocalProvider,差不多用一次就够,因为用多会使应用里很混乱,组件之间的依赖关系变得复杂。但是React为我们提供的这个api还是可以看到它自身还是想弥补其状态管理的短板的,况且Hooks中的useReducer出现后,更说明了这一点。


链接: http://www.fly63.com/article/detial/3583

React Hooks 原理

我们大部分 React 类组件可以保存状态,而函数组件不能? 并且类组件具有生命周期,而函数组件却不能?React 早期版本,类组件可以通过继承PureComponent来优化一些不必要的渲染,相对于函数组件

Vue父子组件传值(复杂数据类型的值)

vue 父子组件传值是很常见的,多数情况下都是父传递给子的值是基础数据类型,如string,number,boolean,当父组件值被修改时,子组件能够实时的作出改变。如果父子传值的类型是复杂数据类型(object,array)这种时

使用Typescript装饰器来劫持React组件

装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上。 装饰器使用 @expression这种形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。

深入理解React中的setState

组件的状态是一种保存、处理和使用给定组件内部信息的方法,并允许你实现其自身的逻辑。状态本身其实是JavaScript中一个简单的对象(Plain Old Java[Script] Object),并且改变它是使组件重新进行渲染的几种方法之一

Vue子父组件方法互调

讲干货,不啰嗦,大家在做vue开发过程中经常遇到父组件需要调用子组件方法或者子组件需要调用父组件的方法的情况,现做一下总结,希望对大家有所帮助。设置子组件的ref

react totast组件开发

最近在主导有jquery类型转react技术栈-- 项目库中引入antd-mobile,但是由于totast组件不能满足要求...自己前端封装一个。分什么技术栈,全是垃圾,真正掌握基础

小程序自定义actionSheet组件

在微信小程序中,针对操作菜单的需求,官方文档提供了相应的api,如下图所示。但是小程序自带的操作菜单没有 “标题” 这么一个参数,不能在操作菜单的顶部显示自定义的标题

记录一次优雅的管理Vue全局组件自动注册和插件自动引入

随着项目的不断变大,可能有些人会把插件的引入和全局组件的注册都放到main.js中,导致后面main.js里面一大坨引入代码,看起来杂乱无比,也不利于后期进行维护,所以我们尽可能的让main.js看起来整洁些

使用vue.js的动态组件模板

组件并不总是具有相同的结构。有时需要管理许多不同的状态(注:指的是根据不同状态显示不同组件)。异步加载组件对此操作很有帮助。在 Scrumpy 中组件用于提醒、评论和附件等多个地方。

vue实现一个全局Message组件

不知道大家在用一些UI框架,比如Element-ui的时候,有没有觉得一些全局组件。this.$message(),this.Toast()等,用起来很爽。他们不像其他的组件,需要去导入,去注册。麻烦的很。

fly63.com版权所有,内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权,请与小编联系!情况属实本人将予以删除!

广告赞助文章投稿关于web前端网站点搜索站长推荐网站地图站长QQ:522607023

小程序专栏: 土味情话心理测试脑筋急转弯幽默笑话段子句子语录成语大全