Context 上下文
Context 通过组件树提供了一个传递数据的方法,从而避免了在每一个层级手动的传递 props 属性。
在一个典型的 React 应用中,数据是通过 props 属性由上向下(由父及子)的进行传递的,但这对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这是应用程序中许多组件都所需要的。 Context 提供了一种在组件之间共享此类值的方式,而不必通过组件树的每个层级显式地传递 props
。
简单说就是,当你不想在组件树中通过逐层传递 props 或者 state 的方式来传递数据时,可以使用 Context 来实现跨层级的组件数据传递。
- 使用 props 或者 state 传递数据,数据自顶下流。
- 使用 Context,可以跨越组件进行数据传递
何时使用 Context
Context 设计目的是为共享那些被认为对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。例如,在下面的代码中,我们通过一个“theme”属性手动调整一个按钮组件的样式:
function ThemedButton(props) {
return <Button theme={props.theme} />;
}
// 中间组件
function Toolbar(props) {
// Toolbar 组件必须添加一个额外的 theme 属性
// 然后传递它给 ThemedButton 组件
return (
<div>
<ThemedButton theme={props.theme} />
</div>
);
}
class App extends React.Component {
render() {
return <Toolbar theme="dark" />;
}
}
使用 context, 我可以避免通过中间元素传递 props:
// 创建一个 theme Context, 默认 theme 的值为 light
const ThemeContext = React.createContext('light');
function ThemedButton(props) {
// ThemedButton 组件从 context 接收 theme
return (
<ThemeContext.Consumer>
{theme => <Button {...props} theme={theme} />}
</ThemeContext.Consumer>
);
}
// 中间组件
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
class App extends React.Component {
render() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
注意
不要仅仅为了避免在几个层级下的组件传递 props 而使用 context,它是被用于在多个层级的多个组件需要访问相同数据的情景。
React.createContext
const {Provider, Consumer} = React.createContext(defaultValue);
创建一对 { Provider, Consumer }
。当 React 渲染 context 组件 Consumer 时,它将从组件树的上层中最接近的匹配的 Provider 读取当前的 context 值。
如果上层的组件树没有一个匹配的 Provider,而此时你需要渲染一个 Consumer 组件,那么你可以用到 defaultValue
。这有助于在不封装它们的情况下对组件进行测试。
Provider
<Provider value={/* some value */}>
React 组件允许 Consumers 订阅 context 的改变。
接收一个 value
属性传递给 Provider 的后代 Consumers。一个 Provider 可以联系到多个 Consumers。Providers 可以被嵌套以覆盖组件树内更深层次的值。
Consumer
<Consumer>
{value => /* render something based on the context value */}
</Consumer>
一个可以订阅 context 变化的 React 组件。
接收一个 函数作为子节点 . 函数接收当前 context 的值并返回一个 React 节点。传递给函数的 value
将等于组件树中上层 context 的最近的 Provider 的 value
属性。如果 context 没有 Provider ,那么 value
参数将等于被传递给 createContext()
的 defaultValue
。
注意
关于此案例的更多信息, 请看 render props .
每当 Provider 的值发生改变时, 作为 Provider 后代的所有 Consumers 都会重新渲染。 从 Provider 到其后代的 Consumers 传播不受 shouldComponentUpdate 方法的约束,因此即使祖先组件退出更新时,后代 Consumer 也会被更新。
通过使用与 Object.is 相同的算法比较新值和旧值来确定变化。
注意
(这在传递对象作为
value
时会引发一些问题 Caveats .)
动态 Context
一个更加复杂的例子:
import React from 'react';
import ReactDOM from 'react-dom';
const ThemeContext = React.createContext({
background: 'red',
color: 'white'
});
通过静态方法 React.createContext()
创建一个 Context
对象,这个 Context 对象包含两个组件, <Provider />
和 <Consumer />
。
class App extends React.Component {
render () {
return (
<ThemeContext.Provider value={ {background: 'green', color: 'white'}}>
<Header />
</ThemeContext.Provider>
);
}
}
复制代码 <Provider />
的 value 相当于现在的 getChildContext()
。
class Header extends React.Component {
render () {
return (
<Title>Hello React Context API</Title>
);
}
}
class Title extends React.Component {
render () {
return (
<ThemeContext.Consumer>
{context => (
<h1 style={ {background: context.background, color: context.color}}>
{this.props.children}
</h1>
)}
</ThemeContext.Consumer>
);
}
}
复制代码 <Consumer />
的 children
必须是一个函数,通过函数的参数获取 <Provider />
提供的 Context
。
几个可以直接获取 Context 的地方
实际上,除了实例的 context
属性( this.context
), React
组件还有很多个地方可以直接访问父组件提供的 Context
。比如构造方法:
constructor(props, context)
比如生命周期:
componentWillReceiveProps(nextProps, nextContext)
shouldComponentUpdate(nextProps, nextState, nextContext)
componetWillUpdate(nextProps, nextState, nextContext)
对于面向函数的无状态组件,可以通过函数的参数直接访问组件的 Context。
const StatelessComponent = (props, context) => (
......
)
作用于多个上下文
为了保持 context 快速进行二次渲染, React 需要使每一个 Consumer 在组件树中成为一个单独的节点。
// 主题上下文, 默认 light
const ThemeContext = React.createContext('light');
// 登陆用户上下文
const UserContext = React.createContext();
// 一个依赖于两个上下文的中间组件
function Toolbar(props) {
return (
<ThemeContext.Consumer>
{theme => (
<UserContext.Consumer>
{user => (
<ProfilePage user={user} theme={theme} />
)}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
);
}
class App extends React.Component {
render() {
const {signedInUser, theme} = this.props;
// App 组件提供上下文的初始值
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={signedInUser}>
<Toolbar />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
}
如果两个或者多个上下文的值经常被一起使用,也许你需要考虑你自己渲染属性的组件提供给它们。
实例
import React, { Component } from 'react'
const LocalContext = React.createContext();
const { Provider, Consumer } = LocalContext;
function Container(props) {
return <Title />
}
function Title(props) {
return (
<div>
<Consumer>
{ context => {
return (
<div>
{context.name} - { context.age}
</div>
)
}}
</Consumer>
</div>
)
}
class DM extends Component {
componentDidMount() {
console.log(this.props);
}
render() {
return (
<div>
<Consumer>
{
user => (<div>
<p>{ user.name }</p>
<p>{ user.age }</p>
</div>
)
}
</Consumer>
---{ this.props.name }----
</div>
)
}
}
class ContextDemo extends Component {
constructor (props, context) {
super(props, context)
this.state = {
User: {
age: 19,
name: 'aicoder'
}
}
}
render () {
return (
<div>
<Provider value={ this.state.User }>
<Container></Container>
<DM></DM>
</Provider>
<hr/>
<input
onClick={
() => this.setState(preState => {
return {User: { ...preState.User, age: preState.User.age + 1 }}
})
}
className="button is-primary"
value={ this.state.User.name }
type="button"
/>
</div>
)
}
}
export default ContextDemo
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论