前言
作者在畢業之後進入了豬廠工作,正是在網易有數產品中認識了redux這個庫。從前端開發的角度來說,網易有數作為一個工具型web產品,是一個開發中充滿挑戰,富含樂趣的產品。
在這之前,作者對於部落格的積累可以說微乎其微,決心有所改變,對於redux應用也很感興趣,於是心中萌生了這麼一個念頭——寫個redux應用系列部落格。
這個系列文章會是我在將近兩年的redux應用開發中的一些總結和心得,其目的有兩個,一個是希望我在寫這個系列部落格的時候自我反思,自我總結,另一個目的是希望能對於同樣喜歡redux應用的同學有所啟發,然後可以互相交流學習心得。
整個系列不會對於redux的基礎進行講解,如果你沒有使用過redux,可以去redux官網進行一些基礎概念的熟悉。
複雜的單模組
步入正題,我們的前端經常自稱有數是一個web複雜應用,之所以這麼說,不是因為有數有著大量的頁面,或者有大量的業務邏輯,而是因為有數的核心邏輯是集中在一個單模組中的,大家可以看看下面這個介面圖,這個頁面是有數的報告編輯頁,也是有數的核心產品頁面。
在這個編輯頁面中,擁有著PPT式的互動方式、包括圖表拖動、resize、畫布縮放、複製黏貼、樣式設定等等,其互動方式的靈活性、複雜性是我至今遇到的最為複雜的,一個大家可以訪問這個產品的試用版玩玩感受一下,網址:www.youdata.163.com
如果沒有引入Redux,應用是如何進行狀態管理的?
沒錯,如標題說的,redux就是用來做應用的狀態管理的。既然今天的話題是redux,那麼我們肯定要看看沒有引入redux之前,我們的應用是如何進行狀態管理的?
答案很簡單,我們的應用的狀態管理主要依賴於父子元件之間進行狀態傳遞。
口說無憑,接下來以單向資料流為例子,來講講這樣怎麼進行狀態管理以及這樣進行狀態管理的侷限性。(雙向繫結的情況類似,大家可以自己思考一下)
注意: 下面的圖片是在網上的某一篇部落格找到的,忘記了地址,這裡沒貼來源對不住了。
簡單的場景:父子元件樹
狀態管理:狀態作為屬性,從父元件傳遞給子元件。
這種情況是最簡單的場景,在下面的程式碼中,父元件ParentComponent把loading這個狀態作為屬性傳遞給了子元件ChildComponent。
ParentComponent.js:
render() {
const { loading } = this.state;
return (
<ChildComponent loading={loading}>
This is content
</ChildComponent>
)
}
複製程式碼
稍微複雜的場景:兩個兄弟元件共享狀態,並在其中一個元件更改這個狀態。
狀態管理:
- 將這個狀態和修改這個狀態的函式提升到兩個元件的共同的祖先元件;
- 在子元件中呼叫作為傳下來的函式,修改這個狀態;
- 父元件中這個狀態得到修改;
- 父元件本身觸發重新update;
- 這個父元件的子元件觸發update;
- 子元件的狀態得到更新;
上面的步驟就是解決這個場景的狀態管理的基本步驟,簡單來說就是狀態提升,再向下傳遞。是不是感覺稍微有點麻煩了呢?如果上面語言描述你覺得難以理解,可以看看下面程式碼,相信如果你寫過React理解起來應該輕而易舉。
Parent.js
changeLoading(loading) {
this.setState({ loading });
}
render() {
const { loading } = this.state;
return (
<div className="m-parent">
<ChildComponentA
loading={loading}
onChange={value => this.changeLoading(value)}
/>
<ChildComponentB loading={loading} />
</div>
);
}
複製程式碼
ChildComponentA.js
render() {
const { loading, onChange } = this.props;
return <div onClick={() => onChange(!loading)}>a loading: {loading}</div>;
}
複製程式碼
ChildComponentB.js
render() {
const { loading } = this.props;
return <div>b loading: {loading}</div>;
}
複製程式碼
更復雜的場景:元件樹層級變深
狀態管理:
這個元件樹相比於上面的場景來說,不僅層級變深了,元件樹的分支也變多了,但是,從應用狀態的角度來說,核心步驟仍然是上面的場景的思想:狀態(函式)提升和向下傳遞屬性,多個狀態無非是重複多次這樣的過程。
這樣會有怎樣的弊端?
-
**造成了大量不必要的屬性傳遞。**在狀態傳遞的過程中,由於我們需要把狀態提升到共同的祖先元件,然後再層層傳遞到目標元件,中間必然要經過一些不相關的元件,而這些元件實際上並不需要這些狀態,這就造成了一些不必要的浪費。
-
**維護變得困難。**一旦增加狀態,或者共享狀態的元件發生了變化(這在業務場景中很常見),那麼你不但要更改用到這個狀態的元件,連帶這個屬性中間經過的元件都要更改,也許你只是要改個變數名,有十幾個元件要改,你煩不煩?所以這樣的狀態管理維護起來可以說是相當地麻煩。
元件樹結構與資料流關係不一致
上面我們介紹了依賴元件樹來傳遞狀態的狀態管理方法,也看到了這樣的管理方法是不方便的,一旦業務複雜起來的時候是難以維護的。
俗話說,知彼知己,百戰不殆。
在討論如何解決這個麻煩之前,我們先來好好剖析一下,這樣的狀態管理為什麼會充滿侷限性,其根本原因為:
元件樹結構與資料流關係不一致
什麼意思呢?
例如我們有這麼一個元件樹結構:
在這個元件樹中,B和E都要用到狀態state_a,所以state_a必須提升到A元件來進行管理,從資料流的角度來說,相對於A,B和E應該是同級節點,其資料樹結構應該為:
如圖片,我們就可以很清楚看出其元件樹結構和資料樹結構是不一致的,這也是問題所在。
引入Redux中介者
知道了上述狀態管理混亂的根本原因是元件樹結構和資料樹結構不一致,那麼我們就可以對症下藥。既然是要擺脫元件樹結構的束縛,那麼就應該把狀態全部拿出來解耦。
這時候就需要第三方介入,我們才可以把狀態從元件樹中拿出來交給它,這也就是所謂的中介者模式。
而Redux就是這麼一箇中介者,當我們引入Redux後,我們的應用的狀態管理就會變成:
狀態管理:
- 把狀態(修改函式)通通交給Redux來管理,對應redux的概念為state和reducer。
- 當某個元件想修改狀態,這時候這個元件只要通知redux進行修改,這就是dispatch過程。
- redux在收到action後,對state進行修改,然後把狀態丟給想要這個狀態的元件,這個過程就是react-redux中的connect和mapStateToProps。
看,是不是就解決了剛才的狀態管理的問題,
結束語
今天redux狀態管理的話題就到這裡,後面會陸陸續續更新redux相關實踐和理解,可寫的內容不少,如:撤銷重做的實現、多人協作的實現、高階reducer的應用、不可變資料介紹使用、容器元件和展示元件介紹和使用、非同步方案選擇等等等很多,所以這個系列可以寫很久,如果你有比較感興趣的點,可以留言在評論區,會考慮先寫。