[ 一起学React系列 -- 4 ] 透传的Context

news/2024/6/16 21:41:36 标签: javascript, ui

抛转引玉

通过上一篇的科普我们知道如果父节点需要向子节点传递数据,那么就得通过Props来实现;那么摆在我们眼前的就有一个问题了:现有N个节点并且它们都是嵌套成父子结构,大致如下

<A>
    <B>
        <C>
            <D>
                ......
                <Last></Last>
            </D>
        </C>
    </B>
</A>

如过last组件需要A组件的某个数据,按照之前的说法我们可以使用Props;但是我觉得一般人都不会这么做,为什么?一个数据在N个组件中通过Props传递,首先写法上会很荣誉、其次就是很可能在某个节点写错了造成最终拿到的数据不是想要的数据,这些都是我们需要考虑的问题。当然有人会想到使用Redux或者Mobx这种第三方库来解决,没毛病;但如果只是一个小小的需求就引入了一个库,是不是杀鸡用了牛刀?在这个问题上React本身有自己的解决方案:Context

Context是什么?

目前React的Context API已经出了两版,在React16.3.0版本之前和之后。实际上我们开发React项目时候很少会用到这个API(至少小编身边是这种情况);而且对于第一版的Context就连官方也不建议用,首先是不好用其次是问题多,不过即使如此不堪的技术却是Redux的基础技术,真的是厉害了!
后来在React16.3.0版本更新之后,全新的Context API与我们见面,可以说是脱胎换骨。官方对Context的介绍是:

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

意思就是Context提供了一种通过组件树传递数据的方法,而无需在每个级别手动传递props。可以看出这个技术刚好可以用来解决我们前面提出的问题。

Context可以做什么?

事实上官方设计这个API的目的是共享可被视为React组件树的“全局”数据,例如当前经过身份验证的用户,主题或首选语言。意图言简意赅,可以理解成为React组件树(从Root节点开始通过不断组合各个组件形成最终的树形结构)中注入了一个上下文对象同时将一些全局通用的数据放在这个对象中,这样我们就可以在这个组件树的任何地方使用这些数据。

如何使用Context?

针对新版Context,官方给我们提供了三个API:

  1. React.createContext
  2. Provider
  3. Consumer

通过字面意思大家应该就能猜到它们分别的作用了吧!

React.createContext: 用来创建Context对象
Provider: 用来向组件树发出Context对象
Consumer: 使用Context对象

不过呢,后两者其实是React.createContext创建出来的对象的组成,用一段代码来解释吧:

const {Provider, Consumer} = React.createContext(defaultValue);

嗯...就酱紫!!!!
其实写到这里我相信用过Redux的朋友就已经开始觉得眼熟了,就是ProvidercreateContext。因为react-redux提供Provider, Redux提供createStore。这也是Redux基于Context API重要物证哈哈....

实例使用Context

学习技术最终是要有产出的。笔者也一步一步来实现一个简单例子,功能:通过点击按钮对屏幕中数字进行加1操作
首先我们需要创建两个js文件:

buildContext.js
import {createContext} from 'react';

const defaultData = {};
export const {Provider, Consumer} = createContext(defaultData);

这里可能有人会有疑问:为什么将创建Context单独抽离出来?
1) 将Context和组件隔离;因为它们不存在必要的联系,Context只是单纯的注入组件而已。
2) 因为Provider, Consumer需要配对使用(注意:Provider, Consumer配对使用的前提是它们都来自同一个createContext);我们可以在Provider下的任意节点使用Consumer,所以就可能存在Provider, Consumer不在同一个组件的情况,所以将将创建Context单独抽离出来使得处理Context更加优雅。

ContextDemo.js
import React, {Component} from 'react'
import {Provider, Consumer} from './buildContext';

class ContextDemo extends Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        };
    }

    addOne = () => {
        this.setState((preState) => ({
                    count: preState.count + 1
                }
            )
        )
    };

    render() {
        return (
            <div>
                <Provider value={this.state}>
                    <div>
                        <Consumer>
                            {
                                (context) => <p>{context.count}</p>
                            }
                        </Consumer>
                    </div>
                </Provider>
                <input type="button" value="加1" onClick={this.addOne}/>
            </div>
        )
    }
}

