<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          聊聊兩個狀態(tài)管理庫 Redux & Recoil

          共 8867字,需瀏覽 18分鐘

           ·

          2020-12-03 11:23

          State Management in React Apps

          背景

          React 是一個十分優(yōu)秀的UI庫, 最初的時候, React 只專注于UI層, 對全局狀態(tài)管理并沒有很好的解決方案, 也因此催生出類似Redux 這樣優(yōu)秀的狀態(tài)管理工庫。

          隨著時間的演變, 又催化了一批新的狀態(tài)管理工具。

          我簡單整理了一些目前主流的:

          1. Redux
          2. React Context & useReducer
          3. Mobx
          4. Recoil
          5. react-sweet-state
          6. hox

          這幾個都是我接觸過的,Npm 上的現(xiàn)狀和趨勢對比:


          毫無疑問,React?和?Redux?的組合是目前的主流。

          今天5月份, 一個名叫?Recoil.js?的新成員進入了我的視野,帶來了一些有趣的模型和概念,今天我們就把它和 Redux 做一個簡單的對比, 希望能對大家有所啟發(fā)。

          正文

          先看 Redux:

          Redux

          React-Redux 架構圖:


          這個模型還是比較簡單的, 大家也都很熟悉。

          先用一個簡單的例子,回顧一下整個模型:

          actions.js

          export const UPDATE_LIST_NAME = 'UPDATE_NAME';

          reducers.js

          export const reducer = (state = initialState, action) => {    const { listName, tasks } = state;    switch (action.type) {        case 'UPDATE_NAME': {            // ...        }        	default: {            return state;        }    }};

          store.js

          import reducers from '../reducers';import { createStore } from 'redux';const store = createStore(reducers);export const TasksProvider = ({ children }) => (           {children}    );

          App.js

          import { TasksProvider } from './store';import Tasks from './tasks';const ReduxApp = () => (              );

          Component

          // componentsimport React from 'react';import { updateListName } from './actions';import TasksView from './TasksView';
          const Tasks = (props) => { const { tasks } = props; return ( );};
          const mapStateToProps = (state) => ({ tasks: state.tasks});
          const mapDispatchToProps = (dispatch) => ({ updateTasks: (tasks) => dispatch(updateTasks(tasks))});
          export default connect(mapStateToProps, mapDispatchToProps)(Tasks);

          當然也可以不用connect,?react-redux?提供了?useDispatch, useSelector?兩個hook, 也很方便。

          import { useDispatch, useSelector } from 'react-redux';const Tasks = () => {    const dispatch = useDispatch();    const name = useSelector(state => state.name);    const setName = (name) => dispatch({ type: 'updateName', payload: { name } });    return (            );};


          整個模型并不復雜,而且redux?還推出了工具集redux toolkit,使用它提供的createSlice方法去簡化一些操作, 舉個例子:

          // Actionexport const UPDATE_LIST_NAME = 'UPDATE_LIST_NAME';
          // Action creatorexport const updateListName = (name) => ({ type: UPDATE_LIST_NAME, payload: { name }});
          // Reducerconst reducer = (state = 'My to-do list', action) => { switch (action.type) { case UPDATE_LIST_NAME: { const { name } = action.payload; return name; }
          default: { return state; } }};
          export default reducer;

          使用?createSlice

          // src/redux-toolkit/state/reducers/list-nameimport { createSlice } from '@reduxjs/toolkit';
          const listNameSlice = createSlice({ name: 'listName', initialState: 'todo-list', reducers: { updateListName: (state, action) => { const { name } = action.payload; return name; } }});
          export const { actions: { updateListName },} = listNameSlice;
          export default listNameSlice.reducer;

          通過createSlice, 可以減少一些不必要的代碼, 提升開發(fā)體驗。

          盡管如此, Redux 還有有一些天然的缺陷

          1. 概念比較多,心智負擔大。
          2. 屬性要一個一個 pick,計算屬性要依賴 reselect。還有魔法字符串等一系列問題,用起來很麻煩容易出錯,開發(fā)效率低。
          3. 觸發(fā)更新的效率也比較差。對于connect到store的組件,必須一個一個遍歷,組件再去做比較,攔截不必要的更新, 這在注重性能或者在大型應用里, 無疑是災難。

          對于這個情況, React 本身也提供了解決方案, 就是我們熟知的?Context.



          {value => /* render something based on the context value */}

          給父節(jié)點加 Provider 在子節(jié)點加 Consumer,不過每多加一個 item 就要多一層 Provider, 越加越多:


          而且,使用Context?問題也不少。

          對于使用?useContext?的組件,最突出的就是問題就是?re-render.

          不過也有對應的優(yōu)化方案:?React-tracked.

          稍微舉個例子:

          // store.jsimport React, { useReducer } from 'react';import { createContainer } from 'react-tracked';import { reducers } from './reducers';
          const useValue = ({ reducers, initialState }) => useReducer(reducer, initialState);const { Provider, useTracked, useTrackedState, useUpdate } = createContainer(useValue);
          export const TasksProvider = ({ children, initialState }) => ( {children} );
          export { useTracked, useTrackedState, useUpdate };

          對應的,也有?hooks?版本:

          const [state, dispatch] = useTracked();const dispatch = useUpdate();const state = useTrackedState();
          // ...

          Recoil

          Recoil.js 提供了另外一種思路, 它的模型是這樣的:


          在 React tree 上創(chuàng)建另一個正交的 tree,把每片 item 的 state 抽出來。

          每個 component 都有對應單獨的一片 state,當數(shù)據(jù)更新的時候對應的組件也會更新。

          Recoil 把 這每一片的數(shù)據(jù)稱為 Atom,Atom 是可訂閱可變的 state 單元。

          這么說可能有點抽象, 看個簡單的例子吧:

          // index.js

          import React from "react";import ReactDOM from "react-dom";import { RecoilRoot } from "recoil";import "./index.css";import App from "./App";import * as serviceWorker from "./serviceWorker";
          ReactDOM.render( , document.getElementById("root"));

          Recoil Root

          Provides the context in which atoms have values. Must be an ancestor of any component that uses any Recoil hooks. Multiple roots may co-exist; atoms will have distinct values within each root. If they are nested, the innermost root will completely mask any outer roots.

          可以把 RecoilRoot 看成頂層的 Provider.

          Atoms

          假設, 現(xiàn)在要實現(xiàn)一個counter:


          先用 useState 實現(xiàn):

          import React, { useState } from "react";
          const App = () => { const [count, setCount] = useState(0); return (
          Count is {count}
          );};
          export default App;

          再用 atom 改寫一下:

          import React from "react";import { atom, useRecoilState } from "recoil";
          const countState = atom({ key: "counter", default: 0,});
          const App = () => { const [count, setCount] = useRecoilState(countState); return (
          Count is {count}
          );};
          export default App;

          看到這, 你可能對atom 有一個初步的認識了。

          那 atom 具體是個什么概念呢?

          Atom

          簡單理解一下,atom 是包含了一份數(shù)據(jù)的集合,這個集合是可共享,可修改的。

          組件可以訂閱atom, 可以是一個, 也可以是多個,當 atom 發(fā)生改變時,觸發(fā)再次渲染。

          const someState = atom({    key: 'uniqueString',    default: [],});

          每個atom 有兩個參數(shù):

          • key:用于內(nèi)部識別atom的字符串。相對于整個應用程序中的其他原子和選擇器,該字符串應該是唯一的。
          • default:atom的初始值。

          atom 是存儲狀態(tài)的最小單位, 一種合理的設計是, atom 盡量小, 保持最大的靈活性。

          Recoil 的作者, 在 ReactEurope video 中也介紹了以后一種封裝定atom 的方法:

          export const itemWithId =    memoize(id => atom({        key: `item${id}`,        default: {...},    }));

          Selectors

          官方描述:

          “A selector is a pure function that accepts atoms or other selectors as input. When these upstream atoms or selectors are updated, the selector function will be re-evaluated.”

          selector 是以 atom 為參數(shù)的純函數(shù), 當atom 改變時, 會觸發(fā)重新計算。

          selector 有如下參數(shù):

          • key:用于內(nèi)部識別 atom 的字符串。相對于整個應用程序中的其他原子和選擇器,該字符串應該是唯一的.
          • get:作為對象傳遞的函數(shù){ get },其中get是從其他案atom或selector檢索值的函數(shù)。傳遞給此函數(shù)的所有atom或selector都將隱式添加到selector的依賴項列表中。
          • set?:返回新的可寫狀態(tài)的可選函數(shù)。它作為一個對象{ get, set }和一個新值傳遞。get是從其他atom或selector檢索值的函數(shù)。set是設置原子值的函數(shù),其中第一個參數(shù)是原子名稱,第二個參數(shù)是新值。

          看個具體的例子:

          import React from "react";import { atom, selector, useRecoilState, useRecoilValue } from "recoil";
          const countState = atom({ key: "myCount", default: 0,});
          const doubleCountState = selector({ key: "myDoubleCount", get: ({ get }) => get(countState) * 2,});
          const inputState = selector({ key: "inputCount", get: ({ get }) => get(doubleCountState), set: ({ set }, newValue) => set(countState, newValue),});
          const App = () => { const [count, setCount] = useRecoilState(countState); const doubleCount = useRecoilValue(doubleCountState); const [input, setInput] = useRecoilState(inputState); return (
          setInput(Number(e.target.value))} />
          Count is {count}
          Double count is {doubleCount}
          );};
          export default App;

          比較好理解,?useRecoilState,?useRecoilValue?這些基礎概念可以參考官方文檔。

          另外, selector 還可以做異步, 比如:

            get: async ({ get }) => {    const countStateValue = get(countState);    const response = await new Promise(      (resolve) => setTimeout(() => resolve(countStateValue * 2)),      1000    );    return response;  }

          不過對于異步的selector, 需要在RecoilRoot加一層Suspense:

          ReactDOM.render(            Loading...
          }> , document.getElementById("root"));

          Redux vs Recoil

          模型對比:


          Recoil 推薦 atom 足夠小, 這樣每一個葉子組件可以單獨去訂閱, 數(shù)據(jù)變化時, 可以達到 O(1)級別的更新.

          Recoil 作者?Dave McCabe?在一個評論中提到:

          Well, I know that on one tool we saw a 20x or so speedup compared to using Redux. This is because Redux is O(n) in that it has to ask each connected component whether it needs to re-render, whereas we can be O(1).

          useReducer is equivalent to useState in that it works on a particular component and all of its descendants, rather than being orthogonal to the React tree.

          Rocil 可以做到 O(1) 的更新是因為,當atom數(shù)據(jù)變化時,只有訂閱了這個 atom 的組件需要re-render。

          不過, 在Redux 中,我們也可以用selector 實現(xiàn)同樣的效果:

          // selectorconst taskSelector = (id) => state.tasks[id];
          // component codeconst task = useSelector(taskSelector(id));

          不過這里的一個小問題是,state變化時,taskSelector 也會重新計算, 不過我們可以用createSelector?去優(yōu)化, 比如:

          import { createSelector } from 'reselect';
          const shopItemsSelector = state => state.shop.items;
          const subtotalSelector = createSelector( shopItemsSelector, items => items.reduce((acc, item) => acc + item.value, 0))

          寫到這里, 是不是想說,就這?扯了這么多, Rocoil 能做的, Redux 也能做, 那要你何用?

          哈哈, 這個確實有點尷尬。

          不過我認為,這是一種模式上的改變,recoil 鼓勵把每一個狀態(tài)做的足夠小, 任意組合,最小范圍的更新。

          而redux, 我們的習慣是, 把容器組件連接到store上, 至于子組件,哪怕往下傳一層,也沒什么所謂。

          我想,Recoil 這么設計,可能是十分注重性能問題,優(yōu)化超大應用的性能表現(xiàn)。

          目前,recoil 還處于玩具階段, 還有大量的 issues 需要處理, 不過值得繼續(xù)關注。

          最后

          感興趣的朋友可以看看, 做個todo-list體驗一下。

          希望這篇文章能幫到你。

          才疏學淺,文中若有錯誤, 歡迎指正。

          瀏覽 65
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲日韩欧美在线中文18 | 日韩99在线 | 操逼高潮视频 | 看日本AA大片 | 波多野结衣一区二区三区在线观看 |