使用React 开发程序的时候,组件中的数据共享是通过数据提升,变成父组件中的属性,然后再把属性向下传递给子组件来实现的。但当程序越来越复杂,需要共享的数据也越来越多,最后可能就把共享数据直接提升到最外层的组件,这时子组件再想获取到共享数据就有点麻烦了,需要向下传递好多层才能到达想要数据的子组件,这就很容易产生了一个问题,由于经过的这些层(组件)可能不需要这个数据,向下传递的过程中,有可能就忘记写共享属性了,程序就会出错,并且还不好调试。有没有一种方法,可以穿透组件,想要数据的子组件直接就能获取到共享数据, 这就是React context。

  Context就是上下文,环境的意思,React 的context 就是提供了一个上下文或环境,在这个环境中,有context提供者和context消费者,提供者(provider) 提供共享数据,消费者(consumer) 消费这些共享数据。所以使用React Context 的Api时,首先要先创建一个context, 假设是proContext, 创建成功后,这个proContext 就有两个属性provider和consumer,它们是两个组件,分别就是提供者和消费者,提供者提供数据是通过组件的value属性实现的, 消费者消费数据是通过函数实现了, 消费者怎么获取到提供者提供的数据呢?都是组件吗,只要消费者是提供者的子组件,它就是能获取到数据,这也就避免了层层传递。

  说起来,可能不好说,写一下代码就清楚了。打开cmd命令窗口, create-react-app react-context,创建项目 react-context,项目很非常简单,如下所示

  点击每个项目的上下箭头,改变total,  为了更能体现context 的用法,我把列表中的数据做成了共享数据,放到了最外层的父组件中。 项目要使用boostrap 提供样式,cd react-context && npm install bootstrap --save, 安装它。使用vs code 编辑器打开项目, 在index.js中引入bootstrap css 样式

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker'; import 'bootstrap/dist/css/bootstrap.css'; // 添加bootstrap 样式 ReactDOM.render(<App />, document.getElementById('root'));

  先不用context,把它实现一下,在src 目录下建Cars.js, ProductList.js和 Total.js 文件

import React, { Fragment } from "react";

export const Cars = props => (
<Fragment>
<h4>Cars:</h4>
<table className="table table-bordered">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Price</th>
<th scope="col">action</th>
</tr>
</thead>
<tbody>
{/* Finally we can use data */}
{Object.keys(props.cars).map(carID => (
<tr key={carID}>
<td>{props.cars[carID].name}</td>
<td>${props.cars[carID].price}</td>
<td>
<button className="btn btn-primary" onClick={() => props.incrementCarPrice(carID)}>&uarr;</button> &nbsp;
<button className="btn btn-primary" onClick={() => props.decrementCarPrice(carID)}>&darr;</button>
</td>
</tr>
))}
</tbody>
</table>
</Fragment>
);
import React from "react";
import { Cars } from "./Cars"; export const ProductList = props => (
<Cars
cars={props.cars}
incrementCarPrice={props.incrementCarPrice}
decrementCarPrice={props.decrementCarPrice}
/>
);
import React from 'react';

export const Total = props => {
const { cars } = props;
return (
<button type="button" class="btn btn-primary btn-lg btn-block">
Total: &nbsp;
{
Object.keys(cars).reduce((sum, car) => {
return sum + cars[car].price
}, )
}
</button>
)
}

  把App.js 修改如下  

