mobx 记录

注意:这不是教学,仅仅是学习笔记

Mobx 原则

  • Mobx 是单向的数据流,也就是 Action 改变 state,state 的更新会改变所有影响的视图。
  • 当 state 更新时,所有衍生都会进行原子级的自动更新。因此永远不肯能观察到中间值。
  • 所有衍生默认都是同步更新。这意味这,动作可以在改变状态之后直接可以安全的检查计算值。
  • 计算值延迟更新的。任何不在使用中的计算值将不会再进行计算,如果视图不再使用,则计算值会被回收。
  • 所有计算值都应该是纯净的,它们不应该用来更新 状态

1. 将数据转换为 Observable 数据。

  • Map,Array,Object 转化为 Observable 数据。
  • Map 的每个属性都将会转换为可观察数据,当频繁的对 对象属性增删时,使用 Map 会比较好
  • Object 的每个属性都会转换成可观察数据,如果属性值是对象,也会递归转换。
  • 对于基本数据类型,需要使用 observable.box 将其转换为观察数据。
import { observable } from 'mobx'
const ob_map = observable(new Map({name: 'map data'}))

const ob_array = observable([1,2,3])
ob_array[1] = 3

const ob_object = observsble({
  name: 'xiaobai',
  age: 18
})

ob_object.name = 'xiaohei'

const ob_num = observable.box(25)
ob_num = 20
  1. 将 class 进行 Observable 转换
  • 能使用装饰器环境时,使用 @observable 对属性进行包装。
  • 在不能使用时,需要 decorate 对类进行包装。
import { observable, decorate, action } from 'mobx'
// 使用装饰器
class Person {
  @observable name = 'xiaobao'
  @observable age = 18
  @action.bound
  changeAge(age) {
    this.age = age
  }
}

// 不使用装饰器时
class Person {
  name;
  age;
  constructor(){
    this.name = 'xiaobao'
    this.age = 18
  }
  changeAge(age) {
    this.age = age
  }
}
decorate(Person, {
  name: observable,
  age: observable,
  changeAge: action.bound
})

3. 关于 Observable.box

observable.box(value) 可以将任意值储存在 box 中。使用 .get() 可以获取值,使用 .set(value) 可以设置值。

import {observable} from 'mobx'

const ob_num = observable.box(20)
ob_num.get() // 20 获取值
ob_num.set(25) // 替换当前储存的值,并通知所有使用的观察者。
ob_num.interept(interceptor) // 可以用来在任何变化时将其拦截
ob_num.observe() // 注册一个观察者,在每次储存值被替换时触发。返回一个取消注册的函数。

observable.box(value, {name: 'value'}) name 选项用来给 value 一个更好的调试名称。
observable.box(value, {deep: false}) 盒子里的任何值都不会转换为可观察对象。

4. Observable 装饰器合集

observable 定义了一系列装饰器来定义行为

名称 描述
observable observable.deep 的别名
observable.deep 任何 observable 都是用的默认的调节器,他将任何(尚未转换为 observable)数组映射或者纯对象克隆并将其转换为 observble 对象。
observable.ref 禁用 observable 的自动转换,值创建一个 observable 的引用
observable.shallow 只能与数组配合使用。将任何集合转换为 observable 对象,但该集合的值不会进行转换
observable.struct 就像 ref,但会忽略结构上等于当前值的新值(值改变时才会刷新应用)
computed 创建一个衍生值,根据某个观察值衍生出来的数据
computed(option) 与 computed 相同,可以配置选项
computed.struct 与 computed 相同,但是只有当视图产生的值与之前的值结构不相同是,才会通知观察者
action 创建一个动作
action(name) 创建一个动作,但是重载了名称
action.bound 创建一个动作,并将 this 绑定到实例

5. (@)computed

  • 计算值是根据现有状态值或者其他计算值衍生出来的值。
  • 计算属性是不可枚举的,而且不能在原形链中被覆盖。
// @computed
// 如果启用了装饰器,可以在任意属性的 getter 上使用 @computed
import { observable, computed } from 'mobx'

class Person {
  @observable name = 'xiaobai'
  @observable age = 18
  @computed get person(){
      return `${this.name}-${this.age}`
   }
}

// 如果未开启 装饰器,可以使用 decorate
import {decorate, observable, computed} from 'mobx'

class Person1{
  name
  age
  constructor(){
    this.name = 'xiaobai'
    this.age = 18
  }
  get person(){
     return `${this.name}-${this.age}`
  }
}
decorate(Person1, {
  name: observable,
  age: observable,
  person: computed
})

