React组件设计模式-Provider-Consumer

React组件设计模式-组合组件

React组件设计模式-Render-props

我们都知道,基于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之内的数据,消费者获取提供者为它提供的数据,自然就解决了上边的问题。

用法

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

image

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

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

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

├─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出现后,更说明了这一点。

欢迎关注我的公众号: 一口一个前端,不定期分享我所理解的前端知识

image
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容