大前端 - react - mobx6更新
代码地址:
MobX6
1. MobX 概述
MobX 是一个简单的可扩展的状态管理库,无样板代码风格简约。
目前最新版本为 6,版本 4 和版本 5 已不再支持。
在 MobX 6 中不推荐使用装饰器语法,因为它不是 ES 标准,并且标准化过程要花费很长时间,但是通过配置仍然可以启用装饰器语法。
MobX 可以运行在任何支持 ES5 的环境中,包含浏览器和 Node。
MobX 通常和 React 配合使用,但是在 Angular 和 Vue 中也可以使用 MobX。
2. MobX 光速入门
2.1 下载
yarn add mobx@6.1.8 mobx-react-lite@3.2.0
2.2 核心概念
- observable state:被 MobX 跟踪的状态。
- action:允许修改状态的方法,在严格模式下只有 action 方法被允许修改状态。
- computed:根据应用程序状态派生的新值,计算值。
2.3 工作流程
2.4 入门案例
2.4.1 案例
计数器:在组件中显示数值状态,单击按钮使数值加一,单击按钮使数值重置。
// Counter Component -> Counter 组件
// observer: 监控当前组件使用到的由 MobX 跟踪的 observable state, 当状态发生变化时通知 React 更新视图
import { observer } from "mobx-react-lite"function Counter({ counterStore }) {return ({counterStore.count}
)
}export default observer(Counter)
// Counter Store -> 管理 Counter 组件的 Store
import { makeAutoObservable } from "mobx"class CounterStore {// 数值状态count = 10constructor() {// 将参数对象中的属性设置为 observable state// 将参数对象中的方法设置为 actionmakeAutoObservable(this)}// 使数值状态加一increment() {this.count += 1}// 重置数值状态reset() {this.count = 0}
}export default CounterStore
// App Component -> 根组件
// 导入 Counter 组件
import Counter from "./components/Counter/Counter"
// 导入管理 Counter 组件的 Store
import CounterStore from "./stores/Counter/CounterStore"
// 创建管理 Counter 组件的 Store 实例对象
const counterStore = new CounterStore()function App() {// 调用 Counter 组件并传入管理其状态的 Storereturn
}export default App
2.4.2 makeAutoObservable
// target: 将目标对象中的属性和方法设置为 observable state 和 action
// overrides: 覆盖默认设置, 将 target 对象中的某些属性或者方法设置为普通属性
// options: 配置对象, autoBind, 使 action 方法始终拥有正确的 this 指向
makeAutoObservable(target, overrides?, options?)
makeAutoObservable(this, {reset: false}, {autoBind: true})
2.4.3 总结
状态变化更新视图的必要条件
- 状态需要被标记为
observable state
- 更改状态的方法需要被标记为
action
方法 - 组件视图必须通过
observer
方法包裹
可以使用 makeAutoObservable
方法将对象属性设置为 observable state
,将对象方法设置为 action
方法
可以使用 observer
方法监控当前组件使用到的由 MobX 跟踪的 observable state
,当状态发生变化时通知 React 更新视图
3. TodoList
3.1 创建初始 TodoListStore
-
创建 TodoListStore,用于管理待办事项列表状态。
// TodoListStore.js class TodoListStore {todos = []constructor(todos) {if (todos) this.todos = todos} } export default TodoListStore
-
创建 TodoViewStore,用于管理待办事项状态。
// TodoViewStore.js class TodoViewStore {id = Math.random()title = ""completed = falseconstructor(title) {this.title = title} } export default TodoViewStore
-
创建 TodoListStore 实例对象并将其传入到 TodoListView 组件
// App.js import TodoListView from "./components/Todos/TodoListView" import TodoListStore from "./stores/Todos/TodoListStore" import TodoStore from "./stores/Todos/TodoStore"const counterStore = new CounterStore()const todoListStore = new TodoListStore([new TodoStore("Hello MobX"),new TodoStore("Hello React") ])function App() {return
} -
渲染初始待办事项列表
// TodoListView.js function TodoListView({ TodoListStore }) {return (
- {TodoListStore.todos.map(todo => (
))} // TodoView.js function TodoView({ todo }) {return }
3.2 创建待办事项
-
在 TodoListStore 类中创建 createTodo 方法,用于向 todos 数组中添加待办事项
// TodoListStore.js import TodoStore from "./TodoStore"class TodoListStore {createTodo(title) {this.todos.push(new TodoStore(title))} }
-
在 TodoHeader 组件中调用 createTodo 方法创建待办事项
// TodoListView.js function TodoListView({ TodoListStore }) {return
TodoListStore.createTodo(title)} /> } // TodoHeader.js import { useState } from "react"function TodoHeader({ createTodo }) {const [title, setTitle] = useState("")return (
) }setTitle(event.target.value)}onKeyUp={event => {if (event.key === "Enter") {createTodo(title)setTitle("")}}}/> -
设置状态变化更新视图的必要条件
// TodoListStore.js import { action, makeObservable, observable } from "mobx"class TodoListStore {constructor() {makeObservable(this, {todos: observable,createTodo: action})} }
// TodoListView.js import { observer } from "mobx-react-lite"function TodoListView (){}export default observer(TodoListView)
3.3 创建 Store 上下文
我们希望在每个组件中都能够直接获取到 TodoListStore,而不是通过 props 属性传递的方式。
// TodoListStore.js
import { createContext, useContext } from "react"const TodoListStoreContext = createContext()const TodoListStoreProvider = ({ store, children }) => {return ({children} )
}const useTodoStore = () => {return useContext(TodoListStoreContext)
}export { TodoListStore, TodoListStoreProvider, useTodoStore }
// App.js
import { TodoListStore, TodoListStoreProvider } from "./stores/Todos/TodoListStore"function App() {return ( )
}
// TodoListView.js
import { useTodoListStore } from "../../stores/Todos/TodoListStore"function TodoListView() {const todoListStore = useTodoListStore()
}
import { useTodoListStore } from "../../stores/Todos/TodoListStore"function TodoHeader() {const todoListStore = useTodoListStore()todoListStore.createTodo(title)
}
3.4 删除待办事项
// TodoListStore.js
class TodoListStore {constructor() {makeObservable(this, {removeTodo: action})}removeTodo(id) {const index = this.todos.findIndex(todo => todo.id === id)this.todos.splice(index, 1)}
}
// TodoView.js
import { useTodoListStore } from "../../stores/Todos/TodoListStore"function TodoView({ todo }) {const todoListStore = useTodoListStore()return
3.5 更改任务状态
// TodoStore.js
import { makeObservable, observable, action } from "mobx"class TodoStore {completed = falseconstructor() {makeObservable(this, {completed: observable,toggle: action})}toggle() {this.completed = !this.completed}
}
// TodoView.js
import { observer } from "mobx-react-lite"function TodoView({ todo }) {return ( todo.toggle()} className="toggle" type="checkbox" /> )
}export default observer(TodoView)
3.6 更正 this 指向
todo.toggle()} />
// this 指向会发生错误
makeObservable(this, {toggle: action.bound
})
3.7 计算待办事项数量
待办事项数量属于派生状态,即该状态依赖现有状态 (todos) 生成。
派生状态可以使用计算值实现,当依赖状态发生变化后,计算值自动更新。
// TodoListStore.js
import { computed } from "mobx"class TodoListStore {constructor() {makeObservable(this, {unCompletedTodoCount: computed})}get unCompletedTodoCount() {return this.todos.filter(todo => !todo.completed).length}
}
// TodoFooter.js
import { useTodoListStore } from "../../stores/Todos/TodoListStore"
import { observer } from "mobx-react-lite"function TodoFooter() {const todoListStore = useTodoListStore()return {todoListStore.unCompletedTodoCount} item left
}export default observer(TodoFooter)
注意:计算值是被缓存的。
get unCompletedTodoCount() {console.log("unCompletedTodoCount")return this.todos.filter(todo => !todo.completed).length
}
{todoListStore.unCompletedTodoCount}
{todoListStore.unCompletedTodoCount}
{todoListStore.unCompletedTodoCount}
// 计算属性被调用多次, 但是方法内部的console.log 只会输出一次, 说明计算属性是被缓存的.
3.8 待办事项过滤
待办事项过滤为计算值的练习任务。
// TodoListStore.js
class TodoListStore {todos = []filter = "all"constructor() {makeObservable(this, {filter: observable,changeFilter: action,filterTodos: computed})}get filterTodos() {switch (this.filter) {case "all":return this.todoscase "active":return this.todos.filter(todo => !todo.completed)case "completed":return this.todos.filter(todo => todo.completed)default:return this.todos}}changeFilter(filter) {this.filter = filter}
}
// TodoFooter.js
function TodoFooter() {const todoListStore = useTodoListStore()return ()
}
// TodoListView.js
function TodoListView() {const todoListStore = useTodoListStore()return ({todoListStore.filterTodos.map(todo => )}
)
}
3.9 加载远端任务
-
下载并启动 json-server
npm install -g json-server
json-server ./src/todo.json --port 3005
yarn add axios
-
创建 loadTodos 方法用于加载初始任务
// TodoListStore.js import axios from "axios" import { runInAction } from "mobx"class TodoListStore {constructor(todos) {this.loadTodos()}async loadTodos() {let todos = await axios.get("http://localhost:3005/todos").then(response => response.data)runInAction(() => todos.forEach(todo => this.todos.push(todo)))} }
-
解决远端加载待办事项无法切换状态的问题
原因是远端加载待办事项的任务对象的原型对象上没有 toggle 方法。
import TodoStore from "./TodoStore"class TodoListStore {async loadTodos() {runInAction(() => todos.forEach(todo => this.todos.push(new TodoStore(todo.title))))} }
3.9 创建 RootStore
通过创建 RootStore 可以将 CounterStore 和 TodoListStore 进行合并,实现在任何组件中都可以访问任何状态,方便全局状态共享。
// RootStore.js
import CounterStore from "./Counter/CounterStore"
import TodoListStore from "./Todos/TodoListStore"
import { createContext, useContext } from "react"class RootStore {constructor() {this.counterStore = new CounterStore()this.todoListStore = new TodoListStore()}
}const RootStoreContext = createContext()const RootStoreProvider = ({ store, children }) => {return ({children} )
}const useRootStore = () => {return useContext(RootStoreContext)
}export { RootStore, RootStoreProvider, useRootStore }
// App.js
import Counter from "./components/Counter/Counter"
import TodoListView from "./components/Todos/TodoListView"
import { RootStore, RootStoreProvider } from "./stores/RootStore"const rootStore = new RootStore()function App() {return ( )
}export default App
// 消费 RootStore 的其他组件
import { useRootStore } from "../../stores/RootStore"function TodoHeader() {const { todoListStore } = useRootStore()
}function Counter() {const { counterStore } = useRootStore()
}
4. 数据监测
4.1 autorun 方法
-
监控数据变化执行副作用,接收一个函数作为参数,参数函数用来执行副作用,当参数函数内部使用的 observable state, computed 发生变化时函数会运行,初始运行 autorun 方法时参数函数也会运行一次。
import { autorun } from "mobx" import { useEffect } from "react"function Counter() {const { counterStore } = useRootStore()useEffect(() => {// 确保 autorun 方法只被初始化一次autorun(() => {console.log(counterStore.count)})}, []) }
-
对于基本数据类型,属于值传递,mobx 只能跟踪到原始属性,跟踪不到复制后的值。
useEffect(() => {let count = counterStore.countautorun(() => {// 错误写法, mobx 跟踪不到变量 countconsole.log(count)})}, [])
-
对于引用数据类型,只要引用地址不发生变化,mobx 就可以进行跟踪
// CounterStore.js class CounterStore {person = { name: "张三" } }
// Counter 组件 function Counter() {const { counterStore } = useRootStore()useEffect(() => {const person = counterStore.personautorun(() => {console.log(person.name)})}, [])return (
) }{counterStore.person.name}
-
去除控制台中和 useEffect 相关的 ⚠️
在 react 17 版本中,官方团队修改了脚手架工具,允许直接在外部声明 .eslintrc 文件覆盖 eslint 配置,不需要使用 package.json, react-app-rewired 和 customize-cra 就可用实现 eslint 配置
在项目的根目录下新建
.eslintrc.js
文件并加入如下内容module.exports = {plugins: ["react-hooks"],rules: {"react-hooks/exhaustive-deps": 0} }
注意此处不需要下载 eslint-plugin-react-hooks 插件,
create-react-app
脚手架工具内部已经内置。配置完成后重新启动应用。
4.2 reaction 方法
监控状态变化执行副作用,接收两个函数作为参数,第一个函数返回要监控的状态,第二个函数用来执行副作用,只有当第一个函数返回的状态发生变化时,第二个函数才会执行。reaction 方法提供了更加细颗粒度的状态控制。
和 autorun 不同,reaction 初始时不会执行副作用。
import { reaction } from "mobx"function Counter() {useEffect(() => {reaction(() => counterStore.count,(current, previous) => {console.log(current)console.log(previous)})}, [])
}
标签:
相关文章
-
无相关信息