Context
Context
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。例如:首选语言,UI 主题等。
React.createContext
const MyContext = React.createContext(defaultValue)创建一个 Context 对象。接受一个 defaultValue 参数,作为默认值。
当 React 渲染一个订阅了这个 Context 对象的组件,该组件会从组件树中匹配最近的
Provider,并从中读取到当前的context值。只有当组件所处的组件树中,没有匹配到
Provider时,其defaultValue参数才会生效。注意:将
undefined传递给Provider的value时,消费组件的defaultValue不会生效。
const ThemeContext = React.createContext(defaultValue) // 创建一个主题 Context
const ThemeProvider = ThemeContext.Provider // 主题 - 提供者
const ThemeConsumer = ThemeContext.Consumer // 订阅主题 - 消费者提供者 Context.Provider
<MyContext.Provider value={/* 某个值 */}>每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。
Provider接收一个value属性,传递给消费组件。- 一个
Provider可以和多个消费组件有对应关系。 - 多个
Provider也可以嵌套使用,里层的会覆盖外层的数据。
当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。
从 Provider 到其内部 consumer 组件(包括 .contextType 和 useContext)的传播不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件跳过更新的情况下也能更新。
当传递对象给 Provider 的 value 时,通过新旧值检测(浅比较)来确定变化,使用了与 Object.is 相同的算法。为了防止当 Provider 的父组件进行重渲染时,可能会在 consumers 组件中触发意外的渲染,可以将 value 状态提升到父节点的 state 里。
const ThemeContext = React.createContext(null) // 创建一个主题 Context
export default function Parent() {
const [contextValue, setContextValue] = useState({
color: '#000',
background: '#fff',
})
return (
<div>
<ThemeContext.Provider value={contextValue}>
<Child />
</ThemeContext.Provider>
</div>
)
}消费者
类组件 Class.contextType 方式
挂载在 class 上的 contextType 属性,可以赋值为由 React.createContext() 创建的 Context 对象,这样就可以通过 this.context 获取从组件树中匹配最近的 <MyContext.Provider> 提供的 Context value 值。
Class.contextType只能订阅单一的context。Class.contextType只适用于类组件。
class ExampleComponent extends React.Component {
componentDidMount() {
let value = this.context
/* 在组件挂载完成后,使用 ExampleContext 组件的值来执行一些有副作用的操作 */
/* do something ... */
}
componentDidUpdate() {
let contextValue = this.context
}
componentWillUnmount() {
let contextValue = this.context
}
render() {
let contextValue = this.context
/* 基于 ExampleContext 组件的值进行渲染 */
/* do something ... */
}
}
ExampleComponent.contextType = ExampleContext// 实验性的 public class fields 语法
class ExampleComponent extends React.Component {
static contextType = ExampleContext
render() {
let contextValue = this.context
/* 基于 ExampleContext 组件的值进行渲染 */
/* do something ... */
}
}函数组件 useContext 方式
const value = useContext(MyContext)接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。
当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。即使祖先使用 React.memo 或 shouldComponentUpdate,也会在组件本身使用 useContext 时重新渲染。
function Button() {
let theme = useContext(ThemeContext)
return <ExpensiveTree className={theme} />
}
/**
* 调用了 useContext 的组件,会在 context 值变化时重新渲染,通过使用 memoization 来优化。
* 因为某些原因你不能拆分上下文,可以通过将一个组件一分为二来优化渲染。
*/
function Button() {
let appContextValue = useContext(AppContext)
let theme = appContextValue.theme
return <ThemedButton theme={theme} />
}
const ThemedButton = memo(({ theme }) => {
return <ExpensiveTree className={theme} />
})
/**
* 调用了 useContext 的组件,会在 context 值变化时重新渲染,通过使用 memoization 来优化。
* 通过将返回值包裹在 useMemo 中并指定其依赖关系,将其保留在一个组件中。
*/
function Button() {
let appContextValue = useContext(AppContext)
let theme = appContextValue.theme
return useMemo(() => {
return <ExpensiveTree className={theme} />
}, [theme])
}订阅者 Context.Consumer 方式
const MyContext = React.createContext(null)
export default function () {
return (
<MyContext.Consumer>
{/* 将 contextValue 内容转化成 props */}
{contextValue => <ConsumerComponent {...contextValue} />}
</MyContext.Consumer>
)
}Context.Consumer 方式采取 render props 函数方式。
- 接收组件树中匹配最近的
<MyContext.Provider>提供的 Contextvalue值,作为 render props 函数的参数。 - 将接受的参数取出,作为函数返回的 React 节点的
props传入。
其他
Context.displayName
context 对象接受一个名为 displayName 的 property,类型为字符串。React DevTools 使用该字符串来确定 context 要显示的内容。
const MyContext = React.createContext(/* some value */);
MyContext.displayName = 'MyDisplayName';
<MyContext.Provider> // "MyDisplayName.Provider" 在 DevTools 中
<MyContext.Consumer> // "MyDisplayName.Consumer" 在 DevTools 中Context 高级用法
动态 Context
export const themes = {
light: { color: '#000', background: '#eee' },
dark: { color: '#fff', background: '#222' },
}
export const ThemeContext = React.createContext(themes.dark)
function Child() {
const { color, background } = React.useContext(ThemeContext)
return useMemo(() => {
return (
<div style={{ margin: '10px 0', padding: '10px', border: '1px solid' }}>
<div>【Child】</div>
<div style={{ color, background }}>消费者</div>
</div>
)
})
}
function Parent() {
const [themeContextValue, setThemeContextValue] = useState(themes.light)
const changeTheme = () => {
const theme = themeContextValue === themes.dark ? themes.light : themes.dark
setThemeContextValue(theme)
}
return (
<div style={{ padding: '10px', border: '1px solid' }}>
<div>【Parent】</div>
<button onClick={changeTheme}>切换主题</button>
<ThemeContext.Provider value={themeContextValue}>
<Child />
</ThemeContext.Provider>
</div>
)
}嵌套 Provider
多个 Provider 之间可以相互嵌套,来保存/切换一些全局数据。
export const themes = {
light: { color: '#000', background: '#eee' },
dark: { color: '#fff', background: '#222' },
}
export const languages = {
chinese: 'CH',
english: 'EN',
}
export const ThemeContext = React.createContext(themes.light) // 主题 Context
export const LanguageContext = React.createContext(languages.chinese) // 语言 Context
function Child() {
return (
<div style={{ margin: '10px 0', padding: '10px', border: '1px solid' }}>
<div>【Child】</div>
<ThemeContext.Consumer>
{themeContextValue => (
<LanguageContext.Consumer>
{languageContextValue => {
const { color, background } = themeContextValue
return (
<div style={{ color, background }}>
{languageContextValue === languages.chinese
? '你好,世界!'
: 'Hello, World!'}
</div>
)
}}
</LanguageContext.Consumer>
)}
</ThemeContext.Consumer>
</div>
)
}
function Parent() {
const [themeContextValue, setThemeContextValue] = React.useState(themes.light)
const [languageContextValue, setLanguageContextValue] = React.useState(
languages.chinese
)
const changeTheme = () => {
const theme = themeContextValue === themes.dark ? themes.light : themes.dark
setThemeContextValue(theme)
}
const changeLanguage = () => {
const language =
languageContextValue === languages.chinese
? languages.english
: languages.chinese
setLanguageContextValue(language)
}
return (
<div style={{ padding: '10px', border: '1px solid' }}>
<div>【Parent】</div>
<button onClick={changeTheme}>切换主题</button>
<button onClick={changeLanguage}>切换语言</button>
<ThemeContext.Provider value={themeContextValue}>
<LanguageContext.Provider value={languageContextValue}>
<Child />
</LanguageContext.Provider>
</ThemeContext.Provider>
</div>
)
}逐层传递 Provider
Provider可以逐层传递context,即一个context可以用多个Provider传递。- 组件获取
context时,会获取离当前组件最近的上一层Provider。 - 下一层级的
Provider会覆盖上一层级的Provider。
export const themes = {
light: { color: '#000', background: '#eee' },
dark: { color: '#fff', background: '#222' },
}
export const ThemeContext = React.createContext(themes.light) // 主题 Context
function GrandChild() {
return (
<div style={{ margin: '10px 0', padding: '10px', border: '1px solid' }}>
<div>【GrandChild】</div>
<ThemeContext.Consumer>
{childThemeContextValue => {
const { color, background } = childThemeContextValue
return (
<div className="sonBox" style={{ color, background }}>
第二层 Provider 消费者
</div>
)
}}
</ThemeContext.Consumer>
</div>
)
}
function Child() {
const { color, background } = useContext(ThemeContext)
const [childThemeContextValue, setChildThemeContextValue] = useState(
themes.dark
)
const changeTheme = () => {
const theme =
childThemeContextValue === themes.dark ? themes.light : themes.dark
setChildThemeContextValue(theme)
}
return (
<div style={{ margin: '10px 0', padding: '10px', border: '1px solid' }}>
<div>【Child】</div>
<div style={{ color, background }}>
<div>第一层 Provider 消费者</div>
<button onClick={changeTheme}>切换主题</button>
<ThemeContext.Provider value={childThemeContextValue}>
<GrandChild />
</ThemeContext.Provider>
</div>
</div>
)
}
function Parent() {
const [themeContextValue, setThemeContextValue] = useState(themes.light)
const changeTheme = () => {
const theme = themeContextValue === themes.dark ? themes.light : themes.dark
setThemeContextValue(theme)
}
return (
<div style={{ padding: '10px', border: '1px solid' }}>
<div>【Parent】</div>
<button onClick={changeTheme}>切换主题</button>
<ThemeContext.Provider value={themeContextValue}>
<Child />
</ThemeContext.Provider>
</div>
)
}