状态管理库
在 React App 中使用 5 种最流行的库/APIS(使用最现代和最新版本的库)如何在 React App程序中使用全局状态管理,并且达到一样的效果。

  1. Recoil
  2. MobX
  3. XState
  4. Redux (with hooks)
  5. Context

    入门

    先创建一个新的 React App,将使用它来开始实践:
    1. npx create-react-app react-state-examples
    2. cd react-state-examples
    使用 start 命令启动。
    1. npm start

    Recoil

    Recoil Docs
    代码行数:30
    Recoil 的点是基于 Hooks 的 API。与其他一些库相比,Recoil 的 API 比大多数库更容易。

    Recoil 实践

    开始使用Recoil前,先安装依赖:
    1. npm install recoil
    接下来,将 RecoilRoot 添加到 App 程序的根/入口点:
    1. import App from './App'
    2. import { RecoilRoot } from 'recoil'
    3. export default function Main() {
    4. return (
    5. <RecoilRoot>
    6. <App />
    7. </RecoilRoot>
    8. );
    9. }
    下一步,要创建一些状态,将使用来自Recoil 的 atom 并设置key和一些初始状态:
    1. import { atom } from 'recoil'
    2. const notesState = atom({
    3. key: 'notesState', // unique ID (with respect to other atoms/selectors)
    4. default: [], // default value (aka initial state)
    5. });
    现在,可以在app的任何位置使用来自 Recoil 的useRecoilState。这是使用 Recoil 实现的笔记 App:
    1. import React, { useState } from 'react';
    2. import { RecoilRoot, atom, useRecoilState } from 'recoil';
    3. const notesState = atom({
    4. key: 'notesState', // unique ID (with respect to other atoms/selectors)
    5. default: [], // default value (aka initial state)
    6. });
    7. export default function Main() {
    8. return (
    9. <RecoilRoot>
    10. <App />
    11. </RecoilRoot>
    12. );
    13. }
    14. function App() {
    15. const [notes, setNotes] = useRecoilState(notesState);
    16. const [input, setInput] = useState('')
    17. function createNote() {
    18. const notesArray = [...notes, input]
    19. setNotes(notesArray)
    20. setInput('')
    21. }
    22. return (
    23. <div>
    24. <h1>My notes app</h1>
    25. <button onClick={createNote}>Create Note</button>
    26. <input value={input} onChange={e => setInput(e.target.value)} />
    27. { notes.map(note => <p key={note}>Note: {note}</p>) }
    28. </div>
    29. );
    30. }

    Recoil selectors

    来自文档

    selectors 用于计算基于 state 的派生属性。这能避免冗余 state,通常无需使用 reducers 来保持状态同步和有效。相反,最小状态集存储在 atoms 中。

使用 Recoil selectors,可以根据 state 计算派生属性,例如,可能是已过滤的待办事项数组(在todo app 中)或已发货的订单数组(在电子商务应用程序中):

  1. import { selector, useRecoilValue } from 'recoil'
  2. const completedTodosState = selector({
  3. key: 'todosState',
  4. get: ({get}) => {
  5. const todos = get(todosState)
  6. return todos.filter(todo => todo.completed)
  7. }
  8. })
  9. const completedTodos = useRecoilValue(completedTodosState)

结论

recoil 文档说:Recoil 是一个用于 React 状态管理的实验性使用工具集。

Mobx

MobX React Lite Docs
代码行数: 30
MobX React 现在有一个轻量级版本(MobX React Lite),这个版本专门针对函数组件而诞生,它的有点是速度更快,更小。
MobX 具有可观察者和观察者的概念,然而可观察的API有所改变,那就是不必指定希望被观察的每个项,而是可以使用 makeAutoObservable 来处理所有事情。
如果希望数据是响应的并且需要修改 store ,则可以用observer来包装组件。

MobX 实践

开始使用Mobx前,先安装依赖:

  1. npm install mobx mobx-react-lite

该应用的状态已在 Store 中创建和管理。
应用的 store 如下所示:

  1. import { makeAutoObservable } from 'mobx'
  2. class NoteStore {
  3. notes = []
  4. createNote(note) {
  5. this.notes = [...this.notes, note]
  6. }
  7. constructor() {
  8. /* makes all data in store observable, replaces @observable */
  9. makeAutoObservable(this)
  10. }
  11. }
  12. const Notes = new NoteStore()

然后,可以导入notes,并在 app 中的任何位置使用它们。要使组件是可观察修改,需要将其包装在observer中:

  1. import { observer } from 'mobx-react-lite'
  2. import { notes } from './NoteStore'
  3. const App = observer(() => <h1>{notes[0]|| "No notes"}</h1>)

