美文网首页
react 多级组件传值/父子、子孙、爷孙组件等(React.c

react 多级组件传值/父子、子孙、爷孙组件等(React.c

作者: 仰望天空的人 | 来源:发表于2023-04-20 18:42 被阅读0次

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

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

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

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

React.context
先看这么一个例子:

// context.js
import React from 'react'

class Bottom extends React.Component {
    render() {
        return (
            <div>
                <h3>Data in Bottom: { this.props.data }</h3>
            </div>
        )
    }
}

class Middle extends React.Component {
    render() {
        return (
            <div>
                <h3>Data in Middle: { this.props.data }</h3>
                <Bottom {...this.props}></Bottom>
            </div>
        )
    }
}


export default class Top extends React.Component {
    render() {
        return (
            <div>
                <h3>Data in Top: { this.props.data }</h3>
                <Middle {...this.props}></Middle>
            </div>
        )
    }
}

// app.js
class App extends React.Component {
    render() {
        return (
            <Top data="source data"></Top>
        )
    }
}

上面这个例子中,我们底层的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 (
            <TopContext.Provider value={{ data: "source data" }}>
                <Top></Top>
            </TopContext.Provider>
        )
    }
}

然后在子组件中,我们就可以进行接收Provider提供的数据了,接收的方式有两种

1、利用contextType:

// context.js
import { TopContext } from './TopContext'

class Bottom extends React.Component {
    render() {
        console.log("Bottom context: ", this.context)
        return (
            <div>
                <h3>Data in Bottom: { this.context.data }</h3>
            </div>
        )
    }
}
Bottom.contextType = TopContext

该属性用一个createContext构造的context对象赋值,赋值之后,组件内部的this.context属性就可以消费该context上的数据了

但是这个方式有个问题:只能订阅单一的context,因为contextType只能赋值一个context对象

所以我们可以用第二种方式

2、利用Consumer:

// context.js
class Middle extends React.Component {
    render() {
        return (
            <TopContext.Consumer>
                {
                    value => {
                        console.log("value:", value)
                        return (
                            <div>
                                <h3>Data in Middle: { value.data }</h3>
                                <Bottom></Bottom>
                            </div>
                        )
                    }
                }
            </TopContext.Consumer>
        )
    }
}

利用对应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 (
            <TopContext.Provider value={{ data: "source data" }}>
                <TestContext.Provider value={{ test: "test data" }}>
                    <Top></Top>
                </TestContext.Provider>
            </TopContext.Provider>
        )
    }
}
Consumer嵌套:

import { TopContext, TestContext } from './TopContext'
class Middle extends React.Component {
    render() {
        return (
            <TopContext.Consumer>
                {
                    topValue => (
                        <TestContext.Consumer>
                            {
                                testValue => {
                                    console.log("topValue:", topValue)
                                    console.log("testValue:", testValue)
                                    return (
                                        <div>
                                            <h3>Data in Middle: { topValue.data }</h3>
                                            <Bottom></Bottom>
                                        </div>
                                    )
                                }
                            }
                        </TestContext.Consumer>
                    )
                }
            </TopContext.Consumer>
        )
    }
}

这样就可以实现组件的跨层级订阅数据源了

当然跨层级的场景不单单只涉及到获取,肯定有时候也需要对数据源进行修改。

其实也挺简单,就是把修改函数也作为数据源的一部分传进去:

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 (
            <TopContext.Provider value={ this.state }>
                <TestContext.Provider value={{ test: "test data" }}>
                    <Top></Top>
                </TestContext.Provider>
            </TopContext.Provider>
        )
    }
}

上面的例子中,我们将Provider的数据data以及函数changeData都放到了state对象中

在子组件中,调用该更新函数即可:

// context.js
class Bottom extends React.Component {
    render() {
        console.log("Bottom context: ", this.context)
        return (
            <div>
                <h3>Data in Bottom: { this.context.data }</h3>
                <button onClick={this.context.changeData}>Bottom Change Data</button>
            </div>
        )
    }
}
Bottom.contextType = TopContext

Consumer的方式同理,更新函数可以在回调参数中拿到,子组件使用即可

一些注意事项:

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

我们将middle组件改一下,也能正常使用:

// context.js
const Middle = () => {
    return (
        <TestContext.Consumer>
            {
                value => (
                    <div>
                        <h3>Data in Middle: {value.data}</h3>
                        <Bottom></Bottom>
                    </div>
                )
            }
        </TestContext.Consumer>
    )
}

但是很显然contextType这种方式对于FC来说就不行了

既然有了useContext这个hook,那在函数式组件中,我们就以useContext的方式来:

// context.js
import React, { useContext } from 'react'
const Middle = () => {
    const value = useContext(TopContext)
    return (
        <div>
            <h3>Data in Middle: {value.data}</h3>
            <Bottom></Bottom>
        </div>
    )
}
useContext实际上相当于Context.Consumer 或者 contextType = MyContext 的作用,用来订阅指定的context对象

https://zhuanlan.zhihu.com/p/340028009

相关文章

  • React02-组件通信

    React父子组件之间如何通信父组件传一个函数给子组件,子组件在适当的时候调用这个函数React爷孙组件之间如何通...

  • react-父子组件间通信

    React-父子组件间通信 父组件传 值 给子组件, 传 方法 给子组件 子组件接收 值 ,触发父组件的 方法

  • 组件之间的传值

    组件之间的传值,包括父子组件传值,兄弟组件之间的传值,其中父子组件包括父组件向子组件传值和子组件向父组件传值,现在...

  • Vue父子组件通信和双向绑定

    本篇文章主要介绍父子组件传值,组件的数据双向绑定。 1. 基础父子组件传值 父子组件传值,这是Vue组件传值最常见...

  • react 父子组件传值(兄弟传值)

    react中父子组件传值 父向子: 父组件通过自定义属性向子组件传值,子组件通过this.props.属性名接收子...

  • vue、react对比

    一、父子通信 1. 父组件通过props向子组件传值 vue:父组件 子组件接收 react:父组件: 子组件: ...

  • React入门(二)组件

    本节知识点 (1)React组件化 (2)React父子组件传值 (一)组件 在React中组件都是对应的一个个类...

  • vue 父子组件传值 $on sync v-model

    1、正常的父子组件传值2、使用sync 实现父子组件传值3、使用v-model实现父子组件传值

  • (VUE3) 四、组件传值(父子组件传值 & 祖孙组件传值 &v

    1.父子组件传值 vue2中的父子组件传值:父组件: 子组件: vue3中的父子组件传值: 还是用props接收父...

  • 3.组件传值 - service传值

    angular 组件service传值 父子组件相互传值 子组件如果想返回去传值给父组件,父子组件相互传值需要使用...

网友评论

      本文标题:react 多级组件传值/父子、子孙、爷孙组件等(React.c

      本文链接:https://www.haomeiwen.com/subject/oxeljdtx.html