React16系列:船新的context

665 阅读4分钟

一个新的api出现肯定是为了解决某一个或某一类问题。

旧版context的问题

众所周知 react 推荐的一个重要思想就是单向数据流,整个 react 应用要遵循数据从上到下的方向传递。这就造成了在多层组件嵌套的情况下,父组件的数据要传给最底层的组件的话就要经过多层中间组件的 props,造成了大量冗余的 props 声明(称为 props drilling 问题)。

新旧 context 都是为了解决 props drilling 问题。但是在react 16.3之前的旧版 context 却破坏了 react 的分形架构思想。

所谓分形就是指从 react 应用的组件树中抽取任意一部分都是可以直接运行的子组件树。

但如果使用了旧版 context 语法,这个思想就被破坏了:

enter code here
class Button extends React.Component {
    render() {
        return (
            <button style={{color: this.context.color}}></button>
        )
    }
}
Button.contextType = {
    color: PropTypes.string
}

class Message extends React.Component{
    getChildContext() {
        return {color: 'red'}
    }

    render() {
        return (
            <Button />
        )
    }
}

Message.childContextTypes = {
    color: PropsTypes.string
}

上述代码中,父组件的getChildContext和子组件的this.context都非常 magic,也违背了分形的思想。

旧版context的致命缺陷

旧版 context 除了上述违背了分形思想的问题,还存在一个影响功能的致命缺陷。

这个缺陷就是当 context 传递过程中某个组件在shouldComponentUpdate中返回 false 时,下面的组件将无法触发 rerender,从而导致新的 context 值无法更新到下面的组件。

例如

<A>
    <B>
        <C>
        </C>
    </B>
</A>

这种结构下中间的B组件一旦在 shouldComponentUpdate 返回 false,将导致C组件更新失败。

新版context

一个新的 api 出现肯定是为了解决某一个或某一类问题。

基于这一思想,我们就很容易理解为什么 react 16要推出新的 context api 来解决上述旧版 context 的问题和缺陷。

新的 context 提供了createContext方法来创建 context 对象,这个对象有 provider 生产者和 consumer 消费者两个属性。

用法三步走: 1.创建一个 context

const ColorContext = React.createContext('red'); // 默认red

2.父组件使用 provider

class Message extends React.Component {
    state = {
        color: 'green'
    }
    
    render() {
        return (
            <ColorContext.Provider value={this.state.color}>
                <Button />
            </ColorContext.Provider>
        )
    }
}

3.子组件使用 consumer

class Button extends React.Component {
    render() {
        return (
            <ColorContext.Consumer>
                {
                    value => 
                    (<span style={{color: value}}></span>)
                }
            </ColorContext.Consumer>
        )
    }
}

新 api 声明式地表达了组件使用了哪个 context 对象的 Provider 或者 consumer,从而符合了 react 的分形思想。

另外新 api 不再依赖shouldComponentUpdate方法,Provider 通过 Object.is 来判断 props 是否发生变化,如果发生变化就把新值通知对应的 consumer 组件。

这样旧版 api 的问题和缺陷就都解决了。

context与redux

redux 作为一个重量级的解决组件间通信的方案,可以说在 react 的数据管理生态中有着一统江湖的地位。

而在新版 context 出现后,社区开始出现是否要使用新版 context 替代 redux 的疑问。

在回答这个疑问前,我们可以先简单了解下 redux 和 react-redux 原理。

react与redux

终端应用的复杂性来源于大量无规律的交互动作和异步动作。而这些动作无时无刻地在改变应用的状态。

redux 的设计初衷就是为了让开发者清晰地掌握状态变更,并且可以复现出来。

redux 只负责很好地管理 store 这个数据仓库,但怎么把 store 里的数据传递给组件使用呢?这时候就需要用到 react-redux 了。

这里不详细介绍 redux 和 react-redux 的语法,我们知道使用 react-redux 有两个核心步骤:

  1. 使用 provider 包裹根组件
  2. 使用 connect 连接子组件和 store

此时我们会发现有个熟悉的名词 provider 出现了。

没错,它就是上文提到的 context.provider。react-redux 正是使用了 react 的 context 语法,将 store 作为 context 值传递下去。

然后在 connect 方法中从 context 中取出 store 的值并订阅 store 的变化,最后将该值通过 props 传给业务组件。

繁琐与规范

简单了解了 redux 和 react-redux 的原理后,我们可以发现 redux 这一套相当于 context +数据流管理。

也就是说 context 是一个更加轻量版的数据管理方案。如果你的应用本身并不复杂,只需要有个地方来存放全局数据,那么 context 可能就已经满足了要求。

但是应用复杂度提升后,复杂的 context 管理起来的难度也会直线提升。这时候使用 redux 约定的设计哲学和 api 可以帮助多人开发的复杂应用保持足够清晰和规范。

总结来说,redux 的模板代码繁琐对应了 context 的简洁 api,前者的规范也对应了 context 的灵活散乱。

技术没有银弹,只有根据具体的项目来进行技术选型才是最合适的。

附录

附上一份社区整理的redux最佳实践,方便大家更加了解redux的设计哲学:

  • 区分smart component(处理state变更)和dump component(只使用state,不关心变更)
  • component中不要出现async call,交给action creator。
  • reducer逻辑尽量简单只复制更新数据,复杂业务逻辑交给action creator。
  • reducer必须返回新的state对象。
  • action creator和reducer使用纯函数
  • 尽量让smart component来和store connect
  • 不要使用redux,除非应用的数据管理足够复杂混乱。