React中组件间通信的方式
React中组件间通信的方式
React中组件间通信包括父子组件、兄弟组件、隔代组件、非嵌套组件之间通信。
Props
props适用于父子组件的通信,props以单向数据流的形式可以很好的完成父子组件的通信,所谓单向数据流,就是数据只能通过props由父组件流向子组件,而子组件并不能通过修改props传过来的数据修改父组件的相应状态,所有的props都使得其父子props之间形成了一个单向下行绑定,父级props的更新会向下流动到子组件中,但是反过来则不行,这样会防止从子组件意外改变父级组件的状态,导致难以理解数据的流向而提高了项目维护难度。实际上如果传入一个基本数据类型给子组件,在子组件中修改这个值的话React中会抛出异常,如果对于子组件传入一个引用类型的对象的话,在子组件中修改是不会出现任何提示的,但这两种情况都属于改变了父子组件的单向数据流,是不符合可维护的设计方式的。
我们通常会有需要更改父组件值的需求,对此我们可以在父组件自定义一个处理接受变化状态的逻辑,然后在子组件中如若相关的状态改变时,就触发父组件的逻辑处理事件,在React中props是能够接受任意的入参,此时我们通过props传递一个函数在子组件触发并且传递值到父组件的实例去修改父组件的state。
<!-- 子组件 -->
import React from "react";
class Child extends React.PureComponent{
render() {
return (
<>
<div>接收父组件的值: {this.props.msg}</div>
<button onClick={() => this.props.changeMsg("Changed Msg")}>修改父组件的值</button>
</>
)
}
}
export default Child;
<!-- 父组件 -->
import React from "react";
import Child from "./child";
class Parent extends React.PureComponent{
constructor(props){
super(props);
this.state = { msg: "Parent Msg" };
}
changeMsg = (msg) => {
this.setState({ msg });
}
render() {
return (
<div>
<Child msg={this.state.msg} changeMsg={this.changeMsg} />
</div>
)
}
}
export default Parent;
Context
React Context适用于父子组件以及隔代组件通信,React Context提供了一个无需为每层组件手动添加props就能在组件树间进行数据传递的方法。在React应用中数据是通过props属性自上而下即由父及子进行传递的,但这种做法对于某些类型的属性而言是极其繁琐的,这些属性是应用程序中许多组件都需要的,Context提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递props,实际上React-Router就是使用这种方式传递数据,这也解释了为什么<Router>要在所有<Route>的外面。。
使用Context是为了共享那些对于一个组件树而言是全局的数据,简单来说就是在父组件中通过Provider来提供数据,然后在子组件中通过Consumer来取得Provider定义的数据,不论子组件有多深,只要使用了Provider那么就可以取得在Provider中提供的数据,而不是局限于只能从当前父组件的props属性来获取数据,只要在父组件内定义的Provider数据,子组件都可以调用。当然如果只是想避免层层传递props且传递的层数不多的情况下,可以考虑将props进行一个浅拷贝之后将之后组件中不再使用的props删除后利用Spread操作符即{...handledProps}将其展开进行传递,实现类似于Vue的$attrs与$listeners的API操作。
import React from "react";
const createNamedContext = name => {
const context = React.createContext();
context.Provider.displayName = `${name}.Provider`;
context.Consumer.displayName = `${name}.Consumer`;
return context;
}
const context = /*#__PURE__*/ createNamedContext("Share");
export default context;
<!-- 子组件 -->
import React from "react";
import ShareContext from "./ShareContext";
class Child extends React.PureComponent{
render() {
return (
<>
<ShareContext.Consumer>
{ /* 基于 context 值进行渲染 */ }
{
value => <div>SharedValue: {value}</div>
}
</ShareContext.Consumer>
</>
)
}
}
export default Child;
<!-- 父组件 -->
import React from "react";
import Child from "./child";
import ShareContext from "./ShareContext";
class Parent extends React.PureComponent{
constructor(props){
super(props);
this.state = { msg: "Parent Msg" };
}
render() {
return (
<div>
<ShareContext.Provider
value={100}
>
<Child msg={this.state.msg} />
</ShareContext.Provider>
</div>
)
}
}
export default Parent;
Refs
Refs适用于父子组件的通信,Refs提供了一种方式,允许我们访问DOM节点或在render方法中创建的React元素,在典型的React数据流中,props是父组件与子组件交互的唯一方式,要修改一个子组件,你需要使用新的props来重新渲染它,但是在某些情况下,需要在典型数据流之外强制修改子组件,被修改的子组件可能是一个React组件的实例,也可能是一个DOM元素,渲染组件时返回的是组件实例,而渲染DOM元素时返回是具体的DOM节点,React提供的这个ref属性,表示为对组件真正实例的引用,其实就是ReactDOM.render()返回的组件实例。此外需要注意避免使用refs来做任何可以通过声明式实现来完成的事情,通常在可以使用props与state的情况下勿依赖refs。
<!-- 子组件 -->
import React from "react";
class Child extends React.PureComponent{
render() {
return (
<>
<div>接收父组件的值: {this.props.msg}</div>
</>
)
}
}
export default Child;
<!-- 父组件 -->
import React from "react";
import Child from "./child";
class Parent extends React.PureComponent{
constructor(props){
super(props);
this.state = { msg: "Parent Msg" };
this.child = React.createRef();
}
componentDidMount(){
console.log(this.child.current); // Child {props: {…}, context: {…}, ...}
}
render() {
return (
<div>
<Child msg={this.state.msg} ref={this.child} />
</div>
)
}
}
export default Parent;
EventBus
EventBus可以适用于任何情况的组件通信,在项目规模不大的情况下,完全可以使用中央事件总线EventBus 的方式,EventBus可以比较完美地解决包括父子组件、兄弟组件、隔代组件之间通信,实际上就是一个观察者模式,观察者模式建立了一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。所以发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。首先我们需要实现一个订阅发布类作为单例模块导出,每个需要的组件再进行import,当然作为Mixins全局静态横切也可以,或者使用event库,此外务必注意在组件销毁的时候卸载订阅的事件调用,否则会造成内存泄漏。
// event-bus.js
var PubSub = function() {
this.handlers = {};
}
PubSub.prototype = {
constructor: PubSub,
on: function(key, handler) { // 订阅
if(!(key in this.handlers)) this.handlers[key] = [];
if(!this.handlers[key].includes(handler)) {
this.handlers[key].push(handler);
return true;
}
return false;
},
once: function(key, handler) { // 一次性订阅
if(!(key in this.handlers)) this.handlers[key] = [];
if(this.handlers[key].includes(handler)) return false;
const onceHandler = (...args) => {
handler.apply(this, args);
this.off(key, onceHandler);
}
this.handlers[key].push(onceHandler);
return true;
},
off: function(key, handler) { // 卸载
const index = this.handlers[key].findIndex(item => item === handler);
if (index < 0) return false;
if (this.handlers[key].length === 1) delete this.handlers[key];
else this.handlers[key].splice(index, 1);
return true;
},
commit: function(key, ...args) { // 触发
if (!this.handlers[key]) return false;
console.log(key, "Execute");
this.handlers[key].forEach(handler => handler.apply(this, args));
return true;
},
}
export default new PubSub();
<!-- 子组件 -->
import React from "react";
import eventBus from "./event-bus";
class Child extends React.PureComponent{
render() {
return (
<>
<div>接收父组件的值: {this.props.msg}</div>
<button onClick={() => eventBus.commit("ChangeMsg", "Changed Msg")}>修改父组件的值</button>
</>
)
}
}
export default Child;
<!-- 父组件 -->
import React from "react";
import Child from "./child";
import eventBus from "./event-bus";
class Parent extends React.PureComponent{
constructor(props){
super(props);
this.state = { msg: "Parent Msg" };
this.child = React.createRef();
}
changeMsg = (msg) => {
this.setState({ msg });
}
componentDidMount(){
eventBus.on("ChangeMsg", this.changeMsg);
}
componentWillUnmount(){
eventBus.off("ChangeMsg", this.changeMsg);
}
render() {
return (
<div>
<Child msg={this.state.msg} ref={this.child} />
</div>
)
}
}
export default Parent;
Redux
Redux同样可以适用于任何情况的组件通信,Redux中提出了单一数据源Store用来存储状态数据,所有的组件都可以通过Action修改Store,也可以从Store中获取最新状态,使用了redux就可以解决多个组件的共享状态管理以及组件之间的通信问题。
import { createStore } from "redux";
/**
* 这是一个 reducer,形式为 (state, action) => state 的纯函数。
* 描述了 action 如何把 state 转变成下一个 state。
*
* state 的形式取决于你,可以是基本类型、数组、对象、
* 甚至是 Immutable.js 生成的数据结构。惟一的要点是
* 当 state 变化时需要返回全新的对象,而不是修改传入的参数。
*
* 下面例子使用 `switch` 语句和字符串来做判断,但你可以写帮助类(helper)
* 根据不同的约定(如方法映射)来判断,只要适用你的项目即可。
*/
function counter(state = 0, action) {
switch (action.type) {
case "INCREMENT": return state + 1;
case "DECREMENT": return state - 1;
default: return state;
}
}
// 创建 Redux store 来存放应用的状态。
// API 是 { subscribe, dispatch, getState }。
let store = createStore(counter);
// 可以手动订阅更新,也可以事件绑定到视图层。
store.subscribe(() => console.log(store.getState()));
// 改变内部 state 惟一方法是 dispatch 一个 action。
// action 可以被序列化,用日记记录和储存下来,后期还可以以回放的方式执行
store.dispatch({ type: "INCREMENT" });
// 1
store.dispatch({ type: "INCREMENT" });
// 2
store.dispatch({ type: "DECREMENT" });
// 1
每日一题
https://github.com/WindrunnerMax/EveryDay
参考
https://zhuanlan.zhihu.com/p/76996552
https://www.jianshu.com/p/fb915d9c99c4
https://juejin.cn/post/6844903828945387528
https://segmentfault.com/a/1190000023585646
https://github.com/andyChenAn/frontEnd/issues/46
https://blog.csdn.net/weixin_42262436/article/details/88852369
React中组件间通信的方式的更多相关文章
- Vue中组件间通信的方式
Vue中组件间通信的方式 Vue中组件间通信包括父子组件.兄弟组件.隔代组件之间通信. props $emit 这种组件通信的方式是我们运用的非常多的一种,props以单向数据流的形式可以很好的完成父 ...
- [转] React 中组件间通信的几种方式
在使用 React 的过程中,不可避免的需要组件间进行消息传递(通信),组件间通信大体有下面几种情况: 父组件向子组件通信 子组件向父组件通信 跨级组件之间通信 非嵌套组件间通信 下面依次说下这几种通 ...
- React 中组件间通信的几种方式
在使用 React 的过程中,不可避免的需要组件间进行消息传递(通信),组件间通信大体有下面几种情况: 父组件向子组件通信 子组件向父组件通信 非嵌套组件间通信 跨级组件之间通信 1.父组件向子组件通 ...
- React中组件之间通信的方式
一.是什么 我们将组件间通信可以拆分为两个词: 组件 通信 回顾Vue系列的文章,组件是vue中最强大的功能之一,同样组件化是React的核心思想 相比vue,React的组件更加灵活和多样,按照不同 ...
- React的组件间通信
一.React的单向数据流 React是单向数据流,数据主要从父节点传递到子节点(通过props).如果顶层(父级)的某个props改变了,React会重渲染所有的子节点.这通常被称为“自顶向下”或“ ...
- React Native组件间通信
React Native组件间通信 React Native组件的关系有:父子关系.无直接关系.组件间通信主要针对这两类来讨论. 一.父组件和子组件之间通信 父组件向子组件传递消息.数据通过对子组件的 ...
- React独立组件间通信联动
React是现在主流的高效的前端框架,其官方文档 http://reactjs.cn/react/docs/getting-started.html 在介绍组件间通信时只给出了父子组件间通信的方法,而 ...
- vue组件间通信六种方式(完整版)
本文总结了vue组件间通信的几种方式,如props. $emit/ $on.vuex. $parent / $children. $attrs/ $listeners和provide/inject,以 ...
- vue程序中组件间的传值方式
vue程序在组件中进行传值有多种方式,这里记录我在项目中使用到的三种: 1. 父组件向子组件传值 2. 子组件向父组件传值 3. 通过路由传参 父组件通过props向子组件传值 在子组件script中 ...
随机推荐
- spring mvc与mybatis与maven+mysql框架整合
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"% ...
- mongoose连接collections会自动加s的问题解决
问题的出现: 最近在用到mongoose连接数据库时遇到了这样的问题,我在mongoodb上创建了一个collection为course,然后在配置完mongoose连接数据库后拿到的是一个空对象. ...
- 为什么import React from 'react',React首字母必须大写?
很奇怪的是,明明没有用到 React,但是我不得不 import React.这是为什么? import React from 'react'; export default function (pr ...
- Android开发用到的几种常用设计模式浅谈(一):组合模式
1:应用场景 Android中对组合模式的应用,可谓是泛滥成粥,随处可见,那就是View和ViewGroup类的使用.在android UI设计,几乎所有的widget和布局类都依靠这两个类.组合模式 ...
- 认识PHP8
PHP 团队于2020年11月26日宣布 PHP 8 正式发布!这意味着将不会有 PHP 7.5 版本.PHP8 目前正处于非常活跃的开发阶段,所以在接下来的几个月里,情况可能会发生很大的变化.我也分 ...
- DBF 文件 ORACLE 数据库恢复
DBF 文件 ORACLE 数据库恢复 清·魏源<庸易通义>:"至道问学之有知无行,分温故为存心,知新为致知,而敦厚为存心,崇礼为致知,此皆百密一疏." 起因 在我们的 ...
- ATM_tests
ATM取款机练习程序 一.程序分析 自顶向下.逐步细化 按照程序执行的流程,将程序分解为若干个功能相对独立的函数(方法),每个函数(方法)负责某一功能,然后根据程序执行的流程,将函数(方法)组装(调用 ...
- 手把手教你搭建SSH框架(Eclipse版)
原文来自公众号[C you again],若需下载完整源码,请在公众号后台回复"ssh". 本期文章详细讲解了SSH(Spring+SpringMVC+Hibernate)框架的搭 ...
- NOIP初赛篇——08计算机安全知识
引言 计算机安全中最重要的是存储数据的安全,其面临的主要威胁包括:计算机病毒.非法访问.计算机电磁辐射.硬件损坏等. 计算机病毒是附在计算机软件中的隐蔽小程序,它和计算机其他工作程序一样,但会 ...
- Java JVM——9.方法区
前言 方法区是运行时数据区的最后一个部分: 从线程共享与否的角度来看: 大家可能在这里有些疑惑,方法区和元空间的关系到底是怎样的?请往下看,下面会为大家解惑. 栈.堆.方法区的交互关系 下面就涉及了对 ...