react父子组件传值_React系列七 非父子组件通信

一. Context使用

1.1. Context应用场景

非父子组件数据的共享:

  • 在开发中,比较常见的数据传递方式是通过props属性自上而下(由父到子)进行传递。

  • 但是对于有一些场景:比如一些数据需要在多个组件中进行共享(地区偏好、UI主题、用户登录状态、用户信息等)。

  • 如果我们在顶层的App中定义这些信息,之后一层层传递下去,那么对于一些中间层不需要数据的组件来说,是一种冗余的操作。

我们来看一个例子:

import React, { Component } from 'react';

function ProfileHeader(props) {
  return (
    <div><h2>用户昵称: {props.nickname}h2><h2>用户等级: {props.level}h2>div>
  )
}

class Profile extends Component {
  render() {
    return (
      
设置1设置2设置3设置4设置5

    )
  }
}

export default class App extends Component {
  constructor() {
    super();

    this.state = {
      nickname: "coderwhy",
      level: 99
    }
  }

  render() {
    const { nickname, level } = this.state;

    return (
      

其他内容


    )
  }
}

我这边顺便补充一个小的知识点:Spread Attributes

  • https://zh-hans.reactjs.org/docs/jsx-in-depth.html

下面两种写法是等价的:

function App1() {
  return ;
}

function App2() {
  const props = {firstName: 'Ben', lastName: 'Hector'};
  return ;
}

那么我们上面的Profile的传递代码可以修改为如下代码:


但是,如果层级更多的话,一层层传递是非常麻烦,并且代码是非常冗余的:

  • React提供了一个API:Context;
  • Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props;
  • Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言;

1.2. Context相关的API

React.createContext

const MyContext = React.createContext(defaultValue);

创建一个需要共享的Context对象:

  • 如果一个组件订阅了Context,那么这个组件会从离自身最近的那个匹配的 Provider 中读取到当前的context值;
  • defaultValue是组件在顶层查找过程中没有找到对应的Provider,那么就使用默认值

Context.Provider

/* 某个值 */}>

每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化:

  • Provider 接收一个 value 属性,传递给消费组件;
  • 一个 Provider 可以和多个消费组件有对应关系;
  • 多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据;

当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染;

Class.contextType

class MyClass extends React.Component {
  componentDidMount() {
    let value = this.context;
    /* 在组件挂载完成后,使用 MyContext 组件的值来执行一些有副作用的操作 */
  }
  componentDidUpdate() {
    let value = this.context;
    /* ... */
  }
  componentWillUnmount() {
    let value = this.context;
    /* ... */
  }
  render() {
    let value = this.context;
    /* 基于 MyContext 组件的值进行渲染 */
  }
}
MyClass.contextType = MyContext;

挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象:

  • 这能让你使用 this.context 来消费最近 Context 上的那个值;
  • 你可以在任何生命周期中访问到它,包括 render 函数中;

Context.Consumer


  {value => /* 基于 context 值进行渲染*/}
</MyContext.Consumer>

这里,React 组件也可以订阅到 context 变更。这能让你在 函数式组件 中完成订阅 context。

  • 这里需要 函数作为子元素(function as child)这种做法;
  • 这个函数接收当前的 context 值,返回一个 React 节点;

1.3. Context使用过程

我们先按照前面三个步骤来使用一个Context:

  • 我们就会发现,这个过程中Profile是不需要有任何的数据传递的
import React, { Component } from 'react';

const UserContext = React.createContext({ nickname: "默认", level: -1 })

class ProfileHeader extends Component {
  render() {
    return (
      <div><h2>用户昵称: {this.context.nickname}h2><h2>用户等级: {this.context.level}h2>div>
    )
  }
}

ProfileHeader.contextType = UserContext;

class Profile extends Component {
  render() {
    return (
      <div><ProfileHeader /><ul><li>设置1li><li>设置2li><li>设置3li><li>设置4li><li>设置5li>ul>div>
    )
  }
}

export default class App extends Component {
  render() {
    return (
      <div><UserContext.Provider value={{ nickname: "why", level: 99 }}><Profile />UserContext.Provider><h2>其他内容h2>div>
    )
  }
}

**什么时候使用默认值defaultValue呢?**如果出现了如下代码:

  • 并没有作为 UserContext.Provider 的子组件;

<UserContext.Provider value={{ nickname: "why", level: 99 }}>UserContext.Provider>

什么时候使用Context.Consumer呢?

  • 1.当使用value的组件是一个函数式组件时;
  • 2.当组件中需要使用多个Context时;

演练一:

function ProfileHeader(props) {
  return (
    <div><UserContext.Consumer>
        {value => {
          return (<div><h2>用户昵称: {value.nickname}h2><h2>用户等级: {value.level}h2>div>
          )
        }}UserContext.Consumer>div>
  )
}

