react 多层级组件传值方案(React.context 和 useContext)

React中,数据流是自顶向下的,如果兄弟组件通信,那就得先状态提升到父组件

但我们平时开发过程中,经常碰到组件树层级很深,如果不同层级的组件需要使用同一份数据,那从顶层组件分别传递props的方案肯定是很麻烦的

而且太深的props层级对后续进行维护追溯数据源来说也是不好的一种解决方式

因此context的使用场景就是:在组件树中,不同层级需要访问相同的数据源时,可以利用context,进行组件之间的通信

React.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的方式同理,更新函数可以在回调参数中拿到,子组件使用即可

一些注意事项:

  • 给Provider的value属性赋值为对象的时候,不要将该对象直接写在render函数内部,将其放入组件的state中。不然每次父组件render的时候,因为value为重建一个对象会导致子组件也重新渲染
  • 上面的例子也可以看到,才用了连个context,Consumer的嵌套已经不浅了,所以我们使用context的时候尽量遵循一个单一数据源的原则,否则很容易造成嵌套地狱(wrapper hell)

useContext

通过上面的文章,我们其实可以看到,当我们以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

你可能感兴趣的:(React)