Skip to content

Mobx 扫盲

mobx(mobx-react

Mutable 状态管理,修改对象属性(可观察)通知订阅者,支持 hooks,不支持 concurrent 模式(未来)

版本及周边解释:

mobx@5 不支持 IE11,原因是使用了 proxy。除此之外与 mobx@4 一样 mobx-react@6 同时支持 hooksclass,包含了 mobx-react-lite mobx-react-lite 只支持 FC hooks,体积小很多 mobx-state-tree 把全局状态集中在一起管理,类似 redux

常用搭配 mobx@4 + mobx-react@6 + mobx-state-tree (没太大必要)

yarn add mobx@4 mobx-react@6

mobx 在 react 中不仅仅是用来管理全局状态,也用来管理组件内部状态。使用观察者 Observer 监听数据属性,避免不必要的渲染。

父组件重新 render 时,子组件内部观察状态如果没变化就不会 render,因此不再需要手动写 shouldComponentUpdate、memo、useMemo 这些了。

现在全局状态的跨组件状态传播使用 Context,不再使用 Provider/inject

1、组件内部状态管理(函数组件)

useLocalStore、useObserver/observer

tsx
import React from 'react'
import { useObserver, useLocalStore } from 'mobx-react'

interface Props {
  changeVal: number
}

const Person = (props: Props) => {
  // useLocalStore 创建可观察对象 person,在组件生命周期内只会执行一次。
  // 如果希望根据外部值变化(比如 props),那么需要把外部值作为第二个参数传入,同时使用计算属性 get 来处理
  const person = useLocalStore((draft: Props) => {
    return {
      status: true,
      initVal: props.changeVal, // person.initVal 不会随着外部传入值变化
      // 使用 get 来定义计算属性
      get changeVal() {
        // person.changeVal 可以随着外部传入值变化
        return draft.changeVal
      },
      // 定义方法来更新数据
      toggle() {
        person.status = !person.status
      },
    }
  }, props)
  // useObserver 观察组件
  return useObserver(() => (
    <div>
      <p>initVal: {person.initVal}</p>
      <p>changeVal: {person.changeVal}</p>
      <p>
        {person.status ? 'true' : 'false'}
        // 可以直接修改属性,但是最好还是在 person 对象内部定义方法 person.toggle 来进行数据更新
        <button onClick={() => (person.status = !person.status)}>Person</button>
      </p>
    </div>
  ))
  // 或者使用 <Observer></Observer> 包裹(可以只包住某一部分进行响应)
  return (
    <div>
      <Observer>
        <p>initVal: {person.initVal}</p>
        <p>changeVal: {person.changeVal}</p>
        <p>
          {person.status ? 'true' : 'false'}
          <button onClick={() => (person.status = !person.status)}>Person</button>
        </p>
      </Observer>
    </div>
  )
}

// 如果没有使用 useObserver/<Observer /> 还可以用 HOC 包装 observer(Person)
export default Person

2、全局状态管理

使用 context 来跨组件传递数据(如果需要中心化的 store 来管理,可以考虑加一个库 mobx-state-tree)

现在不太推荐使用中心化的 state ,包括后面要介绍的 Recoil 也是,更推荐 "原子化" 的 state

src/store/createStore.ts

tsx
export type TFriend = {
  name: string
  isFavorite: boolean
  isSingle: boolean
}

// 将使用 useLocalStore 来创建可观察对象,并作为 context 的 value
export function createStore() {
  return {
    friends: [{ name: 'test', isFavorite: false, isSingle: false }] as TFriend[],
    // 更新数据的方法
    makeFriend(name: string, isFavorite = false, isSingle = false) {
      const oldFriend = this.friends.find((friend) => friend.name === name)
      if (oldFriend) {
        oldFriend.isFavorite = isFavorite
        oldFriend.isSingle = isSingle
      } else {
        this.friends.push({ name, isFavorite, isSingle })
      }
    },
    // 计算属性
    get singleFriends() {
      return this.friends.filter((friend) => friend.isSingle)
    },
  }
}

// store 的类型,在 class 组件里(声明 context value 的类型)
export type TStore = ReturnType<typeof createStore>

src/store/index.ts

tsx
import React from 'react'
import { useLocalStore } from 'mobx-react'
import { createStore, TStore } from './createStore'

// 导出给 class 组件使用的 context 的 value
export const storeContext = React.createContext<TStore | null>(null)

// 容器组件包裹需要共享数据的公共父组件 <StoreProvider><App /></StoreProvider>
export const StoreProvider = ({ children }: any) => {
  const store = useLocalStore(createStore)
  return <storeContext.Provider value={store}>{children}</storeContext.Provider>
}

// 在子组件里获取 context 的 value 值
export const useStore = () => {
  const store = React.useContext(storeContext)
  // 不为 null 为了使用时推断类型
  if (!store) {
    throw new Error('useStore 必须在 StoreProvider 子 FC 组件里使用')
  }
  return store
}

FC 组件里使用

tsx
const store = useStore()

在 class 组件里使用

tsx
class Friends extends React.Component {
  static contextType = storeContext
  context!: TStore // 显示声明 context value 的类型
  render() {
    return (
      <div>
        <pre>{JSON.stringify(this.context.friends, null, 4)}</pre>
        <button onClick={() => this.context.makeFriend('李四')}>class friends</button>
      </div>
    )
  }
}