React中,数据流是自顶向下的,如果兄弟组件通信,那就得先状态提升到父组件
但我们平时开发过程中,经常碰到组件树层级很深,如果不同层级的组件需要使用同一份数据,那从顶层组件分别传递props的方案肯定是很麻烦的
而且太深的props层级对后续进行维护追溯数据源来说也是不好的一种解决方式
因此context的使用场景就是:在组件树中,不同层级需要访问相同的数据源时,可以利用context,进行组件之间的通信
先看这么一个例子:
// context.js
import React from 'react'
class Bottom extends React.Component {
render() {
return (
Data in Bottom: { this.props.data }
)
}
}
class Middle extends React.Component {
render() {
return (
Data in Middle: { this.props.data }
)
}
}
export default class Top extends React.Component {
render() {
return (
Data in Top: { this.props.data }
)
}
}
// app.js
class App extends React.Component {
render() {
return (
)
}
}
上面这个例子中,我们底层的Bottom组件如果想使用Top组件中的data属性,得通过Top -> Middle -> Bottom逐层将data传递下来
显而易见,很不方便
这个时候我们就可以使用context:
新建一份文件TopContext.js,创建一个context对象,用于存放不同子组件需要共同使用的数据源:
// TopContext.js
import React from 'react'
export const TopContext = React.createContext({
data: "default data"
})
注意,createContext中的参数并不是Provider的初始value,而是当没有提供Provider的时候,各个组件消费时的初始值。
然后,引入Top组件的时候用Provider包裹,通过value属性,将需要共享的数据源的值传入:
// app.js
import { TopContext } from './TopContext.js'
class App extends React.Component {
render() {
return (
)
}
}
然后在子组件中,我们就可以进行接收Provider提供的数据了,接收的方式有两种
1、利用contextType:
// context.js
import { TopContext } from './TopContext'
class Bottom extends React.Component {
render() {
console.log("Bottom context: ", this.context)
return (
Data in Bottom: { this.context.data }
)
}
}
Bottom.contextType = TopContext
该属性用一个createContext构造的context对象赋值,赋值之后,组件内部的this.context属性就可以消费该context上的数据了
但是这个方式有个问题:只能订阅单一的context,因为contextType只能赋值一个context对象
所以我们可以用第二种方式
2、利用Consumer:
// context.js
class Middle extends React.Component {
render() {
return (
{
value => {
console.log("value:", value)
return (
Data in Middle: { value.data }
)
}
}
)
}
}
利用对应context的Consumer,来消费对应context的数据
利用Consumer的内部的value,拿到context中的数据源,然后提供给我们的组件使用
如果这个时候有多个context,实际上就是多套几层函数:
// TopContext.js
export const TopContext = React.createContext({
data: "default data"
})
export const TestContext = React.createContext({
test: "test"
})
Provider嵌套:
// app.js
import { TopContext, TestContext } from './TopContext.js'
class App extends React.Component {
render() {
return (
)
}
}
Consumer嵌套:
import { TopContext, TestContext } from './TopContext'
class Middle extends React.Component {
render() {
return (
{
topValue => (
{
testValue => {
console.log("topValue:", topValue)
console.log("testValue:", testValue)
return (
Data in Middle: { topValue.data }
)
}
}
)
}
)
}
}
这样就可以实现组件的跨层级订阅数据源了
当然跨层级的场景不单单只涉及到获取,肯定有时候也需要对数据源进行修改。
其实也挺简单,就是把修改函数也作为数据源的一部分传进去:
TopContext.js中创建默认函数:
// TopContext.js
import React from 'react'
export const TopContext = React.createContext({
data: "default data",
changeData: () => {}
})
app.js中定义该函数:
// app.js
import { TopContext, TestContext } from './TopContext.js'
class App extends React.Component {
state = {
data: "source data",
changeData: () => {
this.setState({
data: this.state.data + "haha~"
})
}
}
render() {
return (
)
}
}
上面的例子中,我们将Provider的数据data以及函数changeData都放到了state对象中
在子组件中,调用该更新函数即可:
// context.js
class Bottom extends React.Component {
render() {
console.log("Bottom context: ", this.context)
return (
Data in Bottom: { this.context.data }
)
}
}
Bottom.contextType = TopContext
Consumer的方式同理,更新函数可以在回调参数中拿到,子组件使用即可
一些注意事项:
通过上面的文章,我们其实可以看到,当我们以Consumer的方式对Provider的数据进行使用时,函数式组件和类组件其实差别不大
我们将middle组件改一下,也能正常使用:
// context.js
const Middle = () => {
return (
{
value => (
Data in Middle: {value.data}
)
}
)
}
但是很显然contextType这种方式对于FC来说就不行了
既然有了useContext这个hook,那在函数式组件中,我们就以useContext的方式来:
// context.js
import React, { useContext } from 'react'
const Middle = () => {
const value = useContext(TopContext)
return (
Data in Middle: {value.data}
)
}
useContext实际上相当于Context.Consumer 或者 contextType = MyContext 的作用,用来订阅指定的context对象
原文链接:React.context 和 useContext