import React, { Component } from "react";
import { ProductList } from "./ProductList";
import { Total } from "./Total";
export default class App extends Component {
state = {
cars: {
car001: { name: 'Honda', price: },
car002: { name: 'BMW', price: },
car003: { name: 'Mercedes', price: }
}
}; incrementCarPrice = (selectedID) => {
const cars = Object.assign({}, this.state.cars);
cars[selectedID].price = cars[selectedID].price + ;
this.setState({
cars
});
} decrementCarPrice = (selectedID) => {
const cars = Object.assign({}, this.state.cars);
cars[selectedID].price = cars[selectedID].price - ;
this.setState({
cars
});
} render() {
return (
<div style={{width: '1000px', margin: '50px auto'}}>
<ProductList
cars={this.state.cars}
incrementCarPrice={this.incrementCarPrice}
decrementCarPrice={this.decrementCarPrice}
/>
<Total cars={this.state.cars}></Total>
</div>
);
}
}

  可以看到真正使用App组件中共享state的是组件Cars,ProductList组件根本没有使用。但是由于Cars 组件不是App组件的子组件,所以需要经过ProductList 组件进行传递,ProductList 组件也不得不接收和传递它自己不需要的属性,造成了代码的冗余。这正是Context 解决的问题。现在使用context 解决一下。

  按照上面所说,首先要创建一个context, 使用的是React.createContext() 方法,它接受一个可选的参数,就是共享内容的默认值,我们要共享什么,就在这里定义好,提供默认值,当然也可以不传,直接调用.createContext() 方法。不过,建议写上参数,把它看作是共享内容的格式定义,一看到这个context, 就知道要共享什么。那创建的context 放在什么地方呢?context 可以定义在任意位置,在src 目录下新建一个文件CarContext.js 来存放context.

import React from 'react';

// 共享的数据是一个对象,有一个属性cars, 默认值是空对象
export const CarContext = React.createContext({
cars: {}
})

  创建一个provider 来提供真正的共享数据,使用的是CarContext.Provider 组件。它放到什么地方呢?因为共享的数据是App中的state, 而且需要消费数据的是ProductList 组件中的Car, 所以要在App.js 中引入CarContext, 并使用CarContext.Provider 把ProductList 组件包起来,这样ProductList 及其子组件就是获取到共享数据, 共享数据又是怎么提供呢?CarContext.Provider 有一个value 属性,所有共享的内容都放到value属性中。这样productList 就可以不用传递cars 属性了,把它去掉   

import { CarContext } from "./CarContext";  // app.js 顶部引入 

....
// render 内容修改如下
<div style={{width: '1000px', margin: '50px auto'}}>
<CarContext.Provider value={{cars: this.state.cars}}>
<ProductList
incrementCarPrice={this.incrementCarPrice}
decrementCarPrice={this.decrementCarPrice}
/>
</CarContext.Provider>
<Total cars={this.state.cars}></Total>
</div>

  最后就是修改Cars组件来消费共享数据,使用Consumer 组件。相比Provider 组件,它的使用稍微有一点麻烦,它不是把Cars组件原来的内容包起来,而是使用函数表达式。Consumer 组件的内容是一个函数表达式,函数的参数,就是Consumer 组件帮我们注入到Cars组件中的共享数据,返回的内容就是Cars 原有的内容,它这时就可以直接使用共享数据了。

import React, { Fragment } from "react";
import { CarContext } from "./CarContext"; export const Cars = props => (
<CarContext.Consumer>
{/* contextData 就是共享数据 */}
{contextData => {
const {cars} = contextData;
return <Fragment>
<h4>Cars:</h4>
<table className="table table-bordered">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Price</th>
<th scope="col">action</th>
</tr>
</thead>
<tbody>
{/* Finally we can use data */}
{Object.keys(cars).map(carID => (
<tr key={carID}>
<td>{cars[carID].name}</td>
<td>${cars[carID].price}</td>
<td>
<button className="btn btn-primary" onClick={() => props.incrementCarPrice(carID)}>&uarr;</button> &nbsp;
<button className="btn btn-primary" onClick={() => props.decrementCarPrice(carID)}>&darr;</button>
</td>
</tr>
))}
</tbody>
</table>
</Fragment>
}} </CarContext.Consumer>
);

  有时候,修改共享数据的方法也需要共享,假设在Total 中也可以更改app的state,那么incrementCarPrice 和decrementCarPrice 就要共享, 那么 CarContext.Provider 组件的value 属性就要包含这两个方法

 <div style={{width: '1000px', margin: '50px auto'}}>
<CarContext.Provider value={
{cars: this.state.cars, incrementCarPrice: this.incrementCarPrice, decrementCarPrice: this.decrementCarPrice}
}>
<ProductList/>
</CarContext.Provider>
<Total cars={this.state.cars}></Total>
</div>

  最好也在carCotext 中提供这两个方法的默认实现,carContext.js 修改如下:

export const CarContext = React.createContext({
cars: {},
incrementCarPrice: () => {},
decrementCarPrice: () => {}
})

   Cars 消费组件,调用incrementCarPrice 和decrementCarPrice, 就要从共享数据contextData 中获取

