天天看點

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
           
下一篇: 配額管理