如何在 React 應用中使用 Hooks、Redux 等管理狀態

大家好,我是若川。持續組織了近一年的源碼共讀活動,感興趣的可以?點此掃碼加我微信?ruochuan12?參與,每周大家一起學習200行左右的源碼,共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》?包含20余篇源碼文章。歷史面試系列。另外:目前建有江西|湖南|湖北籍前端群,可加我微信進群。


在本文中,我們將了解在 React 應用程序中管理狀態的多種方式。

我們將從討論什么是狀態開始,然后介紹許多用于管理狀態的工具。

我們將了解簡單的 useState hook,并學習更復雜的庫,如 Redux。然后我們將查看最新可用的庫,例如 Recoil 和 Zusand。

目錄

  • React 中的狀態是什么

  • 如何使用 useState hook

    • 如何使用 useEffect 讀取狀態更新

    • 如何傳遞一個回調給狀態更新函數

  • 管理規模和復雜性

    • React context

    • 如何使用 useReducer hook

    • 那么 Redux 呢

  • Redux 的替代品

    • 提到 Redux Thunk 和 Redux Saga

    • Redux toolkit

    • Recoil

    • Jotai

    • Zustand

  • 總結

React 中的狀態是什么

在現代 React 中,我們使用函數組件構建我們的應用程序。組件本身就是 JavaScript 函數,是獨立且可復用的代碼。

使用組件構建應用程序的目的是使其具有模塊化架構,具有明確的關注點分離。這使代碼更易于理解、更易于維護并且在可能的情況下更易于復用。

而狀態(state)是一個保存有組件信息的對象。普通 JavaScript 函數沒有存儲信息的能力。一旦執行完成,它們中的代碼就會執行并“消失”。

但是有了狀態之后,React 函數組件即使在執行后也可以存儲信息。當我們需要一個組件來存儲或“記住”某些東西,或者根據環境以不同的方式執行時,狀態就是我們所需要的可以讓這些生效的東西。

值得一提的是,在 React 應用程序中的并非所有組件都必須具有狀態,也有無狀態組件,它們只呈現其內容而無需存儲任何信息,這也很好。

另一件重要的事情是狀態變化是使 React 組件重新渲染的兩個原因之一(另一個是 props 的變化)。因此,狀態存儲了組件的信息同時也控制了它的行為。

如何使用 useState hook

為了在我們的組件中實現狀態,React 為我們提供了一個名為 useState 的鉤子(hook)。讓我們看看它是如何與以下示例一起工作的。

我們將使用經典的計數器示例,其中我們將顯示一個數字,并且我們有幾個按鈕用于增加、減少或重置該數字。

這是一個很好的應用程序示例,我們需要存儲一條信息并在每次信息更改時呈現不同的內容。

2821faa00936bdd8aaebfb2b659e97ad.gif

此應用程序的代碼如下所示:

//?App.js
import?{?useState?}?from?'react'function?App()?{const?[count,?setCount]?=?useState(0)return?(<div?className="App"><p>Count?is:?{count}</p><div><button?onClick={()?=>?setCount(count+1)}>Add?1</button><button?onClick={()?=>?setCount(count-1)}>Decrease?1</button><button?onClick={()?=>?setCount(count+10)}>Add?10</button><button?onClick={()?=>?setCount(count-10)}>Decrease?10</button><button?onClick={()?=>?setCount(0)}>Reset?count</button></div></div>)
}export?default?App
  • 首先我們從 React 導入鉤子(hook):import { useState } from 'react'

  • 然后我們初始化狀態:const [count, setCount] = useState(0)

在這里,我們為狀態提供了一個變量名(count)和一個我們將在每次需要更新該狀態時使用的函數名(setCount)。最后,我們設置狀態的初始值(0),這將是應用程序每次啟動時默認加載的值。

  • 最后,如上所述,每次我們想要更新狀態時,都必須使用我們聲明的函數:setCount,只需要調用它并將我們想要的新狀態作為參數傳遞給它。也就是說,如果我們想在前一個狀態加 1,我們需要調用 setCount(count+1)

如前所述,這將導致狀態更新,從而導致組件的重新渲染。在我們的應用程序中我們將在屏幕上看到計數器增加。

如何使用 useEffect 讀取狀態更新

一個需要提到的重要信息是 setState 函數是異步的。因此,如果我們嘗試在更新狀態后立即讀取它,例如:

<button?onClick={()?=>?{setCount(count+1)console.log(count)
}}>Add?1</button>

我們會得到之前的狀態值,并沒有得到更新。

在更新狀態后讀取狀態的正確方法是使用 useEffect hook。它允許我們在每個組件重新渲染后(默認情況下)或在我們聲明更改的任何特定變量之后執行一個函數。

就像這樣:

useEffect(()?=>?console.log(value),?[value])

如何傳遞一個回調給狀態更新函數

在非常頻繁和快速的狀態變更時,異步的 useState 也會產生一些影響。

舉個例子,用戶連續多次按下 ADD 按鈕,或者一個循環中發出一定次數的點擊事件。

通過setCount(count+1)更新狀態,在下一個事件被觸發時 count 會有不被更新的風險。

例如,假設在開始時 count = 0,然后調用 setCount(count+1) 異步更新狀態。

但是在狀態更新完成之前再次調用了 setCount(count+1)。這意味著仍然是count = 0,這意味著第二個setCount不會正確更新狀態。

