你不知道的React系列(十)Context

95 阅读2分钟

「回顾 2022,展望 2023,我正在参与2022 年终总结征文大赛活动

概念

  • 共享那些对于一个组件树而言是“全局”的数据,主题,用户信息,路由,状态管理

  • 多个 Provider 嵌套使用,里层的会覆盖外层的数据

  • Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染

  • 即使祖先使用 React.memo 也会重新渲染

  • 只是想避免层层传递一些属性,使用组件组合(component composition)

    • props 多个层级传递,替换为 props 传递整个组件

    • 传递多个子组件,甚至会为这些子组件(children)封装多个单独的“接口(slots)

      • 需要将子组件和直接关联的父组件解耦

      • render props

使用

  • 创建 一个 context。
  • 在需要数据的组件内 使用 刚刚创建的 context。
  • 在指定数据的组件中 提供 这个 context。
const MyContext = createContext(defaultValue);
<MyContext.Provider value={/* 某个值 */}></MyContext.Provider;
MyContext.displayName = 'MyDisplayName';

重渲染组件的开销较大,使用 memoization 优化

  • 拆分 context,从原有 context 内部拆分为另一 context

    function Button() {
    let theme = useContext(ThemeContext);
    // The rest of your rendering logic
    return <ExpensiveTree className={theme} />;
    }
    
  • 拆分组件,接收 context 组件拆分为两个组件,父组件接收 context 传递给子组件,子组件使用 memo

        function Button() {
          let appContextValue = useContext(AppContext);
          let theme = appContextValue.theme; // Your "selector"
          return <ThemedButton theme={theme} />
        }
    
        const ThemedButton = memo(({ theme }) => {
          // The rest of your rendering logic
          return <ExpensiveTree className={theme} />;
        });
    
  • 组件内部 return 一个 useMemo 函数,指定依赖列表

    function Button() {
      let appContextValue = useContext(AppContext);
      let theme = appContextValue.theme; // Your "selector"
    
      return useMemo(() => {
        // The rest of your rendering logic
        return <ExpensiveTree className={theme} />;
      }, [theme])
    }
    

结合使用 Reducer 和 Context

  • 创建 context
  • 将 state 和 dispatch 放入 context
  • 在组件树的任何地方 使用 context
  • 将相关逻辑迁移到一个文件当中
import { createContext, useContext, useReducer } from 'react';
const TasksContext = createContext(null);
const TasksDispatchContext = createContext(null);
export function TasksProvider({ children }) {
  const [tasks, dispatch] = useReducer(
    tasksReducer,
    initialTasks
  );
  return (
    <TasksContext.Provider value={tasks}>
      <TasksDispatchContext.Provider value={dispatch}>
        {children}
      </TasksDispatchContext.Provider>
    </TasksContext.Provider>
  );
}
export function useTasks() {
  return useContext(TasksContext);
}
export function useTasksDispatch() {
  return useContext(TasksDispatchContext);
}
function tasksReducer(tasks, action) {
  switch (action.type) {
    case 'added': {
      return [...tasks, {
        id: action.id,
        text: action.text,
        done: false
      }];
    }
    case 'changed': {
      return tasks.map(t => {
        if (t.id === action.task.id) {
          return action.task;
        } else {
          return t;
        }
      });
    }
    case 'deleted': {
      return tasks.filter(t => t.id !== action.id);
    }
    default: {
      throw Error('Unknown action: ' + action.type);
    }
  }
}
const initialTasks = [
  { id: 0, text: 'Philosopher’s Path', done: true },
  { id: 1, text: 'Visit the temple', done: false },
  { id: 2, text: 'Drink matcha', done: false }
];

注意

  • 不要传递对象给 value
  • undefined 传递给 Provider 的 value 时,消费组件的 defaultValue 不会生效
  • 只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效