#题引:我认为跟着官方文档学习不会走歪路
通常来说,你会通过 props 将信息从父组件传递到子组件。但是,如果你必须通过许多中间组件向下传递 props,或是在你应用中的许多组件需要相同的信息,传递 props 会变的十分冗长和不便。Context 允许父组件向其下层无论多深的任何组件提供信息,而无需通过 props 显式传递。
假设Heading
组件接收一个level
参数来决定它标题尺寸的场景
export default function Page() {return (<Section><Heading level={1}>主标题</Heading><Section><Heading level={2}>副标题</Heading><Heading level={2}>副标题</Heading><Heading level={2}>副标题</Heading><Section><Heading level={3}>子标题</Heading><Heading level={3}>子标题</Heading><Heading level={3}>子标题</Heading><Section><Heading level={4}>子子标题</Heading><Heading level={4}>子子标题</Heading><Heading level={4}>子子标题</Heading></Section></Section></Section></Section>);
}export default function Section({ children }) {return (<section className="section">{children}</section>);
}export default function Heading({ level, children }) {switch (level) {case 1:return <h1>{children}</h1>;case 2:return <h2>{children}</h2>;case 3:return <h3>{children}</h3>;case 4:return <h4>{children}</h4>;case 5:return <h5>{children}</h5>;case 6:return <h6>{children}</h6>;default:throw Error('未知的 level:' + level);}
}
这就是 context 大显身手的地方
Step 1:创建 context
创建这个 context,并 将其从一个文件中导出,这样你的组件才可以使用它
LevelContext.js
import { createContext } from 'react';export const LevelContext = createContext(1);
createContext 只需默认值这么一个参数。在这里, 1 表示最大的标题级别,但是你可以传递任何类型的值(甚至可以传入一个对象)
Step 2:使用 Context
从 React 中引入 useContext Hook 以及你刚刚创建的 context
export default function Heading({ children }) {const level = useContext(LevelContext);// ...
}
useContext
告诉 React Heading
组件想要读取 LevelContext
Step 3:提供 context
Section 组件目前渲染传入它的子组件,把它们用 context provider 包裹起来 以提供 LevelContext 给它们
import { LevelContext } from './LevelContext.js';export default function Section({ level, children }) {return (<section className="section"><LevelContext.Provider value={level}>{children}</LevelContext.Provider></section>);
}
这告诉 React:“如果在 Section
组件中的任何子组件请求 LevelContext,给他们这个 level。”组件会使用 UI 树中在它上层最近的那个 <LevelContext.Provider> 传递过来的值。
由于 context 让你可以从上层的组件读取信息,每个 Section
都会从上层的 Section
读取 level,并自动向下层传递 level + 1。
你可以像下面这样做:
import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';export default function Section({ children }) {const level = useContext(LevelContext);return (<section className="section"><LevelContext.Provider value={level + 1}>{children}</LevelContext.Provider></section>);
}
这样修改之后,你不用将 level 参数传给 Section>
或者是 Heading
了
export default function Page() {return (<Section><Heading>主标题</Heading><Section><Heading>副标题</Heading><Heading>副标题</Heading><Heading>副标题</Heading><Section><Heading>子标题</Heading><Heading>子标题</Heading><Heading>子标题</Heading><Section><Heading>子子标题</Heading><Heading>子子标题</Heading><Heading>子子标题</Heading></Section></Section></Section></Section>);
}
在 React 中,覆盖来自上层的某些 context 的唯一方法是将子组件包裹到一个提供不同值的 context provider 中
Context 的使用场景
- 主题: 如果你的应用允许用户更改其外观
- 当前账户: 许多组件可能需要知道当前登录的用户信息
- 路由: 大多数路由解决方案在其内部使用 context 来保存当前路由
- 状态管理: 随着你的应用的增长,最终在靠近应用顶部的位置可能会有很多 state。许多遥远的下层组件可能想要修改它们。通常 将 reducer 与 context 搭配使用来管理复杂的状态并将其传递给深层的组件来避免过多的麻烦。
Context 不局限于静态值。如果你在下一次渲染时传递不同的值,React 将会更新读取它的所有下层组件。
useContext与与useState结合
1: 创建 Context 和 Provider 组件:
使用 Provider 来包裹需要访问这个上下文的组件,并提供一个状态来管理上下文的值
import React, { createContext, useState } from 'react';const MyContext = createContext();
const MyProvider = ({ children }) => {const [value, setValue] = useState("初始值");return (<MyContext.Provider value={{ value, setValue }}>{children}</MyContext.Provider>);
};
2: 使用 useContext:
在子组件中使用 useContext 来访问上下文的值和变更函数
import React, { useContext } from 'react';const MyComponent = () => {const { value, setValue } = useContext(MyContext);const changeValue = () => {setValue("新值");};return (<div><p>当前值: {value}</p><button onClick={changeValue}>变更值</button></div>);
};
3: 包裹应用
const App = () => {return (<MyProvider><MyComponent /></MyProvider>);
};
useContext与与useReducer结合
1: 定义 Reducer 函数
const initialState = { count: 0 };const reducer = (state, action) => {switch (action.type) {case 'increment':return { count: state.count + 1 };case 'decrement':return { count: state.count - 1 };default:return state;}
};
2:创建 Context 和 Provider 组件
使用 useReducer 在 Provider 中管理状态,并将状态和 dispatch 方法提供给上下文
import React, { createContext, useReducer } from 'react';const MyContext = createContext();const MyProvider = ({ children }) => {const [state, dispatch] = useReducer(reducer, initialState);return (<MyContext.Provider value={{ state, dispatch }}>{children}</MyContext.Provider>);
};
3:使用 Context 和 Reducer
在子组件中使用 useContext 来访问状态和 dispatch 方法
const Counter = () => {const { state, dispatch } = useContext(MyContext);return (<div><p>计数: {state.count}</p><button onClick={() => dispatch({ type: 'increment' })}>增加</button><button onClick={() => dispatch({ type: 'decrement' })}>减少</button></div>);
};
4:包裹应用
const App = () => {return (<MyProvider><MyComponent /></MyProvider>);
};