一種比較防守型的方法是向 setCount 傳遞一個回調,如下所示:setCount(prevCount => prevCount+1)

這樣可以確保要更新的值是最新的,并使我們遠離上述問題。每次我們對先前的狀態執行更新時,我們都應該使用這種方法。

管理規模和復雜性

到目前為止,狀態管理似乎是小菜一碟。我們只需要一個 hook、一個值和一個函數來更新它,我們就可以開始了。

但是,一旦應用程序開始變得更大更復雜時,僅使用這一種方式可能會開始導致一些問題。

React context

第一個可能出現的問題是當我們有很多嵌套組件時,我們需要許多“兄弟”組件來共享相同的狀態。

顯而易見的答案是“提升”狀態,這意味著父組件將成為持有狀態的組件,并將狀態作為 prop 傳遞給子組件。

這很好用,但是當我們有很多嵌套組件時,可能需要通過許多層級組件傳遞 props。這被稱為 prop drilling,不僅很難看,而且會創建難以維護的代碼。

Prop drilling 還可能導致不必要的重新渲染,這可能會影響我們應用程序的性能。如果在我們的父組件(存儲狀態)和子組件(使用狀態)之間還有其他組件(“中間組件”),我們也需要通過這些中間組件傳遞 prop,即使它們并不需要 prop。

這意味著這些“中間組件”將在 prop 變更時重新渲染,即使它們沒有不同的內容需要渲染。

解決這個問題的一種方法是使用 React context,簡單來說,這是一種創建包裝組件的方法,該組件包裝我們那些想要并且可以直接傳遞 props 的組件組,而且無需 “drill” 通過那些不是必須使用該狀態的組件。

使用 context 時要注意的是,當 context 狀態發生變化時,所有接收該狀態的被包裝組件都將重新渲染。這種情況下,這可能不是必然的,也可能導致性能問題。

因此,我們是否真的需要讓一個狀態對許多組件可用,或者我們是否可以將其保持在單個組件中, 在這兩者之前取一個平衡是非常重要的。如果我們需要讓許多組件都可以使用它,把它放在 context 中真的是一個好主意嗎?或者我們是否可以把它提升一個層次?

Kent C Dodds 有一篇關于這個主題的很酷的文章。

如何使用 useReducer hook

當你使用 useState 時,要設置的新狀態取決于先前的狀態(如我們的計數示例),或者當我們的應用程序中狀態更改非常頻繁,這種情況下可能會出現另一個問題。

在這些情況下,useState 可能會引發一些意想不到的和不可預知的行為。接下來的 reducers 將解決這個問題。

reducer 是一個純函數,它將前一個狀態和一個動作作為參數,并返回下一個狀態。它被稱為 reducer,是因為它與你傳遞給數組的函數類型相同:Array.prototype.reduce(reducer, initialValue)

useReducer 是 React 提供的 hook,它讓我們實現 reducer 來管理狀態。使用這個 hook,我們之前的示例應用程序看起來像這樣:

//?App.js
import?{?useReducer?}?from?'react'
import?'./App.scss'function?App()?{function?reducer(state,?action)?{switch?(action.type)?{case?'ADD':?return?{?count:?state.count?+?1?}case?'SUB':?return?{?count:?state.count?-?1?}case?'ADD10':?return?{?count:?state.count?+?10?}case?'SUB10':?return?{?count:?state.count?-?10?}case?'RESET':?return?{?count:?0?}default:?return?state}}const?[state,?dispatch]?=?useReducer(reducer,?{?count:?0?})??return?(<div?className="App"><p>Count?is:?{state.count}</p><div><button?onClick={()?=>?dispatch({type:?'ADD'})}>Add?1</button><button?onClick={()?=>?dispatch({type:?'SUB'})}>Decrease?1</button><button?onClick={()?=>?dispatch({type:?'ADD10'})}>Add?10</button><button?onClick={()?=>?dispatch({type:?'SUB10'})}>Decrease?10</button><button?onClick={()?=>?dispatch({type:?'RESET'})}>Reset?count</button></div></div>)
}export?default?App
  • 我們再次從 React 導入 hook 開始:import { useReducer } from 'react'

  • 然后我們將聲明一個 reducer 函數,將接收當前的狀態和對其執行的動作(action)作為參數。并且在函數里有一個 switch 語句,該語句將讀取動作類型,對狀態執行相應的動作,并返回更新后的狀態。

通常做法是在 reducer 上使用 switch 語句, 并且使用大寫字母來聲明動作。

function?reducer(state,?action)?{switch?(action.type)?{case?'ADD':?return?{?count:?state.count?+?1?}case?'SUB':?return?{?count:?state.count?-?1?}case?'ADD10':?return?{?count:?state.count?+?10?}case?'SUB10':?return?{?count:?state.count?-?10?}case?'RESET':?return?{?count:?0?}default:?return?state}}
  • 之后,是時候聲明我們的 useReducer hook 了,它看起來與 useState hook 非常相似。我們為我們的狀態聲明一個變量(在我們的例子中是'state'),和一個我們將用來修改這個變量的函數('dispatch'),然后 useReducer 將接收上面的 reducer 函數 作為第一個參數,和一個默認狀態作為第二個參數。

