第三章 從Flux到Redux(Flux篇)

? ? ? ? 在前一章中我們已經感受到完全用React來管理應用數據的麻煩,在這一章中,我們將介紹Redux這種管理應用狀態的框架,包含以下的內部:

? ? ? ? 單項數據流框架的始祖Flux

? ? ? ? Flux理念的一個更強實現Redux;

? ? ? ? 結合React和Redux。


3.1? Flux

? ? ? ? 要了解Redux,首先要從Flux說起,可以認為Redux是Flux思想的另一種實現方式,通過了解Flux,可以認為Redux是Flux思想的另一種實現方式,通過了解Flux,我們可以知道Flux一族框架(其中包括(Redux)貫徹的最重要的觀點--單向數據流,更重要的事,我們可以發現Flux框架的缺點,從而深刻地認識到Redux相對Flux的改進之處。

讓我們來看看Flux的歷史,實際上,Flux是和React同時面世的,在2013年,Facebook公司讓React亮相的同時,也推出了Flux框架,React和Flux相輔相成,Facebook認為兩者結合起來才能構建大型的JavaScript應用。

做一個容易理解的對比,React是用來替換jQuery的,那么Flux就是以替換Backbone.js、Ember.js等MVC一族框架為目的。

在MVC的世界里,React相當于V(也就是View)的部分,只涉及頁面的渲染,一旦涉及應用的數據管理部分,還是交給Model和Controller,不過,Flux并不是一個MVC框架,事實上,Flux認為MVC框架存在很大問題,它推翻了MVC框架,并用一個新的思維來管理數據流轉。

3.1.1 MVC框架的缺陷

MVC框架是業界廣泛接受的一種前端應用框架類型,這種框架把應用分為三個部分:

Model(模型)負責管理數據,大部分業務邏輯也應該放在Model中;

View(視圖)負責渲染用戶界面,應該避免在View中涉及業務邏輯;

Controller(控制器)負責接受用戶輸入,根據用戶輸入調用對應的Model部分邏輯,把產生的數據結果交給View部分,讓View渲染出必要的輸出。

這樣的邏輯劃分,實質上把以一個應用劃分為多個組件一樣,就是“分而治之”。毫無疑問,相比把業務邏輯和界面渲染混在一起,MVC框架要先進的多。這種方式得到了廣發認可,Facebook最初也是用這種框架。

但是,Facebook的工程部門逐漸發現,對于巨大的代碼庫和龐大的組織,MVC很快變的非常復實際的框架實現中,總是允許View和Model可以直接通信,而在MVC中直接讓其對話是一種災難。

有這么一個有意思的現象:凡是只在服務器端使用過MVC框架的朋友,就很容易理解和接受Flux。而對于已有很多瀏覽器端MVC框架經驗的朋友,理解MVC和Flux要費點勁。

這個原因就是:服務器端MVC往往就是每個請求就只在Controller-Model-View三者之間走一圈,結果就返回給瀏覽器去渲染或者其他處理了。然后這個生命周期的Controller-Model-View就可以回收銷毀了,這是一個嚴格意義的單向數據流;對于瀏覽器MVC框架,存在用戶的交互處理,界面渲染出來后,Model和View依然存在于瀏覽器中,這時候就會誘發開發者為了簡便,讓現存的Model和View直接對話。

對于MVC框架,為了讓數據流可控,Controller應該是中心,當View要傳遞信息給Model時,應該調用Controller的方法,同樣,當Model要更新View時,也應該通過Controller引發新的渲染。

當Facebook推出Flux時,招致了很多質疑。很多人說,Flux不過是對數據流管理更加嚴格的MVVC框架而已,這種說明不準確,但一定意義說出了Flux的一個特點:更嚴格的數據流控制。

一個Flux應該包含四個部分,我們先粗略了解一下:

①Dispatcher,處理動作分發,維持Store之間的依賴關系。

②Store,負責存儲數據處理數據相關邏輯。

③Action,驅動Dispatcher的JavaScript對象。

④View,視圖部分,負責顯示用戶界面。

若非要將MVC與Flux做個結構對比:那么,Flux的Dispatcher相當于MVC的Controller,Flux的Store相當于MVC的Model,Flux的View對應MVC的View,至于多出來的Action,可以理解為對應給MVC框架的用戶請求。

在MVC框架中,系統能夠提供什么樣的服務,通過Controller暴露函數來實現。每增加一個功能,Controller就要增加一個函數;在Flux的世界里,新增加功能并不需要Dispatcher增加新的函數,實際上,Dispatcher自始至終只需要暴露一個函數Dispatch,當需要增加新的功能時,要做的事增加一種新的Action類型,Dispatcher的對外接口并不用改變。

我們已基本了解Flux是怎么一回事,那么接下來實踐一下看看怎么用Flux改進一下我們的React應用。

3.1.2 Flux應用

因為Redux其實與Flux一脈相承,從Flux例子入手,在理解Redux的時候就會感覺非常順暢了。

為了理解Flux,首先通過命令行在項目目錄下安裝Flux。

npm install --save flux

利用Flux來實現ControlPanel應用的相關代碼在-https://giyhub.com/mocheng/react-and-redux/tree/master/chapter-03/flux-上可以找到。最終頁面應用效果和第二章完全 一樣,通過不同實現方式,來體會每個方式的優劣。

1.Dispatcher

首先,創造一個Dispatcher,幾乎所有應用都只需要擁有一個Dispatcher,本例也不例外。在src/AppDispatcher.js中,我們創建這個唯一Dispatcher對象:

import? {Dispatcher } from 'flux';

export default new Dispatcher();

非常簡單,我們引入flux庫中的Dispatcher類,然后創造一個新的對象作為這個文件的默認輸出就足夠了。在其他代碼中,將會引用這個全局唯一飛Dispatcher對象。

Dispatcher存在的作用,就是用來派發action,接下來我們來定義應用中涉及的action。

2.action

action顧名思義代表一個“動作“,不過這個 動作只是一個普通的JavaScript對象代表一個動作的純數據,類似DOM API中的事件(event)。甚至,和事件相比,action其實還是更加純粹的數據對象,因為事件往往還包含一些方法,但是action對象不自帶方法,就是純粹的數據。

作為管理,action對象必須有一個名為type的字段,代表這個action對象的類型,為了記錄日志和debug方便,這個type應該是字符串類型。

定義action通常需要兩個文件,一個定義action類型,一個定義action的構造函數(也稱為action creator)。分成兩個文件的主要原因是在Store中會根據 action類型做不同操作,也就有單獨導入action類型的需要。

在src/ActionTypes.js中,我們定義action的類型:

export? const INCREMENT ='increment';

export? const DECREMENT ='decrement';

在這個例子中,用戶只能做兩個動作,一個是點擊“+”按鈕,一個是點擊"-"按鈕,所以我們只有兩個action類型INCREMENT和DECREMENT。

現在我們在src/Actions.js文件中定義action構造函數:

import * as ActionTypes from './ActionTypes';

import AppDispatcher from './AppDispatcher.js';

export const increment = (counterCaption)=>{

AppDispatcher.dispatcher({

type:ActionTypes.INCREMENT,

counterCaption:counterCaption

});

};

export const decrement = (counterCaption)=>{

AppDispatcher.dispatcher({

type:ActionTypes.DECREMENT,

counterCaption:counterCaption

});

};

這個Action.js導出了兩個action構造函數increment和decrement,當這兩個函數被調用的時候,創造了對應的action對象,并立即通過AppDispatcher.dispatcher函數派發出去。

派發出去的action對象最后怎么樣了呢?在下面關于Store的部分可以看到。

3.Store

一個Store也是一個對象,這個對象存儲應用狀態,同時還要接受Dispatcher派發的動作,根據動作來決定是否更新應用狀態。

接下來創造Store相關代碼,便于代碼維護,我們在src下創建一個子目錄stores,在這個目錄放置所有Store代碼。

在前面章節的ControlPanel應用例子里,有三個Counter組件,還有一個統計三個Counter計數值之和的功能,我們遇到的麻煩就是這兩者之間的狀態如何同步的問題,現在,我們創造兩個Store,一個是為Counter組件服務的CounterStore,另一個就是為總數服務的SummaryStore。

src/store/CounterStore.js,代碼如下:

const counterValues = {

'First':0,

'Second':10,

'Third':30

}

const CounterStore = Object.assign({},EventEmitter.prototype,{

getCounterVlues:function(){

return counterValues;

},

emitChange : function(){

this.emit(CHANNGE_EVENT);

},

addChangeListener:function(callback){

this.on(CHANGE_EVENT,callback);

},

removeChangeListener:function(callback){

this.removeListener(CHANGE_EVENT,callback);

}

});

當Store狀態發生變化的時候,需要通知應用的其他部分做必要的響應。在我們的應用中,做出響應的部分當然就是View部分,但是我們不應該硬編碼這種聯系,應該用消息的方式建立Store和View的聯系。這就是為什么我們用CounterStore擴展了EventEmitter.prototype,等于讓CounterStore成了EventEmitter對象,一個EventEmitte實例對象支持下列相關函數。

①emit函數,可以廣播一個特定事件,第一個參數是字符串類型的事件名稱。

②on函數,可以增加一個掛在這個EventEmitter對象特定事件上的處理函數,第一個參數是字符串類型的事件名稱,第二個參數是處理函數。

③removeListener函數,和on函數做的事情相反,刪除掛在這個EventEmitter對象特定事件上的處理函數,和on函數一樣,第一個參數是事件名稱,第二個參數是處理函數。要注意,如果要調用removeListener函數,就一定要保留對處理函數的引用。

對于CounterStorer對象,emitChange、addChangeListener和removeChangeListener函數就是利用EvenetEmitter上述的三個函數完成對CounterStore狀態更新的廣播、添加監聽函數和刪除監聽函數等操作。

CounterStore函數還提供了一個getCounterValues函數,用于讓應用中其他模塊可以讀取當前的計數值,當前的計數值存儲在文件模塊級的變量counterValues中。

上面實現的Store只有注冊到Dispatcher實例上才能真正發揮作用,所以還需要增加下列代碼:

import AppDispatcher from '../AppDispatcher.js';

CounterStore.dispatcherToken =AppDispatcher.register((action) =>{

if(action.type ===ActionTypes.INCREMENT){

CounterValues[action.counterCaption]++;

CounterStore.emitChange();

}else if(action.type ===ActionTypes.DECREMENT){

CounterValues[action.counterCaption]--;

CounterStore.emitChange();

}

});

這是最重要的一個步驟,要把CounterStore注冊到全局唯一的Dispatcher上去。Dispatcher有一個函數叫做register,接受一個回調函數作為參數。返回值是一個token,這個token可以用于Store之間的同步,我們在CounterStore中還用不上這個返回值,在稍后的SummaryStore中會用到,現在我們只是把register函數的返回值保存在CounterStore對象的dispatcherToken字段上,待會就會用得到。

現在我們仔細看看register接受的這個回調函數參數,這是Flux流程中最核心的部分,當通過register函數把一個回調函數注冊到Dispatcher之后,所有派發給Dispatcher的action對象,都會傳遞到這個回調函數中來。

比如通過Dispatcher派發一個動作,代碼如下:

AppDispatcher.dispatcher({

type:ActionTypes.INCREMENT,

counterCaption:'First'

});

那么CounterStore注冊的回調函數就會被調用,唯一的一個參數就是那個action對象,回調函數要做的 ,就是根據action對象來決定該如何更新自己的狀態。

作為一個普遍接受的傳統,action對象中必有一個type字段,類型是字符串,用于表示這個action對象是什么類型。

在我們的例子中,action對象的type和counter Caption字段結合在一起,可以確定是哪個計數器應該做加一或減一的動作,上面例子中的動作含義就是:“名字為First的 計數器要做加一動作”。

無論是加一或者減一,最后都要調用CounterStore.emitChange函數,假如有調用者通過Counter.addChangeListner關注了CounterStore的狀態變化,這個emitChange函數調用就會 引發監聽函數的執行。

接下來,我們再來看看另一個Store,也就是代表所有計數器計數值總和的Store,在src/stores/SummaryStore.js中編寫源代碼。

SummaryStore也有emitChange、addChangeListener還有removeChangeListener函數,功能一樣也是用于通知監聽者狀態變化,這幾個函數的代碼和CounterStore中 完全重復,不同點是對獲取狀態函數的定義,代碼如下:

? function? computeSummary(counterValues){

let summary = 0;

for(const key in counterValues){

if(counterValues hasOwnPoperty(key)){

summary+=counterVlues(key);

}

}

return summary;

}

const SummaryStore = Object.assign({},EventEmitter.prototype,{

getSummary:function(){

return computeSummary(CounterStore.getCounterValues());

}

});

可以注意到,SummaryStore并沒有存儲自己的狀態,當getSummary被調用時,它是直接從CounterStore里獲取狀態 計算的。

CounterStore提供了getCounterValues函數讓其他模塊能夠獲得所有計數器的值,SummaryStore也提供了getSummary讓其他模塊可以獲得所有計數器當前值的總和。不過,既然總可以通過CounterStore.getCounterValues函數獲取最新鮮的數據,SummaryStore似乎也就沒有必要把計數器當前值總和存儲到某個變量里。事實上,可以看到SummaryStore并不像CounterStore一樣用一個變量counterValus存儲數據,SummaryStorer不存儲數據,而是每次對getSummary的調用,都實時讀取CounterStore.getCounterValus,然后實時計算出總和返回給調用者。

可見,雖然名為Store,但并不表示一個Store必須要存儲什么東西,Store只是提供獲取數據的方法,而Store提供的數據完全可以用另一個Store計算得來。。

SummaryStore在Dispatcher上注冊的回調函數

也和CounterStore很不一樣,代碼如下:

SummaryStore.dispatcherToken = Dispatcher.register((action)=>{

? ? if((action.type===ActionTypes.INCREMENT)||

(action.type===ActionTypes.DECREMENT)){

AppDispatcher.waitFor([CounterStore.dispatchehToken]);

SummaryStore.emitChange();

? }

});

SummaryStore同樣也通過AppDispatcher.register函數注冊一個回調函數,用于接受派發的action對象。在回調函數中,也只關注INCREMENT和DECREMENT類型的action對象,并通過emitChange通知監聽者,注意在這里使用了waitFor函數,這個函數解決的是下面描述的問題。

既然一個action對象會被派發給所有回調函數,這就產生了一個問題,到底是按照什么順序調用各個回調函數呢?

即使Flux按照register調用的順序去調用各個回調函數,我們也完全無法把握各個Store哪個先裝載從而調用register函數。所以,可以認為Dispatcher調用回調函數的順序是無法預期的,不要假設它會按照我們期望的順序逐個調用。

設想一下,當一個INCREMENT類型的動作被派發了,如果首先調用SummaryStore的回調函數,在這個回調函數中立即用emitChange通知了監聽者,這時監聽者會立即通過SummarryStore的getSummary獲取結果,而這個getSummary是通過CounterStore暴露的getCounterValues函數獲取當前計數器值,計算總和返回......然而,這時候,INCREMENT動作還沒來得及派發到CounterStore啊!也就是說,CounterStore的getCounterrValues返回的還是一個未更新的值,那樣SummaryStore的getSummary返回值也就是一個錯誤值了。

于是,解決這個問題就靠Dispatcher的waitFor函數。在SummaryStore的回調函數中,之前在CounterStore中注冊回調函數時保存下來的dispatcherToken終于派上用場了。

Dispatcher的waitFor可以接收一個數組作為參數,數組中的每個元素都是一個Disppatcher.register函數的返回結果,也就是所謂的dispatcherToken。這個waitFor函數告Dispatcher,當前的處理必須要暫停,直到dispatcherToken代表的那些已注冊回調函數執行結束才能繼續。

我們知道,JavaScript是單線程的語音,不可能有線程等待這回事,這個waitFor函數當然并不是多線程實現的,只是在調用waitFor的時候,把控制權交給Dispatcher,讓Dispatcher檢查一下dispatcherToken代表的回調函數有沒有被執行,如果已經執行,那就直接繼續,如果還沒有執行,那就調用dispatcherToken代表的回調函數之后waitFor才返回。

回到上面假設的例子,即使SummaryStore比CounterStore提前接收到了action對象,在emitChange中調用waitFor,也就能夠保證在emitChange函數被調用的時候,CounterStore也已經處理過這個action對象,一切完美解決。

這里要注意一個事實,Dispatcher的register函數,只提供了注冊一個回調函數的功能,但卻不能讓調用者在register時選擇只監聽某些action,換句話說,每個register的調用者只能這樣請求:“當有任何動作被派發時,請調用我。”但不能夠這么請求:“當這種類型或者那種類型被派發的時候,請調用我”。

當一個動作被派發的時候,Dispatcher就是簡單地把所有注冊的回調函數全部都調用一遍,至于這個動作是不是對方關心的,Flux的Dispatcher不關心,要求每個回調函數去鑒別。

看起來,這似乎是一種浪費,但是這個設計讓Flux的Dispatcher邏輯最簡單話,Dispatcher的責任越簡單,就越不會出現問題。畢竟,由回調函數全權決定如何處理actioon對象,也是非常合理的。

4. view

首先需要說明,Flux框架下,View并不是說必須使用React,View本身是一個獨立的部分,可以用任何一種UI 庫來實現。

不過,話說回來,既然我們都使用上Flux了,除非項目有大量歷史遺留代碼需要利用,否則實在沒有理由不用React來實現view。

存在于Flux框架中的React組件需要實現以下幾個功能:

①創建時要讀取Store上狀態來初始化組件內部狀態。

②當Store上狀態發生變化時,組件要立刻同步更新組件內部狀態保持一致;

③View如果要改變Store狀態,必須而且只能派發action。

最后讓我們來看看例子中的View部分,為了方便管理,所有的View文件都放在src/views目錄下。

先看src/vies/ControlPanel.js中的ControlPanel組件,其中render函數的實現和上一章很不一樣,代碼如下:

render(){

return(

<div style={ style }>

<Counter caption ="First"/>

<Counter caption ="Seond"/>

<Counter caption ="Third"/>

<hrr/>

<Summary />

</div>

);

}

可以注意到,和前面章節中的ControlPanel不同,Counter組件實例只有caption屬性,沒有initValues屬性。因為我們把計數值包括初始值全都放到CounterStore中去了,所以在創造Counter組件實例的時候就沒有必要指定initValue了。

接著看src/views/Counter組件,構造函數中初始化this.state的方式有了變化,代碼如下:

constructor(props){

super(props);

this.onChange = this.onChange.bind(this);

this.onClickIncrementButton =this.onClickIncrementButton.bind(this);

this.onClickDecrementButton= this.onClickDecrementButton.bind(this);

this.state = {

count:CounterStore.getCounterValues()[props.caption]

}

}

在構造函數中,CounterStore.getCounterValues函數獲得了所有計數器的當前值,然后把this.state初始化為對應caption字段的值,也就是說Counter組件store來源不再是prop,而是Flux的Store。

Counter組件中的state應該成為Flux Store上狀態的一個同步鏡像,為了保持兩者一致,除了在構造函數中的初始化之外,在之后CounterStore上狀態變化時,Counter組件也要對應變化,相關代碼如下:

componentDidMount(){

CounterStore.addChangeListener(this.onChange);

}

componentWillUnmount(){

CounterStore.removeChangeListener(this.onCange);

}

onChange(){

const newCount = CounterStore.getCounterValues()[this.props.caption];

this.setState({count:newCount});

}

如上面的代碼所示,在componentDidMount函數中通過CounterStore.addChangeListener函數監聽了CounterStore的變化之后,只有CounterStore發生化,Couunter組件的onChange函數就會被調用。與componentDidMount函數中監聽事件相對應,在componentWillUnmount函數中刪除了這個監聽。

接下來,要看React組件如何派發action,代碼如下:

onClickIncrementButton(){

Action.increment(this.props.caption);

}

onClickDecrementButton(){

Actions.decrement(this.props.caption);

}

render(){

const { caption } =this.props;

return(

<div>

<button style={buttonStyle} onClick={this.onClickIncrementButton} >+</button>

<button style={buttonStyle} onClick={this.onClickDecrementButton} >-</button>

<span>{caption} count:{this.state.count}</span>

</div>

);

}

可以注意到,在Counter組件中有兩處用到CounterStore的getCounterValues函數的地方,第一處是在構造函數中初始化this.state的時候,第二處是在響應CounterStorre狀態變化的onChange函數中,同樣一個Store狀態,為了轉換為React組件的狀態,有兩次重復的調用,這看起來似乎不是很友好。但是,React組件的狀態就是這樣,在構造函數中要對this.state初始化,要更新它就要調用this.setState函數。

有沒有更簡潔的辦法?比如說只使用CounterStore.getCounterValues一次?可惜,只要我們想用組件的狀態來驅動組件的渲染,就不可避免要有這兩步。那么如果我們不利于用組件的狀態呢?

如果不使用組件的狀態,那么我們就可以逃出這個必須在代碼中使用Store兩次的宿命,在接下來的章節里,我們會遇到這種“無狀態”組件。

Summary組件,存在于src/views/Summary.js,和Counter類似,在constructor中初始化組件狀態,通過在componentDidMount中添加SummaryStore的監聽來同步狀態,因為這個View不會有任何交互功能,所以沒有派發出任何action。

3.1.3Flux的優勢

本章例子和第二章只用React的實現效果一樣,但是工作方式有了大變化。

回顧一下完全只使用React實現的版本,應用的狀態數據只存在于React組件之中,每個組件都要維護驅動自己渲染的狀態數據,單個組件的狀態還好維護,但是如果多個組件之間的狀態有關聯,那就麻煩了。比如Counter組件和Summary組件,Summary組件要維護所有Counter組件計數值的總和,Counter組件和Summary分別維護自己的狀態,如何同步Summary和Counter狀態就成了問題,React只提供了props方法讓組件之間通信,組件之間關系稍微復雜一點,這種方式就顯得非常笨拙。。

Flux的架構下,應用的狀態放在了Store中,React組件只是扮演View的作用,被動根據Store的狀態來渲染。在上面的例子中,React組件依然有自己的狀態,但是已經完全淪為Store組件的一個映射,而不是主動變化的數據。

在完全只用React實現的版本里,用戶的交互操作,比如點擊“+“按鈕,引發的時間處理函數直接通過this.setState改變組件的狀態。在Flux的實現版本里,用戶的操作引發的是一個“動作“的派發,這個派發的動作會發送給所有的Store對象,引起Store對象的狀態改變,而不是直接引發組件的狀態改變。因為組件的狀態是Store狀態的映射,所以改變了Store對象也就觸發了React組件對象的狀態改變,從而引發了界面重新渲染。

Flux帶來了什么好處呢?最重要的就是“單向數據流”的管理方式。

在Flux的理念里,如果要改變界面,必須改變Store中的狀態,如果要改變Store中的狀態,必須派發一個action對象,這就是規矩。在這個規矩之下,想要追溯一個應用的邏輯就變得非常容易。

我們已經討論過MVC框架的缺點,MVC最大的問題就是無法禁絕View和Model直接的直接對話,對應于MVC中View就是Flux中的View,對應于MVC中的Model就是Flux中的Store,在Flux中,Store只有get方法,沒有set方法,根本不可能直接去修改其內部狀態,View只能通過get方法獲取Store的狀態,無法直接去修改狀態,如果View想要修改Store狀態的話,只有派發一個action對象給Dispatcher。

這看起來是一個“限制”,但卻是一個很好的“限制”,禁絕了數據流混亂的可能。

簡單來說,在Flux的體系下,驅動界面改變始于一個動作的派發,別無他法。

3.1.4Flux的不足

任何工具不可能只有優點沒有缺點,接下來讓我們看看Flux的不足之處,只有了解了Flux的不足之處,才能理解為什么會出現Flux的改進框架Redux。

1.Store之間依賴關系

在Flux的體系中,如果兩個Store之間有邏輯依賴關系,就必須用上Dispatcher的waitFor函數。在上面的例子中我們已經使用過waitFor函數,SummmarryStore對action類型的處理,依賴于CounterStore已經處理過了。所以,必須要通過waitFor函數告訴Dispatcher,先讓CounterStorer處理這些action對象,只有CounterStore搞定之后SummaryStore才繼續。

那么,SummaryStore如何標識CounterStore呢?靠的是register函數的返回值dispatchToken。

而dispatcherToken的產生,當然是CounterStore控制的,換句話說,要這樣設計:

□ CounterStore必須要把注冊回調函數產生的dispatchToken公布與眾。

□ SummaryStore必須要在代碼里建立對CounterStore的dispatchToken的依賴。

雖然Flux這個設計的確解決了Store之間的依賴關系,但是,這樣明顯的模塊之間的依賴,看著還是讓人感覺不太舒服,畢竟,最后的依賴管理是根本不讓依賴產生的。

2.難以進行服務器端渲染

關于服務器端渲染,我們在后面的章節(第12章)“同構”中詳細介紹,在這里,我們只需要知道,如果要在服務器端渲染,輸出不是一個DOM樹,而是一個字符串,準確來說就是一個全是HTML的字符串。

在Flux的體系中,有一個全局的Dispatch,然后每一個Store都是一個全局唯一的對象,這對于瀏覽器端應用完全沒有問題,但是如果放在服務器端,就會有大問題。

和一個瀏覽器只服務于一個用戶不同,在服務器端要同時接受很多用戶的請求,如果每個Store都是全局唯一的對象,那不同請求的狀態肯定就亂套了。

并不是說Flux不能做服務器端的渲染,只是說Flux做服務器端渲染很困難,實際上,Facebook也說的很清楚,Flux不是設計用作服務器端渲染的,他們也從來沒有嘗試過把Flux應用于服務器端。

3.Store混雜了邏輯和狀態

Store封裝了數據和處理數據的邏輯,用面向對象思維來看,這是一件好事,畢竟對象就是這樣定義的。但是,當我們需要動態替換一個Store的邏輯時,只能把這個Store整體替換掉,那也就無法保持Store中存儲的狀態。

讀者可能會問,有什么場景是需要替換Store的呢?

在開發模式下,開發人員要不停的對代碼進行修改,如果Store在某個狀態下引發了bug,如果能在不毀掉狀態的情況下替換Store的邏輯,那就最好了,開發人員就可以不斷地改進邏輯來驗證這個狀態下bug是否修復了。

還有一些應用,在生產環境下就要根據用戶屬性來動態加載不同的模塊,而且動態加載模塊還希望不要網頁重新加載,這時候也希望能夠在不修改應用狀態的前提下重新加載應用邏輯,這就是熱加載(Hot Load),在第12章會詳細介紹如何實現熱加載。

可能讀者會覺得這里所說的“偷梁換柱”一樣的替換應用邏輯是不能做到的。實際上,真的能夠做到,Redux就能做到,所以讓我們進入Redux的世界吧。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容