6. observable.object 和 extendObservable

这两个 API 会自动将 getter 属性转换为计算属性。

import {observable, extendObservable} from 'mobx'
const ob_ob1 = observable.object({
  name: 'box',
  age: 18,
  get person(){
    return `${this.name}-${this.age}`
  }
})

// extendObservable 版本
extendObservable({
  name: 'box',
  age: '18',
  get person(){
    return `${this.name}-${this.age}`
  }
})
// extendObservable 会将所有属性转换为可观察值。新添加的属性也会自动转换为可观察值。

关于 setter
还可以指定计算值的 setter ,但是不能用来改变计算值,可以用来逆向衍生

import {computed, observable} from 'mobx'

class Person{
  @observable name = 'xiaobai'
  @observable age = 18
  computed get person(){
    return `${this.name}-${this.age}`
  }
  set person(value) {
    this.age = value + 2
  }
}
// 注意: setter 需要在 getter 后定义

computed(expression)函数
computed 可以直接作为函数被调用,就像 observable.box(value) 一样调用,并获得一个 observable。

import {observable, computed} from "mobx";
var name = observable("John");

var upperCaseName = computed(() =>
    name.get().toUpperCase()
);
upperCaseName .get() // 20 获取值
upperCaseName .set(25) // 替换当前储存的值,并通知所有使用的观察者。
upperCaseName .interept(interceptor) // 可以用来在任何变化时将其拦截
var disposer = upperCaseName.observe(change => console.log(change.newValue)); 
// 注册一个观察者,在每次储存值被替换时触发。返回一个取消注册的函数

name.set("Dave");
// 输出: 'DAVE'

7. Autorun

当你想创建一个响应式函数,但该函数本身永远不会有观察者时,可以使用 mobx.autorun。
autorun 会在创建时就运行一次。

import {observable, autorun, computed} from 'mobx'

const arr = observable([1,2,3])
const sum  = computed(() => arr.reduce(sum, item) => sum + item)

const disposer = autorun( () => { console.log(sum.get()) } )
// 输出 6

arr.push(4)
// 输出 10
disposer()
arr.push(5)
// 不会在输出任何值

选项
Autorun 可以接受第二个参数,有如下参数可选:

属性 描述
delay 可用于效果函数的去抖时间(单位是毫秒)。默认为 0,不去抖
name 字符串,方便调试
onError 处理 Autorun 的错误,而不是传播下去
scheduler 设置自定义调度器以决定如何调度 autorun 函数的重新运行

8. when

when(predicate: () => boolean, effect?: () => void, options?)

当 predicate 返回值为 true 时,才会执行 effect 函数。返回一个取消函数,方便手动取消。注意,只要返回 true,就会运行 effect 函数,然后就会被取消。

import {computed, when} from 'mobx'

class A{
  constructor(){
    when( 
     () => this.visible,
      () => {this.dispose}
    )
  }
  @computed get visible(){
    return true
  }
  dispoes(){
    // 做一些操作
  }
}

when-promise
如果没提供 effect 函数,则会返回一个 Promise,可以与 async/await 结合使用

import {when} from 'mobx'

async function(){
  await when(() => true)
}

9. Reaction

// 用法
reaction(() => data, (data, reaction) => {sideEffect}, options)

reaction 在创建时不会运行,只有当返回的 data 有变化时,才会运行第二个参数,reaction 会返回一个清理函数。
下面给出一则完整的演示示例:

import {reaction, observable, autorun} from 'mobx'
const ob = observable([{
    title: 'xiaobai',
    age: 18
  },
  {
    title: 'xiaohei',
    age: 19
  }
])
// reaction1 错误示例:对length做出反应,而不是对 title
const reaction1 = reaction(
  () => ob.length, 
  (data, reaction) => {
    console.log('reaction1',data.map(item) => item.title).join(',') 
  }
)

// reaction 的正确用法,对 length 和 title 的变化做出反应
const reaction2 = reaction(
  () => ob.map(item => item.title).join(','), 
  (data, reaction) => {console.log('reaction2',data)}
)

//autorun 对任何的数据改变做出反应
const autorun = autorun( () => { 
  console.log('autorun', ob.map(item => item.title).join(',')) 
} )

todos.push({ title: "explain reactions", done: false });
// 输出:
// reaction 1: xiaobai, xiaohei, explain reactions
// reaction 2: xiaobai, xiaohei, explain reactions
// autorun 1: xiaobai, xiaohei, explain reactions

todos[0].title = "Make tea"
// 输出:
// reaction 2: xiaobai, xiaohei, explain reactions
// autorun 1: xiaobai, xiaohei, explain reactions

