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
相關文章
相關標籤/搜索