React 状态的 5 种方式
畏缩
MobX
状态
Redux
语境
封面图片由Bryan Goff提供
要查看这些示例的代码,请单击此处
在 React 中,处理状态管理的方法似乎无穷无尽。尝试理解各种选项、它们之间的权衡以及它们的工作原理可能会让人不知所措。
当我尝试学习某些东西时,看到并排比较实现一些常见的现实世界功能有助于我理解各种选项之间的差异,并形成一个关于如何在我自己的应用程序中使用它们的心理模型。
在这篇文章中,我将介绍如何在 React 应用程序中使用相同的模式在 5 个最流行的库和 API 中实现全局状态管理,并使用最现代和最新版本的库。
我还将尝试解释它们之间的差异以及我对每种方法的想法和概述。
为了演示 API,我们将使用每个库/方法实现一个笔记应用程序,展示如何创建和列出笔记数组。
入门
如果您想继续,请创建一个新的 React 应用程序,我们将使用它来测试这些方法:
npx create-react-app react-state-examples
cd react-state-examples
要随时运行该应用程序,请运行以下start
命令:
npm start
畏缩
代码行数:30
我真正喜欢 Recoil 的一点是其基于钩子的 API,以及它的直观性。
与其他一些选项相比,我想说设置和带有后坐力的 API 比大多数都更容易。
后坐力作用
要开始使用 Recoil,请安装该库作为依赖项:
npm install recoil
接下来,将RecoilRoot添加到应用程序的根/入口点:
import App from './App'
import { RecoilRoot } from 'recoil'
export default function Main() {
return (
<RecoilRoot>
<App />
</RecoilRoot>
);
}
接下来,为了创建一些状态,我们将使用atom
Recoil 并设置一个键以及一些初始状态:
import { atom } from 'recoil'
const notesState = atom({
key: 'notesState', // unique ID (with respect to other atoms/selectors)
default: [], // default value (aka initial state)
});
现在,你可以useRecoilState
在应用的任何位置使用 Recoil 来访问这个值。以下是使用 Recoil 实现的笔记应用:
import React, { useState } from 'react';
import { RecoilRoot, atom, useRecoilState } from 'recoil';
const notesState = atom({
key: 'notesState', // unique ID (with respect to other atoms/selectors)
default: [], // default value (aka initial state)
});
export default function Main() {
return (
<RecoilRoot>
<App />
</RecoilRoot>
);
}
function App() {
const [notes, setNotes] = useRecoilState(notesState);
const [input, setInput] = useState('')
function createNote() {
const notesArray = [...notes, input]
setNotes(notesArray)
setInput('')
}
return (
<div>
<h1>My notes app</h1>
<button onClick={createNote}>Create Note</button>
<input value={input} onChange={e => setInput(e.target.value)} />
{ notes.map(note => <p key={note}>Note: {note}</p>) }
</div>
);
}
后坐力选择器
来自文档:
选择器用于计算基于状态的派生数据。这让我们避免了冗余状态,通常无需使用 Reducer 来保持状态同步和有效。相反,最小状态集以原子形式存储,而
使用 Recoil 选择器,您可以根据状态计算派生数据,例如可能是已完成的待办事项的过滤数组(在待办事项应用程序中),或已发货的订单数组(在电子商务应用程序中):
import { selector, useRecoilValue } from 'recoil'
const completedTodosState = selector({
key: 'todosState',
get: ({get}) => {
const todos = get(todosState)
return todos.filter(todo => todo.completed)
}
})
const completedTodos = useRecoilValue(completedTodosState)
判决
Recoil 的文档里说“Recoil 是一套用于 React 状态管理的实验性工具”。当我决定在生产环境中使用一个库时,听到“实验性”这个词让我感到很不舒服,所以至少目前我还不确定现在使用 Recoil 的感受。
Recoil 非常棒,我会在我的下一个应用程序中使用它,但我担心experimental
标签,所以我会密切关注它,但现在不会在生产中使用它。
MobX
代码行数:30
MobX React 一直是我最喜欢的 React 状态管理方式之一,主要是因为它是我在 Redux 之后尝试的下一个产品。两者之间的巨大差异让我多年来一直选择 MobX React。
MobX React 现在有一个轻量级版本(MobX React Lite),专为功能组件制作,速度更快,体积更小。
MobX 有可观察对象和观察者的概念,但是可观察API 已经发生了一些变化,您不必指定想要观察的每个项目,而是可以使用makeAutoObservable
它将为您处理所有事情。
如果您希望您的数据能够被reactive
订阅到商店中的更改,那么您可以使用它将组件包装起来observer
。
MobX 实际应用
要开始使用 MobX,请安装该库作为依赖项:
npm install mobx mobx-react-lite
应用程序的状态是在商店中创建和管理的。
我们的应用程序商店如下所示:
import { makeAutoObservable } from 'mobx'
class NoteStore {
notes = []
createNote(note) {
this.notes = [...this.notes, note]
}
constructor() {
/* makes all data in store observable, replaces @observable */
makeAutoObservable(this)
}
}
const Notes = new NoteStore()
然后,我们可以导入Notes
并在应用程序的任何位置使用它们。要使组件观察更改,请将其包装在observer
:
import { observer } from 'mobx-react-lite'
import { notes } from './NoteStore'
const App = observer(() => <h1>{notes[0]|| "No notes"}</h1>)
让我们看看它们是如何协同工作的:
import React, { useState } from 'react'
import { observer } from "mobx-react-lite"
import { makeAutoObservable } from 'mobx'
class NoteStore {
notes = []
createNote(note) {
this.notes = [...this.notes, note]
}
constructor() {
makeAutoObservable(this)
}
}
const Notes = new NoteStore()
const App = observer(() => {
const [input, setInput] = useState('')
const { notes } = Notes
function onCreateNote() {
Notes.createNote(input)
setInput('')
}
return (
<div>
<h1>My notes app</h1>
<button onClick={onCreateNote}>Create Note</button>
<input value={input} onChange={e => setInput(e.target.value)} />
{ notes.map(note => <p key={note}>Note: {note}</p>) }
</div>
)
})
export default App
判决
MobX 已经存在一段时间了,并且经过了实践检验。我和其他许多人一样,在企业级的大规模生产应用中使用过它。
最近再次使用它之后,我觉得它的文档比其他一些选项略显不足。我建议你先尝试一下,看看自己的想法,然后再下注。
状态
代码行数:44
XState 正在尝试解决现代 UI 复杂性的问题,并依赖于有限状态机的思想和固执己见的实现。
XState 是由David Khourshid创建的,自从它发布以来,我就经常看到他谈论它,所以我一直很想尝试一下。在写这篇文章之前,这是我唯一不熟悉的库。
尝试之后,我可以肯定地说,它与其他任何方法都截然不同。它的复杂性比其他任何方法都要高,但它的状态工作模式非常酷炫,而且非常强大。在我使用它构建了几个示例应用之后,我感觉自己变得很聪明了🧠。
要了解 XState 试图解决的问题的更多信息,请观看David Khourshid 的这个视频或这篇我觉得很有趣的文章。
XState 在这里翻译得不是特别好,因为它在更复杂的状态下才真正闪耀,但这个简单的介绍至少可以给你一个介绍,帮助你了解它是如何工作的。
XState 实际作用
要开始使用 XState,请安装以下库:
npm install xstate @xstate/react
要创建状态机,请使用Machine
中的实用程序xstate
。以下是我们将用于 Notes 应用的状态机:
import { Machine } from 'xstate'
const notesMachine = Machine({
id: 'notes',
initial: 'ready',
context: {
notes: [],
note: ''
},
states: {
ready: {},
},
on: {
"CHANGE": {
actions: [
assign({
note: (_, event) => event.value
})
]
},
"CREATE_NOTE": {
actions: [
assign({
note: "",
notes: context => [...context.notes, context.note]
})
]
}
}
})
我们将要处理的数据存储在context
对象中。这里,我们有一个注释数组以及一个将由文本输入控制的注释。有两个操作,一个用于创建注释(CREATE_NOTE
),另一个用于设置文本输入(CHANGE
)。
总结一下:
import React from 'react'
import { useService } from '@xstate/react'
import { Machine, assign, interpret } from 'xstate'
const notesMachine = Machine({
id: 'notes',
initial: 'ready',
context: {
notes: [],
note: ''
},
states: {
ready: {},
},
on: {
"CHANGE": {
actions: [
assign({
note: (_, event) => event.value
})
]
},
"CREATE_NOTE": {
actions: [
assign({
note: "",
notes: context => [...context.notes, context.note]
})
]
}
}
})
const service = interpret(notesMachine).start()
export default function App() {
const [state, send] = useService(service)
const { context: { note, notes} } = state
return (
<div>
<h1>My notes app</h1>
<button onClick={() => send({ type: 'CREATE_NOTE' })}>Create Note</button>
<input value={note} onChange={e => send({ type: 'CHANGE', value: e.target.value})} />
{ notes.map(note => <p key={note}>Note: {note}</p>) }
</div>
)
}
要订阅整个应用程序的状态变化,我们使用useService
来自的钩子xstate-react
。
判决
XState 就像状态管理中的劳斯莱斯或瑞士军刀。它能帮你实现很多功能,但所有功能的实现都伴随着额外的复杂性。
我希望将来能够更好地学习和理解它,以便能够将它应用于 AWS 的问题和参考架构,但对于小型项目来说,我认为它可能有点过度了。
Redux
代码行数:33
Redux 是整个 React 生态系统中最早、最成功的状态管理库之一。我已经在无数项目中使用过 Redux,它至今依然表现强劲。
新的 Redux hooks API 使得 redux 样板不再成为问题,并且更易于使用。
Redux Toolkit还改进了 DX,并大大降低了学习曲线。
Redux 实际应用
要开始使用 Redux,请安装必要的库:
npm install @reduxjs-toolkit react-redux
要使用 Redux,您需要创建并配置以下内容:
- 一家商店
- Reducers
- 提供商
为了帮助解释这一切是如何工作的,我在 redux 中实现 Notes 应用程序的代码中做了注释:
import React, { useState } from 'react'
import { Provider, useDispatch, useSelector } from 'react-redux'
import { configureStore, createReducer, combineReducers } from '@reduxjs/toolkit'
function App() {
const [input, setInput] = useState('')
/* useSelector allows you to retrieve the state that you'd like to work with, in our case the notes array */
const notes = useSelector(state => state.notes)
/* dispatch allows us to send updates to the store */
const dispatch = useDispatch()
function onCreateNote() {
dispatch({ type: 'CREATE_NOTE', note: input })
setInput('')
}
return (
<div>
<h1>My notes app</h1>
<button onClick={onCreateNote}>Create Note</button>
<input value={input} onChange={e => setInput(e.target.value)} />
{ notes.map(note => <p key={note}>Note: {note}</p>) }
</div>
);
}
/* Here we create a reducer that will update the notes array when the `CREATE_NOTE` action is dispatched */
const notesReducer = createReducer([], {
'CREATE_NOTE': (state, action) => [...state, action.note]
})
/* Here we create the store using the reducers in the app */
const reducers = combineReducers({ notes: notesReducer })
const store = configureStore({ reducer: reducers })
function Main() {
return (
/* Here we configure the Provider with the store */
<Provider store={store}>
<App />
</Provider>
)
}
export default Main
判决
如果你正在寻找一个拥有庞大社区、大量文档和答案的工具,Redux 是一个非常不错的选择。由于它已经存在很长时间了,你几乎可以在 Google 上搜索任何问题,至少都能找到一些相关的答案。
当使用数据提取等异步操作时,通常需要添加额外的中间件,这会增加额外的样板和复杂性。
对我来说,Redux 一开始很难学。一旦我熟悉了这个框架,它就变得非常容易上手和理解。过去,它有时会让新开发者感到不知所措,但随着 Redux Hooks 和 Redux Toolkit 的最新改进,学习曲线变得容易得多,我仍然强烈推荐 Redux 作为首选。
语境
代码行数:31
context 的优点在于无需安装和更新库,它本身就是 React 的一部分。它有大量的使用示例,并且与其他 React 文档一起提供文档。
使用上下文非常简单,当您尝试管理大量不同的上下文值时,问题通常出现在更大或更复杂的应用程序中,因此您通常必须构建自己的抽象来自己管理这些情况。
上下文在行动
要创建和使用上下文,请直接从 React 导入 hooks。具体操作如下:
/* 1. Import the context hooks */
import React, { useState, createContext, useContext } from 'react';
/* 2. Create a piece of context */
const NotesContext = createContext();
/* 3. Set the context using a provider */
<NotesContext.Provider value={{ notes: ['note1', 'note2'] }}>
<App />
</NotesContext.Provider>
/* 4. Use the context */
const { notes } = useContext(NotesContext);
总结一下:
import React, { useState, createContext, useContext } from 'react';
const NotesContext = createContext();
export default function Main() {
const [notes, setNotes] = useState([])
function createNote(note) {
const notesArray = [...notes, note]
setNotes(notesArray)
}
return (
<NotesContext.Provider value={{ notes, createNote }}>
<App />
</NotesContext.Provider>
);
}
function App() {
const { notes, createNote } = useContext(NotesContext);
const [input, setInput] = useState('')
function onCreateNote() {
createNote(input)
setInput('')
}
return (
<div>
<h1>My notes app</h1>
<button onClick={onCreateNote}>Create Note</button>
<input value={input} onChange={e => setInput(e.target.value)} />
{ notes.map(note => <p key={note}>Note: {note}</p>) }
</div>
);
}
判决
Context 是一种非常可靠且直接的管理应用状态的方法。它的 API 可能不如其他一些选项那么好用,但如果您了解如何使用它,并且能够在应用中创建正确的抽象,那么选择 Context 来管理应用中的全局状态绝对不会错。
文章来源:https://dev.to/dabit3/react-state-6-ways-2bem