看看它们如何一起运行的:

  1. import React, { useState } from 'react'
  2. import { observer } from "mobx-react-lite"
  3. import { makeAutoObservable } from 'mobx'
  4. class NoteStore {
  5. notes = []
  6. createNote(note) {
  7. this.notes = [...this.notes, note]
  8. }
  9. constructor() {
  10. makeAutoObservable(this)
  11. }
  12. }
  13. const Notes = new NoteStore()
  14. const App = observer(() => {
  15. const [input, setInput] = useState('')
  16. const { notes } = Notes
  17. function onCreateNote() {
  18. Notes.createNote(input)
  19. setInput('')
  20. }
  21. return (
  22. <div>
  23. <h1>My notes app</h1>
  24. <button onClick={onCreateNote}>Create Note</button>
  25. <input value={input} onChange={e => setInput(e.target.value)} />
  26. { notes.map(note => <p key={note}>Note: {note}</p>) }
  27. </div>
  28. )
  29. })
  30. export default App

总结

MobX 已经诞生了一段时间,它很好用。与许多其他公司一样,在企业公司的大量线上应用中使用了它。

XState

XState Docs
代码行数:44
XState 试图解决现代UI复杂性的问题,并且依赖于有限状态机的思想和实现。
XState 是由 David Khourshid, 创建的。它的实现方式是与其他库截然不同的。它的复杂性比其他任何一种都要高,但是关于状态如何工作的思维模型确实很 cool 而且对于提高能力很有帮助。

要了解有关 XState 试图解决的问题的更多信息,请查看David Khourshid的这段视频或一些有趣的帖子

XState 在这里的使用不是特别好,因为它更适合在更复杂的状态下使用。

XState实践

要开始使用XState,请安装这些库:

  1. npm install xstate @xstate/react

要创建machine,请使用xstate中的Machine实用程序。这是将用于 Notes app 的machine

  1. import { Machine } from 'xstate'
  2. const notesMachine = Machine({
  3. id: 'notes',
  4. initial: 'ready',
  5. context: {
  6. notes: [],
  7. note: ''
  8. },
  9. states: {
  10. ready: {},
  11. },
  12. on: {
  13. "CHANGE": {
  14. actions: [
  15. assign({
  16. note: (_, event) => event.value
  17. })
  18. ]
  19. },
  20. "CREATE_NOTE": {
  21. actions: [
  22. assign({
  23. note: "",
  24. notes: context => [...context.notes, context.note]
  25. })
  26. ]
  27. }
  28. }
  29. })

将使用的数据存储在 context 中。在这里,有一个 notes 列表 和一个 input 输入框。有两种操作,一种用于创建 note(CREATE_NOTE),另一种用于设置 input(CHANGE)。
整个示例:

  1. import React from 'react'
  2. import { useService } from '@xstate/react'
  3. import { Machine, assign, interpret } from 'xstate'
  4. const notesMachine = Machine({
  5. id: 'notes',
  6. initial: 'ready',
  7. context: {
  8. notes: [],
  9. note: ''
  10. },
  11. states: {
  12. ready: {},
  13. },
  14. on: {
  15. "CHANGE": {
  16. actions: [
  17. assign({
  18. note: (_, event) => event.value
  19. })
  20. ]
  21. },
  22. "CREATE_NOTE": {
  23. actions: [
  24. assign({
  25. note: "",
  26. notes: context => [...context.notes, context.note]
  27. })
  28. ]
  29. }
  30. }
  31. })
  32. const service = interpret(notesMachine).start()
  33. export default function App() {
  34. const [state, send] = useService(service)
  35. const { context: { note, notes} } = state
  36. return (
  37. <div>
  38. <h1>My notes app</h1>
  39. <button onClick={() => send({ type: 'CREATE_NOTE' })}>Create Note</button>
  40. <input value={note} onChange={e => send({ type: 'CHANGE', value: e.target.value})} />
  41. { notes.map(note => <p key={note}>Note: {note}</p>) }
  42. </div>
  43. )
  44. }

要在应用中修改状态,使用 xstate-react 中的 useService hooks。

总结

XState 就像劳斯莱斯 或者说 状态管理的瑞士军刀。可以做很多事情,但是所有功能都带来额外的复杂性。

Redux

React Redux docs
代码行数:33
Redux 是整个 React 生态系统中最早,最成功的状态管理库之一。如今它依然很强大。
新的 Redux Hooks API 使 redux 使用起来不再那么麻烦,而且使用起来也更容易。
Redux Toolkit 还改进了 Redux,并大大降低了学习曲线。

Redux 实践

开始使用Redux前,先安装依赖:

  1. npm install @reduxjs-toolkit react-redux