下面示例中,reaction3 会对 counter 的 count 做出反应。当调用 reaction 是第二个参数会作为清理参数使用。

import {reaction, observable} from 'mobx'

const num = observable({count: 0})

// 只调用一次 reaction ,对 observable 的 count 做出反应。
const reaction1 = reaction(
  () => num.count,
  (data, reaction) => { 
    console.log(data)
    reaction.dispose() 
  }
)
counter.count = 1;
// 输出:
// reaction 3: invoked. counter.count = 1

counter.count = 2;
// 输出:
// (There are no logging, because of reaction disposed. 
// But, counter continue reaction)

console.log(counter.count);
// 输出:
// 2

粗略地讲,reaction 是 computed(expression).observe(action(sideEffect))autorun(() => action(sideEffect)(expression) 的语法糖。

10. action

用法:

action(fn)
action(name, fn)
@action classMethod(){}
@action(name) classMethod(){}
@action boundClassMethod = () => {}
@action(name) boundClassMethod = () => {}
@action.bound classMethod

何时使用动作
永远只在修改状态时使用动作。

绑定动作
装饰器/函数遵循 javascript 中标准的绑定规则。 但是,action.bound 可以用来自动地将动作绑定到目标对象。 注意,与 action 不同的是,(@)action.bound 不需要一个name参数,名称将始终基于动作绑定的属性。

注意: action.bound 不要和箭头函数一起使用;箭头函数已经是绑定过的并且不能重新绑定。

示例:

class Ticker {
    @observable tick = 0

    @action.bound
    increment() {
        this.tick++ // 'this' 永远都是正确的
    }
}

const ticker = new Ticker()
setInterval(ticker.increment, 1000)

runInAction(name?, thunk)
runInAction 是个简单的工具函数,它接收代码块并在(异步的)动作中执行。这对于即时创建和执行动作非常有用,例如在异步过程中。runInAction(f)action(f)() 的语法糖。

11. 编写异步 Actions

action 包装/装饰器只会对当前运行的函数作出反应,而不会对当前运行函数所调用的函数(不包含在当前函数之内)作出反应! 这意味着如果 action 中存在 setTimeoutpromisethenasync 语句,并且在回调函数中某些状态改变了,那么这些回调函数也应该包装在 action 中。创建异步 action 有几种方式。不能说某种方式一定比其他的好,本章只是列出编写异步代码的几种不同方式而已。 我们先从一个基础的示例开始:

Promise

mobx.configure({ enforceActions: true }) // 不允许在动作之外进行状态修改

class Store {
    @observable githubProjects = []
    @observable state = "pending" // "pending" / "done" / "error"

    @action
    fetchProjects() {
        this.githubProjects = []
        this.state = "pending"
        fetchGithubProjectsSomehow().then(
            projects => {
                const filteredProjects = somePreprocessing(projects)
                this.githubProjects = filteredProjects
                this.state = "done"
            },
            error => {
                this.state = "error"
            }
        )
    }
}

上面的示例会抛出异常,因为传给 fetchGithubProjectsSomehow promise 的回调函数不是 fetchProjects 动作的一部分,因为动作只会应用于当前栈。

首选的简单修复是将回调函数变成动作。(注意使用 action.bound 绑定在这很重要,以获取正确的 this!):

mobx.configure({ enforceActions: true })

class Store {
    @observable githubProjects = []
    @observable state = "pending" // "pending" / "done" / "error"

    @action
    fetchProjects() {
        this.githubProjects = []
        this.state = "pending"
        fetchGithubProjectsSomehow().then(
          this.fetchProjectsSuccess, 
          this.fetchProjectsError
        )

    }

    @action.bound
    fetchProjectsSuccess(projects) {
        const filteredProjects = somePreprocessing(projects)
        this.githubProjects = filteredProjects
        this.state = "done"
    }
    @action.bound
        fetchProjectsError(error) {
            this.state = "error"
        }
    }

尽管这很整洁清楚,但异步流程复杂后可能会略显啰嗦。另外一种方案是你可以使用 action关键字来包装 promises 回调函数。推荐这么做,但不是强制的,还需要给它们命名:

mobx.configure({ enforceActions: true })

class Store {
    @observable githubProjects = []
    @observable state = "pending" // "pending" / "done" / "error"

    @action
    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"
            })
        )
    }
}

runInAction 工具函数

内联动作的缺点是 TypeScript 无法对其进行类型推导,所以你应该为所有的回调函数定义类型。 你还可以只在动作中运行回调函数中状态修改的部分,而不是为整个回调创建一个动作。 这种模式的优势是它鼓励你不要到处写 action,而是在整个过程结束时尽可能多地对所有状态进行修改:

