在上一章中,我们学习了 Redux 以及如何将 Redux 与 Hook 结合使用。我们还学习了如何将现有的 Redux 应用程序迁移到基于 Hook 的解决方案。此外,我们还了解了使用 Reducer Hook 与 Redux 的权衡,以及何时使用它们。
在本章中,我们将学习如何将 MobX 与 Hook 结合使用。我们将首先学习如何使用 MobX 处理状态,然后继续使用带有 Hook 的 MobX。此外,我们还将学习如何将现有的 MobX 应用程序迁移到 Hook。最后,我们将讨论使用 MobX 的利弊。在本章结束时,您将完全了解如何使用 Hook 编写 MobX 应用程序。
本章将介绍以下主题:
- 了解什么是 MobX 及其工作原理
- 使用 MobX 处理状态
- 将 MobX 与 Hook 一起使用
- 迁移 MobX 应用程序
- 了解 MobX 的权衡取舍
技术要求
应该已经安装了相当新版本的 Node.js(vl1. l2.0 或更高版本)。还需要安装 Node.js 的 npm
包管理器。
本章的代码可以在 GitHub 存储库中找到:https://github.com/PacktPublishing/Learn-React-Hooks/tree/master/Chapter13。
观看以下视频,了解代码的实际应用:
请注意,强烈建议您自己编写代码。不要简单地运行已提供的代码示例。重要的是你自己编写代码,以便能够正确学习和理解它。但是,如果遇到任何问题,始终可以参考代码示例。
现在,让我们从本章开始。
什么是 MobX?
MobX 采用了与 Redux 不同的方法。它不是应用限制来使状态更改可预测,而是自动更新从应用程序状态派生的任何内容。在 MobX 中,我们可以直接修改状态对象,而不是调度 action,MobX 将负责更新使用该状态的任何内容。
MobX 生命周期的工作原理如下:
- 事件(如
onClick
)调用 action(动作),这是唯一可以修改状态的内容:
@action onClick = () => {
this.props.todo.completed = true
}
- State(状态)是可观察的,不应包含冗余或可派生的数据。状态非常灵活——它可以包含类、数组、引用,甚至可以是图形:
@observable todos = [
{ title: 'Learn MobX', completed: false }
]
- Computed value 通过纯函数从状态派生。这些将由 MobX 自动更新:
@computed get activeTodos () {
return this.todos.filter(todo => !todo.completed)
}
- Reaction 类似于 Computed value ,但它们会产生副作用而不是创建值,例如在 React 中更新用户界面:
const TodoList = observer(({ todos }) => (
<div>
{todos.map(todo => <TodoItem {...todo} />)}
</div>
)
我们可以在下图中看到 MobX 生命周期的可视化:
MobX 生命周期的可视化
MobX 和 React 配合得很好。每当 MobX 检测到状态已更改时,它都会导致重新渲染相应的组件。
与 Redux 不同,使用 MobX 没有太多需要了解的限制。我们只需要了解一些核心概念,例如 observable、computed value 和 reaction。
现在我们已经了解了 MobX 生命周期,接下来让我们继续在实践中使用 MobX 处理状态。
使用 MobX 处理状态
了解 MobX 的最好方法是在实践中使用它并了解它是如何工作的。因此,让我们首先将我们的 ToDo 应用程序从 第 11 章从 React 类组件迁移到 MobX。我们首先从 Chapter11/chapter11_2/
复制代码示例。
安装 MobX
第一步是通过 npm
安装 MobX 和 MobX React。执行以下命令:
npm install --save mobx mobx-react
现在 MobX 和 MobX React 已经安装完毕,我们可以开始设置 store 了。
设置 MobX store
安装 MobX 后,是时候设置我们的 MobX store 了。store 将存储所有状态、相关的 computed value 和 action。它通常由类定义。
现在让我们定义 MobX store:
- 创建一个新的
src/store.js
文件。 - 从 MobX 导入
observable
、action
和computed
装饰器,以及decorate
函数。这些将用于标记我们 store 中的各种函数和值:
import { observable, action, computed, decorate } from 'mobx'
- 同时从我们的 API 代码导入
fetchAPITodos
和generateID
函数:
import { fetchAPITodos, generateID } from './api'
- 现在,我们使用类来定义 store:
export default class TodoStore {
- 在此 store 中,我们存储
todos
数组和filter
字符串值。这两个值是 observable。我们稍后将这样标记它们:
todos = []
filter = 'all'
通过特殊的项目设置,我们可以使用一个实验性的 JavaScript 功能,称为装饰器,通过编写 @observable todos = []
。但是,create-react-app 不支持此语法,因为它还不是 JavaScript 标准的一部分。
- 接下来,我们定义一个 computed value,以便从我们的 store 获取所有过滤的
todos
。该函数类似于我们在src/App.js
中的函数,但现在我们将使用this.filter
和this.todos
。同样,我们稍后必须将该函数标记为computed
。MobX 将在需要时自动触发此函数,并存储结果,直到它所依赖的状态发生变化:
get filteredTodos() {
switch (this.filter) {
case 'active':
return this.todos.filter((t) => t.completed === false)
case 'completed':
return this.todos.filter((t) => t.completed === true)
default:
case 'all':
return this.todos
}
}
- 现在,我们定义 action。我们从
fetch
action 开始。和以前一样,我们必须在以后使用action
装饰器标记我们的 action 函数。在 MobX 中,我们可以通过设置this.todos
直接修改我们的状态。由于todos
值是可观察的,MobX 将自动跟踪对它的任何更改:
fetch() {
fetchAPITodos().then((fetchedTodos) => {
this.todos = fetchedTodos
})
}
- 然后,我们定义
addTodo
action。在 MobX 中,我们不使用不可变的值,因此我们不应该创建一个新数组。相反,我们总是修改现有的this.todos
值:
addTodo(title) {
this.todos.push({
id: generateID(),
title,
completed: false,
})
}
如您所见,MobX 采用了一种更命令式的方法,其中值被直接修改,MobX 会自动跟踪更改。我们不需要使用 rest 或者扩展运算符语法来创建新数组;相反,我们直接修改现有的状态数组。
- 接下来定义
toggleTodo
action。在这里,我们遍历所有todos
并使用匹配的id
修改待办事项。请注意我们如何修改数组中的元素,MobX 仍将跟踪更改。事实上,MobX 甚至会注意到数组只有一个值发生了变化。结合 React,这意味着列表组件不会重新渲染,仅重新渲染发生变化的 item 组件。请注意,为了实现这一点,我们必须适当地拆分我们的组件,例如制作单独的列表和项目组件:
toggleTodo(id) {
for (let todo of this.todos) {
if (todo.id === id) {
todo.completed = !todo.completed break
}
}
}
构造将遍历数组的所有项或任何其他可迭代值。 for (let .. of ..) {
- 现在,我们定义
removeTodo
action。首先,我们找到要删除的todo
项的index
:
removeTodo(id) {
let index = 0
for (let todo of this.todos) {
if (todo.id === id) {
break
} else {
index++
}
}
}
- 然后,我们使用
splice
删除一个元素——从找到的元素的index
开始。这意味着我们从数组中删除具有给定id
的项:
this.todos.splice(index, 1)
}
- 我们最后定义的是
filterTodos
action。在这里,我们只需将this.filter
值设置为新过滤器:
filterTodos (filterName) {
this.filter = filterName
}
}
- 最后,我们需要用我们之前提到的各种装饰器来装饰我们的 store。为此,我们在 store 类上调用
decorate
函数并将对象映射值和方法传递给装饰器:
decorate(TodoStore, {
- 我们从
todos
和filter
值开始,它们是 observable:
todos: observable,
filter: observable,
- 然后,我们装饰
computed
值——filteredTodos
:
filteredTodos: computed,
- 最后但并非最不重要的一点是,装饰我们的 action:
fetch: action,
addTodo: action,
toggleTodo: action,
removeTodo: action,
filterTodos: action
})
现在,我们的 MobX store 已正确装饰并准备使用!
定义 Provider 组件
现在,我们可以在 App
组件中初始化存储,并将其传递给所有其他组件。但是,使用 React Context 是一个更好的主意。这样,我们可以从应用程序中的任何位置访问 store。
MobX React 提供了一个 Provider
的组件,它在上下文中提供存储。
现在让我们开始使用 Provider
组件:
- 编辑
src/index.js
,并从mobx-react
导入Provider
组件:
import { Provider } from 'mobx-react'
- 然后,从我们的
store.js
文件中导入TodoStore
:
import TodoStore from './store'
- 现在,我们创建
TodoStore
类的新实例:
const store = new TodoStore()
- 最后,我们必须将调整
ReactDOM.render()
的第一个参数,以便将App
组件与Provider
组件包装在一起:
ReactDOM.render(
<Provider todoStore={store}>
<App />
</Provider>,
document.getElementByid('root')
)
与 Redux 不同,使用 MobX 可以在我们的应用程序中提供多个 store。但是,在这里,我们只提供一个 store,我们称之为 todoStore
。
现在,我们的 store 已初始化并准备好在所有其他组件中使用。
连接组件
现在,我们的 MobX store 已作为 context 提供,我们可以开始给它连接组件。为此,MobX React 提供了 inject
的高阶组件,使用它可以将 store 注入到我们的组件中。
在本节中,我们将以下组件连接到我们的 MobX store:
App
TodoList
TodoItem
AddTodo
TodoFilter
连接 App 组件
我们将从连接我们的 App
组件开始,当应用程序初始化时,我们将使用 fetch
action 从我们的 API 获取所有 todos
。
现在让我们连接 App
组件:
- 编辑
src/App.js
,并从mobx-react
导入inject
函数:
import { inject } from 'mobx-react'
- 然后,用
inject
包装App
组件。inject
函数用于将 store(或多个 store )作为 props 注入到组件:
export default inject('todoStore')(function App ({ todoStore })
可以在inject
函数中指定多个 store,比如inject('todoStore', 'otherStore')
。然后,将注入两个 props:todoStore
和otherStore
。
- 现在我们有
todoStore
可用,我们可以使用它来调用 Effect Hook 中的fetch
action:
useEffect(() => {
todoStore.fetch()
}, [todoStore])
- 我们现在可以删除
filteredTodos
Memo Hook、处理函数、StateContext.Provider
组件以及我们传递给其他组件的所有 props:
return (
<div style={{ width: 400 }}>
<Header />
<AddTodo />
<hr />
<TodoList />
<hr />
<TodoFilter />
</div>
)
})
现在,我们的 App
组件将从 API 获取 todos
,然后它们将存储在 TodoStore
中。
连接 TodoList 组件
将 todos
存储在我们的 store 中后,我们可以从 store 中获取它们,然后我们可以在 TodoList
组件中列出所有待办事项。
现在让我们连接 TodoList
组件:
- 编辑
src/TodoList.js
并导入inject
和observer
函数:
import { inject, observer } from 'mobx-react'
- 删除所有与上下文相关的导入和 Hook。
- 和以前一样,我们使用
inject
函数来包装组件。此外,我们现在使用observer
函数包装组件。observer
函数告诉 MobX 此组件应在 store 更新时重新渲染:
export default inject('todoStore')(
observer(function TodoList ({ todoStore
}){
- 现在,我们可以使用 store 中的
filteredTodos
computed value 来列出应用了筛选器的所有待办事项。为了确保 MobX 仍然可以跟踪item
对象何时发生更改,我们在这里不使用扩展运算符语法。如果我们使用扩展运算符语法,所有待办事项都会重新渲染,即使只有一个发生了变化:
return todoStore.filteredTodos.map(item => (
<TodoItem key={item.id} item={item} />
)
}))
现在,我们的应用程序已经列出了所有待办事项。但是,我们还无法切换或删除待办事项。
连接 TodoItem 组件
为了能够切换或删除待办事项,我们必须连接 TodoItem
组件。我们还将 TodoItem
组件定义为观察者,以便 MobX 知道当 item
对象更改时,将不得不重新渲染该组件。
现在让我们连接 TodoItem
组件:
- 编辑
src/TodoItem.js
,并从mobx-react
导入inject
和observer
函数:
import { inject, observer } from 'mobx-react'
- 然后,用
inject
和observer
包装TodoItem
组件:
export default inject('todoStore')(
observer(function TodoItem ({ item, todoStore }) {
- 现在,我们可以在组件中使用
item
对象的解构。由于它被定义为观察者,MobX 将能够跟踪对item
对象的更改,即使在解构之后也是如此:
const { title, completed, id } = item
- 现在我们有
todoStore
可用,我们可以使用它来调整我们的处理函数,并调用相应的 action:
function handleToggle() {
todoStore.toggleTodo(id)
}
function handleRemove() {
todoStore.removeTodo(id)
}
现在,我们的 TodoItem
组件将从我们的 todoStore
调用 toggleTodo
和 removeTodo
action,因此我们现在可以切换并删除待办事项!
连接 AddTodo 组件
为了能够添加新的待办事项,我们必须连接 AddTodo
组件。
现在让我们连接 AddTodo
组件:
- 编辑
src/AddTodo.js
并从mobx-react
导入inject
函数:
import { inject } from 'mobx-react'
- 然后,用
inject
包装AddTodo
组件:
export default inject('todoStore')(function AddTodo ({ todoStore }) {
- 现在我们有
todoStore
可用,我们可以使用它来调整我们的处理函数,并调用addTodo
action:
handleAdd() {
if (input) {
todoStore.addTodo(input)
setinput('')
}
}
现在,我们的 AddTodo
组件将从我们的 todoStore
调用 addTodo
action,因此我们现在可以添加新的待办事项!
连接 TodoFilter 组件
最后,我们必须连接 TodoFilter
组件才能选择不同的过滤器。我们还希望显示当前选定的筛选器,因此此组件需要为 observer
。
现在让我们连接 TodoFilter
组件:
- 编辑
src/TodoFilter.js
并导入inject
和observer
函数:
import { inject, observer } from 'mobx-react'
- 我们使用
inject
和observer
函数来包装组件:
const TodoFilteritem = inject('todoStore')(observer(function
TodoFilteritemWrapped ({ name, todoStore }) {
- 现在,我们调整处理函数以从 store 中调用
filterTodos
action:
function handleFilter() {
todoStore.filterTodos(name)
}
- 最后,我们将
style
对象调整为todoStore
中的filter
值,以检查当前是否选择了该过滤器:
const style = {
color: 'blue',
cursor: 'pointer',
fontWeight: todoStore.filter === name ? 'bold' : 'normal',
}
- 此外,我们现在可以不在
Filteritem
组件中传递 props。删除以下所有的props
:
export default function TodoFilter(props) {
return (
<div>
<TodoFilteritem {...props} name="all" />
{' / '}
<TodoFilteritem {...props} name="active" />
{' / '}
<TodoFilteritem {...props} name="completed" />
</div>
)
}
现在,我们可以选择新的过滤器,这些过滤器将以粗体标记为已选中。TodoList 也将自动过滤,因为 MobX 检测到 filter
值的更改,这会导致 filteredTodos
computed value 更新,并且TodoList
观察者组件重新渲染。
示例代码
可以在 Chapter13/chapter13_1
文件夹中找到示例代码。
只需运行 npm install
即可安装所有依赖项,运行 npm start
以启动应用程序,然后在您的浏览器中访问 http://localhost:3000
(如果它没有自动打开)。
将 MobX 与 Hook 一起使用
在上一节中,我们学习了如何将 MobX 与 React 一起使用。正如我们所看到的,为了能够将我们的组件连接到 MobX store,我们需要使用 inject
函数包装它们,在某些情况下,还需要使用 observer
函数包装它们。自从 mobx-react
的 v6 发布以来,我们还可以使用 Hook 将我们的组件连接到 MobX store,而不是使用这些高阶组件来包装我们的组件。我们现在将使用 MobX 和 Hook!
定义 store Hook
首先,我们必须定义一个 Hook 才能访问我们自己的 store。正如我们之前所学到的,MobX 使用 React Context 来提供状态并将其注入到各种组件中。我们可以从 mobx-react
获取 MobXProviderContext
,并创建我们自己的自定义 Context Hook 以访问所有 store。然后,我们可以创建另一个 Hook,专门访问我们的 TodoStore
。
那么,让我们开始定义一个 store Hook:
- 创建一个新的
src/hooks.js
文件。 - 从
react
导入useContext
Hook,从mobx-react
导入MobXProviderContext
:
import { useContext } from 'react'
import { MobXProviderContext } from 'mobx-react'
- 现在,我们定义并导出一个
useStores
Hook,它为MobXProviderContext
返回一个 Context Hook:
export function useStores() {
return useContext(MobXProviderContext)
}
- 最后,我们定义一个
useTodoStore
Hook,它从我们之前的 Hook 中获取todoStore
,然后返回它:
export function useTodoStore() {
const { todoStore } = useStores()
return todoStore
}
现在,我们有一个通用的 Hook,用于访问 MobX 的所有 store,以及一个特定的 Hook 来访问 TodoStore
。如果需要,我们也可以在以后为其他 stores 定义更多的 Hook。
将组件升级到 Hook
创建 Hook 以访问我们的 store 后,我们可以使用它,而不是使用 inject
高阶组件函数包装我们的组件。在接下来的部分中,我们将看到如何使用 Hook 来升级我们的各种组件。
将 Hook 用于 App 组件
我们将从升级 App
组件开始。可以逐渐重构组件,以便它们改用 Hook。我们不需要一次重构每个组件。
现在让我们为 App
组件使用 Hook:
- 编辑
src/App.js
并删除以下import
语句:
import { inject } from 'mobx-react'
- 然后,从我们的
hooks.js
文件中导入useTodoStore
Hook:
import { useTodoStore } from './hooks'
- 现在,删除包装
App
组件的inject
函数,并删除所有 props。App
函数定义现在应如下所示:
export default function App () {
- 最后,使用我们的 Todo Store Hook 获取
todoStore
对象:
const todoStore = useTodoStore()
如您所见,我们的应用程序仍然以相同的方式工作!但是,我们现在在 App
组件中使用 Hook,这使得代码更加干净简洁。
对 TodoList 组件使用 Hook
接下来,我们将升级 TodoList
组件。此外,我们还将使用 useObserver
Hook,它取代了 observer
的高阶组件。
现在让我们为 TodoList
组件使用 Hook:
- 编辑
src/TodoList.js
,并删除以下导入语句:
import { inject, observer } from 'mobx-react'
- 然后,从
mobx-react
导入useObserver
Hook,从我们的hooks.js
文件中导入useTodoStore
Hook:
import { useObserver } from 'mobx-react'
import { useTodoStore } from './hooks'
- 现在,删除包装
TodoList
组件的inject
和observer
函数,并删除所有 props。TodoList
函数定义现在应如下所示:
export default function TodoList () {
- 同样,我们使用 Todo Store Hook 来获取
todoStore
对象:
const todoStore = useTodoStore()
- 最后,我们使用
useObserver
Hook 包装返回的元素。当 Observer Hook 中使用的状态更改时,将重新计算 Hook 中的所有内容:
return useObserver(() =>
todoStore.filteredTodos.map((item) => <TodoItem key={item.id} item={item} />)
)
}
在我们的例子中,MobX 将检测到通过 useObserver
Hook 定义的观察者依赖于 todoStore.filteredTodos
,而 filteredTodos
取决于 filter
和 todos
值。因此,每当 filter
值或 todos
数组发生更改时,将重新渲染列表。
将 Hook 用于 TodoItem 组件
接下来,我们将升级 TodoItem
组件,这将与我们对 TodoList
组件执行的过程类似。
现在让我们为 TodoItem
组件使用 Hook:
- 编辑
src/TodoItem.js
并删除以下import
语句:
import { inject, observer } from 'mobx-react'
- 然后,从
mobx-react
导入useObserver
Hook ,从我们的hooks.js
文件中导入useTodoStore
Hook :
import { useObserver } from 'mobx-react' import { useTodoStore } from './hooks'
- 现在,删除包装
TodoItem
组件的inject
和observer
函数,并删除todoStore
prop。TodoItem
函数定义现在应如下所示:
export default function TodoItem ({ item }) {
- 接下来,我们必须删除以下解构代码,因为我们的整个组件不再定义为可观察的,因此 MobX 将无法跟踪对
item
对象的更改:
const { title, completed, id } = item
- 然后,使用 Todo Store Hook 获取
todoStore
对象:
const todoStore = useTodoStore()
- 现在,我们必须调整处理函数,以便它们直接使用
item.id
而不是id
。请注意,我们假设id
不会改变,因此,它没有包装在 Observer Hook 中:
function handleToggle() {
todoStore.toggleTodo(item.id)
}
function handleRemove() {
todoStore.removeTodo(item.id)
}
- 最后,我们用 Observer Hook 包装
return
语句并在那里进行解构。这可确保 MobX 跟踪item
对象的更改,并且当对象的属性更改时,组件将相应地重新渲染:
return useObserver(() => {
const { title, completed } = item
return (
<div style={{ width: 400, height: 25 }}>
<input type="checkbox" checked={completed} onChange-
{handleToggle} />
{title}
<button style={{ float: 'right' }} onClick-
{handleRemove}>x</button>
</div>
)
})
}
现在,我们的 TodoItem
组件已正确连接到 MobX store。
如果 item.id
属性发生更改,我们必须将处理函数和 return
函数包装在一个 useObserver
Hook 中,如下所示:
return useObserver(() => {
const { title, completed, id } = item
function handleToggle() {
todoStore.toggleTodo(id)
}
function handleRemove() {
todoStore.removeTodo(id)
}
retun(
<div style={{ width: 400, height: 25 }}>
<input type="checkbox" checked={completed} onChange={handleToggle} />
{title}
<button style={{ float: 'right' }} onClick={handleRemove}>
x
</button>
</div>
)
})
请注意,我们不能将处理函数和 return
语句包装在单独的 Observer Hook 中,因为这样处理函数只会在第一个 Observer Hook 的闭包中定义。这意味着我们将无法从第二个 Observer Hook 中访问处理函数。
接下来,我们将继续升级组件,对 AddTodo
组件使用 Hook。
将 Hook 用于 AddTodo 组件
我们对 AddTodo
组件重复与 App
组件中相同的升级过程,如下所示:
- 编辑
src/AddTodo.js
并删除以下import
语句:
import { inject } from 'mobx-react'
- 然后,从我们的
hooks.js
文件中导入useTodoStore
Hook :
import { useTodoStore } from './hooks'
- 现在,删除包装
AddTodo
组件的inject
函数,并删除所有 props。AddTodo
函数定义现在应如下所示:
export default function AddTodo () {
- 最后,使用 Todo Store Hook 获取
todoStore
对象:
const todoStore = useTodoStore()
现在,我们的 AddTodo
组件已连接到 MobX store,我们可以继续升级 TodoFilter
组件。
将 Hook 用于 TodoFilter 组件
对于 TodoFilter
组件,我们将使用与用于 TodoList
组件的过程类似的过程。我们将使用 useTodoStore
Hook 和 useObserver
Hook。
现在,让我们为 TodoFilter
组件使用 Hook:
- 编辑
src/TodoFilter.js
并删除以下import
语句:
import { inject, observer } from 'mobx-react'
- 然后,从
mobx-react
导入useObserver
Hook ,从我们的hooks.js
文件中导入useTodoStore
Hook :
import { useObserver } from 'mobx-react'
import { useTodoStore } from './hooks'
- 现在,删除包装
TodoFilteritem
组件的inject
和observer
函数,并删除todoStore
prop。TodoFilteritem
函数定义现在应如下所示:
function TodoFilteritem ({ name }) {
- 同样,我们使用 Todo Store Hook 来获取
todoStore
对象:
const todoStore = useTodoStore()
- 最后,我们用
useObserver
Hook 包装style
对象。请记住,当 Observer Hook 中使用的状态发生变化时,将重新计算 Observer Hook 中的所有内容:
const style = useObserver(() => ({
color: 'blue',
cursor: 'pointer',
fontWeight: todoStore.filter === name ? 'bold' : 'normal',
}))
在本例中,每当 todoStore.filter
值更改时,都会重新计算 style
对象,这将导致元素重新渲染,并在选择其他筛选器时更改字体粗细。
示例代码
可以在 Chapter13/chapter13_2
文件夹中找到示例代码。
只需运行 npm install
即可安装所有依赖项,运行 npm start
以启动应用程序,然后在您的浏览器中访问 http://localhost:3000
(如果它没有自动打开)。
使用本地存储 Hook
除了提供全局存储来存储应用程序范围的状态外,MobX 还提供本地存储保存本地状态。要创建本地存储,我们可以使用 useLocalStore
Hook。
我们现在将在 AddTodo
中实现 Local Store Hook:
- 编辑
src/AddTodo.js
并导入useLocalStore
Hook,以及mobx-react
中的useObserver
Hook:
import { useLocalStore, useObserver } from 'mobx-react'
- 然后,删除以下 State Hook:
const [input, setinput] = useState('')
将其替换为 Local Store Hook:
const inputStore = useLocalStore(() => ({
在此本地 store 中,我们可以定义 state value、computed value 和 action。useLocalStore
Hook 会自动将值装饰为 observable,将 getter 函数(get
前缀)装饰为 computed value,将普通函数装饰为 action。
- 我们从
input
字段的value
状态开始:
value: '',
- 然后,我们定义一个 computed value ,它将告诉我们添加按钮是否应该是
disabled
的:
get disabled () {
return !this.value
},
- 接下来,我们定义 action。第一个 action 更新输入事件的
value
:
updateFrominput (e) {
this.value = e.target.value
},
- 然后,我们定义另一个 action 来更新简单字符串中的
value
:
update (val) {
this.value = val
}
}))
- 现在,我们可以调整输入处理函数,并调用
updateFrominput
action:
function handleinput(e) {
inputStore.updateFromInput(e)
}
- 我们还必须调整
handleAdd
函数:
function handleAdd() {
if (inputStore.value) {
todoStore.addTodo(inputStore.value)
inputStore.update('')
}
}
- 最后,我们用
useObserver
Hook 包装元素,以确保在更改时input
字段值得到更新,并调整disabled
和value
props:
return useObserver(() => (
<form onSubmit={ e => {
e.preventDefault();
handleAdd()
}}>
<input
type="text"
eholder="enter new task..."
style={{ width: 350, height: 15 }}
value={inputStore.value}
onChange={handleinput}
/>
<input
type="submit"
style={{ float: 'right', marginTop: 2 }}
disabled={inputStore.disabled}
value="add"
/>
</form>
))
}
现在,我们的 AddTodo
组件使用本地 MobX store 来处理其输入值,并禁用/启用按钮。如您所见,使用 MobX,可以使用多个 store,用于本地和全局状态。困难的部分在于决定如何以一种对给定应用程序有意义的方式划分和分组 store。
示例代码
可以在 Chapter13/chapter13_3
文件夹中找到示例代码。
只需运行 npm install
即可安装所有依赖项,运行 npm start
以启动应用程序,然后在您的浏览器中访问 http://localhost:3000
(如果它没有自动打开)。
迁移 MobX 应用程序
在上一节中,我们学习了如何将现有 MobX 应用程序中的 inject
和 observer
替换为 Hook。现在,我们将学习如何在现有 MobX 应用程序中将本地状态迁移到 Hook。
可以通过以下三个步骤将现有的 MobX 应用程序迁移到基于 Hook 的解决方案:
- 为简单的本地状态使用 State Hook
- 为复杂的本地状态使用
useLocalState
Hook - 将全局状态保存在单独的 MobX store 中
在本书的前几章中,我们已经学会了如何使用 State Hook。状态 Hook 对于简单状态(如复选框的当前状态)有意义。
在本章中,我们已经学会了如何使用 useLocalState
Hook。我们可以将 Local State Hook 用于复杂的局部状态,例如多个字段相互交互的复杂表单。然后,我们可以将多个状态和 Effect Hook 替换为单个 Local State Hook、computed value 和 action。
最后,全局状态应存储在单独的 MobX store 中,例如我们在本章中定义的 TodoStore
。在 MobX 中,可以使用 Provider
组件创建多个 store 并传递给组件。然后,我们可以为每个 store 创建一个单独的自定义 Hook。
MobX 的权衡取舍
最后,让我们总结一下在 Web 应用程序中使用 MobX 的优缺点。首先,让我们从积极的方面开始:
- 它提供了一种处理状态更改的简单方法
- 需要更少的样板代码
- 它为我们的应用程序代码的结构提供了灵活性
- 可以使用多个全局和本地 store
- 它使
App
组件变得更加简单(它将状态管理和 action 转移到 MobX)
MobX 非常适合小型和大型项目——这些项目需要处理复杂的状态变化,以及跨多个组件使用的状态。
但是,使用 MobX 也有缺点:
- 状态更改可能发生在任何地方,而不仅仅是在单个 store 中
- 它的灵活性意味着有可能以糟糕的方式构建项目,这可能会导致错误或 bug
- 如果我们想获取所有功能,MobX 需要一个包装器组件 (
Provider
) 才能将应用程序连接到应用程序 store(我们可以直接导入和使用 MobX 应用程序 store,但它会破坏服务器端渲染等功能)
如果状态更改很简单,并且只需要组件中的本地状态,则不应使用 MobX。在这种情况下,State 或 Reducer Hook 可能就足够了。使用 Reducer 和 State Hook,无需包装器组件即可将我们的应用程序连接到 store。
灵活性是一件好事,但它也可能导致我们糟糕地构建项目。但是,MobX 提供了一个名为 mobx-state-tree
的项目,它允许我们使我们的 MobX 应用程序更加结构化并强制执行某种架构。有关详细信息,请参阅以下 GitHub 存储库中的项目页面:https://github.com/mobxjs/mobx-state-tree。
总结
在本章中,我们首先了解了 MobX 是什么,它由哪些元素组成,以及它们如何协同工作。然后,我们学习了如何在实践中使用 MobX 进行状态管理。我们还学习了如何使用 inject
和 observer
的高阶组件将 MobX store 连接到 React 组件。接下来,我们将高阶组件替换为 Hook,这使我们的代码更加简洁。我们还学习了如何使用 Local Store Hook 来处理 MobX 中的复杂本地状态。最后,我们学习了如何将现有的 MobX 应用程序迁移到 Hook,并回顾了使用 MobX 的权衡。
这一章标志着本书的结束。在本书中,我们一开始就想使用 Hook。我们了解到,React 应用程序中存在一些常见问题,如果没有 Hook,这些问题就无法轻松解决。然后,我们使用 Hook 创建了第一个组件,并将其与基于类组件的解决方案进行了比较。接下来,我们深入了解了各种 Hook,从 State Hook 开始,它是所有当中最普遍的。我们还学习了如何解决 Hook 的常见问题,例如条件 Hook 和循环 Hook。
在深入了解了 State Hook 之后,我们使用 Hook 开发了一个小型博客应用程序。然后,我们了解了 Reducer Hook,Effect Hook 和 Context Hook,以便能够在我们的应用程序中实现更多功能。接下来,我们学习了如何使用 Hook 有效地请求资源。此外,我们还学习了如何使用 React.memo
防止不必要的重新渲染,以及如何使用 React Suspense 实现延迟加载。然后,我们在博客应用程序中实现了路由,并了解了 Hook 如何使动态路由变得更加容易。
我们还了解了社区提供的各种 Hook,这使得处理输入字段、各种数据结构、响应式设计和撤消/重做功能变得更加容易。此外,我们还了解了 Hook 的规则,如何创建我们自己的自定义 Hook,以及 Hook 之间的交互如何工作。最后,我们学习了如何有效地从现有的基于类组件的应用程序迁移到基于 Hook 的解决方案。最后,我们还学习了如何在 Redux 和 MobX 中使用 Hook,以及如何将现有的 Redux 和 MobX 应用程序迁移到 Hook。
现在我们已经深入了解了 Hook,我们准备在应用程序中使用它们!我们还学习了如何将现有项目迁移到 Hook,所以我们现在就可以开始做了。我希望您喜欢学习 React Hook,并且您期待在您的应用程序中实现 Hook!我相信使用 Hook 会让你的编码更愉快,就像他们对我所做的那样。
问题
为了回顾我们在本章中学到的内容,请尝试回答以下问题:
- 哪些元素构成了 MobX 生命周期?
- MobX 提供哪些装饰器?
- 如何将组件连接到 MobX?
- MobX 提供哪 Hook?
- 如何使用 Hook 访问 MobX store?
- 我们可以使用 MobX store 本地状态吗?
- 我们应该如何将现有的 MobX 应用程序迁移到 Hook?
- 使用 MobX 有什么优势?
- 使用 MobX 有哪些缺点?
- 什么时候不应该使用 MobX?
延伸阅读
如果您对我们在本章中学到的概念的更多信息感兴趣,请查看以下阅读材料:
- 官方 MobX 文档的 MobX 简介:https://mobx.js.org/getting-started.html
- MobX 官方文档:https://mobx.js.org
- 关于 MobX 基础知识的视频课程:https://egghead.io/lessons/react-sync-the-ui-with-the-app-state-using-mobx-observable-and-observer-in-react
- MobX React 官方文档:https://mobx-react.js.org/
- GitHub 上的
mobx
项目:https://github.com/mobxjs/mobx - GitHub 上的
mobx-react
项目:https://github.com/mobxjs/mobx-react - GitHub 上的
mobx-state-tree
项目:https://github.com/mobxjs/mobx-state-tree
评论 (0)