const?[state,?dispatch]?=?useReducer(reducer,?{?count:?0?})
  • 最后,我們不會直接調用 reducer 去更新狀態,而是調用我們剛剛創建的函數('dispatch'),將我們想要執行的對應的動作類型傳遞給它。其中,dispatch 函數將與 reducer 連接并實際修改 state。

<button?onClick={()?=>?dispatch({type:?'ADD'})}>Add?1</button>

這比使用 useState 多了不少模板,但useReducer畢竟沒有那么復雜。

總結一下,我們只需要:

  • 一個 reducer,合并所有可能的狀態變化的函數

  • 一個 dispatch 函數,將修改動作傳遞給 reducer

這里的問題是, UI 元素將不能像以前那樣通過用一個值調用 setState 去直接更新狀態。現在它們需要調用一個動作類型(action type)并通過 reducer,這使得狀態管理更加模塊化和可預測。

那么 Redux 呢

Redux 是一個已經存在很長時間并且在 React 中被廣泛使用的庫。

Redux 是一個工具,它可以解決前面提到的兩個問題(prop drilling 和頻繁和復雜狀態變更時不可預測的狀態行為)。

值得一提的是,Redux 是一個不可知的庫,這意味著它可以在任何前端應用程序上實現,不僅僅是 React。

Redux 工具集與我們剛剛看到的 useReducer 非常相似,但多了一些東西。Redux 中有三個主要的構建塊:

  • store — 一個保存應用狀態數據的對象

  • reducer — 一個由動作類型(action type)觸發,并返回一些狀態數據的函數

  • action — 一個告訴 reducer 如何改變狀態的對象,它必須包含一個 type 屬性,并且它還可以包含一個可選的 payload 屬性

實現 Redux,我們的示例應用程序如下所示:

//?App.js
import?'./App.scss'import?{?Provider,?useSelector,?useDispatch?}?from?'react-redux'
import?{?addOne,?subOne,?addSome,?subSome,?reset?}?from?'./store/actions/count.actions'import?store?from?'./store'function?App()?{const?dispatch?=?useDispatch()const?count?=?useSelector(state?=>?state.count)return?(<Provider?store={store}><div?className="App"><p>Count?is:?{count}</p><div><button?onClick={()?=>?dispatch(addOne())}>Add?1</button><button?onClick={()?=>?dispatch(subOne())}>Decrease?1</button><button?onClick={()?=>?dispatch(addSome(10))}>Add?10</button><button?onClick={()?=>?dispatch(subSome(10))}>Decrease?10</button><button?onClick={()?=>?dispatch(reset())}>Reset?count</button></div></div></Provider>)
}export?default?App

此外,我們需要一個新的 store 文件夾,包含相應的 store、reducer 和 actions 文件。

//?index.js?(STORE)
import?{?createStore?}?from?'redux'
import?CountReducer?from?'./reducers/count.reducer'export?default?createStore(CountReducer)
//?count.reducer.js
import?{?ADD,?SUB,?ADDSOME,?SUBSOME,?RESET?}?from?'../actions/count.actions'const?CountReducer?=?(state?=?{?count:?0?},?action)?=>?{switch?(action.type)?{case?ADD:?return?{?count:?state.count?+?1?}case?SUB:?return?{?count:?state.count?-?1?}case?ADDSOME:?return?{?count:?state.count?+?action.payload?}case?SUBSOME:?return?{?count:?state.count?-?action.payload?}case?RESET:?return?{?count:?0?}default:?return?state}
}export?default?CountReducer
//?count.actions.js
export?const?ADD?=?'ADD'
export?const?addOne?=?()?=>?({?type:?ADD?})export?const?SUB?=?'SUB'
export?const?subOne?=?()?=>?({?type:?SUB?})export?const?ADDSOME?=?'ADDSOME'
export?const?addSome?=?(value)?=>?({type:?ADDSOME,payload:?value
})export?const?SUBSOME?=?'SUBSOME'
export?const?subSome?=?(value)?=>?({type:?SUBSOME,payload:?value
})export?const?RESET?=?'RESET'
export?const?reset?=?()?=>?({?type:?RESET?})

這比我們之前看到的還要更多的模板(這也是 Redux 被批評的主要原因),所以讓我們把它分解成幾塊:

  • 正如我提到的,Redux 是一個外部庫,所以在進行任何操作之前,我們需要通過運行 npm i redux react-redux 來安裝它。redux 將帶來管理狀態所需的核心函數,而react-redux 將安裝一些很酷的 hook,可以輕松地從我們的組件中讀取和修改狀態。

  • 現在,首先是 store。在 Redux 中,store 是擁有所有應用程序狀態信息的實體。多虧 Redux,我們能夠從任何想要的組件中訪問 store(就像使用 context 一樣)。

為了創建一個 store,我們導入 createStore 函數,并將一個 reducer 函數作為輸入傳遞給它。

要知道,你也可以將不同的 reducers 合并然后傳遞給同一個 store,這樣你就可以將關注點分離到不同的 reducers 中。

import?{?createStore?}?from?'redux'
import?CountReducer?from?'./reducers/count.reducer'export?default?createStore(CountReducer)
  • 然后是 reducer,它的工作方式與之前我們看到的 useReducer 完全相同。它接收默認狀態和一個動作(action)作為參數,然后在它里面有一個 switch 語句來讀取 action type,執行相應的狀態修改,并返回更新后的狀態。

