常见的数据流动
- state: 数据在组件内流动
- props: 数据在父子组件间流动
- redux:数据在单一数据源和组件间流动
- context:数据在Context和使用context的组件间流动 || Provider和使用context的组件间流动
context和redux的区别
- context的初始数据( React.createContext() )不能变更
- redux由于是单一数据源,可通过dispatch改变数据
- context数据不稳定,取决于最近的Provider
- 数据稳定,单一数据源,单一修改方法
- 使用context的组件需import 对应的Context对象进来
- 使用redux的组件需connect 连接对应的数据源
context解决的问题
- 用于跨组件通信
- 可以理解为简略版的redux
context存在的问题
- 由于context的值主要取决于最近的Provider,如果没有匹配的Provider就会使用默认值
- 假如有个嵌套100层的组件结构,你在第1层使用UserContext.Provider指定了数据,第33、66、99层使用了context的值
- 一年后,你同事在第40层加了个UserContext.Provider指定了数据,在60、80、100层使用了context的值
- 这时你33、66、99层的context会去第40层的Provicer中去找,因为它比较近,如果Provider的数据或结构不一样,就容易产生异常
注意点
- 创建Provider时,注意上层有没有相同的Provider,不要覆盖了原来的逻辑
- 合理设置Provider的嵌套关系
- 建议设置Provider的displayName,可以在React DevTools中快速了解Provider的嵌套情况
适用场景
- 深色主题 / 浅色主题 切换
- 多语言的切换
- 一些类似开关的场景
- …
demo
// context.js - 一个专门用来保存cotext的js文件
import React from 'react';
/**
* 全局用户信息
* @param {*} [defaultValue] - 默认值,没有匹配到 Provider生效
* */
export const UserContext = React.createContext({
username: 'admin',
password: '123456'
});
UserContext.displayName = 'UserContext';
/**
* 全局默认主题
* */
export const ThemeContext = React.createContext({
backgroundColor: '#FFF',
fontSize: '16px'
});
import React from 'react';
import { Layout } from 'antd';
import Header from './header';
import Content from './content';
import Footer from './footer';
import { UserContext } from './context';
export default class Home extends React.Component{
render() {
return (
<Layout>
{/**
Provider用于覆盖默认值
Provider包裹的组件不会使用默认值,只会使用Provider上的value的值
value没有指定,这时context的值是undefined
context = {username: 'giegie'}
*/}
<UserContext.Provider value={{ username: 'giegie' }}>
<Header />
</UserContext.Provider>
{/**
外部没有对应的Provider,使用默认数据
context = { username: 'admin', password: '123456' }
*/}
<Content />
{/**
Provider用于覆盖默认值
Provider包裹的组件不会使用默认值,只会使用Provider上的value的值
value没有指定,这时context的值是undefined
context = undefined
*/}
<UserContext.Provider>
<Footer />
</UserContext.Provider>
</Layout>
);
}
};
import React from 'react';
import { UserContext } from './context';
export default class Header extends React.Component {
/**
* 根据名字XXX 找到最近的 XXX.Provider
*
* e.g.
* this.context = UserContext 找到上层最近的 <UserContext.Provider>
* Provider包裹的组件不会使用默认值,只会使用Provider上的value的值
* value没有指定,这时context的值是undefined
*
* 可以在任何地方通过this.context 来使用 context的值
*
* 用contextType只能绑定一个context,需要使用多个context用Consumer
* */
static contextType = UserContext;
render() {
return (
<>
<h1>header</h1>
<h2>user: {this.context?.username}</h2>
{/**
一个订阅context变化的组件
子元素为函数
@param {*} value - context的值
@return - 返回一个React节点
*/}
<UserContext.Consumer>
{
value => <h2>user: {value.username}</h2>
}
</UserContext.Consumer>
</>
)
}
}
// 函数组件使用 - Consumer - 整个组件消费context
import React from 'react';
import { UserContext } from './context';
export default function Content(props) {
return (
/**
* 一个订阅context变化的组件
*
* 子元素为函数
*
* @param {*} value - context的值
* @return - 返回一个React节点
* */
<UserContext.Consumer>
{
value => (
<>
<h1>content</h1>
<h2>user: {value.username}</h2>
</>
)
}
</UserContext.Consumer>
);
};
// 函数组件使用 - Consumer - 局部消费context
import { UserContext } from './context';
export default function Content(props) {
return (
<>
<h1>content</h1>]
<UserContext.Consumer>
{ value => <h2>user: {value.username}</h2> }
</UserContext.Consumer>
<UserContext.Consumer>
{ value => <h2>password: {value.password}</h2> }
</UserContext.Consumer>
</>
);
};
// 函数组件使用 - useContext - 函数任意地方使用
import React, { useContext } from 'react';
import { UserContext } from './context';
export default function Content(props) {
const userContext = useContext(UserContext);
return (
<>
<h1>content</h1>
<h2>user: {userContext.username}</h2>
<h2>password: {userContext.password}</h2>
</>
);
};
使用多个context
import React from 'react';
import { UserContext, ThemeContext } from './context';
// 类组件或函数组件,通过多个不同的Consumer使用
export default class Header extends React.Component {
render() {
return (
<>
<h1>header</h1>
<h2>user: {this.context?.username}</h2>
<ThemeContext.Consumer>
{
value => <h2>fontSize: {value.fontSize}</h2>
}
</ThemeContext.Consumer>
<UserContext.Consumer>
{
value => <h2>user: {value.username}</h2>
}
</UserContext.Consumer>
</>
)
}
}
==========
// 函数组件使用 - useContext - 函数任意地方使用
import React, { useContext } from 'react';
import { UserContext, ThemeContext } from './context';
export default function Content(props) {
const userContext = useContext(UserContext);
const themeContext = useContext(ThemeContext);
return (
<>
<h1>content</h1>
<h2>user: {userContext.username}</h2>
<h2>password: {userContext.password}</h2>
<h2>fontSize: {themeContext.fontSize}</h2>
</>
);
};
context造成的页面渲染
/**
* 组件发生重渲染的情况
* - 组件内部的state、props、context发生变化组件就会重渲染
* - 父组件重渲染,子组件也会重渲染
*
*
* 注意点
* - context改变不会执行shouldComponentUpdate
* - 通过static contextType使用的context,只要context发生变化,整个组件会重渲染
* - 通过XXX.Consumer使用的context,只要context发生变化,Consumer内部会重渲染,不会引起整个组件的重渲染
* - 通过useCallback使用的context,只要context发生变化,整个函数组件内部会重新执行
* */
displayName
设置displayName可以在React DevTools中,更容易被找到
import React from 'react';
export const UserContext = React.createContext({
...
});
UserContext.displayName = 'UserContext';
export const ThemeContext = React.createContext({
...
});
ThemeContext.displayName = 'ThemeContext';