React Hook之useContext管理公共状态

693 阅读1分钟

以一个主题切换的例子,来说明useContext的基本使用

1.创建Context

const ThemeContext = React.createContext<IThemeContext>({
  theme: Theme.Dark,
  // eslint-disable-next-line @typescript-eslint/no-empty-function
  // TODO: 这里想声明一个空函数, 该如何写?
  setTheme: (theme) => {
  },
});

2.创建组件ThemeProvider, 返回ThemeContext.Provider,在组件中使用useReducer来声明公共状态

function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, dispatch] = useReducer(reducer, Theme.Light);

  function reducer(state: Theme, action: { type: Theme }) {
    console.log('prevState=>', state);
    console.log('action=>', action);
    let nextState;
    switch (action.type) {
      case Theme.Light:
        nextState = Theme.Dark;
        break;
      case Theme.Dark:
        nextState = Theme.Light;
        break;
      default:
        nextState = state;
    }
    console.log('nextState=>', nextState);
    return nextState;
  }

  return (
    <ThemeContext.Provider value={{ theme, setTheme: dispatch }}>
      <button
        onClick={() => {
          dispatch({
            type: theme,
          });
        }}
      >
        切换主题-ThemeWarp
      </button>
      {children}
    </ThemeContext.Provider>
  );
}

3.创建使用/不使用context状态的组件、切换主题按钮和一个主要组件

优化点:由于context值更新,会导致Provider中所有子组件重新渲染,我们需要尽量提升渲染无关的子组件到Provider外部


// 不使用context
function ChildNonTheme() {
  console.log('不关心皮肤的子组件渲染了');
  return (
    <div>
      <p>我不关心皮肤,主题色改变时不要让我渲染</p>
    </div>
  );
}

// 使用context
function ChildWithTheme() {
  const { theme, setTheme } = useContext(ThemeContext);
  console.log('我关心皮肤,主题改变时,我随之改变');
  return (
    <div>
      <p>我是有皮肤的哦~ {theme}</p>
      <button
        onClick={() => {
          setTheme({
            type: theme === Theme.Dark ? Theme.Light : Theme.Dark,
          });
        }}
      >
        切换主题-ChildWithTheme
      </button>
    </div>
  );
}

// 切换context值的button
function ToggleThemeButton() {
  const { theme, setTheme } = useContext(ThemeContext);
  return (
    <button
      onClick={() => {
        setTheme({
          type: theme,
        });
      }}
    >
      Toggle Theme Button
    </button>
  );
}

// 入口组件
export default function ThemeDemo() {
  return (
    // Context 的 value 更新 Provider中的子组件都会重新渲染
    // 我们需要拆分,涉及更新的组件到对应的Context下
    <>
      <div>
        <h3>theme useReducer优化</h3>
        <ThemeProvider>
          {/* 在Provider里面useContext才管用!!!!!!!! */}
          <ChildWithTheme/>
          <ToggleThemeButton/>
        </ThemeProvider>
        {/* 错误:Provider外是无效的 */}
        <ToggleThemeButton/>
        <ChildNonTheme/>
        <ChildNonTheme/>
        <ChildNonTheme/>
        <ChildNonTheme/>
        <ChildNonTheme/>
        <ChildNonTheme/>
        <ChildNonTheme/>
        <ChildNonTheme/>
        <ChildNonTheme/>
      </div>
    </>
  );
}

注:useContext使用时,必须在ThemeContext.Provider组件内部才有效。