要使用 Redux,需要创建和配置以下内容:

  1. A store
  2. Reducers
  3. A provider

为了帮助解释所有这些工作原理,在实现 Redux 中的 Notes app 的代码中做了注释:

  1. import React, { useState } from 'react'
  2. import { Provider, useDispatch, useSelector } from 'react-redux'
  3. import { configureStore, createReducer, combineReducers } from '@reduxjs/toolkit'
  4. function App() {
  5. const [input, setInput] = useState('')
  6. /* useSelector 允许你检索你想使用的状态,在我们的例子中是notes数组。 */
  7. const notes = useSelector(state => state.notes)
  8. /* dispatch 允许我们向 store 发送更新信息 */
  9. const dispatch = useDispatch()
  10. function onCreateNote() {
  11. dispatch({ type: 'CREATE_NOTE', note: input })
  12. setInput('')
  13. }
  14. return (
  15. <div>
  16. <h1>My notes app</h1>
  17. <button onClick={onCreateNote}>Create Note</button>
  18. <input value={input} onChange={e => setInput(e.target.value)} />
  19. { notes.map(note => <p key={note}>Note: {note}</p>) }
  20. </div>
  21. );
  22. }
  23. /* 在这里,我们创建了一个 reducer,它将在`CREATE_NOTE`动作被触发时更新note数组。 */
  24. const notesReducer = createReducer([], {
  25. 'CREATE_NOTE': (state, action) => [...state, action.note]
  26. })
  27. /* Here we create the store using the reducers in the app */
  28. const reducers = combineReducers({ notes: notesReducer })
  29. const store = configureStore({ reducer: reducers })
  30. function Main() {
  31. return (
  32. /* 在这里,我们使用app中的reducer来创建store。 */
  33. <Provider store={store}>
  34. <App />
  35. </Provider>
  36. )
  37. }
  38. export default Main

总结

如果正在寻找一个具有庞大社区、大量文档以及大量问答的库,那么Redux是一个非常靠谱的选择。因为它已诞生了很长时间,只要在 Google 搜索,或多或少都能找到一些相关的答案。
在使用异步操作(例如数据获取)时,通常需要添加其他中间件,这会增加它的成本和复杂性。
Redux 起初很难学习。一旦熟悉了框架,就可以很容易地使用和理解它。过去,对于新开发人员而言,有时会感到不知所措,但是随着 Redux Hooks 和 Redux Toolkit 的改进,学习过程变得容易得多,仍然强烈建议 Redux 作为前置的选择。

Context

Context docs
代码行数: 31
context 的优点在于,不需要安装和依赖其他库,它是 React 的一部分。
使用 context 非常简单,当尝试管理大量不同的 context 值时,问题通常会出现在一些大或者复杂的应用程序中,因此通常必须构建自己的抽象来自己管理这些情况。

Context 实践

要创建和使用 context ,请直接从React导入钩子。下面是它的工作原理:

  1. /* 1. Import the context hooks */
  2. import React, { useState, createContext, useContext } from 'react';
  3. /* 2. Create a piece of context */
  4. const NotesContext = createContext();
  5. /* 3. Set the context using a provider */
  6. <NotesContext.Provider value={{ notes: ['note1', 'note2'] }}>
  7. <App />
  8. </NotesContext.Provider>
  9. /* 4. Use the context */
  10. const { notes } = useContext(NotesContext);

全部代码

  1. import React, { useState, createContext, useContext } from 'react';
  2. const NotesContext = createContext();
  3. export default function Main() {
  4. const [notes, setNotes] = useState([])
  5. function createNote(note) {
  6. const notesArray = [...notes, note]
  7. setNotes(notesArray)
  8. }
  9. return (
  10. <NotesContext.Provider value={{ notes, createNote }}>
  11. <App />
  12. </NotesContext.Provider>
  13. );
  14. }
  15. function App() {
  16. const { notes, createNote } = useContext(NotesContext);
  17. const [input, setInput] = useState('')
  18. function onCreateNote() {
  19. createNote(input)
  20. setInput('')
  21. }
  22. return (
  23. <div>
  24. <h1>My notes app</h1>
  25. <button onClick={onCreateNote}>Create Note</button>
  26. <input value={input} onChange={e => setInput(e.target.value)} />
  27. { notes.map(note => <p key={note}>Note: {note}</p>) }
  28. </div>
  29. );
  30. }

总结

context 是一种管理 app 状态的真正可靠且直接的方法。它的API可能不如其他一些库那么好,但是如果了解如何使用它,并且可以在 app 中使用它创建正确的数据抽象,那么选择 context 来管理全局状态就不会错。