这样使用React Context,提升你的工做效率

写在前面

无论你是写React的一两年的程序员老司机,仍是刚刚接触React的小白程序员,若是你想掌握React而且使用它来解决各类各样的需求,那么你就必定须要掌握React Context。html

在我没有掌握Context以前,老是感受这种技术是多余的,在项目中使用它彻底是多此一举,甚至能够说是舍近求远。可是当我结合一些具体的业务深刻学习它以后——嗯~,Context真香。react

Context是什么?该怎么使用?能不能用在class组件和function组件中?有哪些具体的业务场景?程序员

我结合本身的学习心得,在这篇博客中跟你们一一分享。天太热,准备好西瓜,我们开始吧!api

Context是什么

Context直译过来就是“上下文”的意思,咱们能够把它理解成代码所处的环境,若是你的一段代码在某一个“上下文”中,那么能够在代码中访问这个“上下文”中的一些变量和方法,即便这些变量和方法没有在你的这段代码中进行定义。markdown

作一个类比,对于一个学生来说,他的上下文就是学校,那么他可使用学校图书馆中的资源,即便这些资源不是他本身的。ide

在React中也是同样,咱们能够给一个React组件指定一个上下文,在这个上下文中定义一些变量和方法,那么在这个组件中就能够直接使用这些变量和方法了。函数

若是咱们给全部的组件中都设置成一样的上下文,会怎么样呢?固然是全部的组件就均可以共享一样的一批数据,这就是Redux库所提供的功能。oop

该怎么使用Context

了解了Context以后,咱们要怎么使用Context呢?学习

咱们先假设一个使用场景:以下图所示,项目中有App根组件,它下面有一个Page组件,Page组件中又分别有Header, Body, Footer三个组件。ui

image.png

这些组件中有不一样的内容,项目中要有一个能够一键更改主题颜色的功能。具体场景以下图所示:

这个时候,咱们可使用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
}
复制代码

要着重介绍ConsumerProvider属性,这两个属性是两个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-…

相关文章
相关标签/搜索