前言
上文我们说到,可以使用redux来管理数据,这篇文章则使用Context来管理数据
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。 Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言
Context使用顶层共享组件来存储和提供数据,该组件嵌套的子组件均可以使用与更新共享组件的数据,因此子组件也被称为消费组件

Context 主要应用场景在于很多不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差
基本搭建
新建Navbar和SongList组件,添加相应样式,然后在App.js中使用,效果是这样的:

创建Context
- 创建Context对象,定义一个浅色 vs 深色主题的样式,作用到Navbar和SongList组件上
- 取isLightTheme为标识,当值为true时,代表子组件选择使用浅色主题;
- 当值为false时,代表子组件选择使用深色主题
import React, { Component, createContext } from 'react';
export const ThemeContext = createContext();
class ThemeContextProvider extends Component {
state = {
isLightTheme:true,
light:{
ui:'#ddd',
font:'#555',
bg:'#eee'
},
dark:{
ui:'#333',
font:'#ddd',
bg:'#555'
}
}
render(){
return (
<ThemeContext.Provider value={{...this.state}}>
{this.props.children}
</ThemeContext.Provider>
)
}
}
export default ThemeContextProvider;
在App.js中引入ThemeContextProveder组件; 包裹住需要设置主题的组件:Navbar和SongList
import Navbar from "./components/Navbar";
import SongList from "./components/SongList";
import ThemeContextProvider from "./contexts/ThemeContext";
function App() {
return (
<div className="App">
<ThemeContextProvider>
<Navbar />
<SongList />
</ThemeContextProvider>
</div>
);
}
export default App;
这样我们就能在Navbar和SongList里使用共享的数据了
获取数据的方式有两种,一种是通过contextType获取,另一种是通过consumer获取
使用contextType获得数据
Navbar.js
import React, { Component } from 'react'
import { ThemeContext } from '../contexts/ThemeContext';
class Navbar extends Component {
static contextType = ThemeContext;
render() {
const { isLightTheme, light, dark } = this.context;
const theme = isLightTheme ? light : dark;
return (
<nav style={{background:theme.bg, color:theme.font}}>
<h1>Context App</h1>
<ul>
<li>Home</li>
<li>About</li>
<li>Contact</li>
</ul>
</nav>
)
}
}
export default Navbar;
效果:浅色主题的导航栏

SongList.js
import React, {Component} from 'react'
import { ThemeContext } from '../contexts/ThemeContext';
class SongList extends Component {
static contextType = ThemeContext;
render(){
const { isLightTheme, light, dark } = this.context;
const theme = isLightTheme ? light : dark;
return (
<div className='song-list' style={{background:theme.bg, color:theme.font}}>
<ul>
<li style={{background:theme.ui}}>大鱼</li>
<li style={{background:theme.ui}}>幽灵公主</li>
<li style={{background:theme.ui}}>望</li>
</ul>
</div>
)
}
}
export default SongList;
效果:浅色主题的导航栏和主体部分

使用consumer获取共享数据
Navbar.js
import React, { Component } from 'react'
import { ThemeContext } from '../contexts/ThemeContext';
class Navbar extends Component {
render() {
return (
<ThemeContext.Consumer>
{
(context) => {
const { isLightTheme, light, dark } = context;
const theme = isLightTheme ? light : dark;
return (
<nav style={{ background: theme.bg, color: theme.font }}>
<h1>Context App</h1>
<ul>
<li>Home</li>
<li>About</li>
<li>Contact</li>
</ul>
</nav>
)
}
}
</ThemeContext.Consumer>
)
}
}
export default Navbar;
SongList.js
import React, { Component } from 'react'
import { ThemeContext } from '../contexts/ThemeContext';
class SongList extends Component {
render() {
return (
<ThemeContext.Consumer>
{
(context) => {
const { isLightTheme, light, dark } = context;
const theme = isLightTheme ? light : dark;
return (
<div className='song-list' style={{ background: theme.bg, color: theme.font }}>
<ul>
<li style={{ background: theme.ui }}>大鱼</li>
<li style={{ background: theme.ui }}>幽灵公主</li>
<li style={{ background: theme.ui }}>望</li>
</ul>
</div>
)
}
}
</ThemeContext.Consumer>
)
}
}
export default SongList;
效果同上
更新共享数据
- 数据是存储在ThemeContext里的,props也可以传递函数,
- 因此,我们可以在ThemeContext里定义更新数据的函数,并传递给消费组件,
- 消费组件返回要更新的信号,ThemeContext就开始更新
toggleTheme = () => {
this.setState({
isLightTheme: !this.state.isLightTheme
})
}
render() {
return (
<ThemeContext.Provider value={{ ...this.state, toggleTheme: this.toggleTheme }}>
{this.props.children}
</ThemeContext.Provider>
)
}
SongList组件接收该函数并给出回应 增加一个”切换主题“的按钮,点击一下就调用toggleTheme,切换主题样式
<ThemeContext.Consumer>
{
(context) => {
const { isLightTheme, light, dark, toggleTheme } = context;
const theme = isLightTheme ? light : dark;
return (
<div className='song-list' style={{ background: theme.bg, color: theme.font }}>
<ul>
<li style={{ background: theme.ui }}>大鱼</li>
<li style={{ background: theme.ui }}>幽灵公主</li>
<li style={{ background: theme.ui }}>望</li>
</ul>
<button onClick={toggleTheme}>切换主题</button>
{}
</div>
)
}
}
</ThemeContext.Consumer>
效果:

创建多个context
首先是创建新的context,功能比较简单,就是打招呼,hello~
- 定义了一个布尔变量isAGirl,默认为true
- 定义一个问候语的切换函数toggleGreeting
- 把state和函数都传递给消费组件
import React, { Component, createContext } from 'react';
export const GreetingContext = createContext();
class GreetingContextProvider extends Component {
state = {
isAGirl: true
}
toggleGreeting = () => {
this.setState({
isAGirl: !this.state.isAGirl
})
}
render() {
return (
<GreetingContext.Provider value={{ ...this.state, toggleGreeting: this.toggleGreeting }}>
{this.props.children}
</GreetingContext.Provider>
)
}
}
export default GreetingContextProvider;
然后是在App.js中引入,用GreetingContextProvider包裹消费组件(其他库和包的引入省略)
import GreetingContextProvider from "./contexts/GreetingContext";
function App() {
return (
<div className="App">
<GreetingContextProvider>
<ThemeContextProvider>
<Navbar />
<SongList />
</ThemeContextProvider>
</GreetingContextProvider>
</div>
);
}
最后是在Navbar组件中使用
使用contextType和consumer的区别之一是:
- contextType只能接收一个context的数据,且是距离该消费组件最近的context
- consumer可以接收多个context的数据
因此,创建多个context的案例,我们用consumer来获取多个context的数据
render() {
return (
<GreetingContext.Consumer>
{
(greetingContext) => {
const { isAGirl, toggleGreeting } = greetingContext;
return (
<ThemeContext.Consumer>
{
(themeContext) => {
const { isLightTheme, light, dark } = themeContext;
const theme = isLightTheme ? light : dark;
return (
<nav style={{ background: theme.bg, color: theme.font }}>
<h1>Context App</h1>
<div onClick={toggleGreeting}>
{ isAGirl ? 'hello,girl~' : 'hello,boy~'}
</div>
<ul>
<li>Home</li>
<li>About</li>
<li>Contact</li>
</ul>
</nav>
)
}
}
</ThemeContext.Consumer>
)
}
}
</GreetingContext.Consumer>
)
}
总体效果:

|