useContext
使用 Context 深层传递参数
useContext(SomeContext)
参数
SomeContext:用createContext创建的 context。context 本身不包含信息,它只代表你可以提供或从组件中读取的信息类型。
返回值
useContext为调用组件返回 context 的值。它被确定为传递给树中调用组件上方最近的 SomeContext.Provider 的 value。如果没有这样的 provider,那么返回值将会是为创建该 context 传递给 createContext 的 defaultValue。返回的值始终是最新的。如果 context 发生变化,React 会自动重新渲染读取 context 的组件。
接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。调用了 useContext 的组件总会在 context 值变化时重新渲染。如果重渲染组件的开销较大,你可以 通过使用 memoization 来优化。
当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。即使祖先使用 React.memo 或 shouldComponentUpdate,也会在组件本身使用 useContext 时重新渲染。
基础用法
// ThemeContext.js
import { useState, createContext, useContext } from "react";
const themes = {
  light: {
    color: "#000000",
    background: "#eeeeee"
  },
  dark: {
    color: "#ffffff",
    background: "#222222"
  }
};
// 设置Context 默认值
export const ThemeContext = createContext(themes.light);
// App.js
function App() {
  const [model, setModel] = useState('light');
  return (
    // 当value 变化时,调用了 useContext的组件重新渲染
    <ThemeContext.Provider value={themes[model]}>
      <Toolbar />
      <button onClick={() => setModel(model === 'light' ? 'dark' : 'light')}>Change Theme</button>
    </ThemeContext.Provider>
  );
}
function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}
function ThemedButton() {
  //useContext(MyContext) 只是让你能够读取 context 的值以及订阅 context 的变化。你仍然需要在上层组件树中使用 <Provider> 来为下层组件提供 context
  //调用了 useContext 的组件总会在 context 值变化时重新渲染 
  const theme = useContext(ThemeContext);
  return (
    <button style={{ ...theme }}>
      I am styled by theme context!
    </button>
  );
}
在传递对象和函数时优化重新渲染
你可以通过 context 传递任何值,包括对象和函数。
function MyApp() {
  const [currentUser, setCurrentUser] = useState(null);
  function login(response) {
    storeCredentials(response.credentials);
    setCurrentUser(response.user);
  }
  // 每当 MyApp 出现重新渲染(例如,路由更新)时,这里将会是一个 不同的 对象指向 不同的 函数,因此 React 还必须重新渲染树中调用 useContext(AuthContext) 的所有组件
  return (
    <AuthContext.Provider value={{ currentUser, login }}>
      <Page />
    </AuthContext.Provider>
  );
}
你可以使用 useCallback 包装 login 函数,并将对象创建包装到 useMemo 中
import { useCallback, useMemo } from 'react';
function MyApp() {
  const [currentUser, setCurrentUser] = useState(null);
  const login = useCallback((response) => {
    storeCredentials(response.credentials);
    setCurrentUser(response.user);
  }, []);
  const contextValue = useMemo(() => ({
    currentUser,
    login
  }), [currentUser, login]);
  // 即使 MyApp 需要重新渲染,调用 useContext(AuthContext) 的组件也不需要重新渲染,除非 currentUser 发生了变化。
  return (
    <AuthContext.Provider value={contextValue}>
      <Page />
    </AuthContext.Provider>
  );
}
传递hooks
context 对象可以是任意值, 所以你也可以通过 context 往下传一个 dispatch 或 hooks 函数
const TodosDispatch = React.createContext(null);
function TodosApp() {
  // 提示:`dispatch` 不会在重新渲染之间变化
  const [todos, dispatch] = useReducer(todosReducer);
  return (
    <TodosDispatch.Provider value={dispatch}>
      <DeepTree todos={todos} />
    </TodosDispatch.Provider>
  );
}
将相关逻辑迁移到一个文件当中
你可以选择将 context 逻辑迁移到一个单独的文件当中, 然后在需要使用的组件中导入它
import { createContext, useReducer } from 'react';
export const TasksContext = createContext(null);
export 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>
  );
}
