[译] SpaceAce 了解一下,一个新的前端状态管理库
- 原文地址:Introducing SpaceAce, a new kind of front-end state library
- 原文作者:Jon Abrams
- 譯文出自:掘金翻譯計劃
- 本文永久鏈接:https://github.com/xitu/gold-miner/blob/master/TODO1/introducing-spaceace-a-new-kind-of-front-end-state-library.md
- 譯者:Noah Gao
- 校對者:Hopsken
開發前端應用的大家都知道,狀態管理是開發中最重要,最具挑戰性的一部分。目前流行的基于組件的視圖庫,如 React,包括功能齊全的(最基本的)狀態管理能力。它們使應用中的每個組件都能夠管理自己的狀態。這對于小型應用程序來說足夠了,但你很快就會感到挫敗。因為決定哪些組件具有狀態以及如何在組件之間共享來自每個狀態的數據將會成為一個挑戰。最后還要弄清楚狀態是如何或為何被改變。
為了解決面向組件狀態的上述問題,Redux 一類的庫被引入。它們將該狀態集中到一個集中的“store”中,每個組件都可以讀寫它。為了維護順序,他們將改變狀態的邏輯集中到應用程序的中心部分,稱為 reducer,使用 actions 調用它們,并使其產生新的狀態副本。它非常有效,但學習曲線很高,需要大量的樣板代碼,并強迫你將更新狀態的代碼與渲染視圖的代碼分開。
SpaceAce 是一個新的庫,它具有 Redux 的所有優點,例如集中的 store,不可變狀態,單向數據流,明確定義的 actions,它 還 極大地簡化了代碼更新 store 中狀態的方式。
我們已經在 Trusted Health 的主 React 應用上用 SpaceAce 來管理狀態將近一年了,取得了巨大的成功。我們的工程師團隊相對較小(只有三個人),它在不加大代碼復雜度和犧牲可測試性的基礎上,加速了我們的功能開發。
SpaceAce 是什么?
SpaceAce 提供一個狀態管理的 store 叫做一個 space。一個 space 包括只讀(不可變)的狀態,還有一些用于更新它的工具集。但是這個 store 里面不只是 有 狀態,而是它本身就 是 狀態。同時,他還提供了很多方法來生成新版本的狀態。怎么做到?是一些帶有屬性的函數!很多 JS 開發者不知道 JS 函數也是對象。只是它能執行而已,所以它也能有一些屬性,就像對象一樣(因為它就是個對象!)。
每個 space 都是一個有屬性的不可變對象,但是只能被讀取,不能直接寫入。每個 space 也是 一個函數,能夠創建應用改動后的狀態副本。
最后,放個例子:
import Space from 'spaceace';const space = new Space({appName: "SpaceAce demoe",user: { name: 'Jon', level: 9001 } });const newSpace = space({ appName: "SpaceAce demo" });console.log(`Old app name: ${space.appName}, new app name: ${newSpace.appName}`);將會輸出:“Old app name: SpaceAce demoe, new app name: SpaceAce demo”
上面的例子展示了如何創建一個 space 并通過調用它將一個對象合并到狀態來直接“更改”它。這和 React 的 setState 很像,應用了一次淺合并。記住,原本的 space 并沒有變化,只是被一個應用了改動的副本給替換了。
然而,這對應用在有新狀態時需要進行重新渲染的場景來說,沒用。為了讓解決這個場景更簡單,一個 subscribe 函數被提供出來。它能在相關 space 被“改動”時去調用回調:
import Space, { subscribe } from 'spaceace';const space = new Space({appName: "SpaceAce demoe",user: { name: 'Jon', level: 9001 } });subscribe(space, ({ newSpace, causedBy }) => {console.log(`State updated by ${causedBy}`);ReactDOM.render(<h1>{newSpace.appName}</h1>, document.getElementById('app')); });// 將使 React 重新渲染 space({ appName: "SpaceAce demo" });大多數情況下,狀態都是因為用戶做的事情而發生變化。比如,他們單擊一個復選框、從下拉列表中選擇一個選項或填入一個字段。SpaceAce 通過這些簡單的交互來更新狀態 非常簡單。如果使用字符串調用 space,它將生成并返回處理函數:
export const PizzaForm = ({ space }) => (<form><label>Name</label><inputtype="text"value={space.name || ''}onChange={space('name')} // 當用戶輸入時,`space.name` 會被更新/><label>Do you like pizza?</label><inputtype="checkbox"checked={space.pizzaLover || false}onChange={space('pizzaLover')} // 分配 true 或 false 給 `space.pizzaLover`/></form> );雖然大多數應用只有許多簡單的交互,但它們有時也會包含一些復雜的 action。SpaceAce 允許你自定義 action,所有 action 都與組件在同一文件中。調用時,會為這些 action 提供一個對象,其中包含用于更新狀態的便捷函數:
import { fetchPizza } from '../apiCalls';/*handleSubmit 是一個自定義 action。第一個參數由 SpaceAce 提供。其余參數是需要傳入的,在這個案例中由 React 的事件對象組成。 */ const handleSubmit = async ({ space, merge }, event) => {event.preventDefault();// merge 函數將進行淺合并,允許一次分配多個屬性merge({ saving: true }); // 立即更新 space,將觸發重新渲染const { data, error } = await fetchPizza({ name: space.name });if (error) return merge({ error: errorMsg, saving: false });merge({saving: false,pizza: data.pizza // 期待得到 'Pepperoni'}); };/*handleReset 是另一個自定義 action。這個函數可以用來將 space 的所有屬性抹除,將它們用另一些替換掉。 */ const handleReset = ({ replace }) => {replace({name: '',pizzaLover: false}); };export const PizzaForm = ({ space }) => (<form onSubmit={space(handleSubmit)}>{/* ... 一些 input 元素 */}<p className="error">{space.errorMsg}</p>{space.pizza && <p>You’ve been given: {space.pizza}</p>}<button disabled={space.saving} type="submit">Get Pizza</button><button disabled={space.saving} type="button" onClick={space(handleReset)}>Reset</button></form> );你可能會注意到,所有這些改變 space 狀態的方式都會假定狀態相對較淺,但如果每個應用程序只有一個 space,那怎么可能呢?不可能的!每個 space 都可以有任意數量的 sub-space,它們也只是 space,但它們有父級。每當更新其中一個 sub-space 時,改動會冒泡,一旦更改到達根 sapce,就會觸發應用的重新渲染。
有關子 space 最棒的地方在于,你不用特地去制造它,它將在你··訪問 space 中的對象或是數組時,自動被創建出來:
const handleRemove = ({ remove }, itemToBeRemoved) => {// `remove` 將在數組型 space 中可用,// 它將為每個元素運行回調。// 如果回調的結果是 true,元素將被刪除。remove(item => item === itemToBeRemoved); };/*一個購物車的 space 將是一個物品的數組,每個物品都是對象,它也將是一個 space。 */ export const ShoppingCart = ({ space }) => (<div><ul>{space.map(item => (<li key={item.uuid}><CartItemspace={item}onRemove={space(handleRemove).bind(null, item)}/></li>)}</ul></div> ); const CartItem = ({ space, onRemove }) => (<div><strong>{space.name}</strong><inputtype="number"min="0"max="10"onChange={space('count')}value={space.count}/><button onClick={onRemove}>Remove</button></div> );還有很多功能可以繼續探索,我很快就會分享這些有趣的技巧。請繼續關注我的下一篇文章!
與此同時,你可以在 Github 上的代碼和文檔 中了解更多信息,也可以 讓我知道你的想法!
感謝 Zivi Weinstock 的付出。
如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改并 PR,也可獲得相應獎勵積分。文章開頭的 本文永久鏈接 即為本文在 GitHub 上的 MarkDown 鏈接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、后端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。
總結
以上是生活随笔為你收集整理的[译] SpaceAce 了解一下,一个新的前端状态管理库的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小程序居然可以用WXS模拟实现过滤器!
- 下一篇: 面向 Web 前端的原生语言总结手册