前言
Context
被翻译为上下文,在编程领域,这是一个经常会接触到的概念,React中也有。
在React
的官方文档中,Context
被归类为高级部分(Advanced)
,属于React的高级API,但官方并不建议在稳定版的App中使用Contex
。
不过,这并非意味着我们不需要关注Context
。事实上,很多优秀的React
组件都通过Context
来完成自己的功能,比如react-redux的<Provider />
,就是通过Context
提供一个全局态的store
,拖拽组件react-dnd
,通过Context
在组件中分发DOM
的Drag
和Drop
事件,路由组件react-router
通过Context
管理路由状态等等。在React组件开发中,如果用好Context,可以让你的组件变得强大,而且灵活。
初识React Context
当你不想在组件树中通过逐层传递props
或者state
的方式来传递数据时,可以使用Context来实现跨层级的组件数据传递。
使用
props
或者state
传递数据,数据自顶下流。使用Context,可以跨越组件进行数据传递。
如何使用Context
如果要Context发挥作用,需要用到两种组件,一个是Context生产者(Provider)
,通常是一个父节点,另外是一个Context的消费者(Consumer)
,通常是一个或者多个子节点。所以Context的使用基于生产者消费者模式。
对于父组件,也就是Context生产者,需要通过一个静态属性childContextTypes
声明提供给子组件的Context对象的属性,并实现一个实例getChildContext
方法,返回一个代表Context的纯对象 (plain object) 。
import React from 'react'
import PropTypes from 'prop-types'
class MiddleComponent extends React.Component {
render () {
return <ChildComponent />
}
}
class ParentComponent extends React.Component {
// 声明Context对象属性
static childContextTypes = {
propA: PropTypes.string,
methodA: PropTypes.func
}
// 返回Context对象,方法名是约定好的
getChildContext () {
return {
propA: 'propA',
methodA: () => 'methodA'
}
}
render () {
return <MiddleComponent />
}
}
而对于Context的消费者,通过如下方式访问父组件提供的Context。
import React from 'react'
import PropTypes from 'prop-types'
class ChildComponent extends React.Component {
// 声明需要使用的Context属性
static contextTypes = {
propA: PropTypes.string
}
render () {
const {
propA,
methodA
} = this.context
console.log(`context.propA = ${propA}`) // context.propA = propA
console.log(`context.methodA = ${methodA}`) // context.methodA = undefined
return ...
}
}
子组件需要通过一个静态属性contextTypes
声明后,才能访问父组件Context
对象的属性,否则,即使属性名没写错,拿到的对象也是undefined
。
对于无状态子组件(Stateless Component),可以通过如下方式访问父组件的Context
import React from 'react'
import PropTypes from 'prop-types'
const ChildComponent = (props, context) => {
const {
propA
} = context
console.log(`context.propA = ${propA}`) // context.propA = propA
return ...
}
ChildComponent.contextProps = {
propA: PropTypes.string
}
而在接下来的发行版本中,React对Context的API做了调整,更加明确了生产者消费者模式的使用方式。
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的新API更加贴近React的风格。
总结:
React的context就是一个全局变量,可以从根组件跨级别在React的组件中传递。React context 的API 有两个版本,React16.x 之前 的是老版本的 context,之后的是新版本的context。
1.老版本的context
getChildContext
根组件中声明,一个函数,返回一个对象,就是context childContextTyp
e根组件中声明,指定context的结构类 型,如不指定,会产生错误
contextTypes
子孙组件中声明,指定要接收的context的结构类型,可以只是context的一部分结构。contextTypes 没有定义,context将是一个空对象。
this.context
在子孙组件中通过此来获取上下文
(注:从React v15.5开始 ,React.PropTypes 助手函数已被弃用,可使用 prop-types 库 来定义contextTypes)
举例如下:
//根组件
class MessageList extends React.Component {
getChildContext() {
return {color: "purple",text: "item text"};
}
render() {
const children = this.props.messages.map((message) =>
<Message text={message.text} />
);
return <div>{children}</div>;
}
}
MessageList.childContextTypes = {
color: React.PropTypes.string
text: React.PropTypes.string
};
//中间组件
class Message extends React.Component {
render() {
return (
<div>
<MessageItem />
<Button>Delete</Button>
</div>
);
}
}
//孙组件(接收组件)
class MessageItem extends React.Component {
render() {
return (
<div>
{this.context.text}
</div>
);
}
}
MessageItem.contextTypes = {
text: React.PropTypes.string
};
class Button extends React.Component {
render() {
return (
<button style={{background: this.context.color}}>
{this.props.children}
</button>
);
}
}
Button.contextTypes = {
color: React.PropTypes.string
};
2.新版本的context
新版本的React context使用了Provider和Customer模式,和react-redux的模式非常像。在顶层的Provider中传入value,
在子孙级的Consumer中获取该值,并且能够传递函数,用来修改context,如下代码所示:
//创建Context组件
const ThemeContext = React.createContext({
theme: 'dark',
toggle: () => {}, //向上下文设定一个回调方法
});
//运行APP
class App extends React.Component {
constructor(props) {
super(props);
this.toggle = () => { //设定toggle方法,会作为context参数传递
this.setState(state => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark,
}));
};
this.state = {
theme: themes.light,
toggle: this.toggle,
};
}
render() {
return (
<ThemeContext.Provider value={this.state}> //state包含了toggle方法
<Content />
</ThemeContext.Provider>
);
}
}
//中间组件
function Content() {
return (
<div>
<Button />
</div>
);
}
//接收组件
function Button() {
return (
<ThemeContext.Consumer>
{({theme, toggle}) => (
<button
onClick={toggle} //调用回调
style={{backgroundColor: theme}}>
Toggle Theme
</button>
)}
</ThemeContext.Consumer>
);
}
案例2
// 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(props) {
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} />;
}
}
// 也可以按照这种形式获取
function ThemedButton(){
return (
<ThemeContext.Counsumer>
{theme =>(
<Button theme={theme} />
)
}
</ThemeContext.Counsumer>
);
}
详细用法可以参考官方文档:https://react.docschina.org/docs/context.html#reactcreatecontext
3. context 在react hooks中的使用
React Hooks是React 16.8.6版本为函数式组件添加了在各生命周期中获取state和props的通道。可让您在不编写类的情况下使用 state(状态) 和其他 React 功能。不再需要写class组件,你的所有组件都将是Function。
hooks为我们提供了useContext方法来操作context。useContext可以很方便去订阅context的改变,并在合适的时候重渲染组件。例如上面的函数式组件中,通过Consumer的形式获取Context的数据,有了useContext可以改写成下面:
function ThemedButton(){
const value = useContext(ThemeContxet);
return (
<Button theme={value} />
);
}
通过useCOntext我们可以直接拿到context的值,而不再需要在函数式组件外面用context.Consumer包裹了
4. context在如下的生命周期钩子中可以使用
constructor(props, context)
componentWillReceiveProps(nextProps, nextContext)
shouldComponentUpdate(nextProps, nextState, nextContext)
componentWillUpdate(nextProps, nextState, nextContext)
componentDidUpdate(prevProps, prevState, prevContext)
5. 在无状态组件中可以通过参数传入
function D(props, context) {
return (
<div>{this.context.user.name}</div>
);
}
D.contextTypes = {
user: React.PropTypes.object.isRequired
}
5. React context的局限性
- 在组件树中,如果中间某一个组件
ShouldComponentUpdate returning false
了,会阻碍 context 的正常传值,导致子组件无法获取更新。 - 组件本身
extends React.PureComponent
也会阻碍 context 的更新。
注意点:
- Context 应该是唯一不可变的.
- 组件只在初始化的时候去获取 Context
参考: