mobx
本文最后更新于:2022年4月22日 上午
近期mobx更新了6.0版本,该版本可以说是一次大更新,对比之前的mobx4/5改变很大
mobx 6
mobx核心概念
对于observer工作而言,可观察对象如何到达组件无关紧要,只需要读取它们即可。深入阅读可观察对象是todos[0].author.displayName开箱即用的精细,复杂表达。与必须显式声明或预先计算数据依赖项(例如选择器)的其他框架相比,这使订阅机制更加精确和高效。
状态的组织方式具有很大的灵活性,因为状态(从技术上来说)与我们阅读的可观察对象或可观察对象来自何处无关紧要
mobx核心 API
- Observable state
- makeObservable
 - makeAutoObservable
 - observable
 
 - Actions
- action
 - action.bound
 - runInAction
 
 - Computeds
 - Reactions
 
observable 组件获取状态的3种方式
1. props父组件传值的方式 >folded//可观察的对象可以作为props传递到组件中(如下例所示):
import { observer } from "mobx-react-lite"
import { makeAutoObservable } from "mobx"
class Timer {
    secondsPassed = 0
    constructor() {
        makeAutoObservable(this)
    }
    increaseTimer() {
        this.secondsPassed += 1
    }
}
const myTimer = new Timer() // Timer的定义请查看以上
const TimerView = observer(({ timer }) => {
    return <span>Seconds passed: {timer.secondsPassed}</span>
    
})
// 把 myTimer 作为 props 传入
ReactDOM.render(<TimerView timer={myTimer} />, document.body)
2. 全局变量的方式 >foldedimport { observer } from "mobx-react-lite"
// 由于我们对可观察对象(observable)如何引入的方式并不重要,
// 因此我们可以直接从外部(包括导入等)的方式使用可观察对象
const myTimer = new Timer() // Timer 的定义请参考上述示例 
// 没有props,`myTimer` 直接从闭包中使用
const TimerView = observer(() => {
    return  <span>Seconds passed: {myTimer.secondsPassed}</span>
    
})
// 直接使用可观察对象非常方便,但是由于这通常会引入模块状态,
// 因此这种模式可能会使单元测试复杂化。所以 mobx 建议使用 React Context的方式
ReactDOM.render(<TimerView />, document.body)3. React context的方式 >folded// React Context是一种与整个子组件共享可观察对象的很好的机制
import { observer } from 'mobx-react-lite'
import { createContext, useContext } from "react"
const TimerContext = createContext<Timer>()
const TimerView = observer(() => {
    // 从 context 获取 timer
    const timer = useContext(TimerContext) // Timer 的定义请参考上述示例 
    return  <span>Seconds passed: {timer.secondsPassed}</span>
    
})
// 注意 Mobx 不建议将 Provider 的值替换为其他值。
// 在 MobX 中,不需要这样做,因为共享的可观察对象可以自己更新。
ReactDOM.render(
    <TimerContext.Provider value={new Timer()}
        <TimerView />
    </TimerContext.Provider>,
    document.body
)actions
创建actions的几种常用方式
mobx中创建action主要是以下几种方式
1. makeObservable >foldedimport { makeObservable, observable, action } from "mobx"
class Doubler {
    value = 0
    constructor(value) {
        makeObservable(this, {
            value: observable,
            increment: action
        })
    }
    increment() {
        // 中间状态对观察者将不可见。
        this.value++
        this.value++
    }
}2. makeAutoObservable >foldedimport { makeAutoObservable } from "mobx"
class Doubler {
    value = 0
    constructor(value) {
        makeAutoObservable(this)
    }
    increment() {
        this.value++
        this.value++
    }
}3. action.bound 的方式,绑定this >foldedimport { makeObservable, observable, computed, action } from "mobx"
class Doubler {
    value = 0
    constructor(value) {
        makeObservable(this, {
            value: observable,
            increment: action.bound
        })
    }
    increment() {
        this.value++
        this.value++
    }
}
const doubler = new Doubler()
// 这种方式的this是正确的
setInterval(doubler.increment, 1000)4. action >foldedimport { observable, action } from "mobx"
const state = observable({ value: 0 })
const increment = action(state => {
    state.value++
    state.value++
})
increment(state)5. runActions >foldedimport { observable } from "mobx"
const state = observable({ value: 0 })
// 会立即执行
runInAction(() => {
    state.value++
    state.value++
})action.bound
action.bound可以自动绑定到正确的this,从而使this总是被正确地在函数内部约束。
建议优先使用箭头函数
如果您想结合使用绑定动作,makeAutoObservable通常使用箭头功能会更简单。
>foldedimport { makeAutoObservable } from "mobx"
class Doubler {
    value = 0
    constructor(value) {
        makeAutoObservable(this)
    }
    increment = () => {
        this.value++
        this.value++
    }
}runInAction
使用此runInAction可以创建一个立即调用的临时操作。在异步过程中很有用。
异步actions
处理异步actions的几种方式
在处理promise时,更新状态的处理方法应该使用action或be action进行包装,如下所示。
1. 使用action作为包装 >folded// Promise解析处理函数是内联处理的,但是它在原始操作完成后运行,因此需要用以下代码包装action
import { action, makeAutoObservable } from "mobx"
class Store {
    githubProjects = []
    state = "pending" // "pending", "done" or "error"
    constructor() {
        makeAutoObservable(this)
    }
    fetchProjects() {
        this.githubProjects = []
        this.state = "pending"
        fetchGithubProjectsSomehow().then(
            action("fetchSuccess", projects => { // 成功的回调
                const filteredProjects = somePreprocessing(projects)
                this.githubProjects = filteredProjects
                this.state = "done"
            }),
            action("fetchError", error => {      // 失败的回调
                this.state = "error"
            })
        )
    }
}2. >folded// 如果 promise 的处理函数是类里面的字段,它们将被 makeAutoObservable 自动包装在 action 中
import { makeAutoObservable } from "mobx"
class Store {
    githubProjects = []
    state = "pending" // "pending", "done" or "error"
    constructor() {
        makeAutoObservable(this)
    }
    fetchProjects() {
        this.githubProjects = []
        this.state = "pending"
        fetchGithubProjectsSomehow().then(
            this.projectsFetchSuccess,      // 成功的回调
            this.projectsFetchFailure       // 失败的回调
        )
    )
    // 成功的回调
    projectsFetchSuccess = (projects) => {
        const filteredProjects = somePreprocessing(projects)
        this.githubProjects = filteredProjects
        this.state = "done"
    }
    // 失败的回调
    projectsFetchFailure = (error) => {
        this.state = "error"
    }
}3. async/await 的方式 >folded// await 之后的步骤不在同一个tick中,因此它们需要进行包装。在这里,我们可以利用 runInAction
import { runInAction, makeAutoObservable } from "mobx"
class Store {
    githubProjects = []
    state = "pending" // "pending", "done" or "error"
    constructor() {
        makeAutoObservable(this)
    }
    async fetchProjects() {
        this.githubProjects = []
        this.state = "pending"
        try {
            const projects = await fetchGithubProjectsSomehow()
            const filteredProjects = somePreprocessing(projects)
            
            // 立即执行
            runInAction(() => {
                this.githubProjects = filteredProjects
                this.state = "done"
            })
        } catch (e) {
            // 立即执行
            runInAction(() => {
                this.state = "error"
            }
        }
    )
}4. flow + generator 的方式 >foldedimport { flow, makeAutoObservable, flowResult } from "mobx"
class Store {
    githubProjects = []
    state = "pending"
    constructor() {
        makeAutoObservable(this, {
            fetchProjects: flow
        })
    }
    // * 号创建一个 generator函数
    *fetchProjects() {
        this.githubProjects = []
        this.state = "pending"
        try {
            // 用 yield 来替换 await
            const projects = yield fetchGithubProjectsSomehow()
            // 以下不需要再使用 runInAction 包裹了
            const filteredProjects = somePreprocessing(projects)
            this.state = "done"
            this.githubProjects = filteredProjects
        } catch (error) {
            this.state = "error"
        }
    }
}
const store = new Store()
const projects = await flowResult(store.fetchProjects())使用 flow 来替代 async/await
flow 可以更好的和 Mobx 结合,flow 将确保 generator 在生成 promise 时是继续运行或抛出
- 因此,flow 是 async/await 的替代方法,不需要进一步 action 包装。
- 使用 flow 包装异步处理
 - 代替 async 使用 function *
 - 代替 await 使用 yield
 
 
注意,flowResult 只有在使用 TypeScript 时才需要该函数。由于使用 flow 来包装方法,因此它将返回的生成器包装在 Promise 中。但是,TypeScript 不知道该如何转换,所以使用 flowResult 会让 TypeScript 知道这种类型的变化。
flow 和 action 一样,可以直接包装函数,所以上述的实例也换成以下写法
import { flow } from "mobx"
class Store {
    githubProjects = []
    state = "pending"
    fetchProjects = flow(function* (this: Store) {
        this.githubProjects = []
        this.state = "pending"
        try {
            // 用 yield 来替换 await
            const projects = yield fetchGithubProjectsSomehow()
            const filteredProjects = somePreprocessing(projects)
            this.state = "done"
            this.githubProjects = filteredProjects
        } catch (error) {
            this.state = "error"
        }
    })
}
const store = new Store()
const projects = await store.fetchProjects()Computeds
>foldedimport { makeObservable, observable, computed,autorun } from "mobx"
class OrderLine {
    price = 0
    amount = 1
    constructor(price) {
        makeObservable(this, {
            price: observable,
            amount: observable,
            total: computed
        })
        this.price = price
    }
    get total() {
        console.log("Computing...")
        return this.price * this.amount
    }
}
const order = new OrderLine(0)
const stop = autorun(() => {
    console.log("Total: " + order.total)
})
上面的示例很好地演示了computed值的好处,它充当缓存点。即使我们更改了amount,这也会触发total重新进行计算,但不会触发autorun,因为total它将检测到其输出未受到影响,因此无需更新autorun。
相比之下,如果total不加注解 computed ,autorun效果将运行3次,因为它直接取决于total和amount。自己尝试一下。
Reactions
when
when(predicate: () => boolean, effect?: () => void, options?)
when观察并运行给定的谓词函数,直到返回为止true。一旦发生这种情况,将执行给定的效果功能并处置自动运行器。
方式1 >foldedimport { when, makeAutoObservable } from "mobx"
class MyResource {
    constructor() {
        makeAutoObservable(this, { dispose: false })
        // 如果为true,则会执行一些操作
        when(
            // Once...
            () => !this.isVisible,
            // ... then.
            () => this.dispose()
        )
    }
    get isVisible() {
        // Indicate whether this item is visible.
    }
    dispose() {
        // Clean up some resources.
    }
    
    // 一旦isVisible成为false,该dispose方法就会被调用,然后对进行一些清理MyResource。
    
}when(predicate: () => boolean, options?): Promise
该when函数返回一个处理程序,允许您手动取消它,除非您不传入第二个effect函数,在这种情况下,它将返回a Promise。
when过早取消,可以调用.cancel()自身返回的承诺。
方式二 >foldedasync function() {
    await when(() => that.isVisible)
    // etc...
}关闭自动任务
该函数传递给autorun,reaction并且when只垃圾回收使用的所有对象,他们观察到的垃圾回收自己。原则上,他们一直在等待使用的可观察物发生新的变化。为了能够阻止它们等待直到永远过去,它们都返回了一个Disposer函数,该函数可用于停止它们并取消订阅所使用的任何可观察对象。
>foldedconst counter = observable({ count: 0 })
// 第一次打印0
const disposer = autorun(() => {
    console.log(counter.count)
})
// 打印 1
counter.count++
// 停止自动运行
disposer()
// 不会打印
counter.count++本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议,转载请注明出处。