React-context
Context
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。
在下面的代码中,我们通过一个 “theme” 属性手动调整一个按钮组件的样式,使用 context, 我们可以避免通过中间元素传递 props:
// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
// 在这个例子中,我们将 “dark” 作为当前的值传递下去。
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// React 会往上找到最近的 theme Provider,然后使用它的值。
// 在这个例子中,当前的 theme 值为 “dark”。
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
API
React.createContext
const MyContext = React.createContext(defaultValue);
创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。
只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。这有助于在不使用 Provider 包装组件的情况下对组件进行测试。注意:将 undefined 传递给 Provider 的 value 时,消费组件的 defaultValue 不会生效
Context.Provider
<MyContext.Provider value={/* 某个值 */}>
每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。
Provider 接收一个 value 属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。
当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新。
通过新旧值检测来确定变化,使用了与 Object.is 相同的算法
React Hooks-useContext
useContext
const value = useContext(MyContext);
接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。
当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。
useContext 的参数必须是 context 对象本身:
- 正确:
useContext(MyContext) - 错误:
useContext(MyContext.Consumer) - 错误:
useContext(MyContext.Provider)
示例
interface IThemeProps{
[key: string]: { color: string; background: string;}
}
const themes: IThemeProps = {
'light': {
color: '#000',
background: '#eee',
},
'dark': {
color: '#fff',
background: '#222',
}
}
const ThemesContext = React.createContext(themes.light)
上例中,通过Context创建了一个全局的样式:深色dark和浅色light。
以下是有关ThemesContext类型定义
interface Context<T> {
Provider: Provider<T>;
Consumer: Consumer<T>;
displayName?: string;
}
Context接口定义了两个属性:Provider数据的提供者和Consumer数据的消费者。
继续完善
import React from 'react';
import LikeButton from './components/LikeButton'
interface IThemeProps{
[key: string]: { color: string; background: string;}
}
const themes: IThemeProps = {
'light': {
color: '#000',
background: '#eee',
},
'dark': {
color: '#fff',
background: '#222',
}
}
export const ThemesContext = React.createContext(themes.light)
function App() {
return (
<ThemeContext.Provider value={themes.light}>
<LikeButton />
</ThemeContext.Provider>
);
}
这时在它里面的组件LikeButton想要使用全局创建的Context,只要时Provider包裹的组件啊都可以快捷的使用:
import React, { useState, useContext } from 'react'
import { ThemesContext } from '../App'
const LikeButton: React.FC = () => {
const theme = useContext(ThemesContext)
const style = {
color: theme.color,
background: theme.background
}
const [like, setLike] = useState(0)
return (
<button style={style} onClick={() => { setLike(like + 1) }}>
{like}👍
</button>
)
}
export default LikeButton
上例中,我们首先引入 ThemesContext,然后通过useContext来获取Context定义的theme的值,再将style设置给button。即完成了数据的传递。