import React, { Fragment } from "react";
import { CarContext } from "./CarContext"; export const Cars = props => (
<CarContext.Consumer>
{/* contextData 就是共享数据 */}
{contextData => {
const {cars, incrementCarPrice, decrementCarPrice} = contextData;
return <Fragment>
<h4>Cars:</h4>
<table className="table table-bordered">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Price</th>
<th scope="col">action</th>
</tr>
</thead>
<tbody>
{/* Finally we can use data */}
{Object.keys(cars).map(carID => (
<tr key={carID}>
<td>{cars[carID].name}</td>
<td>${cars[carID].price}</td>
<td>
<button className="btn btn-primary" onClick={() => incrementCarPrice(carID)}>&uarr;</button> &nbsp;
<button className="btn btn-primary" onClick={() => decrementCarPrice(carID)}>&darr;</button>
</td>
</tr>
))}
</tbody>
</table>
</Fragment>
}} </CarContext.Consumer>
);

  由于Consumer 组件使用起来相对麻烦一点,所以React也提供了简便方法,可以给消费组件设一个静态属性contextType, 值为CarContext , 然后组件内就可以通过this.context 来获取到context 共享数据,而不用使用Consumer 组件包括。 静态属性,可以直接给消费组件类赋值一个属性,Car.contextType = CarContext ,  或者在类中使用static 修饰 static contextType = CarContext , 这时要把组件变成类组件

import React, { Fragment } from "react";
import { CarContext } from "./CarContext"; export class Cars extends React.Component{
static contextType = CarContext; render() {
const {cars, incrementCarPrice, decrementCarPrice} = this.context;
return (
<Fragment>
<h4>Cars:</h4>
<table className="table table-bordered">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Price</th>
<th scope="col">action</th>
</tr>
</thead>
<tbody>
{/* Finally we can use data */}
{Object.keys(cars).map(carID => (
<tr key={carID}>
<td>{cars[carID].name}</td>
<td>${cars[carID].price}</td>
<td>
<button className="btn btn-primary" onClick={() => incrementCarPrice(carID)}>&uarr;</button> &nbsp;
<button className="btn btn-primary" onClick={() => decrementCarPrice(carID)}>&darr;</button>
</td>
</tr>
))}
</tbody>
</table>
</Fragment>
)
}
}

  但为了这一个context ,改成类组件,也不是很爽,如果你的React 版本是16.8 及以上,你可以使用React Hook, useContext

import React, { Fragment, useContext } from "react";
import { CarContext } from "./CarContext"; export const Cars = () => {
const {cars, incrementCarPrice, decrementCarPrice} = useContext(CarContext);
return <Fragment>
<h4>Cars:</h4>
<table className="table table-bordered">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Price</th>
<th scope="col">action</th>
</tr>
</thead>
<tbody>
{/* Finally we can use data */}
{Object.keys(cars).map(carID => (
<tr key={carID}>
<td>{cars[carID].name}</td>
<td>${cars[carID].price}</td>
<td>
<button className="btn btn-primary" onClick={() => incrementCarPrice(carID)}>&uarr;</button> &nbsp;
<button className="btn btn-primary" onClick={() => decrementCarPrice(carID)}>&darr;</button>
</td>
</tr>
))}
</tbody>
</table>
</Fragment>
};

  这时ProductList 组件就可以不用传递任何数据了

export const ProductList = props => (
<Cars/>
);

  但如果仅仅是为了不想层层伟递数据,就使用context,那没有必要,那可以传递组件。在父组件中声明要传递的组件,直接把组件传递过去。经过的子组件以数据并没有感知。

