深入学习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 = false
    constructor(title: string) {
        makeAutoObservable(this,
            {  // 自定义各个类属性的mobx注解,如false(不注解)等

            },
            {  //  options参数,autoBind是指自动绑定this
                autoBind: 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是指自动绑定this
                autoBind: 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: string
    get isLogin() {
        return this.name.length>0
    }
    constructor(name:string) {
        makeAutoObservable(this,
            {  // 自定义各个类属性的mobx注解,如false(不注解)等

            },
            {  //  options参数,autoBind是指自动绑定this
                autoBind: 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<typeof Store>;
//     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>
      <input
        type="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在线体验

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值