import?{?ADD,?SUB,?ADDSOME,?SUBSOME,?RESET?}?from?'../actions/count.actions'const?CountReducer?=?(state?=?{?count:?0?},?action)?=>?{switch?(action.type)?{case?ADD:?return?{?count:?state.count?+?1?}case?SUB:?return?{?count:?state.count?-?1?}case?ADDSOME:?return?{?count:?state.count?+?action.payload?}case?SUBSOME:?return?{?count:?state.count?-?action.payload?}case?RESET:?return?{?count:?0?}default:?return?state}
}export?default?CountReducer
  • 接著是 actions。actions 用于告訴 reducer 如何更新狀態。在代碼中,你可以看到,對于每個 action,我們都聲明了常量來代替普通的字符串(這是一個可以提高可維護性的好做法),以及一些僅返回一個 type 或者 一個 type 和一個 payload 的函數。這些函數就是我們要從組件中 dispatch 去更改狀態的函數。

請注意,我對這個例子做了一些改變,以顯示在談論 actions 時 payload 的含義。如果我們想在 dispatch action 時從組件傳遞一個參數payload 就是存放該信息的地方

在示例中,你可以看到我們在調用 ADDSOME/SUBSOME 時可以直接從組件中傳遞我們想要加/減的數字。

export?const?ADD?=?'ADD'
export?const?addOne?=?()?=>?({?type:?ADD?})export?const?SUB?=?'SUB'
export?const?subOne?=?()?=>?({?type:?SUB?})export?const?ADDSOME?=?'ADDSOME'
export?const?addSome?=?value?=>?({type:?ADDSOME,payload:?value
})export?const?SUBSOME?=?'SUBSOME'
export?const?subSome?=?value?=>?({type:?SUBSOME,payload:?value
})export?const?RESET?=?'RESET'
export?const?reset?=?()?=>?({?type:?RESET?})
  • 最后是我們的組件。這里有 3 件事需要注意:

  1. 首先,我們有一個 provider 組件,它接收 store 作為 props。這是對所有被包裝在其中的組件訪問 store 的授權。

  2. 然后我們有一個名為 **useDispatch()**(我們將用于 dispatch actions)和另一個名為 useSelector() 的 hook(我們將用于從 store 中讀取狀態)。

  3. 最后,請注意我們將要 dispatch 我們在 action 文件中聲明的函數,并傳遞一個匹配的值作為輸入。這個值是 actions 接收作為 payload 的值,以及 reducer 將用來修改狀態的值。

import?'./App.scss'import?{?useSelector,?useDispatch?}?from?'react-redux'
import?{?addOne,?subOne,?addSome,?subSome,?reset?}?from?'./store/actions/count.actions'function?App()?{const?dispatch?=?useDispatch()const?count?=?useSelector(state?=>?state.count)return?(<div?className="App"><p>Count?is:?{count}</p><div><button?onClick={()?=>?dispatch(addOne())}>Add?1</button><button?onClick={()?=>?dispatch(subOne())}>Decrease?1</button><button?onClick={()?=>?dispatch(addSome(10))}>Add?10</button><button?onClick={()?=>?dispatch(subSome(10))}>Decrease?10</button><button?onClick={()?=>?dispatch(reset())}>Reset?count</button></div></div>)
}export?default?App

Redux 是一個很好的工具,它同時解決了兩個問題(prop drilling 和復雜的狀態變化)。不過,它確實產生了很多模板,使狀態管理成為一個更難理解的話題,特別是在處理不同的文件和實體,如 actions、reducers、store......

這里要提到的重要一點是,這些管理狀態的工具或方法并不是相互排斥的,它們可以而且可能應該同時使用,并各自解決它們所擅長的具體問題。

對于 Redux,要解決的問題是處理全局狀態(指影響整個應用程序或其中很大一部分的狀態)。用 Redux 來處理像我們的例子中的計數器或模態的打開和關閉是沒有意義的。

一個好的黃金法則是 useState 用于組件狀態,Redux 用于應用程序狀態

Redux 的替代品

如果這個主題對你來說還不夠復雜,在過去的幾年里,出現了許多作為 Redux 替代品的新的庫,每個庫都有自己的狀態管理方法。

為了獲得很好的概述,讓我們快速了解它們。

Redux toolkit

Redux toolkit 是一個建立在 Redux 之上的庫,其目的是去除 Redux 產生的一些復雜性和模板。

Redux toolkit 基于兩件事:

  • store,它的工作方式與普通 Redux store 完全相同

  • slices 將普通的 Redux actions 和 reducer 壓縮成一個單一的東西

實現 Redux Toolkit,我們的示例應用程序如下所示:

