React@16.x(13)context 上下文

1,介绍

React 中的 context 类似与 Vue 中的 project/inject,祖先提供数据,可以在后代组件中共享。

特点

  1. 当某个组件创建了上下文,则上下文中的所有数据,可以被后代组件共享。
  2. 如果某个组件依赖了上下文,会导致该组件不再纯粹(也就是说,外部数据不止来源于 props)。
  3. 使用场景,一般是在第三方组件中。

2,创建和使用

2.1,创建

上下文数据是一个独立于组件的对象,创建:

const ctx = React.createContext()

返回值主要有2个重要的属性(使用了一种开发模式:生产消费模式):

  1. Provider 生产者。它是一个组件,该组件会创建上下文,并且有一个 value 属性,通过该属性来传递数据。
import React, { Component } from "react";

const ctx = React.createContext();

export default class App extends Component {
    state = {
        a: 0,
        b: "abc",
        changeA: (newA) => {
            this.setState({
                a: newA,
            });
        },
    };

    render() {
        return (
            <ctx.Provider value={this.state}>
                <ChildA></ChildA>
            </ctx.Provider>
        );
    }
}
  1. Consumer 使用者,用于在后代组件中使用上下文。

2.2,后代组件中使用

2.2.1,函数组件

只能使用上下文对象的 Consumer 属性来获取上下文数据。

function ChildB(props) {
    return (
        <ctx.Consumer>
            {(value) => (
                <div>
                    <div>组件B</div>
                    <div>
                        a:{value.a},b:{value.b}
                    </div>
                    <button
                        onClick={() => {
                            value.changeA(value.a + 1);
                        }}
                    >
                        在后代组件中更改上下文的数据,a+1
                    </button>
                </div>
            )}
        </ctx.Consumer>
    );
}

注意Consumer 是一个组件,它的子节点是一个函数,会将上下文数据通过函数的参数提供出来。该函数的返回值会进行渲染。

也就是说,它的 props.children 需要传递一个函数。

2.2.2,类组件

有2种使用方式:

1,使用 Consumer 属性来使用上下文。

2,首先必须添加一个静态属性 contextType 来标记要使用的上下文。之后可通过 this.context.xxx 使用上下文。

class ChildA extends Component {
    static contextType = ctx;
    render() {
        return (
            <h1>
                <div>组件A</div>
                <div>
                    a:{this.context.a},b:{this.context.b}
                </div>
                <button
                    onClick={() => {
                        this.context.changeA(this.context.a + 1);
                    }}
                >
                    在后代组件中更改上下文的数据,a+1
                </button>
            </h1>
        );
    }
}

3,注意点

3.1,在后代中更改上下文

上面的示例代码中,已经提到了,不多赘述。

3.2,Provider 禁止多组件使用

这个多组件,指的同级组件。比如,如果 A和B是同级,并且都有后代组件,那最好不要共用一个上下文数据。

换句话说:下面2个是一一对应的,某个组件通过 Provider 提供的上下文 ctx,只有它的后代组件才能使用。

const ctx = React.createContext();

<ctx.Provider value={}>

这种场景有2种解决方式:

1,要么将该上下文数据提升到A和B的父级。

2,要么重新创建一个上下文,让A和B互不影响。

3.3,后代组件始终重新渲染

如果上下文的提供者 Context.Providervalue 属性发生变化,会导致该上下文提供的所有后代元素全部重新渲染,子组件会强制执行 render

之前在生命周期中提到,只有当 shouldComponentUpdate() 返回 true 时才会执行 render
但在上下文中的这种情况,shouldComponentUpdate 不会执行,直接跳过它来执行 render,这种情况叫强制渲染

来看下面的例子:

export default class App extends Component {
    state = {
        a: 0,
        b: "abc",
        changeA: (newA) => {
            this.setState({
                a: newA,
            });
        },
    };

    render() {
        return (
            <ctx.Provider value={this.state}>
                <ChildA></ChildA>
                <ChildB></ChildB>
                <button
                    onClick={() => {
                        this.setState({});
                    }}
                >
                    修改 state
                </button>
            </ctx.Provider>
        );
    }
}

ChildAChildAB 的代码上文有,这里补充一些,其他代码省略了。

class ChildA extends Component {
    shouldComponentUpdate(nextProps, nextState) {
        console.log("运行了优化");
        return false;
    }
    render() {
        console.log('renderA');
        return ({/* ... */});
    }
}
function ChildB(props) {
    console.log("B函数再次执行");
    return (
        <ctx.Consumer>
            {/* ... */}
        </ctx.Consumer>
    );
}

不管是祖先组件中执行 this.setState({}),还是在后代组件中调用 changeA 方法(间接执行祖先组件的 this.setState({})),都会导致Context.Providervalue 属性发生变化。

可以看到不管点击祖先组件中的按钮,还是后代组件中的按钮,每次点击,3个 console.log 只有 shouldComponentUpdate 中的没有执行。

另外,看到祖先组件中写的是 this.setState({}),传递的是空对象,但也会触发状态更新。
因为新旧 state是通过 Object.is() 来比较的,每次会用新的 state 对象覆盖旧的 state 对象。所以 Context.Providervalue 属性还是发生了变化。

但是在开发中,类组件有时还是需要通过 shouldComponentUpdate 来控制是否执行 render

解决 shouldComponentUpdate 不执行问题

知道 this.setState({}) 的更新原理之后,可以将要传递的上下文对象包装一层即可。

这样即便 state 被替换,原来的对象属性还是在内存中没有发生变化。

export default class App extends Component {
    state = {
        ctx: {
            a: 0,
            b: "abc",
            changeA: (newA) => {
                this.setState({
                    a: newA,
                });
            },
        },
    };

    render() {
        return (
            <ctx.Provider value={this.state.ctx}>
                {/* 其他代码不变 */}
            </ctx.Provider>
        );
    }
}

以上。

  • 19
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

下雪天的夏风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值