前言
Context 通过组件树提供了一个传递数据的方法(类似一个简易的redux来存放公共数据),从而避免了在每一个层级手动的传递 props 属性。
有部分小伙伴应该使用props属性进行组件向下传值的操作。当多个组件嵌套时候,你需要从最外层的组件一层一层地通过props将数据传到最里层的组件时,你就需要慢慢向上寻找最初的值是什么,何苦呢?
Context 提供了一种在组件之间共享这些值的方法,而无需显式地通过树的每一层传递 prop。
1. 基本使用
(1)创建和注入context
context一般在顶层组件创建,方便数据的全局注入和全局共享
import React,{Component,createContext} from 'react'
//使用createContext创建context的初始默认值
//可以是对象,字符串等任意类型的值
export const GlobalContext = createContext({name:'scw'})
//App为顶层组件
class App extends Component{
constructor(props) {
super(props);
}
handleClick = (e) => {
console.log('组件B点击了');
}
render() {
return (
//使用Provider进行context全局注入
//这里的value表示对context重新赋值
<GlobalContext.Provider value={{name:'scw1',onClick:this.handleClick.bind(this)}}>
<B />
</GlobalContext.Provider>
)
}
}
export default App;
(2)消费Context
import { GlobalContext } from './App'
const B = class extends Component{
render() {
return <GlobalContext.Consumer>
{(globalContext)=>
//消费Context的数据
<span onClick={globalContext.onClick}>
{globalContext.name}
</span>
}
</GlobalContext.Consumer>
}
}
export default B
- 使用Provider在顶层组件注入后,里层的所有子组件及其后代组件均可访问到对应的context数据
- 调用creatContext时可以传入默认值,但该值不是Provider的默认值,而是作为Consumer的默认值,仅当没有Provider的时候才生效。换句话说,只有在没有使用Provider的情况下使用了Consumer,Consumer消费的才是默认值。如果注入Provider时没有设置value值,则Consumer拿到的value为undefined
- 多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。
- 当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新。
// Theme context,默认的 theme 是 “light” 值
const ThemeContext = React.createContext('light');
// 用户登录 context
const UserContext = React.createContext({
name: 'Guest',
});
class App extends React.Component {
render() {
const {signedInUser, theme} = this.props;
// 提供初始 context 值的 App 组件
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={signedInUser}>
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
}
function Layout() {
return (
<div>
<Sidebar />
<Content />
</div>
);
}
// 一个组件可能会消费多个 context
function Content() {
return (
<ThemeContext.Consumer>
{theme => (
<UserContext.Consumer>
{user => (
<ProfilePage user={user} theme={theme} />
)}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
);
}
2. contextType
给顶层class组件(即Provider注入的那个组件中)设置静态属性contextType并赋值为新建的context,则你可以在任何生命周期中通过this.context访问到它,包括 render 函数中。
const MyContext = React.createContext(defaultValue);
class MyClass extends React.Component {
static contextType = MyContext
componentDidMount() {
let value = this.context;
/* 在组件挂载完成后,使用 MyContext 组件的值来执行一些有副作用的操作 */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* 基于 MyContext 组件的值进行渲染 */
}
}
如果是函数组件,则在props后面,会多出一个参数context:
const Title = (props, context) => {
const {textColor} = context.theme;
return (
<p style={{color: color}}>
我是标题
</p>
);
};
Title.contextTypes = {
theme: PropTypes.object
};
3. 注意事项
因为 context 会使用参考标识(reference identity)来决定何时进行渲染,这里可能会有一些陷阱,当 provider 的父组件进行重渲染时,可能会在 consumers 组件中触发意外的渲染。举个例子,当每一次 Provider 重渲染时,以下的代码会重渲染所有下面的 consumers 组件,因为 value 属性总是被赋值为新的对象:
class App extends React.Component {
render() {
return (
<MyContext.Provider value={{something: 'something'}}>
<Toolbar />
</MyContext.Provider>
);
}
}
为了防止这种情况,将 value 状态提升到父节点的 state 里:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: {something: 'something'},
};
}
render() {
return (
<Provider value={this.state.value}>
<Toolbar />
</Provider>
);
}
}
所以,一般使用context都是在Provider组件中用state来控制全局状态,然后在Consumer组件中通过调用context中定义的函数state来达到最终回到Provider来更新state达到更新context数据的效果
我们并不建议大量使用 context,因为尽管它可以减少逐层传递,但当组件结构复杂的时候,我们并不知道 context 是从哪里传过来的。Context 就像一个全局变量一样,而全局变量正是导致应用走向混乱的罪魁祸首之一,给组件带来了外部依赖的副作用。在大部分情况下,我们并不推荐使用 context 。使用 context 比较好的场景是真正意义上的全局信息且不会更改,例如界面主题、用户信息等