原文在此:Redux Selectors: A Quick Tutorialjavascript
你可能會在Redux中碰到"selector"這個概念.在這個教程中,我會解釋一下selectors是什麼,用途以及使用selectors的時機選擇java
selector
是咱們編寫的一個小函數,接受整個Redux
的state
,並從中返回挑選出的值.程序員
你知道mapStateToProps
是如何工做的嗎?瞭解它是如何接受整個state,篩選出值得嗎?若是你瞭解話, Selector基本上作着一樣的工做,附帶的好處是,selector還同時經過緩存不變的state提高了性能.的確如此,Selector能夠改善性能.咱們後邊會討論整個問題.數據庫
若是你還不瞭解mapStateToProps
的工做原理, 如今可能理解不了selector. 我建議你立刻停下來, 去看看個人Complete Redux Tuorial for Beginners. Selectors屬於高級概念,是在常規Redux之上的附加抽象層. 理解Redux的基礎概念是明白selector工做的必須條件.redux
Redux爲咱們提供了一個store
,你能夠放置state,在稍大型的app中,state一般是一個對象,這個對象的每一個鍵都管理着一個獨立的reducer.[^譯註:若是你如今還不知道reducer是什麼,就不要再往下看了, 須要學習Redux的基礎知識] 假設咱們的state對象結構以下:數組
{
currentUser: {
token,
userId,
username
},
shoppingCart: {
itemIds,
loading,
error
},
products: {
itemsById,
loading,
error
}
}
複製代碼
在這個虛構的實例中,保存了登陸用戶的信息,shop中的貨物,以及用戶購物車中的商品. 這裏的數據是通過範式化(normalized)的,因此購物車中的商品看到的只是指向實體貨物的ID.當一個用戶在購物車中添加一件商品,商品自己並無被複制的購物車中-只有商品的ID添加到 shoppongCart.itemIds
數組中.[^譯註:範式化是數據庫的概念,避免冗餘和嵌套的結構,實體都經過ID應用]緩存
在須要從Redux的state提取數據並注入到React組件的時候, 咱們須要編寫一個mapStateToProps
函數,用於接受整個state,並選擇出組件須要的部分.app
假設你須要展現用戶購物車中的商品列表.所以須要items,但可是,shoppingCart
中並無items這一項.只有items們的IDs. 你須要用每一個ID在products.items
數組中查找出對應的商品信息. 下面是具體的執行方法:函數
function mapStateToProps(state) {
return {
items: state.shoppingCart.itemIds.map(id =>
state.products.itemsById[id]
)
}
}
複製代碼
如今假設你(或者是開發組中的其餘人)突發奇想, shoppingCart
應該是currentUser
的一個屬性而不是獨立的屬性.那麼如今的State結構變成下面的樣子:性能
{
currentUser: {
token,
userId,
username,
shoppingCart: {
itemIds,
loading,
error
},
},
products: {
itemsById,
loading,
error
}
}
複製代碼
看看你乾的好事,以前定義好的mapStateToProps
函數就毀了. 原來指向state.shoppingCart
,如今指向了state.currentUser.shoppingCart
.
若是在代碼中有一大堆的代碼都引用了state.shoppongCart
,那可有的改了.在你意識到徹底有必要對state從新組織的時候,這個更新過程會阻礙你完成任務.
若是咱們只有惟一的途徑把全部的state集中管理, 還有一些函數,調用時能夠找到咱們須要的數據...
是的,這正是selector所作的工做:)
來從新編寫一下被破壞掉的mapStateToProps
, 把state的篩選放在selector中完成.
// 這個函數放在全局做用於內
// 例如命名爲 selectors.js,
// 在須要訪問這一部分state的時候導入這個函數就能夠了
function selectShoppingCartItems(state) {
return state.currentUser.shoppingCart.itemIds.map(id =>
state.products.itemsById[id]
);
}
function mapStateToProps(state) {
return {
items: selectShoppingCartItems(state)
}
}
複製代碼
下一次,若是state的組織結構發生變化, 你只須要更新一個selector函數就完成了全部的工做.超級簡單.
說道命名,selector函數的的常見前綴是select
或者get
.固然遵循你app中其餘的傳統定義也徹底能夠.
你可能注意到了,這裏有點小題大作. 又多了一個要測試和編寫的函數.若是須要添加一個新的state分支[^譯註:這裏按照reducer的用法,添加的新部分稱爲一個分支].因此又潛在的增長了要考慮的文件.
所以,對於簡單的state訪問,或者是一些你已經知道不大可能在不少地方被訪問的state,就徹底不須要selector.我我的遵循"要麼所有使用,要麼就徹底不用的原則",編寫要有意義,不然就直接跳過不用. 這個一個可選的抽象部分.
以前編寫的selecotor僅僅是單純的函數. 這些selector隱藏了state結構的細節問題- 很棒!-可是對於性能徹底沒有幫助. 由於他們沒有魔法caching(緩存).
名字有點讓人想不明白,太(讓人困惑),reselector被稱爲"selector"由於他從state中選擇了數據,可是大多數狀況下,程序員談論"selectors"時,他們的真實意圖是緩存(*memoized*)
.
一個緩存函數會記住最後一次接收的參數. 以後,下一次被調用時, 它會首先檢查新參數是否是和前一次的參數相同.若是相同,就返回舊的值(不須要從新計算了),反之就會進行新的計算,而且在再次記住新的一套參數和返回值. Memoization(備忘,不是memorization(記憶),即便概念相同),是緩存的基本意義. 爲了建立備忘 selector,你能夠編寫本身的memoization函數,或者能夠安裝reselect
庫(還有其餘的庫,可是selector是最流行的)
yarn add reselect
複製代碼
以後就可使用reselect
庫提供的createSelector
函數建立備忘selector. 咱們要把以前的selector分解成一組更小的原子selector[^譯註:原子性基本若是是函數就能夠理解爲單一職責,不願再分的功能].稍後解釋.
import { createSelector } from 'reselect';
const getProducts = state => state.products.itemsById;
const getCartItemIds = state => state.currentUser.shoppingCart.itemIds;
export const selectShoppingCartItems = createSelector(
getProducts,
getCartItemIds,
(products, itemIds) => itemIds.map(id => products[id])
);
複製代碼
這裏是什麼狀況?
咱們已經把函數分割成了小的片斷. 每一個片斷就是針對每一塊數據的單獨selector. getProducts
知道在哪裏查找products,getCartItemIds
知道怎麼找到購物車中的東西.
以後,用createSelector
把小的片斷組合起來.這個函數接受片斷(全部你定義的)一個變換函數(transform function),就是最後一個調用的參數.
變換函數從片斷接受結果,而後能夠在須要的時候執行,不管何時,它都會經過總selector返回("master" selector).
createSelector
返回的總selector時,接收的是state
和可選的props
,傳遞(state,props)
至每個片斷selector.
全部這些工做給了你一個很大的收益: transform function, 這個函數可能計算很耗時間,花銷也很大,它只有在片斷之一返回不一樣於以前的值是纔會執行(要確保片斷足夠快,要執行例如單純的屬性訪問,不要作數組的遍歷訪問).
我重申一下,若是的變換函數代價不大(僅僅只執行屬性訪問,相似sate.foo.bar
,或者添加一對數字),徹底沒有必要建立備忘函數.
reselect
庫只能記住最近一次的調用結果.若是你想讓他記住多組的參數和返回值,看看re-reselect
庫,這個庫包裝了reselect
,因此外在是同樣的,可是在內部,能夠緩存更多的東西.
包括了基礎的內容!但願你如今對selector有點印象了.