React Context|青训营笔记

49 阅读3分钟

这是我参与「第四届青训营 」笔记创作活动的第16天

  • context提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
  • 共享那些对于一个组件树而言是 全局 的数据
  • 某个组件只要往自己的 context 里面放了某些状态,这个组件之下的所有子组件都直接访问这个状态而不需要通过中间组件的传递。一个组件的 context 只有它的子组件能够访问,它的父组件是不能访问到的。

生产者(Provider):父节点

消费者(Consumer):一个或多个子节点

React.createContext

 const MyContext  = React.createContext(defaultValue);

父级组件创建一个上下文(context)对象,订阅了这个上下文的组件中可以拿到上下文提供的数据或者其他信息。

如果需要使用创建的上下文,需要通过:

 <MyContext.Provider value={{xx:xx}}></MyContext.Provider>

传入value,指定context要对外暴露的信息。

当组件所处的树没有匹配到Provider(不使用Provider组件) 时,默认参数defaultValue才会生效。

useContext

通过 React.createContext 创建出来的上下文,在子组件中可以通过 useContext 获取 Provider 提供的内容。

 const {funcName} = useContext(MyContext);

useContext 需要将 MyContext 这个 Context 实例传入,不是字符串,就是实例本身。

实例

子组件中修改父组件的state,使用Context可以避免层层传递。

  1. 上下文管理

创建一个上下文管理的组件,用来统一导出 Context 实例

 // context-manager.js
 import React from 'react';
 ​
 export const MyContext = React.createContext(null);
  1. 父组件 Provider 提供上下文value

父组件引入了实例,并且通过 MyContext.Provider 将父组件包装,并且通过 value 将方法提供出去。

 import React, { useState } from 'react';
 import Child from './Child';
 import { MyContext } from './context-manager';
 ​
 const fetchData = () => {
     return new Promise((resolve, reject) => {
         setTimeout(() => {
             resolve(1);
         })
     });
 }
 ​
 export default Parent = (props = {}) => {
     const [step, setStep] = useState(0);
     const [count, setCount] = useState(0);
     const [number, setNumber] = useState(0);
 ​
 ​
     return (
         <MyContext.Provider value={{ setStep, setCount, setNumber, fetchData }}>
             <Child step={step} number={number} count={count} />
         </MyContext.Provider>
     );
 }
  1. 子组件 useContext 解析上下文
 import React, { useContext, useEffect, memo } from 'react';
 ​
 import { MyContext } from './context-manager';
 ​
 export default Child = memo((props = {}) => {
     const { setStep, setNumber, setCount, fetchData } = useContext(MyContext);
 ​
     useEffect(() => {
         fetchData().then((res) => {
             console.log(`FETCH DATA: ${res}`);
         })
     }, []);
 ​
     return (
         <div>
             <p>step is : {props.step}</p>
             <p>number is : {props.number}</p>
             <p>count is : {props.count}</p>
             <hr />
             <div>
                 <button onClick={() => { setStep(props.step + 1) }}>step ++</button>
                 <button onClick={() => { setNumber(props.number + 1) }}>number ++</button>
                 <button onClick={() => { setCount(props.step + props.number) }}>number + step</button>
             </div>
         </div>
     );
 });

在子组件中点击按钮,直接调用 Context 传递过来的方法,可以修改父组件的 state,子组件则会重新渲染。

  1. 使用 useReducer 减少 Context 的复杂程度

上述示例暴露了一个问题,所有的方法都放在了 Context.Providervalue属性中传递,必然造成整个 Context.Provider 提供的方法越来越多,也会臃肿。因此可以对setStepsetCountsetNumber 这三个方法,是可以通过 useReducer 包装,并且通过 dispatch 触发的。

修改父组件如下:

 import React, { useReducer } from 'react';
 import Child from './Child';
 import { MyContext } from './context-manager';
 ​
 const initState = { count: 0, step: 0, number: 0 };
 ​
 const reducer = (state, action) => {
     switch (action.type) {
         case 'stepInc': 
             return Object.assign({}, state, { step: state.step + 1 });
         case 'numberInc': 
             return Object.assign({}, state, { number: state.number + 1 });
         case 'count': 
             return Object.assign({}, state, { count: state.step + state.number });
         default: 
             return state;
     }
 }
 ​
 export default Parent = (props = {}) => {
     const [state, dispatch] = useReducer(reducer, initState);
     const { step, number, count } = state;
 ​
     return (
         <MyContext.Provider value={{ dispatch }}>
             <Child step={step} number={number} count={count} />
         </MyContext.Provider>
     );
 }

此时子组件只需要拿到 dispatch 即可修改父组件的 state

 import React, { useContext, memo } from 'react';
 ​
 import { MyContext } from './context-manager';
 ​
 export default Child = memo((props = {}) => {
     const { dispatch } = useContext(MyContext);
 ​
     return (
         <div>
             <p>step is : {props.step}</p>
             <p>number is : {props.number}</p>
             <p>count is : {props.count}</p>
             <hr />
             <div>
                 <button onClick={() => { dispatch({ type: 'stepInc' }) }}>step ++</button>
                 <button onClick={() => { dispatch({ type: 'numberInc' }) }}>number ++</button>
                 <button onClick={() => { dispatch({ type: 'count' }) }}>number + step</button>
             </div>
         </div>
     );
 });