React 开发 - React.createContext 上下文

110 阅读3分钟

在前不久的一篇文章 React 开发 - 认识Zustand 中,我们了解了通过 Zustand 进行状态管理。这篇文章,我们直接来学习通过 createContext 来进行组件树的状态管理。

React.createContext 是用于创建上下文,方便组件树中共享数据,而不需要通过 props 逐层传递数据。

用法

使用它也是很简单:

  1. 创建上下文 - createContext
  2. 使用 Provider 提供上下文
  3. 使用 useContext 钩子消费上下文
// demo.jsx
import React, { createContext, useContext } from "react";
import { ChildComponent } from "path/to/ChildComponent.tsx";

// 1. 创建上下文对象
const MyContext = createContext(null);

function Demo() {
  const value = { name: "Jimmy", address: "Canton"};
  
  return (
    // 2. 使用 Provider 提供上下文
    <MyContext.Provider value={ value }>
      <ChildComponent />
    </MyContext.Provider>
  );
}

上面是父组件,下面我们来编写消费的子组件 ChildComponent,如下:

// ChildComponent.tsx

function ChildComponent() {
  // 3. 使用 useContext 钩子来消费上下文值
  const value = useContext(MyContext);
  
  return (
    <div>
      <p>Name: {value.name}</p>
      <p>Address: {value.address}</p>
    </div>
  );
}

上面我们通过一个简单的案例已经了解了怎么使用它。下面,我们通过一个详细的案例来熟悉它。

案例

假设我们需要在父组件中传递数据给子组件,然后,在子组件的各个地方都能够 dispatch 数据回传到父组件进行处理。

在进入正文之前,我们有必要了解下 React.useReducer 这个钩子函数。

useReducerReact 提供的用于管理组件的状态的钩子函数,类似于 useState,但是更加适合处理复杂的状态逻辑。

useReducer 接受三个参数:

  • reducer: 一个函数,用于处理状态更新逻辑。它接受当前状态和一个 action 对象,并且返回新的状态。如下例子中的 shootReducer
  • initialState:初始状态
  • init: 可选,一个函数,用于惰性初始化状态

什么叫做惰性初始化。惰性初始化意味着初始状态的计算会被延迟到组件首次渲染时进行,而不是在定义 useReducer 时立即计算。这种方式可以提高性能,特别是当初始状态的计算比较复杂或者依赖于某些异步的操作时。当传入第三个参数的时候,第二个参数的意义就没那么重要了

嗯~进入正题。我们有下面的父组件👇

const noop = () => {}

// 接口 action
interface IShootAction {
  type: "updateShootCount" | "updateCountDown"
}

// 接口 state
interface IShootState {
  shootCount: number;
  countDown: number;
}

// 上下文
export const ShootContext = React.createContext({
  shootCount:1,
  countDown: -1,
  dispatch: noop as React.Dispatch<IShootAction>
});

// 更改 state
function shootReducer(state: IShootState, action: IShootAction): IShootState {
  switch (action.type) {
    case "updateShootCount": 
      state.shootCount = action.shootCount ?? state.shootCount;
      break;
    case "updateCountDown":
      state.countDown = action.countDown ?? state.countDown;
    break;
  }
  return { ...state };
}


// 父函数组件
export const ShootView: React.FC = () => {
  const operate = "paperwork";
  
  // useReducer 钩子
  const [
    {
      shootCount,
      countDown
    },
    dispatch,
  ] = React.useReducer(
    // 处理状态更新逻辑的函数
    shootReducer,
    // 初始状态
    {
      shootCount: 3,
      countDown: -1
    },
    // 惰性初始化
    (state) => {
      if(operate === "paperwork") {
        state.shootCount = 5;
      } else {
        state.shootCount = 8;
      }
    }
  );
  
  // 接下来,我们就进行上下文的提供了
  return (
    <ShootContext.Provider value = {{
      countDown,
      shootCount,
      dispatch,
    }}>
      <ChildComponent />
    </ShootContext.Provider>
  );
}

我们将一些状态和 dispatch 传递给子组件,接下来,我们在子组件中进行对应的操作👇

export interface IChildComponentProps {}

export ChildComponent: React.FC<IChildComponentProps> = (props) => {
  // 子组件中消费上下文
  const { countdown, dispatch } = React.useContext(ShootContext);
  
  return (
    // 直接展示相关的状态
    <>
      <p>CountDown: { countdown }</p>
      {/* 这里我们通过 dispatch 来更改 shootCount */}
      <button onClick={(dispatch) =>{
        if ( //  something true happen) {
          dispatch({ type: "updateCountdown", countDown: 13 });
        }
      }}>Change ShootCount</button>
    </>
  );
}

当然,上面我们只是局限在页面中进行展示使用 dispatch,我们也可以在组件调用的钩子函数中去使用 dispath 来管理代码。

嗯~ 今天的话题到这里结束,【完】❀