rccoder / blog

😛 个人博客 🤐 订阅是 watch 是 watch 是 watch 是 watch
585 stars 36 forks source link

React 中的新旧 Context 简单对比 #30

Open rccoder opened 6 years ago

rccoder commented 6 years ago

妈妈,我再也不会滥用 Redux 了

前言

context 是各种前后端框架中经常会用到的一个概念,著名 Node 框架 Koa 更是把 context 玩的尽兴。React 在很早之前就有 context 的概念,虽然是一个实验性质的 API,但 react-redux、react -router 等框架类库却把它玩了个够。

React 中爷爷和孙子,甚至是重孙之间传递值或者事件一直是个比较麻烦的事情,随着 Redux 等状态管理类库的出现,大家纷纷开始用这种框架去解决这种隔代传信息的问题,并且在或大或小的项目中都开始使用。

一回喜,二回忧,在前端视资源体积为金子的情况下 “滥用 Redux” 的情况越来越多。

React 新版本中的 context 终于要转正了,并且经过了重新的思考与沉淀,与之前 context 在设计哲学上不一样,但解决的却是同一个问题。这样,或许数据流真的不是很多的项目或许可以真的摆脱一下 Redux 等去试试自带的 Context 了。

本文将介绍新老 Context 的一些用法和自己的一些思考。

子孙传值问题

首先看一下最简单的 爷爷给孙子 传值的问题:

class Children extends React.Component {
  render() {
    return (
      <div>{ this.props.text }</div>
      )
    }
}

class Parent extends React.Component {
  render() {
    return (
      <Children text="this.props.text"/>
     )
  }
}

class GrandParent extends React.Component {
  render() {
    return (
      <Parent text="Hi, my baby"/>
    )
  }
}

这几行代码中都是手动的让 text 一辈一辈 的往下传,如果是传给重孙的话还需要继续手动往下传。

利用 Context 就能让这种信息自动的传递,不再让 中间辈 去担任消息传递人,做一些没太大意义的事。

老 Context

const PropTypes = require('prop-types');

class Children extends React.Component {
  static contextTypes = {
    text: PropTypes.string
  }
  render() {
    return (
      <div>{ this.context.text }</div>
    )
  }
}

class Parent extends React.Component {
  render() {
    return (
      <Children/>
    )
  }
}

class GrandParent extends React.Component {
  static childContextTypes = {
    text: PropTypes.string,
  }
  getChildContext() {
    return {
      text: 'Hi, my baby'
    }
  }
  render() {
    return (
      <Parent text="Hi, my baby" /> 
    )
  }
}

GrandParent 上通过 getChildContext 给 context 对象添加了 text 的属性,这个属性可以在 GrandParent 的任何一个子孙(子组件)中访问。

同时,为了方便在各种生命周期中使用 context,部分生命周期都给 context 留了接口,具体可以参考 Referencing Context in Lifecycle Methods

新 Context

老式的 context 和依靠中间辈一层层去传递数据相比确实是酷酷的,但总感觉不 React。或许这也是为什么之前一直不建议使用的原因吧。

同时还有一个比较🤢的 context 更新问题,更多可以参考 How to safely use React context

在 React 16.3 中 新版本的 Context 出炉了,感兴趣的可以参考 rfc提案

新版 context 之下上面的代码这样写:

const AppContext = React.createContext();

class Children extends React.Component {
  render() {
    return (
      <AppContext.Consumer>
        {
          context => {
            return (
              <div>{context.text}</div>
            )
          }
        }
       </AppContext.Consumer>
     )
   }
}

class Parent extends React.Component {
  render() {
    return (
      <Children />
    )
  }
}

class GrandParent extends React.Component {
  render() {
    return (
      <AppContext.Provider
        value={{
          text: 'Hi, my baby'
        }}
       >
         <Parent />
       </AppContext.Provider>
     )
   }
}

新版 Context 用 createContext 去初始化一个 context,返回的对象中有 providerconsumer 方法。provider 是以外层容器的方式去包裹住 context 要作用的最外层组件(使用过 Redux 的同学对这点应该有种似曾相似的感觉),然后需要使用到 context 的时候需要用 consumer 去包裹一下。需要注意的是 consumer 包裹的里面的写法,不是普通的组件。

虽然 context 可以是一个 Object,但还是避免不了业务逻辑中会出现多个 context 的问题,consumerprovider 一一对应的模式会造成花式嵌套地狱,可以使用伟大社区产生的 react-context-composer 对 context 进行 composer,源码也非常简单易懂。

Context 会让 Redux 消失吗?

不会,解决的终极问题不完全一样!

Redux 解决的是大型软件架构中数据流传输的问题;context 解决的是子孙之间方便数据交互的问题。有一定的相似性,但不属于同等性质。

延伸阅读