Mobx4 改变 Observables

Action

1
2
3
4
5
6
7
8
// 用法
action(fn)
action(name, fn)
@action classMethod() {}
@action(name) classMethod () {}
@action boundClassMethod = (args) => { body }
@action(name) boundClassMethod = (args) => { body }
@action.bound classMethod() {}

任何应用都有动作。动作是任何用来修改状态的东西。 使用 MobX 你可以在代码中显式地标记出动作所在的位置。

它接收一个函数并返回具有同样签名的函数,但是用 transaction、untracked 和 allowStateChanges 包裹起来,尤其是 transaction 的自动应用会产生巨大的性能收益, 动作会分批处理变化并只在(最外层的)动作完成后通知计算值和反应。 这将确保在动作完成之前,在动作期间生成的中间值或未完成的值对应用的其余部分是不可见的。

何时使用 Action?

应该永远只对修改状态的函数使用动作。 只执行查找,过滤器等函数不应该被标记为动作,以允许 MobX 跟踪它们的调用。

绑定的 Action

action 装饰器/函数遵循 javascript 中标准的绑定规则。 但是,action.bound 可以用来自动地将动作绑定到目标对象。

编写异步 Actions

action 包装/装饰器只会对当前运行的函数作出反应,而不会对当前运行函数所调用的函数(不包含在当前函数之内)作出反应。

这意味着如果 action 中存在 setTimeout、promise 的 then 或 async 语句,并且在回调函数中某些状态改变了,那么这些回调函数也应该包装在 action 中。

Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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";
}
}

runInAction 工具函数

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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";
});
}
);
}
}

async / await

async / await 只是围绕基于 promise 过程的语法糖。 结果是 @action 仅应用于代码块,直到第一个 await 。 在每个 await 之后,一个新的异步函数将启动,所以在每个 await 之后,状态修改代码应该被包装成动作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 是相同的 (只是关键字不同),并且不需要手动用 @action 来包装异步代码,这样代码更简洁。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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"
}
})
}

直接操控 Observable

现在有一个统一的工具 API 可以操控 observable 映射、对象和数组。这些 API 都是响应式的,这意味着如果使用 set 进行添加,使用 values 或 keys 进行迭代,即便是新属性的声明都可以被 MobX 检测到。

  • values(thing) 将集合中的所有值作为数组返回
  • keys(thing) 将集合中的所有键作为数组返回
  • entries(thing) 返回集合中的所有项的键值对数组