//?App.js
import?'./App.scss'import?{?useSelector,?useDispatch?}?from?'react-redux'
import?{?addOne,?subOne,?addSome,?subSome,?reset?}?from?'./store/slices/count.slice'function?App()?{const?dispatch?=?useDispatch()const?count?=?useSelector(state?=>?state.counter.count)return?(<div?className="App"><p>Count?is:?{count}</p><div><button?onClick={()?=>?dispatch(addOne())}>Add?1</button><button?onClick={()?=>?dispatch(subOne())}>Decrease?1</button><button?onClick={()?=>?dispatch(addSome(10))}>Add?10</button><button?onClick={()?=>?dispatch(subSome(10))}>Decrease?10</button><button?onClick={()?=>?dispatch(reset())}>Reset?count</button></div></div>)
}export?default?App
//?index.js
import?React?from?'react'
import?ReactDOM?from?'react-dom'
import?App?from?'./App'
import?{?Provider?}?from?'react-redux'
import?store?from?'./store/index'ReactDOM.render(<React.StrictMode><Provider?store={store}><App?/></Provider></React.StrictMode>,document.getElementById('root')
)
//?Index.jsx?(STORE)
import?{?configureStore?}?from?'@reduxjs/toolkit'
import?counterReducer?from?'./slices/count.slice'export?const?store?=?configureStore({reducer:?{counter:?counterReducer},
})export?default?store
//?count.slice.jsx
import?{?createSlice?}?from?'@reduxjs/toolkit'const?initialState?=?{?count:?0?}export?const?counterSlice?=?createSlice({name:?'counter',initialState,reducers:?{addOne:?state?=>?{state.count?+=?1},subOne:?state?=>?{state.count?-=?1},addSome:?(state,?action)?=>?{state.count?+=?action.payload},subSome:?(state,?action)?=>?{state.count?-=?action.payload},reset:?state?=>?{state.count?=?0}},
})export?const?{?addOne,?subOne,?addSome,?subSome,?reset?}?=?counterSlice.actionsexport?default?counterSlice.reducer
  • 首先我們需要通過運行 npm install @reduxjs/toolkit react-redux 來安裝它

  • 在我們的 store 中,我們從 Redux toolkit 中導入 configureStore 函數,通過調用此函數來創建 store,并將一個帶有 reducer 的對象傳遞給它,該對象本身就是一個包含 slice 的對象。

export?const?store?=?configureStore({reducer:?{counter:?counterReducer},
})
  • 正如我所提到的,slice 是一種將 action 和 reducer 壓縮為同一個的方法。我們從 Redux toolkit 中導入createSlice 函數,然后聲明初始狀態并初始化 slice。

這個函數將接收 slice 的名稱、初始狀態以及我們將從組件派發以修改狀態的函數作為參數。

注意這里沒有任何 actions。UI 將直接調用 reducer 函數。這就是 Redux toolkit “帶走”的復雜性。

export?const?counterSlice?=?createSlice({name:?'counter',initialState,reducers:?{addOne:?state?=>?{state.count?+=?1},subOne:?state?=>?{state.count?-=?1},addSome:?(state,?action)?=>?{state.count?+=?action.payload},subSome:?(state,?action)?=>?{state.count?-=?action.payload},reset:?state?=>?{state.count?=?0}},
})
  • 在 index.js 中,我們用 provider 組件包裝應用程序,這樣的話我們可以從任何地方訪問狀態。

<Provider?store={store}><App?/></Provider>
  • 最后,我們使用 hooks 從組件中讀取狀態和 dispatch 修改函數,就像使用普通的 Redux 一樣。

function?App()?{const?dispatch?=?useDispatch()const?count?=?useSelector(state?=>?state.counter.count)return?(<div?className="App"><p>Count?is:?{count}</p><div><button?onClick={()?=>?dispatch(addOne())}>Add?1</button><button?onClick={()?=>?dispatch(subOne())}>Decrease?1</button><button?onClick={()?=>?dispatch(addSome(10))}>Add?10</button><button?onClick={()?=>?dispatch(subSome(10))}>Decrease?10</button><button?onClick={()?=>?dispatch(reset())}>Reset?count</button></div></div>)
}

Redux toolkit 旨在成為處理 Redux 的一種更簡單的方法,但在我看來,它仍然是幾乎相同的模板,與普通的 Redux 沒有太大區別。

提到 Redux Thunk 和 Redux Saga

Redux thunk 和 Redux Saga 是兩個與 Redux 一起使用的很流行的中間件庫;

具體來說,Thunk 和 Saga 都是為了處理副作用或異步任務所使用的。

Recoil

32014e3d855db0112894a84d91b06cf4.png

Recoil 是一個開源狀態管理庫,專門用于由 Facebook(或 Meta,等等)構建的 React。根據他們的網站,Recoil是為“最小化和響應式”而建立的,在這個意義上,它看起來和感覺都像普通的 React 代碼。

Recoil 基于原子(atom) 的概念。來自他們的文檔,

“一個原子代表一個狀態。原子可以從任何組件讀取和寫入。讀取原子值的組件隱式訂閱了該原子,因此任何原子更新都會導致所有訂閱該原子的組件重新渲染”。

使用 Recoil,我們的示例應用程序將如下所示:

//?App.js
import?countState?from?'./recoil/counter.atom'
import?'./App.scss'import?{?useRecoilState?}?from?'recoil'function?App()?{const?[count,?setCount]?=?useRecoilState(countState)return?(<div?className="App"><p>Count?is:?{count}</p><div><button?onClick={()?=>?setCount(count+1)}>Add?1</button><button?onClick={()?=>?setCount(count-1)}>Decrease?1</button><button?onClick={()?=>?setCount(count+10)}>Add?10</button><button?onClick={()?=>?setCount(count-10)}>Decrease?10</button><button?onClick={()?=>?setCount(0)}>Reset?count</button></div></div>)
}export?default?App
//?index.js
import?React?from?'react'
import?ReactDOM?from?'react-dom'
import?App?from?'./App'
import?{?RecoilRoot?}?from?'recoil'ReactDOM.render(<React.StrictMode><RecoilRoot><App?/></RecoilRoot></React.StrictMode>,document.getElementById('root')
)
//?counter.atom.jsx
import?{?atom?}?from?'recoil'const?countState?=?atom({key:?'countState',?//?唯一?ID?(區別于其他?atoms/selectors)default:?0?//?默認值?(又稱初始值)
})export?default?countState

