關於React組件之間如何優雅地傳值的探討

来源:https://www.cnblogs.com/rynxiao/archive/2017/12/25/8109546.html
-Advertisement-
Play Games

閑話不多說,開篇擼代碼,你可以會看到類似如下的結構: See the Pen react props by 糊一笑 (@rynxiao) on CodePen. 當一個組件嵌套了若幹層子組件時,而想要在特定的組件中取得父組件的屬性,就不得不將 一層一層地往下傳,我這裡只是簡單的列舉了3個子組件,而當 ...


閑話不多說,開篇擼代碼,你可以會看到類似如下的結構:

import React, { Component } from 'react';

// 父組件
class Parent extends Component {
    constructor() {
        super();
        this.state = { color: 'red' };
    }
  
    render() {
        return <Child1 { ...this.props } />
    }
}

// 子組件1
const Child1 = props => {
    return <Child2 { ...props } />
}

// 子組件2
const Child2 = props => {
    return <Child3 { ...props } />
}

// 子組件3
const Child3 = props => {
    return <div style={{ color: props.color }}>Red</div>
}

See the Pen react props by 糊一笑 (@rynxiao) on CodePen.

當一個組件嵌套了若幹層子組件時,而想要在特定的組件中取得父組件的屬性,就不得不將props一層一層地往下傳,我這裡只是簡單的列舉了3個子組件,而當子組件嵌套過深的時候,props的維護將成噩夢級增長。因為在每一個子組件上你可能還會對傳過來的props進行加工,以至於你最後都不確信你最初的props中將會有什麼東西。

那麼React中是否還有其他的方式來傳遞屬性,從而改善這種層層傳遞式的屬性傳遞。答案肯定是有的,主要還有以下兩種形式:

Redux等系列數據倉庫

使用Redux相當於在全局維護了整個應用數據的倉庫,當數據改變的時候,我們只需要去改變這個全局的數據倉庫就可以了。類似這樣的:

var state = {
    a: 1
};

// index1.js
state.a = 2;

// index2.js
console.log(state.a);   // 2

當然這隻是一種非常簡單的形式解析,Reudx中的實現邏輯遠比這個要複雜得多,有興趣可以去深入瞭解,或者看我之前的文章:用react+redux編寫一個頁面小demo以及react腳手架改造,下麵大致列舉下代碼:

// actions.js
function getA() {
  return {
        type: GET_DATA_A
  };
}

// reducer.js
const state = {
    a: 1
};

function reducer(state, action) {
    case GET_DATA_A: 
        state.a = 2;
        return state;
    default:
        return state;
}

module.exports = reducer;

// Test.js
class Test extends React.Component {
    constructor() {
        super();
    }
  
    componentDidMount() {
        this.props.getA();
    }
}

export default connect(state => {
    return { a: state.reducer.a }
}, dispatch => {
    return { getA: dispatch => dispatch(getA()) }
})(Test);

這樣當在Test中的componentDidMount中調用了getA()之後,就會發送一個action去改變store中的狀態,此時的a已經由原先的1變成了2。

這隻是一個任一組件的大致演示,這就意味著你可以在任何組件中來改變store中的狀態。關於什麼時候引入redux我覺得也要根據項目來,如果一個項目中大多數時候只是需要跟組件內部打交道,那麼引入redux反而造成了一種資源浪費,更多地引來的是學習成本和維護成本,因此並不是說所有的項目我都一定要引入redux

context

關於context的講解,React文檔中將它放在了進階指引裡面。具體地址在這裡:https://reactjs.org/docs/context.html。主要的作用就是為瞭解決在本文開頭列舉出來的例子,為了不讓props在每層的組件中都需要往下傳遞,而可以在任何一個子組件中拿到父組件中的屬性。

但是,好用的東西往往也有副作用,官方也給出了幾點不要使用context的建議,如下:

  • 如果你想你的應用處於穩定狀態,不要用context
  • 如果你不太熟悉Redux或者MobX等狀態管理庫,不要用context
  • 如果你不是一個資深的React開發者,不要用context

鑒於以上三種情況,官方更好的建議是老老實實使用propsstate

下麵主要大致講一下context怎麼用,其實在官網中的例子已經十分清晰了,我們可以將最開始的例子改一下,使用context之後是這樣的:

class Parent extends React.Component {
    constructor(props) {
        super(props);
        this.state = { color: 'red' };
    }
  
    getChildContext() {
        return { color: this.state.color }
    }
  
    render() {
            return <Child1 />
    }
}

const Child1 = () => {
    return <Child2 />
}

const Child2 = () => {
    return <Child3 />
}

const Child3 = ({ children }, context) => {
    console.log('context', context);
    return <div style={{ color: context.color }}>Red</div>
}

Parent.childContextTypes = {
    color: PropTypes.string
};

Child3.contextTypes = {
    color: PropTypes.string
};  

ReactDOM.render(<Parent />, document.getElementById('container'));

可以看到,在子組件中,所有的{ ...props }都不需要再寫,只需要在Parent中定義childContextTypes的屬性類型,以及定義getChildContext鉤子函數,然後再特定的子組件中使用contextTypes接收即可。

See the Pen react context by 糊一笑 (@rynxiao) on CodePen.

這樣做貌似十分簡單,但是你可能會遇到這樣的問題:當改變了context中的屬性,但是由於並沒有影響父組件中上一層的中間組件的變化,那麼上一層的中間組件並不會渲染,這樣即使改變了context中的數據,你期望改變的子組件中並不一定能夠發生變化,例如我們在上面的例子中再來改變一下:

// Parent
render() {
    return (
        <div className="test">
        <button onClick={ () => this.setState({ color: 'green' }) }>change color to green</button>  
        <Child1 />
      </div>
    )
}

