深入学习MobX6,使用React平台typeScript语言写MobX6的编码规范
开篇
MobX是一款身经百战的状态管理库,它比Redux性能更优秀、功能更强大、使用更灵活、代码量更少!但使用率却不如Redux,我觉得有很大一部分原因是过于灵活,即完成一件事可以有多种方式。这使得不同人编写的MobX风格差异极大!也使得版本升级历史包袱重!MobX6与之前版本有不少差异!
最近我把官方文档从头到尾过了一遍,感觉MobX官方文档真的非常不错,里面有很多状态管理的知识值得细细咀嚼,强烈建议有时间的话还是要亲自啃一遍官方文档。
以下我将以todo案例模拟实战项目,写一篇关于使用typeScript 编写Mobx6的编码规范。在遇到一件事有多种实现方式时我只重点讲一种以及选择它的原因,其他的一笔带过。如有不妥之处,欢迎留言讨论!
引入MobX,如何选择mobx-react与mobx-react-lite?
mobx-react与mobx-react-lite优劣比较
- mobx-react支持类组件,mobx-react-lite不支持类组件。
- mobx-react支持Provider 和inject,mobx-react-lite可以用React.createContext替代。
- mobx-react支持特殊的观察对象 propTypes,mobx-react-lite可以用typeScript代替。
- mobx-react(7.3.0)的大小:压缩前16kb,压缩后5.5kb;mobx-react-lite(3.3.0)的大小:压缩前6.2kb,压缩后2.2kb。
结论
如果只写函数组件,建议用mobx-react-lite。如果需要写类组件,建议用mobx-react。
目前我写项目全部用函数组件,拥抱hooks,所以我选择mobx-react-lite。
只用函数组件
- 安装:
yarn add mobx mobx-react-lite
- 引入:
import { observer } from "mobx-react-lite"
需用类组件
- 安装:
yarn add mobx mobx-react
- 引入:
import { observer } from "mobx-react"
按功能划分状态
UI状态
常见内容有:
- Session 信息
- 应用加载阶段的信息
- 影响全局 UI 的信息
- Window 尺寸
- 当前语言
- 当前主题
- 会影响多个无关组件的界面状态
- 工具栏可见性等等
- 向导的状态
- 全局遮罩层的状态
- 其他和业务、用户无关的且跟界面强相关的信息
用户状态
常见内容有:
- 用户id、姓名、部门、头像等
- 用户权限表
- 用户登陆时间戳
- 用户空闲自动登出时限
- 用户密码过期时限
- 用户上一次更新密码时间戳
- 其他用户信息
业务状态
这个要视应用程序的功能而定。
小结
根据用途划分store,建议将强相关的数据集中在一个store中,方便处理。
例如:创建一个todo应用,通常store中要有ui、user、todo这3个store。
不建议像Redux那样所有状态集中在一个store里面。太过深奥的理由不谈,最直接的原因就是开发体验不好。在写redux时会遇到大量类似store.todo.todoitem.name这样的代码。
定义数据存储
个人建议
- 不建议使用useLocalStore和useObserver,因为typescript对这种方式的类型提示支持不好。最推荐用class的形式写。
- 不要使用继承模式,继承模式会带来很多不必要的麻烦。关于使用继承模式时会遇到的哪些问题及解决方法在官方文档里有,内容真不少!真心不建议使用继承模式!!!
- 强烈建议使用类来定义数据存储。
使用类定义数据存储的好处
以下内容来自官方文档:
- 更容易被索引以实现自动补全等功能,例如使用 TypeScript。
- instanceof 检查对于类型推断来说非常强大,并且类实例不会被包装在 Proxy 对象中,这一点给了它们更好的调试体验。
- 使用类会从引擎优化中受益良多,因为它们的形态是可预测的并且方法在原型上是共享的。
容器选择
- 强烈建议使用createContext。
- 不建议使用useLocalStore、useObserver。
- 也不建议使用Provider 、inject以及装饰器。
代码示例及讲解
src/store/todos/todo.ts
import { makeAutoObservable } from "mobx"export class Todo {title = ""finished = falseconstructor(title: string) {makeAutoObservable(this,{ // 自定义各个类属性的mobx注解,如false(不注解)等},{ // options参数,autoBind是指自动绑定thisautoBind: true,})this.title = title}toggle() {this.finished = !this.finished}
}
讲解
- 以上是单条todo的state。建议定义数据存储的类时使用makeAutoObservable。关于注解类型及选项,请参考官方文档。
- 不建议用装饰器(旧版本的方式,兼容性差);也不建议用makeObservable(需要手动逐条指定注解,繁琐)。
- 这个Todo类不需要实例化并导出,因为它是用来作数据的!不是用来作store的!
src/store/todos/index.ts
import { makeAutoObservable } from "mobx"
import { Todo } from "./todo"class TodoList {todos: Todo[] = []get unfinishedTodoCount() {return this.todos.filter((todo) => todo.finished).length}constructor(todos: Todo[]) {makeAutoObservable(this,{ // 自定义各个类属性的mobx注解,如false(不注解)等},{ // options参数,autoBind是指自动绑定thisautoBind: true,})this.todos = todos}add(todo: Todo) {this.todos.push(todo)}
}const todoStore = new TodoList([])
export default todoStore
讲解
- 这里的重点是类的组合!todoStore中主要的数据是todos,它是Todo[]类型,这个Todo即是上面的那个Todo类。
- 这个TodoList类定义完以后一定要创建一个实例并导出该实例,因为TodoList类是用来作store的!
- 在MobX中存储的是数据及数据相关动作。请牢记:MobX负责数据的状态管理!React负责数据的渲染展示!
- 数据的状态变更必须通过store里的action来实施。极其不建议在React组件中使用runInAction去直接更新state数据!
src/store/user/index.ts
import { makeAutoObservable } from "mobx"class User {name: stringget isLogin() {return this.name.length>0}constructor(name:string) {makeAutoObservable(this,{ // 自定义各个类属性的mobx注解,如false(不注解)等},{ // options参数,autoBind是指自动绑定thisautoBind: true,})this.name = name}login(name: string) {this.name = name}logout() {this.name=""}}
const userStore = new User("")
export default userStore
讲解
- 这里做了一个非常简陋用户信息的store,只是演示如何在一个React.Context容器中存储多个store。
- 这个User类定义完以后一定也要创建一个实例并导出该实例,因为User类也是用来作store的!
src/store/index.tsx
import { createContext } from "react"
import todoStore from "./todos"
import userStore from "./user"// 创建context用来保存各项数据store
export const Store = createContext({ todoStore,userStore })// StoreProvide组件,用来给子组件传递store
const StoreProvide: React.FC<{children: React.ReactNode[];
}> = (props) => {return (<Store.Provider value={{ todoStore,userStore }} >{props.children}</Store.Provider>)
}export { Todo } from "./todos/todo";
export default StoreProvide;
讲解
- 重点一是创建context用来保存各项数据store。
- 重点二是创建StoreProvide组件,用来给子组件传递store。
- 重点三是文件名是index.tsx,注意文件名后缀是tsx,如果名字是index.ts一定会报错!
src/app.tsx
import React, { useContext, useRef } from "react"
// import { observer } from "mobx-react"
import { observer } from "mobx-react-lite"
import StoreProvide, { Store, Todo } from './store'const TodoListView: React.FC = observer(function TodoListView() {const { todoStore } = useContext(Store)console.log("渲染了TodoListView")return (<div><ul>{todoStore.todos.map((todo, index) => (<TodoView todo={todo} key={index} />))}</ul></div>)
})const TodoListLeft: React.FC = observer(function TodoListLeft() {const { todoStore } = useContext(Store)console.log("渲染了TodoListLeft")return (<>Tasks left: {todoStore.unfinishedTodoCount}</>)
})// const TodoListLeft = observer(
// class TodoListLeft extends React.Component {
// static contextType = Store;
// context!: React.ContextType;
// render() {
// return (
// <>
// Tasks left: {this.context!.todoStore.unfinishedTodoCount}
// >
// )
// }
// })const AddTodo: React.FC = observer(function AddTodo() {const { todoStore } = useContext(Store)console.log("渲染了AddTodo")const ref = useRef<HTMLInputElement>(null)return (<><input ref={ref} type="text" /><button onClick={() => {const item = new Todo(ref.current!.value)todoStore.add(item)ref.current!.value = ""}} > 新增 </button></>)
})const TodoView: React.FC<{ todo: Todo }> = observer(function TodoView({ todo }) {console.log("渲染了TodoView")return (<li><inputtype="checkbox"checked={todo.finished}onChange={() => todo.toggle()}/>{todo.title}</li>)
})const LoginUser: React.FC = observer(function LoginUser() {console.log("LoginUser")const { userStore } = useContext(Store)const ref = useRef<HTMLInputElement>(null)return (<div><p>{userStore.isLogin?`当前登录的用户是:${userStore.name}`:"当前没有登录用户!"}</p><p><input ref={ref} type="text" /><button onClick={() => userStore.login(ref.current!.value)} > 登录 </button><button onClick={() => userStore.logout()} > 退出 </button></p></div>)
})const App = () => {return (<StoreProvide><LoginUser /><hr/><AddTodo /><TodoListView /><TodoListLeft /></StoreProvide>)
}export default App
代码讲解
- App组件返回的内容最外层是StoreProvide组件。这是用来将context中的MobX store传递到所有子组件。
- 凡是使用MobX store数据的组件,外面都要用observer函数包围,这样MobX就可以根据被观察数据决定被包围的组件是否要做重新渲染。
- 请留意TodoListLeft组件,我写了2个版本,一个是函数组件的版本,被注释的部分是类组件的版本。请留意函数组件与类组件使用context中的MobX store用法区别。
- 请留意顶部还注释了一行代码,类组件必须使用
mobx-react库,函数组件mobx-react库和mobx-react-lite两种库都可以使用。有关这2个库的差异在文章第二大段讲过了。 - 请细看LoginUser组件开头部分
const LoginUser: React.FC = observer(function LoginUser() {,这里的`function LoginUser() {``是为了让组件在React Developer Tools中可以正常显示名字。以下是名字显示正常的截图:

- 如果将上面的代码替换成这样
const LoginUser: React.FC = observer(()=> {,组件虽然可以正常工作,但在React Developer Tools中不能正常显示名字。以下是名字显示异常的截图:

最后
MobX的内容真的很多,小小的一篇博客没法讲完。以上只是选最常用最基本的内容。后续我还会出一篇有关reactions的博客。
再次建议有时间的朋友一定要细读官方文档,这里不光有很多MobX的使用细则,还有很多怎么做好状态管理的建议。
另外附以上代码的CodeSandbox在线体验。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