你可能馬上就能看到,這比 Redux 的模板要少很多。

  • 首先我們通過運行 npm install recoil 來安裝它

  • 那些使用 recoil 狀態的組件需要在其父組件的某處使用 RecoilRoot,所以我們用它來包裝我們的應用程序

<React.StrictMode><RecoilRoot><App?/></RecoilRoot></React.StrictMode>
  • 然后我們聲明我們的 atom,它只是一個包含鍵和默認值的對象:

const?countState?=?atom({key:?'countState',?//?唯一?ID?(區別于其他?atoms/selectors)default:?0?//?默認值?(又稱初始值)
})
  • 最后,在我們的組件中,我們導入 useRecoilState hook,用它聲明我們的狀態,并將我們剛剛在 atom 中聲明的唯一鍵傳遞給它

const?[count,?setCount]?=?useRecoilState(countState)

正如你所看到的,這看起來與常規的 useState hook 非常相似。

在我們的 UI 中,我們只是調用 setCount 函數來更新我們的狀態。

<button?onClick={()?=>?setCount(count+1)}>Add?1</button>

最小,并且非常易于使用。Recoil 仍然是一種實驗性的,并沒有被廣泛使用,但你可以看到世界各地的開發人員將如何轉向這個工具。

Jotai

Jotai 是一個為 React 構建的開源狀態管理庫,其靈感來自 Recoil。它與 Recoil 的不同之處在于尋找一個更加簡約的 API -- 它不使用字符串鍵,而且是面向 TypeScript 的。

與 Recoil 一樣,Jotai 使用 atoms。atom 代表一片狀態。你只需要指定一個初始值,它可以是原始值,如字符串和數字、對象和數組。然后在你的組件中使用該 atom,在每次 atom 更改時該組件將重新渲染。

使用 Jotai,我們的示例應用程序如下所示:

//?App.js
import?'./App.scss'import?{?useAtom?}?from?'jotai'function?App()?{const?[count,?setCount]?=?useAtom(countAtom)return?(<div?className="App"><p>Count?is:?{count}</p><div><button?onClick={()?=>?setCount(count+1)}>Add?1</button><button?onClick={()?=>?setCount(count-1)}>Decrease?1</button><button?onClick={()?=>?setCount(count+10)}>Add?10</button><button?onClick={()?=>?setCount(count-10)}>Decrease?10</button><button?onClick={()?=>?setCount(0)}>Reset?count</button></div></div>)
}export?default?App
//?counter.atom.jsx
import?{?atom?}?from?'jotai'const?countAtom?=?atom(0)export?default?countAtom

如你所見,它比 Recoil 還要小。

  • 我們通過運行 npm install jotai 來安裝它

  • 然后我們聲明一個具有默認值的 atom:

const?countAtom?=?atom(0)
  • 在我們的組件中,通過使用 useAtom 去使用它:

const?[count,?setCount]?=?useAtom(countAtom)

真的很好,很簡單!

Zustand

Zusand 是另一個為 React 構建的開源狀態管理庫。它的靈感來自于在 Redux 出現之前廣泛使用的庫 Flux,它的目標是

“一個小型的、快速的、非觀點性的、可擴展的準系統狀態管理解決方案,具有基于 hooks 的舒適 API,并且幾乎沒有模板”

Zusand 使用 store 的方式與 Redux 類似,但不同之處在于,在 Zusand 中,store 是一個 hook,它需要的模板要少得多。

使用 Zusand,我們的示例應用程序將如下所示:

//?App.js
import?'./App.scss'
import?useStore?from?'./store'function?App()?{const?count?=?useStore(state?=>?state.count)const?{?addOne,?subOne,?add10,?sub10,?reset?}?=?useStore(state?=>?state)return?(<div?className="App"><p>Count?is:?{count}</p><div><button?onClick={()?=>?addOne()}>Add?1</button><button?onClick={()?=>?subOne()}>Decrease?1</button><button?onClick={()?=>?add10()}>Add?10</button><button?onClick={()?=>?sub10()}>Decrease?10</button><button?onClick={()?=>?reset()}>Reset?count</button></div></div>)
}export?default?App
//?Index.jsx?(STORE)
import?create?from?'zustand'const?useStore?=?create(set?=>?({count:?0,addOne:?()?=>?set(state?=>?({count:?state.count?+=?1})),subOne:?()?=>?set(state?=>?({count:?state.count?-=?1})),add10:?()?=>?set(state?=>?({count:?state.count?+=?10})),sub10:?()?=>?set(state?=>?({count:?state.count?-=?10})),reset:?()?=>?set({count:?0})
}))export?default?useStore
  • 我們通過運行 npm install zustand 安裝它

  • 我們使用從 Zusand 導入的 create 函數創建了一個 store,在函數里我們設置了默認狀態和我們將用來修改狀態的函數