React Context API的更多相关文章

  1. [译]迁移到新的 React Context Api

    随着 React 16.3.0 的发布,context api 也有了很大的更新.我已经从旧版的 api 更新到了新版.这里就分享一下我(作者)的心得体会. 回顾 下面是一个展示如何使用旧版 api ...

  2. [React] Use the new React Context API

    The React documentation has been warning us for a long time now that context shouldn't be used and t ...

  3. 10分钟学会React Context API

    Create-react-app来学习这个功能: 注意下面代码红色的即可,非常简单. 在小项目里Context API完全可以替换掉react-redux. 修改app.js import React ...

  4. React 16.3来了:带着全新的Context API

    文章概览 React在版本16.3-alpha里引入了新的Context API,社区一片期待之声.我们先通过简单的例子,看下新的Context API长啥样,然后再简单探讨下新的API的意义. 文中 ...

  5. React 新 Context API 在前端状态管理的实践

    本文转载至:今日头条技术博客 众所周知,React的单向数据流模式导致状态只能一级一级的由父组件传递到子组件,在大中型应用中较为繁琐不好管理,通常我们需要使用Redux来帮助我们进行管理,然而随着Re ...

  6. 简单整理React的Context API

    之前做项目时经常会遇到某个组件需要传递方法或者数据到其内部的某个子组件,中间跨越了甚至三四层组件,必须层层传递,一不小心哪层组件忘记传递下去了就不行.然而我们的项目其实并没有那么复杂,所以也没有使用r ...

  7. React 全新的 Context API

    Context API 可以说是 React 中最有趣的一个特性了.一方面很多流行的框架(例如react-redux.mobx-react.react-router等)都在使用它:另一方面官方文档中却 ...

  8. 手写一个React-Redux,玩转React的Context API

    上一篇文章我们手写了一个Redux,但是单纯的Redux只是一个状态机,是没有UI呈现的,所以一般我们使用的时候都会配合一个UI库,比如在React中使用Redux就会用到React-Redux这个库 ...

  9. React Hooks & Context API

    React Hooks & Context API responsive website https://reactjs.org/docs/hooks-reference.html https ...

随机推荐

  1. 10-cmake语法-CMakeParseArguments

    include(CMakeParseArguments) 是为了使用 cmake_parse_arguments(),看样子是用来解析输入参数的. 给出参考: https://cmake.org/pi ...

  2. svg形状相关的学习(二)

    _ 阅读目录 一:线段 二:笔画特性 1. stroke-width 2. stroke-opacity 3. stroke-dasharray 属性 三:常见的形状 1. 矩形 2. 圆角矩形 3. ...

  3. 单例模式-DCL双重锁检查实现及原理刨析

    以我的经验为例(如有不对欢迎指正),在生产过程中,经常会遇到下面两种情况: 1.封装的某个类不包含具有具体业务含义的类成员变量,是对业务动作的封装,如MVC中的各层(HTTPRequest对象以Thr ...

  4. BFS实现8数码问题,思考与总结

    BFS实现8数码问题,思考与总结 今天中午学习了二叉树的线索化与线索化遍历,突然有一种想实现八数码问题的冲动,因为它的初级解决方式是BFS(广度优先搜索算法).于是我开始编程. 没想到一编就是一个下午 ...

  5. 利用$a_n$与$S_n$的关系求通项$a_n$

    前言 由\(a_n\)与\(S_n\)的关系求数列\(\{a_n\}\)的通项公式,在求通项公式题型中占有比较大的份额,是一个重要的求解思路和方法.是要求重点掌握的类型. 一.方法依据 二者关系:\( ...

  6. 第08组 Beta冲刺(1/5)

    队名:955 组长博客:点这里! 作业博客:点这里! 组员情况 组员1(组长):庄锡荣 过去两天完成了哪些任务 文字/口头描述  检测网站不合理的地方,给组员定下相应时间进度的安排 展示GitHub当 ...

  7. Oracle_其他人连接不上自己电脑

    1. 修改文件G:\app\eric\product\11.2.0\dbhome_1\NETWORK\ADMIN\listener.ora文件,listener中的address中加一个你的ip开头的 ...

  8. SQL Server表 & 存储过程 创建日期查询

    查询表创建时间 SELECT [name] ,create_date ,modify_date FROM sys.tables ORDER BY modify_date DESC 查下存储过程创建时间 ...

  9. Gamma阶段第九次scrum meeting

    每日任务内容 队员 昨日完成任务 明日要完成的任务 张圆宁 #91 用户体验与优化https://github.com/rRetr0Git/rateMyCourse/issues/91(持续完成) # ...

  10. 【07月03日】A股ROE最高排名

    个股滚动ROE = 最近4个季度的归母净利润 / ((期初归母净资产 + 期末归母净资产) / 2). 查看更多个股ROE最高排名 兰州民百(SH600738) - ROE_TTM:86.45% - ...