export default ContextDemo

这里我们重点解释下Provider与Consumer:

Provider
被视作一个React组件,它的作用就是接收一个value属性并把它当做最终Context实体注入到Provider的所有子组件中;同时Provider允许被Consumer订阅一个或多个Context的变动,也就是说Provider内部可以有N个Consumer并且它们都可以拿到 最新&&相同的Context对象。

如例子所示,我们将组件的State对象注入到Provider字组件中,如果State发生变化那么Provider中的Context对象必定会同步发生变化。

Consumer
依然被视作一个React组件,不过不同的是它的子组件必须是一个方法并且该方法接收当前Context对象并最终返回一个React节点。同时这里有两个问题需要重点关注:

  1. Consumer接收到的Context对象为离它最近的那个Provider注入的Context对象(且必须是通过value属性)。因为Provider作为一个组件也可以进行嵌套。不过笔者认为单独一个React项目最好只存在一个Context对象而且应该作为一个App级的Context对象(也就是将项目的根节点作为Provider的子组件)。这样做笔者认为有两个好处:1)全局只有一个Context更有利于方便使用和管理;2)作为一个App级的Context对象可以让我们在项目的任何一个地方使用到Context对象,发挥Context最大的力量。
  2. 如果Provider不存在(如果存在那么必须要有value属性,否则报错),那么Consumer获取到的Context对象为最初createContext方法的默认参数。

综上所述:Provider的value == Consumer子组件(function)的入参

当我们理解了这两个概念,我们再回过头来看代码;
我们将组件的State(this.state)通过Provider注入到其子组件中,其实可以预料到当我们更改State时候Context对象也会同步变化最终保持一致。所以:

<Consumer>
    {
        (context) => <p>{context.count}</p>
    }
</Consumer>

此时Consumer的子组件(function)的入参context就可以认为是this.state的复制体,所以可以在方法中获取到相应的数据并且在点击按钮更改了State后Context也发生变化,从而实现UI的重新渲染。

小小的测试

前面有句话说:Provider, Consumer配对使用的前提是它们都来自同一个createContext。因此笔者针对这点做了两个实验,目的是测试当Provider, Consumer不是来自同一个createContext会出现什么情况。这里新建两个文件buildContext.js和ContextTest.js

情况一

buildContext.js
import {createContext} from 'react';

export const {Provider} = createContext({'name': 'Mario'});
export const {Consumer} = createContext({'age': '26'});
ContextTest.js
import React, {Component} from 'react';
import {Provider, Consumer} from "./buildContext";

class Context extends Component {
    render() {
        return (
            <Provider>{/*name*/}
                <Consumer>{/*age*/}
                    {
                        (context) => (
                            <div>
                                <p>age: {context.age}</p>
                                <p>name: {context.name}</p>
                            </div>
                        )
                    }
                </Consumer>
            </Provider>
        )
    }
}

export default Context;

clipboard.png

运行的结果有点意想不到,Consumer拿到的Context并不是离它最近的Provider提供的,而是创造它的createContext方法的默认值,即:export const {Consumer} = createContext({'age': '26'});。再换个写法看看!

情况二

buildContext.js
import {createContext} from 'react';

export const NameContext = createContext({'name': 'Mario'});
export const AgeContext = createContext({'age': '26'});
ContextTest.js
import React, {Component} from 'react';
import {NameContext, AgeContext} from "./buildContext";

class Context extends Component {
    render() {
        return (
            <NameContext.Provider value={{'name':'React'}}>{/*name*/}
                <AgeContext.Consumer>{/*age*/}
                    {
                        (context) => (
                            <div>
                                <p>age: {context.age}</p>
                                <p>name: {context.name}</p>
                            </div>
                        )
                    }
                </AgeContext.Consumer>
            </NameContext.Provider>
        )
    }
}

export default Context;;

