React中组件间通讯的方式

React中组件间通讯的方式

React中组件间通讯包括父子组件、兄弟组件、隔代组件、非嵌套组件之间通讯。javascript

Props

props适用于父子组件的通讯,props以单向数据流的形式能够很好的完成父子组件的通讯,所谓单向数据流,就是数据只能经过props由父组件流向子组件,而子组件并不能经过修改props传过来的数据修改父组件的相应状态,全部的props都使得其父子props之间造成了一个单向下行绑定,父级props的更新会向下流动到子组件中,可是反过来则不行,这样会防止从子组件意外改变父级组件的状态,致使难以理解数据的流向而提升了项目维护难度。实际上若是传入一个基本数据类型给子组件,在子组件中修改这个值的话React中会抛出异常,若是对于子组件传入一个引用类型的对象的话,在子组件中修改是不会出现任何提示的,但这两种状况都属于改变了父子组件的单向数据流,是不符合可维护的设计方式的。
咱们一般会有须要更改父组件值的需求,对此咱们能够在父组件自定义一个处理接受变化状态的逻辑,而后在子组件中如若相关的状态改变时,就触发父组件的逻辑处理事件,在Reactprops是可以接受任意的入参,此时咱们经过props传递一个函数在子组件触发而且传递值到父组件的实例去修改父组件的statejava

<!-- 子组件 -->
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$listenersAPI操做。react

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来作任何能够经过声明式实现来完成的事情,一般在可使用propsstate的状况下勿依赖refsgit

<!-- 子组件 -->
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库,此外务必注意在组件销毁的时候卸载订阅的事件调用,不然会形成内存泄漏。github

// 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就能够解决多个组件的共享状态管理以及组件之间的通讯问题。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
相关文章
相关标签/搜索