mobx.configure({ enforceActions: true })

class Store {
    @observable githubProjects = []
    @observable state = "pending" // "pending" / "done" / "error"

    @action
    fetchProjects() {
        this.githubProjects = []
        this.state = "pending"
        fetchGithubProjectsSomehow().then(
            projects => {
                const filteredProjects = somePreprocessing(projects)
                // 将‘“最终的”修改放入一个异步动作中
                runInAction(() => {
                    this.githubProjects = filteredProjects
                    this.state = "done"
                })
            },
            error => {
                // 过程的另一个结局:...
                runInAction(() => {
                    this.state = "error"
                })
            }
        )
    }
}

注意,runInAction 还可以给定第一个参数作为名称。runInAction(f) 实际上是 action(f)() 的语法糖。

async / await
基于 async / await 的函数当开始使用动作时起初似乎会令人感到困惑。 因为在词法上它们看起来是同步函数,它给人的印象是 @action 应用于整个函数。 但事实并非若此,因为 async / await 只是围绕基于 promise 过程的语法糖。 结果是 @action 仅应用于代码块,直到第一个 await 。 在每个 await 之后,一个新的异步函数将启动,所以在每个 await 之后,状态修改代码应该被包装成动作。 这正是 runInAction 再次派上用场的地方:

mobx.configure({ enforceActions: true })

class Store {
    @observable githubProjects = []
    @observable state = "pending" // "pending" / "done" / "error"

    @action
    async fetchProjects() {
        this.githubProjects = []
        this.state = "pending"
        try {
            const projects = await fetchGithubProjectsSomehow()
            const filteredProjects = somePreprocessing(projects)
            // await 之后,再次修改状态需要动作:
            runInAction(() => {
                this.state = "done"
                this.githubProjects = filteredProjects
            })
        } catch (error) {
            runInAction(() => {
                this.state = "error"
            })
        }
    }
}

flows

然而,更好的方式是使用 flow 的内置概念。它们使用生成器。一开始可能看起来很不适应,但它的工作原理与 async / await 是一样的。只是使用 function * 来代替 async,使用 yield 代替 await 。 使用 flow 的优点是它在语法上基本与 async / await 是相同的 (只是关键字不同),并且不需要手动用 @action 来包装异步代码,这样代码更简洁。

flow 只能作为函数使用,不能作为装饰器使用。 flow 可以很好的与 MobX 开发者工具集成,所以很容易追踪 async 函数的过程。

flow 的关键作用是 处理异步代码时确保代码被action包装 ,因为正常的 observable state 对异步操作无法通过 enforceActions 检查。

import { configure, flow } from 'mobx';

mobx.configure({ enforceActions: true })

class Store {
    @observable githubProjects = []
    @observable state = "pending"

    fetchProjects = flow(function * () { // <- 注意*号,这是生成器函数!
        this.githubProjects = []
        this.state = "pending"
        try {
            const projects = yield fetchGithubProjectsSomehow() // 用 yield 代替 await
            const filteredProjects = somePreprocessing(projects)
            // 异步代码块会被自动包装成动作并修改状态
            this.state = "done"
            this.githubProjects = filteredProjects
        } catch (error) {
            this.state = "error"
        }
    })
  // 注意调用模式
  @action actionFlow(){
    this.fetchProjects() 
  }
}

Flows 可以撤销,调用promise的cancel() 方法会停止异步状态取值, 会继续执行 finally 子句 。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容

  • 写的都是自己的一些感受=。=可能都是野路子=。=大牛就别看了=。=代码:https://github.com/Xi...
    无星灬阅读 1,848评论 5 1
  • 1. 介绍 1.1. 原理 React的render是 状态 转化为树状结构的渲染组件的方法而MobX提供了一种存...
    三月懒驴阅读 12,816评论 1 28
  • Mobx是一个功能强大,上手非常容易的状态管理工具。就连redux的作者也曾经向大家推荐过它,在不少情况下你的确可...
    绯色流火阅读 121,263评论 51 170
  • Mobx解决的问题 传统React使用的数据管理库为Redux。Redux要解决的问题是统一数据流,数据流完全可控...
    前端大神888阅读 424评论 1 0
  • 这个小飞机是妞妞的,我们前两天带着孩子们去大草坪的时候,玩了这个小飞机。 晚上,妞妞放学回来了,马队长喊了妞妞说:...
    美乐君霞阅读 3,599评论 0 1