const?useStore?=?create(set?=>?({count:?0,addOne:?()?=>?set(state?=>?({count:?state.count?+=?1})),subOne:?()?=>?set(state?=>?({count:?state.count?-=?1})),add10:?()?=>?set(state?=>?({count:?state.count?+=?10})),sub10:?()?=>?set(state?=>?({count:?state.count?-=?10})),reset:?()?=>?set({count:?0})
}))
  • 然后在我們的組件中導入剛剛創建的 store,并通過以下方式從中讀取狀態和修改函數:

const?count?=?useStore(state?=>?state.count)const?{?addOne,?subOne,?add10,?sub10,?reset?}?=?useStore(state?=>?state)

在我們的 UI 中可以像這樣調用修改函數:

<button?onClick={()?=>?addOne()}>Add?1</button>

你可以看到來自 Redux 相同概念的 Zusand,卻有一個更干凈、更簡單的方法。

總結

狀態管理是前端開發中最復雜的主題之一。你可以看到有多少人試圖讓它以可預測和可擴展的方式,而且是以干凈和易于使用的方式工作。

特別是在過去的幾年里,出現了很多很好的工具,提供了處理狀態管理的好方法。

不過,作為開發者,我們必須牢記,Redux 和其他庫的創建是為了解決特定的狀態管理問題,特別是在真正的大型、復雜和大量使用的應用程序中。

我認為,如果你沒有遇到這些問題,真的沒有必要增加額外的模板,使你的代碼復雜化。即使使用那些幾乎不添加樣板的現代庫。

React 本身是一個非常強大和可靠的庫,useState、useReducer 和 useContext 等工具足以解決大多數問題。因此,我會堅持基本的東西,除非因為某些原因,基本的東西已經不夠用了。

當需要更具體、更強大的狀態管理庫時,我認為應該在可靠性和簡單性之間做出選擇。

Redux 是最成熟和使用最廣泛的庫,它附帶大量文檔、在線社區以及在每個新版本中發現和解決的以前錯誤。

它的壞處是,作為開發者,它給我們帶來了一些我們必須學習和思考的新概念。我們還需要添加相當多的代碼來使其工作,而且它所增加的復雜性可能超過它所幫助解決的問題。

相反,我們之前所看到的現代庫要簡單得多,而且直截了當,但是它們沒有得到廣泛使用和測試,仍然是一種實驗性的。

但就我們目前所看到的而言,其中一個或一些帶頭成為更廣泛使用的工具似乎只是時間問題。

我希望你喜歡這篇文章并學到了一些新東西。

干杯,下次見!


原文鏈接:https://www.freecodecamp.org/news/how-to-manage-state-in-a-react-app/

作者:Germán Cocca

譯者:Jing Wu

我在阿里招前端,我該怎么幫你?(現在還可以加模擬面試群)
如何拿下阿里巴巴 P6 的前端 Offer
如何準備阿里P6/P7前端面試--項目經歷準備篇
大廠面試官常問的亮點,該如何做出?
如何從初級到專家(P4-P7)打破成長瓶頸和有效突破
若川知乎問答:2年前端經驗,做的項目沒什么技術含量,怎么辦?

如何準備20K+的大廠前端面試

fb5211e2e3393c4828617e7c5dcf8290.gif

·················?若川簡介?·················

你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》20余篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經堅持寫了8年,點擊查看年度總結。
同時,最近組織了源碼共讀活動,幫助4000+前端人學會看源碼。公眾號愿景:幫助5年內前端人走向前列。

065042ceae653eeaae33d6a1a8554383.jpeg

掃碼加我微信 lxchuan12、拉你進源碼共讀

今日話題

目前建有江西|湖南|湖北?籍 前端群,想進群的可以加我微信 lxchuan12?進群。分享、收藏、點贊、在看我的文章就是對我最大的支持~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/274544.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/274544.shtml
英文地址,請注明出處:http://en.pswp.cn/news/274544.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

長語音識別體驗_如何為語音體驗寫作

長語音識別體驗重點 (Top highlight)“Voice User Interface (VUI) Designer” is an increasingly prominent job title in the tech world. A VUI designer typically writes the conversation and designs the flow between a VUI — an invisible interface that communica…

表連接

初學SQL表連接的時候&#xff0c;什么笛卡爾積&#xff0c;左連接&#xff0c;右連接看的頭都大了 后來看了《SQL Server技術內幕2008&#xff1a;T-SQL查詢》之后&#xff0c;豁然開朗。今天寫數據庫又用到了表連接&#xff0c;印象有點模糊了&#xff0c;趕緊找地方寫下來先。…

分析了1011個程序員的裁員情況后得出的啟示

大家好&#xff0c;我是若川。持續組織了近一年的源碼共讀活動&#xff0c;感興趣的可以 點此掃碼加我微信 ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試系…

定義設計系統

System is “the whole creation, the universe,” from Late Latin systema “an arrangement, system,” from Greek systema “organized whole, a whole compounded of parts”.系統是晚期拉丁語系統的“整體創造物&#xff0c;宇宙”&#xff0c;是希臘語系統的“一種安排…

如何備份linux系統(轉)

