无论你是写React的一两年的程序员老司机,仍是刚刚接触React的小白程序员,若是你想掌握React而且使用它来解决各类各样的需求,那么你就必定须要掌握React Context。html
在我没有掌握Context以前,老是感受这种技术是多余的,在项目中使用它彻底是多此一举,甚至能够说是舍近求远。可是当我结合一些具体的业务深刻学习它以后——嗯~,Context真香。react
Context是什么?该怎么使用?能不能用在class组件和function组件中?有哪些具体的业务场景?程序员
我结合本身的学习心得,在这篇博客中跟你们一一分享。天太热,准备好西瓜,我们开始吧!api
Context直译过来就是“上下文”的意思,咱们能够把它理解成代码所处的环境,若是你的一段代码在某一个“上下文”中,那么能够在代码中访问这个“上下文”中的一些变量和方法,即便这些变量和方法没有在你的这段代码中进行定义。markdown
作一个类比,对于一个学生来说,他的上下文就是学校,那么他可使用学校图书馆中的资源,即便这些资源不是他本身的。ide
在React中也是同样,咱们能够给一个React组件指定一个上下文,在这个上下文中定义一些变量和方法,那么在这个组件中就能够直接使用这些变量和方法了。函数
若是咱们给全部的组件中都设置成一样的上下文,会怎么样呢?固然是全部的组件就均可以共享一样的一批数据,这就是Redux库所提供的功能。oop
了解了Context以后,咱们要怎么使用Context呢?学习
咱们先假设一个使用场景:以下图所示,项目中有App根组件,它下面有一个Page组件,Page组件中又分别有Header, Body, Footer三个组件。ui
这些组件中有不一样的内容,项目中要有一个能够一键更改主题颜色的功能。具体场景以下图所示:
这个时候,咱们可使用Context一步步来完成这些需求。若是你想直接查看demo代码,能够点击:codesandbox.io/s/react-con…
首先咱们须要定义一个Context对象,在项目中新建ContextData.js文件,代码以下:
import React, { createContext } from "react";
export const ContextData = createContext("light");
复制代码
这里使用createContext这个api建立的Context对象,在建立的时候,咱们须要传给它一个默认值。这个默认值基本没有什么做用,后面在使用的时候,会把它给覆盖掉。不过它也不是一无可取,后面咱们会讲到会在什么状况下用到了
Context对象建立以后,若是咱们在控制台中把它打印出来,能够看到它有这些属性:
{
$$typeof: xxxx,
**Consumer**: xxxx,
**Provider**: xxxx,
_calculateChangedBits: null,
_currentRenderer: {},
_currentRenderer2: null,
_currentValue: "light",
_currentValue2: "light",
_threadCount: 0
}
复制代码
要着重介绍Consumer和Provider属性,这两个属性是两个React组件,翻译过来就是“生产者组件”和“消费者组件”。
Consumer生产者组件一般是一个父组件,咱们定义的其余组件要放在它的里面,成为它的子节点或后代节点。Consumer组件用来提供共享的数据,咱们能够理解成它定义一个上下文,这样一来,他的后代节点就可使用上下文中的变量和方法了。
Provider消费者节点一般是一个或多个子节点,这些节点可使用生产者节点提供的上下文中的数据。
Context对象定义以后,咱们就能够在其余组件中引入并使用它,咱们在App组件中使用它,代码以下:
import React from 'react';
import Page from "./Page.jsx";
import { ContextData } from './ContextData'; // 引入Context对象
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
themeColor: "orange"
}
}
changeThemeColor = (colorName) => {
this.setState({
themeColor: colorName
})
}
render() {
// 定义上下文中的数据,这里不只能够定义变量,还能够定义方法
const contextValue = {
changeThemeColor: this.changeThemeColor,
themeColor: this.state.themeColor
}
return (
<ContextData.Provider value={contextValue}> <div> <p>Please select your theme color</p> <button onClick={() => { this.changeThemeColor("red") }}>Red</button> <button onClick={() => { this.changeThemeColor("green") }}>Green</button> <button onClick={() => { this.changeThemeColor("yellow") }}>Yellow</button> </div> {/* 这里须要把Page组件包裹进来 */} <Page /> </ContextData.Provider>
);
}
}
export default App;
复制代码
在上述代码中咱们能够看到,须要使用<ContextData.Provider value={contextValue}>组件包裹其余组件,这里须要指定value属性,这个属性就是上下文中的数据,它的子元素中所共享的变量就是这个属性。
同时咱们也把Page组件做为<ContextData.Provider value={contextValue}>组件的子节点,这样在Page节点及其子结点中使用上下文中的数据了。Page组件的代码以下:
import React, { Component } from "react";
import Header from './Header.jsx';
import Body from './Body.jsx';
import Footer from './Footer.jsx';
class Page extends Component {
constructor(props) {
super(props);
}
render() {
return (<> <Header /> <Body /> <Footer /> </>);
}
}
export default Page;
复制代码
咱们能够直接在Page组件中使用上下文的数据,可是这里没有,而是引入了子节点,咱们在它的子节点中使用上下文数据是同样的。这里以Header组件为例,代码以下:
import React, { Component } from "react";
import { ContextData } from "./ContextData.js"; // 这里要引入Context对象
class Header extends Component {
// 这一行代码很重要,经过class的静态属性contextType,咱们能够把上下文数据绑定到该class实例的context属性
static contextType = ContextData;
constructor(props) {
super(props);
}
render() {
// 经过this.context来使用上下文中的数据
return <div style={{ backgroundColor: this.context.themeColor }}> This is the Header component, the whole theme color is {this.context.themeColor} </div>
}
}
export default Header;
复制代码
Header是class形式定义的组件,可是如今愈来愈推崇使用function组件,那么该如何在function组件中使用上下文中的变量呢?这里咱们能够参考Body组件中的代码:
import React from "react";
import { ContextData } from "./ContextData.js"; // 这里要引入Context对象
const Body = () => {
// 这里须要使用消费者组件包裹,而且须要一个函数做为子元素
return <ContextData.Consumer> { contextValue => { const { themeColor, changeThemeColor } = contextValue; return <button onClick={() => { changeThemeColor("blue") }} style={{ backgroundColor: themeColor }} > Blue Color </button> } } </ContextData.Consumer>;
}
export default Body;
复制代码
function组件中没有静态属性contextType,须要使用消费者组件<ContextData.Consumer>结合一个函数的形式来获取上下文中的数据,而且这个函数要返回一个React节点。
传递给函数的value值等价于组件树上方离这个context最近的 Provider 提供的 value 值。若是没有对应的Provider,value参数等同于传递给createContext()的defaultValue。
咱们还能够看到一个有趣的事情,在Body组件中,咱们不只可使用上下文问中的变量,还可使用上下文中的方法,来修改上下文中的变量,这一点能够支持在后代节点中,修改祖先节点中的数据。
在function组件中,除了上述使用<ContextData.Consumer>以外,还有另一种方式上下文数据的方式,那就是使用useContext这一钩子,咱们能够参考Footer组件中的代码:
import React, { useContext } from "react";
import { ContextData } from "./ContextData.js"; // 这里要引入Context对象
const Footer = () => {
const context = useContext(ContextData); // 把整个context对象传给useContext
// 而后就能够直接访问上下文中的变量和方法了
const handleChangeThemeColor = () => {
context.changeThemeColor("gray")
}
return <div onClick={handleChangeThemeColor} style={{ color: context.themeColor }}> This is the footer text </div>
}
export default Footer;
复制代码
两种方式比较下来,我比较推荐第二种,写法更简洁,并且如今React官方也是比较鼓励使用Hooks。
关于Context的学习心得就分享结束了,若是你想了解更多,能够查看React的官方文档,写得仍是很是清楚的:
Context文档:zh-hans.reactjs.org/docs/contex…
useContext文档: zh-hans.reactjs.org/docs/hooks-…