預設為 true,會根據以下 function 比較 stateProps, dispatchProps, mergeProps 是否相當來決定是否要 re-render。基本上預設的 function 符合 99% 的使用情景,不過如果計算整個 state 的代價相當昂貴,可以考慮使用以下的 function 來改善效能。
❗️注意:只有當 pure 這個選項為 ture 的時候,下面的 function 才有用
areStatesEqual(function(next, prev)): boolean
回傳 boolean 值來決定 state 是否相等,這邊的 state 是指 redux store 的 state 而不是 container 的 state。
import { combineReducers } from 'redux';
import posts from './reducers/posts';
import search from './reducers/search';
export default combineReducers({
posts,
search,
});
compose
使用像是 relay 或是 apollo 的 API 時,有時候希望能夠把 server 端回傳資料拿到 redux store 做處理,讓我們也能夠透過自行定義 action 的方式來操作資料。
在 apollo 1 的時候,預設會在 ApolloProvider 裡頭建立一個 redux store,Apollo 會送出自己的 redux action。不過在 Apollo 2 的時候已經移除這種方式了。官方文件提到:
The 2.0 moves away from using Redux as the caching layer in favor of Apollo maintaining its own store through the provided cache passed when creating a client. This allows the new version to be more flexible around how data is cached, and opens the storage of data to many new avenues and view integrations. — Apollo Client
透過 compose,我們可以把 redux 與 graphql 串接起來,例如:
import { compose, connect } from 'react-redux';
import { graphql } from 'apollo-client';
compose(
connect(mapStateToProps, mapDispatchToProps),
graphql(myGraphqlQuery, options)
)(PostContainer);
深入探討 Redux 與 react-redux
前言
官方文件寫得很好,不過 redux 的學習曲線其實不低,尤其是在剛入門 React 時,很容易就會陷入不知道為什麼要這麼做的窘境。設定一個 redux 的環境超級麻煩,要先幫 action, reducer, container 建立資料夾,寫一堆 action creator。 最後還要搭配
react-redux
寫mapStateToProps
,bindActionCreators
等等,只為了寫出一個 Todo App。這種繁瑣的設定是有其原因的,不過為了保持簡單性,todo app 範例為了保持簡單,很難讓人體會到 redux 的亮眼之處。redux 作者本人也曾經寫過 You might not need Redux,來分析怎樣的情境下可能不需要 Redux。
思考取捨是工程師的職責與專業,盲目崇拜框架只會讓自己寫出綁手綁腳、難以維護的程式碼。
mapStateToProps(state, [ownProps]): stateProps
許多人都是直接把 store 裡頭全部的東西一股腦兒塞進去 root component 裏頭。其實搭配
mapStateToProps
傳入有許多好處:1. 存取 redux store
這是
mapStateToProps
的函式簽名,回傳的 object 會被塞到 connect 的 Component 當中。這樣子不僅可以拿我們想要的資料就好,也能夠把資料轉換成 Component 需要的格式。例如下面的 Component:如果今天想要把資料塞進去
Profile
裏頭:從這裡就能看到,不需要修改
Profile
本身的 prop,而是透過mapStateToProps
的方式把資料傳進來。這樣之後如果回傳的資料有變化(例如username
改成name
),只需要修改mapStateToProps
即可。不一定每個連接到 redux store 的 component 都要另外在建立一個 container,像上述的範例,直接用 stateless component 連接也可以。2. 與 Component props 搭配使用
mapStateToProps
的第二個參數是ownProps
。這讓我們可以把傳入 Component 的 props 也一起傳進來,例如今天想要根據 UI 的某個狀態來 filter 資料:
我們看到在
renderPosts
裡面做了 filter 邏輯,雖然在這裡只是簡單用 indexOf 判斷,不過在實務上可能會有比較複雜的 filter 邏輯。我們希望讓 View 裡頭的邏輯越乾淨越好。透過
ownProps
可以這樣做:❗️注意:並不需要為了使用這些參數而故意這麼做,選擇適當的做法才是最重要的
mapDispatchToProps(dispatch, [ownProps])
你可能不需要
bindActionCreators
許多人都習慣在 Root component 上引入所有的 actions,然後再透過 bindActionCreators 傳入。
不過大部分的使用上,幾乎只會使用到某些特定的 action,其實不用全部傳入,而且透過 Component 層層傳遞 actions 的話,就喪失了 redux 的初衷了,一旦讓 Component 知道太多 actions 的存在,之後要修改 Component 的階層就會非常困難。
mapDispatch
可以直接傳入 object,它會自動跟 dispatch 組裝。例如結合按鈕:不需要再一層 container 的包裝或是修改 Component 的內容就可以復用 Button 這個元件。或者想要傳入 action 在
componentDidMount
或是其他生命週期呼叫時,dispatchToProps
是個相當有用的函數,而且除了傳入function(dispatch)
之外,也能夠直接傳 object,redux 內部會幫我們與 dispatch 綁定,相當方便。❗️注意:如果沒有傳入這個參數,會直接把 dispatch 函數當作 props 傳入
mergeProps(stateProps, dispatchProps, ownProps): stateProps
這是
connect
的第三個參數,比較少人使用,不過如果在dispatch
時想要根據ownProps
的某些屬性綁定參數,或是透過當前 state 來建立客製化的 dispatch,這個函數就相當方便,可以讓 component 更乾淨。上述的例子當中我們另外傳入了
selectedPosts
到 component 當中作為 action 的參數之一,搭配mergeProps
:❗️注意:mergeProps 的預設值為 Object.assign(stateProps, dispatchProps, ownProps),所以要注意 props 名稱有沒有不小心覆蓋掉哦
options
options 是 connect 的第四個參數(原來還有第四個參數)。是專門為了調整效能用的,大部分的場景很少遇到,畢竟
大部分的情況下,效能都不是最大的瓶頸以下一一介紹
pure(boolean)
預設為
true
,會根據以下function
比較stateProps
,dispatchProps
,mergeProps
是否相當來決定是否要re-render
。基本上預設的function
符合 99% 的使用情景,不過如果計算整個 state 的代價相當昂貴,可以考慮使用以下的 function 來改善效能。❗️注意:只有當 pure 這個選項為 ture 的時候,下面的 function 才有用
areStatesEqual(function(next, prev)): boolean
回傳
boolean
值來決定 state 是否相等,這邊的 state 是指 redux store 的 state 而不是 container 的 state。Default 值為
===
。也就是比較 nextState 與 prevState 的 reference 是否相同。使用時機:
mapStateToProps
可能需要大量的運算(例如:對多筆資料作 map, filter 等等),而 connect 的 Component 只關注一部份的 state。這漾一來,這個 Container 就只會關注
manyPosts
的變化,而不會因為其他 state 的改變而更新。() => false
讓 Container 每次都更新,而不用另外再比較一次。storeKey(string)
在 redux 當中可以傳入多個 store(咦,這不是跟 redux 的哲學不一樣嗎?),因此官方不推薦你使用多個 store,除非你已經有一個相當大的 app 以及 store,想要分離 store 來改善效能問題
善用 middleware
在 redux 當中的 middleware 函式簽名長這樣:
任何會有 side effect 或是需要存取外部狀態(非 redux store)、跟 app 本身無關的行為(如 log、錯誤處理)時,就可以考慮用 middleware 來完成。
錯誤處理
存取 cookie, localStorge 狀態
hijack API request
比如我希望對任何的 API call 作統一逾時的動作,在 action creator 的 convention 有
FETCH_
開頭就是 API call。當然為了示範用,這並不是一個很好的 timeout 方式,他只是在一段時間後送出一個
FETCH_REQUEST_TIMEOUT
的 action,而沒有真正取消 API request。offline support
以上只是相當簡略的實作,在 offline 的時候我們希望把任何需要網路的操作儲存起來,並且在連上線的時候把 pending 的 actions 送出。
小結
透過 middleware 可以幫助我們做到許多事情,以上的舉例只是其中一部份而已,善用 middleware 可以簡化許多 action 的操作。
善用 combineReducers
如果一個 reducer 的 switch case 太多,已經多到開始讓人煩燥時,或許是該拆分 reducer 的時候了,redux 本身就是將多個 store 透過 combineReducers 組合起來,每個 reducer 只專注在特定的 actions 上。
重複地寫 actions 與 reducer 是件重複又無聊的事,可以參考像是 redux-actions 的 library 簡化。
compose
使用像是 relay 或是 apollo 的 API 時,有時候希望能夠把 server 端回傳資料拿到 redux store 做處理,讓我們也能夠透過自行定義 action 的方式來操作資料。
在 apollo 1 的時候,預設會在 ApolloProvider 裡頭建立一個 redux store,Apollo 會送出自己的 redux action。不過在 Apollo 2 的時候已經移除這種方式了。官方文件提到:
透過
compose
,我們可以把 redux 與 graphql 串接起來,例如:注意這邊的順序,如果你希望先拿取 graphql 的資料,再跟資料與 redux 做連接的話,那麼 graphql 要先放在下面。接下來就可以透過 mapStateToProps 的第二個參數(ownProps)拿取,graphql 的狀態。
這樣子就不用硬把資料放在 redux store 了。
題外話:如果你是使用
redux-observable
時,可以透過 createEpicMiddleware 的第二個參數傳入 dependencies這樣子就可以透過 epic 發送 query,同時使用 reducer 來接收資料。(兩種方式各有優劣)
不一定只能有一個 Container
為了方便傳遞 props,在許多開發情景上許多工程師都只用一個 Container 來接 redux store 的資料,但 container 的階層也有可能是很多層的,因此在 Container 裡面只要有需要,隨時都可以在放一個 Container 來接 redux store,來避免 props 層層傳遞的問題。
幾個 redux 常見的問題
Q. 要不要把 UI State 放在 redux?
使用
this.setState()
來管理 UI 狀態很簡單,也非常直觀,不需要大費周章地寫 action, reducer 來操作,但一旦使用 state,也就很難被外部控制。因此如果 Component 確定 UI State 都只會由自己管理,不會讓其他 component 操作時,放在 component state 就是個不錯的選擇。很久以前 React 還有
setProps
的時候很難決策到底要怎麼使用 props 還是 state。不過移除後使用時機就非常明顯了:this.setState
操作,但不容易讓外部 Component 存取但像是 modal 這種經常在其他地方使用的 Component,放在 redux 就是不錯的選擇,我們可以在其他 Component (或 Container) 裡頭呼叫
OPEN_MODAL
的 action,就可以直接操作 modal,而不需要層層的 callback 或是 setState總結
mapStateToProps
來讓 redux 發揮 store 統一以及 connectmapDispatchToProps
並不一定每次都要傳bindActionCreators
,可以根據需要傳入。mapDispatchToProps
也接受直接傳入 Object 的方式,看起來更簡潔