2021-01-27 React高级API-Context

272 阅读3分钟

组件化优点

  1. 增强代码重用性,提高开发效率
  2. 简化调试步骤,提升整个项目的可维护性
  3. 便于协同开发
  4. 注意点:降低耦合性

组件跨层级通信-Context

React应用中,大部分数据是通过props属性自上而下(由父及子)进行传递的,遇到复杂的嵌套组件关系时,可以使用Context实现祖代组件想后代组件跨层级传值,类似于Vue中的provide&inject就是来源于Context

Context API

React.createContext:创建一个Context对象,当React渲染一个订阅了这个Context对象的组件,这个组件会从组件树中里自身最近的那个匹配的Provider中读取到当前的context值

Context.Provider:Provider接收一个value属性,传递给消费组件,允许消费组件订阅context的变化。一个Provider可以和多个消费组件有对应关系。多个Provider也可以嵌套使用,里层的会覆盖外层的数据

当Provider的value值发生变化时,它内部的所有消费组件都会重新渲染。Provider及其内部consumer组件不受制于shouldComponentUpdate函数,因此当consumer组件在其祖先组件退出更新的情况下也能更新

Class.contextType:挂载在class上的contextType属性会被重新赋值为一个由React.createContext()创建的Context对象。这能让你使用this.context来消费最近的Context上的那个值。你可以在任何生命周期中访问到它,包括render函数中

你只能通过该API订阅单一context

Context.Consumer:这里,React组件也可以订阅到context变更。这能让你在函数组件中完成订阅context。这个函数接收当前的context值,返回一个React节点。传递给函数的value值等同于往上组件树离这个context最近的Provider提供的value值。如果没有对应的Provider,value参数等同于传递给createContext()的defaultValue

useContext:接收一个context对象(React.createContext的返回值)并返回该context的当前值。当前的context值由上层组件中距离当前组件最近的<MyContext.Provider>value prop决定。只能用在function组件中

如何使用Context:三步走

step1:创建一个context对象

step2:创建Provider,传递value

step3:子组件消费value,有三种途径:contextType、useContext、Consumer

区别

contextType只能用在类组件,只能订阅单一的context来源

useContext只能用在函数组件或自定义hook中,可以读取多个

Consumer不限制函数或者类组件

例子:使用Context共享主题色

创建Context => 获取Provider和Consumer =>Provider提供值 =>Consumer消费值

Context.js

import React from "react";

// 创建Context对象,React.createContext
export const ThemeContext = React.createContext({ themeColor: "pink" });

export const UserContext = React.createContext();

ContextPage.js

import React, { Component } from "react";
import ContextTypePage from "./ContextTypePage";
import { ThemeContext, UserContext } from "./Context";
import ConsumerPage from "./ConsumerPage";
import UseContextPage from "./UseContextPage";

class ContextPage extends Component {
  constructor(props) {
    super(props);
    this.state = {
      theme: {
        themeColor: "red"
      },
      user: {
        name: "xiaoming"
      }
    };
  }
  changeColor = () => {
    const { themeColor } = this.state.theme;
    this.setState({
      theme: {
        themeColor: themeColor === "red" ? "green" : "red"
      }
    });
  };
  render() {
    const { theme, user } = this.state;
    return (
      <div>
        <h3>ContextPage</h3>
        <button onClick={this.changeColor}>change color</button>
        {/* Provide一个context */}
        <ThemeContext.Provider value={theme}>
          <ContextTypePage />
          <UserContext.Provider value={user}>
            {/* 函数组件 */}
            <UseContextPage />
            <ConsumerPage />
          </UserContext.Provider>
        </ThemeContext.Provider>
      </div>
    );
  }
}
export default ContextPage;

ContextTypePage.js 类组件使用class.contextType方式

import React, { Component } from "react";
import { ThemeContext } from "./Context";

// class组件
class ContextTypePage extends Component {
  static contextType = ThemeContext; // Class.contextType使其能用this.context
  render() {
    const { themeColor } = this.context;
    return (
      <div>
        <h3 className={themeColor}>ContextTypePage</h3>
      </div>
    );
  }
}
export default ContextTypePage;

UseContextPage.js 函数组件使用useContext()hook方式

import React, { useContext } from "react";
import { ThemeContext, UserContext } from "./Context";

function UseContextPage(props) {
  // useContext可以使用订阅多个context
  const themeContext = useContext(ThemeContext);
  const { themeColor } = themeContext;
  const userContext = useContext(UserContext);
  return (
    <div>
      <h3 className={themeColor}>UseContextPage</h3>
      <p>{userContext.name}</p>
    </div>
  );
}
export default UseContextPage;

ConsumerPage.js 不限制函数和类组件

import React, { Component } from "react";
import { ThemeContext, UserContext } from "./Context";

class ConsumerPage extends Component {
  render() {
    return (
      <div>
        {/* 除了使用Class.contextType、useContext之外还有Consumer */}
        <ThemeContext.Consumer>
          {themeContext => (
            <>
              <h3 className={themeContext.themeColor}>ConsumerPage</h3>
              <UserContext.Consumer>
                {userContext => <HandleUserPage {...userContext} />}
              </UserContext.Consumer>
            </>
          )}
        </ThemeContext.Consumer>
      </div>
    );
  }
}

function HandleUserPage(userCtx) {
  return <div>{userCtx.name}</div>;
}
export default ConsumerPage;