如何備份linux系統 不像Windows&#xff0c;Linux不限制根用戶存取任何東西&#xff0c;因此&#xff0c;你完全可以把一個分區上每一個的文件放入一個TAR文件中。來實施這一方法&#xff0c;用這個成為根用戶&#xff1a;sudo su接著去你的文件系統的根目錄&#xff08;在我們…

2w行代碼、200個實戰項目,助你修煉5大編程基本功。【送書《設計模式之美》】...

大家好&#xff0c;我是若川。之前送了很多書&#xff0c;現在又和異步圖書合作再次爭取了幾本書&#xff0c;具體送書規則看文末。所謂練武不練功&#xff0c;到老一場空&#xff0c;以技術為驅動的程序員同樣如此。面向對象編程范式、設計原則、代碼規范、重構技巧和設計模式…

C++第10周項目2擴展之2參考——迭代求和

課程首頁地址&#xff1a;http://blog.csdn.net/sxhelijian/article/details/7910565【項目2擴展之2&#xff08;選做&#xff09;】計算下面的式子&#xff0c;不能使用求冪函數pow()式一&#xff1a;#include <iostream> using namespace std; int main( ) { int i,m1;…

swift自行車品牌介紹_品牌101:簡介

swift自行車品牌介紹Sometimes when I’m around designer friends and there’s a lull in the conversation one of us will blurt out, “What is branding, anyway?” Then we shrug our shoulders and chuckle, knowing that the answer is far too complex to sum up in…

flutter 透明度動畫_Flutter中的動畫填充+不透明度動畫?

flutter 透明度動畫Flutter SDK provides us with many widgets which help us in animating elements on screen easily by implicitly managing the animations i.e. we need not worry about creating and managing intances of AnimationController during the lifecycle o…

阿里 P10 是怎樣的存在?

談起中國頂尖的程序員&#xff0c;很多人首先會想到之前的雷軍、張小龍&#xff0c;還有現在的多隆、行癲、道哥等人&#xff0c;但今天我想聊一聊的這位大神&#xff0c;他的技術成就也同樣令人矚目。19 年獲得國家技術發明二等獎、20 年獲得國家計算機協會頒發的“ CCF 杰出工…

vba交付圖表設計_您是在為交付目的而“設計”嗎?

vba交付圖表設計重點 (Top highlight)It’s a regular Monday morning. All the design team is organizing the tasks for the ongoing week and reviewing requirements and deadlines for the various projects at the studio or company you work at. Suddenly, among the …

正則表達式限制文本框只能輸入數字

許多時候我們在制作表單時需要限制文本框輸入內容的類型&#xff0c;下面我們用正則表達式限制文本框只能輸入數字、小數點、英文字母、漢字等各類代碼。 1.文本框只能輸入數字代碼(小數點也不能輸入) <input οnkeyup"this.valuethis.value.replace(/\D/g,)" …

前端必讀書籍推薦

大家好&#xff0c;我是若川。持續組織了近一年的源碼共讀活動&#xff0c;感興趣的可以 點此掃碼加我微信 ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試系…

window程序設計學會_是時候我們學會設計合適的餅圖了

window程序設計學會Pie charts are common in data science — next to the 餅形圖在數據科學中很常見- bar chart and the line plot, the pie chart is incredibly standard and simple. A circle is split into several slices, with each slice’s angle representing how…

「非廣告」程序員如何才能盡量避免被裁?

大家好&#xff0c;我是若川。持續組織了近一年的源碼共讀活動&#xff0c;感興趣的可以 點此掃碼加我微信 ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試系…

使用css制作三角,兼容IE6,用到的標簽divsspan

使用css來制作三角&#xff0c;在日常用得較多。恰好這幾天項目中有用到&#xff0c;之前只是從網上copy下來代碼直接用&#xff0c;但是今天在用的時候遇到一些問題&#xff0c;于是借此機會把這個css繪制三角好好研究下吧。 我們分別寫一個<div>,<s>,<span>…

培訓師 每小時多少錢_每個產品設計師需要了解的品牌知識

培訓師 每小時多少錢重點 (Top highlight)These days, it pays to know about brand. The rise of startups has created thousands of new brand design opportunities, and people of all disciplines are working to help brands compete in a crowded world. Increasingly,…

Android 綁定遠程服務出現 Not Allowed to bind service

E/AndroidRuntime(3783): Caused by: java.lang.SecurityException: Not allowed to bind to service Intent { actcom.lenovo.pleiades.conntek.pad } 主要原因是服務中設有簽名保護&#xff0c;該服務上一次是通過A設備中的Eclipse簽名的&#xff0c;這一次是通過B設備中的Ec…

axios 發布 v1.1.0 據說導致很多網站癱瘓~那么如何自動提升版本號呢~

- 大家好&#xff0c;我是若川。友情提醒&#xff0c;今天還是周二。就不發長篇技術文了~近日&#xff0c;axios 發布了 v1.1.0 版本&#xff0c;調用 axios.get 時報錯&#xff0c;據說導致請求無效很多網站癱瘓。目前官方已發布了 v1.1.1 v1.1.2 修復了該問題。讓我想起群友在…

七月時忙碌而充實的_如何減少忙碌而更有效

七月時忙碌而充實的In our hectic modern world, we believe that rushing from one task to the next and managing multiple priorities shows everyone that we are productive.在忙碌的現代世界中&#xff0c;我們相信從一項任務過渡到下一項任務并處理多項優先事項可以向所…