Appearance
Mobx 扫盲
mobx(mobx-react)
Mutable 状态管理,修改对象属性(可观察)通知订阅者,支持 hooks,不支持 concurrent 模式(未来)
版本及周边解释:
mobx@5 不支持 IE11,原因是使用了 proxy。除此之外与 mobx@4 一样 mobx-react@6 同时支持 hooks 和 class,包含了 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 Person2、全局状态管理
使用 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>
)
}
}