精讀《react-easy-state 源碼》

1. 引言

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

固然,爲了實現這一點,須要對全部組件包裹一層 viewgit

2. 精讀

這個庫利用了 nx-js/observer-util 作 Reaction 基礎 API,其餘核心功能分別是 store view batch,因此咱們就從這四個點進行解讀。github

Reaction

這個單詞名叫 「反應」,是實現雙向綁定庫的最基本功能單元。微信

擁有最基本的兩個單詞和一個概念: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 就不遠了。函數

store

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 徹底指南》

view

根據 Function Component 與 Class Component 的不一樣,分別進行兩種處理,本文主要介紹對 Function Component 的處理方式,由於筆者推薦使用 Function Component 風格。

首先最外層會套上 memo,這相似 PureComponent 的效果:

return memo(/**/);

而後構造一個 forceUpdate 用來強制渲染組件:

const [, forceUpdate] = useState();

以後,只要利用 observe 包裹組件便可,須要注意兩點:

  1. 使用剛纔建立的 forceUpdatestore 修改時調用。
  2. observe 初始化不要執行,由於初始化組件本身會渲染一次,再渲染一次就會形成浪費。

因此做者經過 scheduler lazy 兩個參數完成了這兩件事:

const render = useMemo(
  () =>
    observe(Comp, {
      scheduler: () => setState({}),
      lazy: true
    }),
  []
);

return render;

最後別忘了在組件銷燬時取消監聽:

useEffect(() => {
  return () => unobserve(render);
}, []);

batch

這也是雙向綁定數據流必須解決的經典問題,批量更新合併。

因爲修改對象就觸發渲染,這個過程太自動化了,以致於咱們都沒有機會告訴工具,連續的幾回修改可否合併起來只觸發一次渲染。 尤爲是 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 效果。

4. 總結

好了,react-easy-state 神奇的效果解釋完了,但願你們在使用第三方庫的時候都能理解背後的原理。

PS:最後,筆者目前不推薦在 Function Component 模式下使用任何三方數據流庫,由於官方功能已經足夠好用了!

討論地址是:精讀《react-easy-state》 · Issue #144 · dt-fe/weekly

若是你想參與討論,請 點擊這裏,每週都有新的主題,週末或週一發佈。前端精讀 - 幫你篩選靠譜的內容。

關注 前端精讀微信公衆號

<img width=200 src="https://img.alicdn.com/tfs/TB...;>

special Sponsors

版權聲明:自由轉載-非商用-非衍生-保持署名( 創意共享 3.0 許可證
相關文章
相關標籤/搜索