增加一個按鈕來改變state中的顏色

// Child2
class Child2 extends React.Component {
    
      shouldComponentUpdate() {
          return true;
      }

      render() {
          return <Child3 />
      }
}

增加shouldComponentUpdate來決定這個組件是否渲染。當我在shouldComponentUpdate中返回true的時候,一切都是那麼地正常,但是當我返回false的時候,顏色將不再發生變化。

See the Pen react context problem by 糊一笑 (@rynxiao) on CodePen.

既然發生了這樣的情況,那是否意味著我們不能再用context,沒有絕對的事情,在這篇文章How to safely use React context中給出了一個解決方案,我們再將上面的例子改造一下:

// 重新定義一個發佈對象,每當顏色變化的時候就會發佈新的顏色信息
// 這樣在訂閱了顏色改變的子組件中就可以收到相關的顏色變化訊息了
class Theme {
    constructor(color) {
        this.color = color;
        this.subscriptions = [];
    }
  
    setColor(color) {
        this.color = color;
        this.subscriptions.forEach(f => f());
    }
  
    subscribe(f) {
      this.subscriptions.push(f)
    }
}

class Parent extends React.Component {
    constructor(props) {
        super(props);
        this.state = { theme: new Theme('red') };
        this.changeColor = this.changeColor.bind(this)
    }
  
    getChildContext() {
        return { theme: this.state.theme }
    }
  
    changeColor() {
        this.state.theme.setColor('green');
    }
  
    render() {
            return (
            <div className="test">
              <button onClick={ this.changeColor }>change color to green</button>  
              <Child1 />
            </div>
        )
    }
}

const Child1 = () => {
    return <Child2 />
}

class Child2 extends React.Component {
    
    shouldComponentUpdate() {
        return false;
    }
  
    render() {
        return <Child3 />
    }
}

// 子組件中訂閱顏色改變的信息
// 調用forceUpdate強制自己重新渲染
class Child3 extends React.Component {
    
    componentDidMount() {
        this.context.theme.subscribe(() => this.forceUpdate());
    }
  
    render() {
        return <div style={{ color: this.context.theme.color }}>Red</div>
    }
}

Parent.childContextTypes = {
    theme: PropTypes.object
};

Child3.contextTypes = {
    theme: PropTypes.object
};  

ReactDOM.render(<Parent />, document.getElementById('container'));

看上面的例子,其實就是一個訂閱發佈者模式,一旦父組件顏色發生了改變,我就給子組件發送消息,強制調用子組件中的forceUpdate進行渲染。

See the Pen react context problem resolve by 糊一笑 (@rynxiao) on CodePen.

但在開發中,一般是不會推薦使用forceUpdate這個方法的,因為你改變的有時候並不是僅僅一個狀態,但狀態改變的數量只有一個,但是又會引起其他屬性的渲染,這樣會變得得不償失。

另外基於此原理實現的有一個庫: MobX,有興趣的可以自己去瞭解。

總體建議是:能別用context就別用,一切需要在自己的掌控中才可以使用。

總結

這是自己在使用React時的一些總結,本意是朝著偷懶的方向上去瞭解context的,但是在使用的基礎上,必須知道它使用的場景,這樣才能夠防範於未然。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 BFC 的概念始於 CSS2,是個蠻古老的 CSS 話題了,網上也到處能搜到 BFC 的介紹,但是都不夠簡潔。本文系翻譯自 Rachel Andrew 女士的博文 Understanding CSS Layout And The Block Formatting Context,內容足夠簡潔明 ...
  • 首先我們先要導入幾張圖片(我已導入完畢): ; 好,我們先寫一個 定義一個 在這<div>中再定義一個<div></div>,定義一個id="hhs"(隨便定義的),然後在下麵定義五個圖片 我們再在樣式表中寫上一些需要用的,如下代碼: 我們寫一個 1 function $(oId){ 2 retur ...
  • //1var name='world';(function(){ if(typeof name 'undefined'){ var name='jack'; console.log('Goodbye'+name); }else{ console.log('Hello'+name); }})();// ...
  • 轉自腳本之家: 本篇文章主要介紹了angular中實現li或者某個元素點擊變色的兩種方法,非常具有實用價值,需要的朋友可以參考下 本文介紹了angular中實現li或者某個元素點擊變色的兩種方法,分享給大家,希望對大家有幫助 先說一種最直接了當的不需要js控制。 方法一:直接在用ng-class就可 ...
  • 本來以為 Vue 的編譯器模塊比較好欺負,結果發現並沒有那麼簡單。每一種語法指令都要考慮到,處理起來相當複雜。上篇已經生成了 AST,本篇依然對 Vue 源碼做簡化處理,探究 Vue 是如果根據 AST 生成所需要的 render 函數的。 ...
  • 代碼: 方案一: div絕對定位水平垂直居中【margin:auto實現絕對定位元素的居中】, 相容性:,IE7及之前版本不支持 .father{ width:400px; height:400px; background: red; position:relative; /* 或者position ...
  • 最近剛剛用vue寫了個公司項目,使用vue-cli構建的,算是中大型項目吧,然後這裡想記錄並且分享一下其中的知識點,希望對大家有幫助,後期會逐漸分享;話不多說,直接上代碼!! app.vue main.js router文件夾裡面的index.js home.vue pagevue.vue next ...
  • 一、前端MVC概要 1.1、庫與框架的區別 框架是一個軟體的半成品,在全局範圍內給了大的約束。庫是工具,在單點上給我們提供功能。框架是依賴庫的。Vue是框架而jQuery則是庫。 1.2、AMD與CMD 在傳統的非模塊化JavaScript開發中有許多問題:命名衝突、文件依賴、跨環境共用模塊、性能優 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...