这里我们给Provider提供一个value属性;

clipboard.png

运行结果与第一种情况相同;

结论

因此我们可以猜测:如果Provider, Consumer不是来自同一个createContext,那么Consumer获取到的Context则是自己的createContext方法的默认值,此时的Provider被视为不存在。

Context的简单实用就介绍到这里,本篇很多地方都来自笔者的个人看法,如有理解不当烦请丢砖。
同时笔者也尝试写了一个更具有代表性(纯属笔者意淫)的Context应用实例,将Context、ContextHandler、Component分离出来,实现更改相邻组件的样式。麻雀虽小五脏俱全,请各位朋友多多海涵!!
对了项目启动脚本是npm install || npm start


http://www.niftyadmin.cn/n/1270276.html

相关文章

mysql唯一索引什么意思_MySQL唯一索引什么意思

创建唯一索引的目的不是为了提高访问速度&#xff0c;而只是为了避免数据出现重复。唯一索引可以有多个但索引列的值必须唯一&#xff0c;索引列的值允许有空值。如果能确定某个数据列将只包含彼此各不相同的值&#xff0c;在为这个数据列创建索引的时候就应该使用关键字UNIQUE…

fastreport .net代码设置居中_FastReport.Net报表设计器如何连接到SQLCe

Microsoft SQL Server Compact Edition是一个简单的本地关系数据库&#xff0c;不需要安装&#xff0c;并且已与数据库文件建立连接。您不需要管理员权限即可使用基础功能。您也只能“密码”基础功能。对于许多开发人员而言&#xff0c;这样简单的数据库仅是解决不需要删除访问…

如何将h5网页改成微信网页

1、如何将h5网页改成微信网页 1、设置安全域名 先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。 备注&#xff1a;登录后可在“开发者中心”查看对应的接口权限。 2、引用一个js文件 在微信开发者里面 js sdk里面 <sc…

mysql8.0.16修改密码_mysql8.0.16版本修改密码以及设置无需密码登录

引言密码忘记了&#xff0c;尴尬怎么办&#xff1b;报了10045的错误&#xff0c;怎么办&#xff1b;蒙逼中。answer&#xff1a;只能修改密码了。上一次接触数据库已经是一年前的事情了&#xff0c;怎么弄&#xff1f;&#xff1f;&#xff1f;只能寻求帮助了。但是发现网上的博…

UVA-1335(UVALive-3177) Beijing Guards 贪心 二分

题面 题意&#xff1a;有n个人为成一个圈&#xff0c;其中第i个人想要r[i]种不同的礼物&#xff0c;相邻的两个人可以聊天&#xff0c;炫耀自己的礼物。如果两个相邻的人拥有同一种礼物&#xff0c;则双方都会很不高兴&#xff0c;问最少需要多少种不同的礼物才能满足所有人的需…

mysql同步并联两张表_Mysql同一主机两张表结构相同的表数据同步-----触发器

1、执行过程&#xff1a;1)、################################插入DELIMITER//CREATE TRIGGER insert_BI_AppointmentOrder_trigger AFTER insert ON BI_AppointmentOrder FOR EACH ROW BEGIN--检查当前 环境&#xff0c;避免递归.IF disable_insert_trigger_o IS NULL THEN--…

Inter-process Communication (IPC)

For Developers‎ > ‎Design Documents‎ > ‎Inter-process Communication (IPC) 目录 1 Overview1.1 IPC in the browser1.2 IPC in the renderer2 Messages2.1 Types of messages2.2 Declaring messages2.2.1 Pickling values2.3 Sending messages2.4 Handling messa…

mysql创建乘积语法的触发器_创建Mysql触发器的语法介绍

Mysql触发器是Mysql数据库非常重要的部分&#xff0c;下文对创建Mysql触发器及删除Mysql触发器作了详细的介绍&#xff0c;希望对您有所帮助。1、创建Mysql触发器:语法:CREATE TRIGGER trigger_name trigger_time trigger_event ON tbl_nameFOR EACH ROWBEGINtrigger_stmtEND&a…