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

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对象
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
React,父组件向子组件传值可以通过props来实现。父组件可以将需要传递的数据作为属性传递给子组件,子组件通过props来接收这些数据。下面是几种常见的父子组件传值的方式: 1. 父组件通过props传递数据给子组件: 父组件定义一个属性,并将需要传递的数据作为该属性的值,然后将子组件引入到父组件,并将该属性作为子组件的一个属性传递进去。子组件可以通过props来接收并使用这个数据。例如,父组件定义属性`txt0`,并将它传递给子组件`Child`: ```javascript <Child txt={this.state.txt0} /> ``` 子组件可以通过props来接收并使用父组件传递的数据: ```javascript this.props.txt ``` 2. 父组件通过回调函数传递数据给子组件: 父组件定义一个回调函数,并将该函数作为属性传递给子组件。子组件可以通过调用这个回调函数,将需要传递的数据作为参数传递给父组件。例如,父组件定义一个回调函数`getDatas`: ```javascript getDatas(msg){ this.setState({ mess: msg }); } ``` 然后将该函数作为属性传递给子组件`Son`: ```javascript <Son getdata={this.getDatas.bind(this)}></Son> ``` 子组件可以通过调用父组件传递的回调函数,并将需要传递的数据作为参数传递给它: ```javascript this.props.getdata(data); ``` 3. 父组件通过context传递数据给子组件ContextReact提供的一种跨组件传递数据的机制。父组件可以通过定义一个Context,并将需要传递的数据放在Context,然后子组件可以通过访问Context来获取这些数据。具体的实现可以参考React官方文档关于Context的介绍。 以上是React父子组件传值的几种常见方式,你可以根据具体的需求选择合适的方式来实现父子组件的数据传递。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [React父子组件的传值的方法](https://download.csdn.net/download/weixin_38595850/13633672)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [React教程:父子组件传值组件通信)](https://blog.csdn.net/p445098355/article/details/104519363)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [React父子组件传值](https://blog.csdn.net/weixin_45817109/article/details/103628428)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值