摘要:本周精讀內容是重新思考。數據流對數據緩存,性能優化,開發體驗優化都有進一步施展的空間,擁抱插件生態是一個良好的發展方向。
本周精讀內容是 《重新思考 Redux》。
1 引言《重新思考 Redux》是 rematch 作者 Shawn McKay 寫的一篇干貨軟文。
dva 之后,有許多基于 redux 的狀態管理框架,但大部分都很局限,甚至是倒退。但直到看到了 rematch,總算覺得 redux 社區又進了一步。
這篇文章的寶貴之處在于,拋開 Mobx、RXjs 概念,僅針對 redux 做深入的重新思考,對大部分還在使用 redux 的工程場景非常有幫助。
2 概述比較新穎的是,作者給出一個公式,評價一個框架或工具的質量:
工具質量 = 工具節省的時間/使用工具消耗的時間
如果這樣評估原生的 redux,我們會發現,使用 redux 需要額外花費的時間可能超過了其節省下來的時間,從這個角度看,redux 是會降低工作效率的。
但 redux 的數據管理思想是正確的,復雜的前端項目也確實需要這種理念,為了更有效率的使用 redux,我們需要使用基于 redux 的框架。作者從 6 個角度闡述了基于 redux 的框架需要解決什么問題。
簡化初始化redux 初始化代碼涉及的概念比較多,比如 compose thunk 等等,同時將 reducer、initialState、middlewares 這三個重要概念拆分成了函數方式調用,而不是更容易接受的配置方式:
const store = preloadedState => { return createStore( rootReducer, preloadedState, compose(applyMiddleware(thunk, api), DevTools.instrument()) ); };
如果換成配置方式,理解成本會降低不少:
const store = new Redux.Store({ instialState: {}, reducers: { count }, middlewares: [api, devTools] });
筆者注:redux 的初始化方式非常函數式,而下面的配置方式就更面向對象一些。相比之下,還是面向對象的方式更好理解,畢竟 store 是一個對象。instialState 也存在同樣問題,相比顯示申明,將 preloadedState 作為函數入參就比較抽象了,同時 redux 對初始 state 的賦值也比較隱蔽,createStore 時統一賦值比較別扭,因為 reducers 是分散的,如果在 reducers 中賦值,要利用 es 的默認參數特性,看起來更像業務思考,而不是 redux 提供的能力。簡化 Reducers
redux 的 reducer 粒度太大,不但導致函數內手動匹配 type,還帶來了 type、payload 等理解成本:
const countReducer = (state, action) => { switch (action.type) { case INCREMENT: return state + action.payload; case DECREMENT: return state - action.payload; default: return state; } };
如果用配置的方式設置 reducers,就像定義一個對象一樣,會更清晰:
const countReducer = { INCREMENT: (state, action) => state + action.payload, DECREMENT: (state, action) => state - action.payload };支持 async/await
redux 支持動態數據還是挺費勁的,需要理解高階函數,理解中間件的使用方式,否則你不會知道為什么這樣寫是對的:
const incrementAsync = count => async dispatch => { await delay(); dispatch(increment(count)); };
為什么不抹掉理解成本,直接允許 async 類型的 action 呢?
const incrementAsync = async count => { await delay(); dispatch(increment(count)); };
筆者注:我們發現 rematch 的方式,dispatch 是 import 進來的(全局變量),而 redux 的 dispatch 是注入進來的,乍一看似乎 redux 更合理,但其實我更推崇 rematch 的方案。經過長期實踐,組件最好不要使用數據流,項目的數據流只用一個實例完全夠用了,全局 dispatch 的設計其實更合理,而注入 dispatch 的設計看似追求技術極致,但忽略了業務使用場景,導致畫蛇添足,增加了不必要的麻煩。將 action + reducer 改為兩種 action
redux 抽象的 action 與 reducer 的指責很清晰,action 負責改 store 以外所有事,而 reducer 負責改 store,偶爾用來做數據處理。這種概念其實比較模糊,因為往往不清楚數據處理放在 action 還是 reducer 里,同時過于簡單的 reducer 又要寫 action 與之匹配,感覺過于形式化,而且繁瑣。
重新考慮這個問題,我們只有兩類 action:reducer action 與 effect action。
reducer action:改變 store。
effect action:處理異步場景,能調用其他 action,不能修改 store。
同步的場景,一個 reducer 函數就能處理,只有異步場景需要 effect action 處理掉異步部分,同步部分依然交給 reducer 函數,這兩種 action 職責更清晰。
不再顯示申明 action type不要在用一個文件存儲 Action 類型了,const ACTION_ONE = "ACTION_ONE" 其實重復寫了一遍字符串,直接用對象的 key 表示 action 的值,再加上 store 的 name 為前綴保證唯一性即可。
同時 redux 建議使用 payload key 來傳值,那為什么不強制使用 payload 作為入參,而要通過 action.payload 取值呢?直接使用 payload 不但視覺上減少代碼數量,容易理解,同時也強制約束了代碼風格,讓建議真正落地。
Reducer 直接作為 ActionCreatorredux 調用 action 比較繁瑣,使用 dispatch 或者將 reducer 經過 ActionCreator 函數包裝。為什么不直接給 reducer 自動包裝 ActionCreator 呢?減少樣板代碼,讓每一行代碼都有業務含義。
最后作者給出了一個 rematch 完整的例子:
import { init, dispatch } from "@rematch/core"; import delay from "./makeMeWait"; const count = { state: 0, reducers: { increment: (state, payload) => state + payload, decrement: (state, payload) => state - payload }, effects: { async incrementAsync(payload) { await delay(); this.increment(payload); } } }; const store = init({ models: { count } }); dispatch.count.incrementAsync(1);3 精讀
我覺得本文基本上把 redux 存在的工程問題分析透徹了,同時還給出了一套非常好的實現。
細節的極致優化首先是直接使用 payload 而不是整個 action 作為入參,加強了約束同時簡化代碼復雜度:
increment: (state, payload) => state + payload;
其次使用 async 在 effects 函數中,使用 this.increment 函數調用方式,取代 put({type: "increment"})(dva),在 typescript 中擁有了類型支持,不但可以用自動跳轉代替字符串搜索,還能校驗參數類型,在 redux 框架中非常難得。
最后在 dispatch 函數,也提供了兩種調用方式:
dispatch({ type: "count/increment", payload: 1 }); dispatch.count.increment(1);
如果為了更好的類型支持,或者屏蔽 payload 概念,可以使用第二種方案,再一次簡化 redux 概念。
內置了比較多的插件rematch 將常用的 reselect、persist、immer 等都集成為了插件,相對比較強化插件生態的概念。數據流對數據緩存,性能優化,開發體驗優化都有進一步施展的空間,擁抱插件生態是一個良好的發展方向。
比如 rematch-immer 插件,可以用 mutable 的方式修改 store:
const count = { state: 0, reducers: { add(state) { state += 1; return state; } } };
但是當 state 為非對象時,immer 將不起作用,所以最好能養成 return state 的習慣。
最后說一點瑕疵的地方,reducers 申明與調用參數不一致。
Reducers 申明與調用參數不一致比如下面的 reducers:
const count = { state: 0, reducers: { increment: (state, payload) => state + payload, decrement: (state, payload) => state - payload }, effects: { async incrementAsync(payload) { await delay(); this.increment(payload); } } };
定義時 increment 是兩個參數,而 incrementAsync 調用它時,只有一個參數,這樣可能造成一些誤導,筆者建議保持參數對應關系,將 state 放在 this 中:
const count = { state: 0, reducers: { increment: payload => this.state + payload, decrement: payload => this.state - payload }, effects: { async incrementAsync(payload) { await delay(); this.increment(payload); } } };
當然 rematch 的方式保持了函數的無副作性質,可以看出是做了一些取舍。
4 總結重復一下作者提出工具質量的公式:
工具質量 = 工具節省的時間/使用工具消耗的時間
如果一個工具能節省開發時間,但本身帶來了很大使用成本,在想清楚如何減少使用成本之前,不要急著用在項目中,這是我得到的最大啟發。
最后感謝 rematch 作者精益求精的精神,給 redux 帶來進一步的極致優化。
5 更多討論討論地址是:精讀《重新思考 Redux》 · Issue #83 · dt-fe/weekly
如果你想參與討論,請點擊這里,每周都有新的主題,周末或周一發布。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/94948.html
摘要:引言本周精讀的文章是,看看作者是如何解釋這個多態性含義的。讀完文章才發現,文章標題改為的多態性更妥當,因為整篇文章都在說,而使用場景不局限于。更多討論討論地址是精讀的多態性如果你想參與討論,請點擊這里,每周都有新的主題,周末或周一發布。 1 引言 本周精讀的文章是:surprising-polymorphism-in-react-applications,看看作者是如何解釋這個多態性含...
摘要:更容易將組件的與狀態分離。也就是只提供狀態處理方法,不會持久化狀態。大體思路是利用共享一份數據,作為的數據源。精讀帶來的約定函數必須以命名開頭,因為這樣才方便做檢查,防止用判斷包裹語句。前端精讀幫你篩選靠譜的內容。 1 引言 React Hooks 是 React 16.7.0-alpha 版本推出的新特性,想嘗試的同學安裝此版本即可。 React Hooks 要解決的問題是狀態共享,...
摘要:大公司廣泛使用的開源庫,并且有一定國際影響力,而且大廠也有成功開源歷史經驗的話,就會增加說服力。總結下次技術選型討論時,可以拿出規則一條一條比對了然后技術選型只是基礎庫,利用這些基礎可以維護好自己的開源庫,把更多時間用在創造業務價值上。 1 引言 作者給出了從 12 個角度全面分析 JS 庫的可用性,分別是: 特性。 穩定性。 性能。 包生態。 社區。 學習曲線。 文檔。 工具。 發...
摘要:本周精讀內容是逃離地獄。精讀仔細思考為什么會被濫用,筆者認為是它的功能比較反直覺導致的。同時,筆者認為,也不要過渡利用新特性修復新特性帶來的問題,這樣反而導致代碼可讀性下降。 本周精讀內容是 《逃離 async/await 地獄》。 1 引言 終于,async/await 也被吐槽了。Aditya Agarwal 認為 async/await 語法讓我們陷入了新的麻煩之中。 其實,筆者...
閱讀 1751·2021-09-28 09:43
閱讀 1111·2021-09-23 11:22
閱讀 2707·2021-09-14 18:05
閱讀 1823·2019-08-30 15:52
閱讀 2812·2019-08-30 10:55
閱讀 2007·2019-08-29 16:58
閱讀 1323·2019-08-29 16:37
閱讀 3031·2019-08-29 16:25