Context 简介
这里通过回答如下三个问题来对 Context 进行简单介绍,详细内容可以直接阅读官方文档。
Context 是什么?
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
Context 解决了什么问题?
避免显示地通过组件树逐层传递 props 的繁琐操作,可快速在多个组件中共享变量。
Context 最典型的使用场景是什么?
不同层级的组件需要访问相同的数据,例如用户信息、UI主题色、多语言设置等。
典型案例
例如考虑以下的场景:
点击 Header 区域的菜单能够改变整个页面的主题色,点击 Content 区域的按钮能够改变整个页面的语言,达到下面的效果:
可以看到,主题颜色和语言在所有模块都被用到了,这个时候用 Context API 是非常合适的。
创建 Context 对象
我们先建一个单独的文件 PageContext 来存储整个页面共享的变量:
import React from 'react'
const PageContext = React.createContext()
export default PageContext
使用 Provider 来提供 Context
在 Page 页面里面,我们把主题色和语言变量,以及改变主题色和改变语言函数,封装成一个完整的上下文,传给 PageContext.Provider 的 value 里面,这样 Page 的所有子组件、子组件的子组件,无论多少层级,都可以使用这个上下文中的变量和函数了:
import { Component } from 'react'
import Header from './Header'
import Body from './Body'
import PageContext from './PageContext'
import Footer from './Footer'
import './Page.css'
class Page extends Component {
state = { theme: 'red', lang: 'zh' }
changeTheme = () => {
this.setState(state => ({ theme: state.theme === 'red' ? 'green' : 'red' }))
}
changeLang = () => {
this.setState(state => ({ lang: state.lang === 'zh' ? 'en' : 'zh' }))
}
render() {
const contextVal = {
theme: this.state.theme,
changeTheme: this.changeTheme,
lang: this.state.lang,
changeLang: this.changeLang,
}
return (
<PageContext.Provider value={contextVal}>
<div style={{ width: '500px', border: `1px solid ${contextVal.theme}` }}>
<Header />
<Body />
<Footer />
</div>
</PageContext.Provider>
)
}
}
export default Page
三种消费 Context 的方式
在子孙组件中,使用上下文变量的方式有两种:
- 通过 Consumer 组件
- 通过类组件的静态属性 contextType
- 通过函数组件的 useContext 钩子
通过 Consumer 组件
在 Menu 区域中,我们定义 Title 组件,通过 Consumer 组件来消费上下文变量:
import { Component } from 'react'
import PageContext from './PageContext'
class Menu extends Component {
render() {
return (
<PageContext.Consumer>
{ctx => (
<div style={{ float: 'right', border: `1px solid ${ctx.theme}` }}>
{ctx.lang === 'zh' ? '菜单' : 'Menu'}
<button onClick={() => ctx.changeTheme()}>
{ctx.lang === 'zh' ? '改变主题' : 'change theme'}
</button>
</div>
)}
</PageContext.Consumer>
)
}
}
export default Menu
可以看到,Consumer 组件的孩子必须是一个函数,把上下文变量给传进来了,语法是:
<PageContext.Consumer>
{ ctx => /* 基于 context 值进行渲染*/ }
</PageContext.Consumer>
通过类组件的静态属性 contextType
在 Content 组件中,我们让静态属性 contextType 的值等于 PageContext,那么类的实例就可以用 this.context 来获取上下文了:
import { Component } from 'react'
import PageContext from './PageContext'
class Content extends Component {
static contextType = PageContext
render() {
return (
<div style={{ flex: 1, border: `1px solid ${this.context.theme}` }}>
{this.context.lang === 'zh' ? '内容' : 'Content'}
<button onClick={() => this.context.changeLang()}>
{this.context.lang === 'zh' ? '改变语言' : 'change lang'}
</button>
</div>
)
}
}
export default Content
注意,contextType 是固定的属性值,不能写错了,否则 this.context 拿不到上下文。
通过函数组件的 useContext 钩子
在函数组件中是没有 this 的,所以 React 官方提供了 useContext 钩子,我们假想在 Footer 区域有一个 Link 链接,点击也能改变主题,就可以这么写:
import { useContext } from 'react'
import PageContext from './PageContext'
function Link() {
const context = useContext(PageContext)
return (
<div style={{ border: `1px solid ${context.theme}` }}>
<button onClick={() => context.changeTheme()}>改变主题</button>
</div>
)
}
export default Link