深入学习MobX6,使用React平台typeScript语言写MobX6的编码规范

开篇

MobX是一款身经百战的状态管理库,它比Redux性能更优秀、功能更强大、使用更灵活、代码量更少!但使用率却不如Redux,我觉得有很大一部分原因是过于灵活,即完成一件事可以有多种方式。这使得不同人编写的MobX风格差异极大!也使得版本升级历史包袱重!MobX6与之前版本有不少差异!
最近我把官方文档从头到尾过了一遍,感觉MobX官方文档真的非常不错,里面有很多状态管理的知识值得细细咀嚼,强烈建议有时间的话还是要亲自啃一遍官方文档。
以下我将以todo案例模拟实战项目,写一篇关于使用typeScript 编写Mobx6的编码规范。在遇到一件事有多种实现方式时我只重点讲一种以及选择它的原因,其他的一笔带过。如有不妥之处,欢迎留言讨论!

引入MobX,如何选择mobx-react与mobx-react-lite?

mobx-react与mobx-react-lite优劣比较

  1. mobx-react支持类组件,mobx-react-lite不支持类组件。
  2. mobx-react支持Provider 和inject,mobx-react-lite可以用React.createContext替代。
  3. mobx-react支持特殊的观察对象 propTypes,mobx-react-lite可以用typeScript代替。
  4. 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在线体验。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部