演练二:当使用value的组件是一个函数式组件时;

1.创建一个新的Context

const ThemeContext = React.createContext({ color: "black" });

2.Provider的嵌套

nickname: "why", level: 99 }}><ThemeContext.Provider value={{color: "red"}}><Profile />ThemeContext.Provider>UserContext.Provider>

3.使用Consumer的嵌套


  {value => {return (<ThemeContext.Consumer>
        {
          theme => (<div><h2 style={theme}>用户昵称: {value.nickname}h2><h2 style={theme}>用户等级: {value.level}h2>div>
          )
        }ThemeContext.Consumer>
    )
  }}
</UserContext.Consumer>

更多用法可以参考官网:https://zh-hans.reactjs.org/docs/context.html

后续我们会学习Redux来进行全局的状态管理。

二. 事件总线

2.1. 事件总线的概述

前面通过Context主要实现的是数据的共享,但是在开发中如果有跨组件之间的事件传递,应该如何操作呢?

  • 在Vue中我们可以通过Vue的实例,快速实现一个事件总线(EventBus),来完成操作;
  • 在React中,我们可以依赖一个使用较多的库 events 来完成对应的操作;

我们可以通过npm或者yarn来安装events:

yarn add events;

events常用的API:

  • 创建EventEmitter对象:eventBus对象;
  • 发出事件:eventBus.emit("事件名称", 参数列表);
  • 监听事件:eventBus.addListener("事件名称", 监听函数)
  • 移除事件:eventBus.removeListener("事件名称", 监听函数)

2.2. 案例演练

我们通过一个案例来进行演练:

import React, { Component } from 'react';
import { EventEmitter } from "events";

const eventBus = new EventEmitter();

class ProfileHeader extends Component {
  render() {
    return (
      <div><button onClick={e => this.btnClick()}>按钮button>div>
    )
  }

  btnClick() {
    eventBus.emit("headerClick", "why", 18);
  }
}



class Profile extends Component {
  render() {
    return (
      <div><ProfileHeader /><ul><li>设置1li><li>设置2li><li>设置3li><li>设置4li><li>设置5li>ul>div>
    )
  }
}

export default class App extends Component {

  componentDidMount() {
    eventBus.addListener("headerClick", this.headerClick)
  }

  headerClick(name, age) {
    console.log(name, age);
  }

  componentWillUnmount() {
    eventBus.removeListener("headerClick", this.headerClick);
  }

  render() {
    return (
      <div><Profile/><h2>其他内容h2>div>
    )
  }
}

三. 临时知识补充

为什么constructor中不是必须传入props也能使用

在进行React开发中,有一个很奇怪的现象:

  • 在调用super()的时候,我没有传入props,但是在下面的render函数中我依然可以使用;
  • 如果你自己编写一个基础的类,可以尝试一下:这种情况props应该是undefined的;
class ChildCpn extends Component {
  constructor(props) {
    super();
  }

  render() {
    const {name, age, height} = this.props;
    return (
      <h2>子组件展示数据: {name + " " + age + " " + height}h2>
    )
  }
}

为什么这么神奇呢?

  • 我一直喜欢说:计算机中没有黑魔法;
  • 之所以可以,恰恰是因为React担心你的代码会出现上面这种写法而进行了一些 骚操作
  • React不管你有没有通过super将props设置到当前的对象中,它都会重新给你设置一遍;

如何验证呢?

  • 这就需要通过源码来验证了;
  • React的源码packages中有提供一个Test Renderer的package;
  • 这个 package 提供了一个 React 渲染器,用于将 React 组件渲染成纯 JavaScript 对象,不需要依赖 DOM 或原生移动环境;

查看源码:

3e8bc1bcfa89f669827f5f980a3006db.png
源码位置

我们来看一下这个组件是怎么被创建出来的:

  • 我们找到其中的render函数;
330f1123d6ad296a4102a33b2576472c.png
render函数
  • render函数中有这样的一段代码;
    • 这个_instance实例就是组件对象;
50a7e3b6060ae2ddf221c52806b3fef4.png
创建_instance实例
  • 我们再看一下,它在哪里重新赋值的:
    • 这里还包括通过this._instance的方式回调生命周期函数;
51ce946e94ce5e2f08a2621bfb9d979d.png
重新赋值的地方

结论:你无论是否手动的将props保存到组件的实例上,React内部都会帮你保存的,我只能说:骚操作;

  • 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传递数据给子组件: Context是React提供的一种跨组件传递数据的机制。父组件可以通过定义一个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、付费专栏及课程。

余额充值