react-easy-state 是個比較有趣的庫,利用 Proxy 建立了一個很是易用的全局數據流管理方式。前端
import React from "react"; import { store, view } from "react-easy-state"; const counter = store({ num: 0 }); const increment = () => counter.num++; export default view(() => <button onClick={increment}>{counter.num}</button>);
上手很是輕鬆,經過 store
建立一個數據對象,這個對象被任何 React 組件使用時,都會自動創建雙向綁定,任何對這個對象的修改,都會讓使用了這個對象的組件重渲染。react
固然,爲了實現這一點,須要對全部組件包裹一層 view
。git
這個庫利用了 nx-js/observer-util 作 Reaction 基礎 API,其餘核心功能分別是 store
view
batch
,因此咱們就從這四個點進行解讀。github
這個單詞名叫 「反應」,是實現雙向綁定庫的最基本功能單元。微信
擁有最基本的兩個單詞和一個概念:observable
observe
與自動觸發執行的特性。app
import { observable, observe } from "@nx-js/observer-util"; const counter = observable({ num: 0 }); const countLogger = observe(() => console.log(counter.num)); // 會自動觸發 countLogger 函數內回調函數的執行。 counter.num++;
在第 35 期精讀 精讀《dob - 框架實現》 「抽絲剝繭,實現依賴追蹤」 一節中有詳細介紹實現原理,這裏就不贅述了。框架
有了一個具備反應特性的函數,與一個能夠 「觸發反應」 的對象,那麼實現雙向綁定更新 View 就不遠了。函數
react-easy-state 的 store
就是 observable(obj)
包裝一下,惟一不一樣是,因爲支持本地數據:工具
import React from 'react' import { view, store } from 'react-easy-state' export default view(() => { const counter = store({ num: 0 }) const increment = () => counter.num++ return <button={increment}>{counter.num}</div> })
因此當監測到在 React 組件內部建立 store
且是 Hooks 環境時,會返回:this
return useMemo(() => observable(obj), []);
這是由於 React Hooks 場景下的 Function Component 每次渲染都會從新建立 Store,會致使死循環。所以利用 useMemo
並將依賴置爲 []
使代碼在全部渲染週期內,只在初始化執行一次。
更多 Hooks 深刻解讀,能夠閱讀 精讀《useEffect 徹底指南》。
根據 Function Component 與 Class Component 的不一樣,分別進行兩種處理,本文主要介紹對 Function Component 的處理方式,由於筆者推薦使用 Function Component 風格。
首先最外層會套上 memo
,這相似 PureComponent
的效果:
return memo(/**/);
而後構造一個 forceUpdate
用來強制渲染組件:
const [, forceUpdate] = useState();
以後,只要利用 observe
包裹組件便可,須要注意兩點:
forceUpdate
在 store
修改時調用。observe
初始化不要執行,由於初始化組件本身會渲染一次,再渲染一次就會形成浪費。因此做者經過 scheduler
lazy
兩個參數完成了這兩件事:
const render = useMemo( () => observe(Comp, { scheduler: () => setState({}), lazy: true }), [] ); return render;
最後別忘了在組件銷燬時取消監聽:
useEffect(() => { return () => unobserve(render); }, []);
這也是雙向綁定數據流必須解決的經典問題,批量更新合併。
因爲修改對象就觸發渲染,這個過程太自動化了,以致於咱們都沒有機會告訴工具,連續的幾回修改可否合併起來只觸發一次渲染。 尤爲是 For 循環修改變量時,若是不能合併更新,在某些場景下代碼幾乎是不可用的。
因此 batch
就是爲解決這個問題誕生的,讓咱們有機會控制合併更新的時機:
import React from "react"; import { view, store, batch } from "react-easy-state"; const user = store({ name: "Bob", age: 30 }); function mutateUser() { // this makes sure the state changes will cause maximum one re-render, // no matter where this function is getting invoked from batch(() => { user.name = "Ann"; user.age = 32; }); } export default view(() => ( <div> name: {user.name}, age: {user.age} </div> ));
react-easy-state
經過 scheduler
模塊完成 batch
功能,核心代碼只有五行:
export function batch(fn, ctx, args) { let result; unstable_batchedUpdates(() => (result = fn.apply(ctx, args))); return result; }
利用 unstable_batchedUpdates
,能夠保證在其內執行的函數都不會觸發更新,也就是以前建立的 forceUpdate
雖然被調用,可是失效了,等回調執行完畢時再一塊兒批量更新。
同時代碼裏還對 setTimeout
setInterval
addEventListener
WebSocket
等公共方法進行了 batch
包裝,讓這些回調函數中自帶 batch
效果。
好了,react-easy-state
神奇的效果解釋完了,但願你們在使用第三方庫的時候都能理解背後的原理。
PS:最後,筆者目前不推薦在 Function Component 模式下使用任何三方數據流庫,由於官方功能已經足夠好用了!
若是你想參與討論,請 點擊這裏,每週都有新的主題,週末或週一發佈。前端精讀 - 幫你篩選靠譜的內容。
關注 前端精讀微信公衆號
<img width=200 src="https://img.alicdn.com/tfs/TB...;>
special Sponsors
版權聲明:自由轉載-非商用-非衍生-保持署名( 創意共享 3.0 許可證)