Mobx

通过通明的函数响应式编程(transparently applying functional reactive programming - TFRP)使得状态治理变得简略和可扩大。MobX背地的哲学很简略:

任何源自利用状态的货色都应该主动地取得。

React 通过提供机制把利用状态转换为可渲染组件树并对其进行渲染。而MobX提供机制来存储和更新利用状态供 React 应用。

React 提供了优化UI渲染的机制, 这种机制就是通过应用虚构DOM来缩小低廉的DOM变动的数量

MobX 提供了优化利用状态与 React 组件同步的机制,这种机制就是应用响应式虚构依赖状态图表,它只有在真正须要的时候才更新并且永远放弃是最新的。

概念

State(状态)

状态 是驱动利用的数据。

Derivations(衍生)

任何源自状态并且不会再有任何进一步的相互作用的货色就是衍生。 衍生以多种形式存在:

  • 用户界面
  • 衍生数据,比方剩下的待办事项的数量。
  • 后端集成,比方把变动发送到服务器端。

MobX 辨别了两种类型的衍生:

  • Computed values(计算值) - 它们是永远能够应用纯函数(pure function)从以后可察看状态中衍生出的值。
  • Reactions(反馈) - Reactions 是当状态扭转时须要主动产生的副作用。须要有一个桥梁来连贯命令式编程(imperative programming)响应式编程(reactive programming)。或者说得更明确一些,它们最终都须要实现I / O 操作。

Actions(动作)

不同于 flux 系的一些框架,MobX 对于如何解决用户事件是齐全开明的。

  • 能够用相似 Flux 的形式实现
  • 或者应用 RxJS 来处理事件
  • 或者用最直观、最简略的形式来处理事件,正如下面演示所用的 onClick

最初全副演绎为: 状态应该以某种形式来更新。

当状态更新后,MobX 会以一种高效且无障碍的形式解决好剩下的事件。

从技术上层面来讲,并不需要触发事件、调用分派程序或者相似的工作。归根究底 React 组件只是状态的富丽展现,而状态的衍生由 MobX 来治理。

store.todos.push(    new Todo("Get Coffee"),    new Todo("Write simpler code"));store.todos[0].finished = true;

尽管如此,MobX 还是提供了 actions 这个可选的内置概念。它们能够帮忙你把代码组织的更好,还能在状态何时何地应该被批改这个问题上帮忙你做出理智的决定。

准则

MobX 反对单向数据流,也就是动作扭转状态,而状态的扭转会更新所有受影响的视图

状态扭转时,所有衍生都会进行原子级的主动更新。因而永远不可能察看到两头值。

所有衍生默认都是同步更新。这意味着例如动作能够在扭转状态之后间接能够平安地查看计算值。

计算值提早更新的。任何不在应用状态的计算值将不会更新,直到须要它进行副作用(I / O)操作时。 如果视图不再应用,那么它会主动被垃圾回收。

所有的计算值都应该是污浊的。它们不应该用来扭转状态

import {observable, autorun} from 'mobx';var todoStore = observable({    /* 一些察看的状态 */    todos: [],    /* 推导值 */    get completedCount() {        return this.todos.filter(todo => todo.completed).length;    }});/* 察看状态扭转的函数 */autorun(function() {    console.log("Completed %d of %d items",        todoStore.completedCount,        todoStore.todos.length    );});/* ..以及一些扭转状态的动作 */todoStore.todos[0] = {    title: "Take a walk",    completed: false};// -> 同步打印 'Completed 0 of 1 items'todoStore.todos[0].completed = true;// -> 同步打印 'Completed 1 of 1 items'

外围

Observable state(可察看的状态)

observable(value)
@observable classProperty = value

此 API 只有在它能够被制作成可察看的数据结构(数组、映射或 observable 对象)时才会胜利。对于所有其余值,不会执行转换。

通过应用 @observable 装璜器(ES.Next)来给你的类属性增加注解就能够简略地实现这所有。

import { observable } from "mobx";class Todo {    id = Math.random();    @observable title = "";    @observable finished = false;}

Computed values(计算值)

computed(() => expression)
computed(() => expression, (newValue) => void)
computed(() => expression, options)
@computed({equals: compareFn}) get classProperty() { return expression; }
@computed get classProperty() { return expression; }

创立计算值,expression 不应该有任何副作用而只是返回一个值。 如果任何 expression 中应用的 observable 产生扭转,它都会主动地从新计算,但前提是计算值被某些 reaction 应用了。

通过@computed 装璜器或者利用 (extend)Observable 时调用 的getter / setter 函数来进行应用

class TodoList {    @observable todos = [];    @computed get unfinishedTodoCount() {        return this.todos.filter(todo => !todo.finished).length;    }}

Actions(动作)

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

动作能够有助于更好的组织代码。 倡议在任何更改 observable 或者有副作用的函数上应用动作。 联合开发者工具的话,动作还能提供十分有用的调试信息。

对于一次性动作,能够应用 runInAction(name?, fn) , 它是 action(name, fn)() 的语法糖.

Reactions(反馈) & Derivations(衍生)

计算值 是主动响应状态变动的反馈 是主动响应状态变动的副作用。 反馈能够确保当相干状态发生变化时指定的副作用(次要是 I/O)能够主动地执行,比方打印日志、网络申请、等等。 简而言之,reactions 在 响应式编程和命令式编程之间建设沟通的桥梁。

办法形容
observer在组件的 render 函数中的任何已应用的 observable 发生变化时,组件都会主动从新渲染。
autorunautorun 负责运行所提供的 sideEffect 并追踪在sideEffect运行期间拜访过的 observable 的状态。 未来如果有其中一个已应用的 observable 发生变化,同样的sideEffect会再运行一遍。
whencondition 表达式会主动响应任何它所应用的 observable。 一旦表达式返回的是真值,副作用函数便会立刻调用,但只会调用一次。
reaction接管两个函数,第一个是追踪并返回数据,该数据用作第二个函数,也就是副作用的输出。

React 组件

如果你用 React 的话,能够把你的(无状态函数)组件变成响应式组件,办法是在组件上增加 observer 函数/ 装璜器. observer由 mobx-react 包提供的。

import React, {Component} from 'react';import ReactDOM from 'react-dom';import {observer} from 'mobx-react';@observerclass TodoListView extends Component {    render() {        return <div>            <ul>                {this.props.todoList.todos.map(todo =>                    <TodoView todo={todo} key={todo.id} />                )}            </ul>            Tasks left: {this.props.todoList.unfinishedTodoCount}        </div>    }}const TodoView = observer(({todo}) =>    <li>        <input            type="checkbox"            checked={todo.finished}            onClick={() => todo.finished = !todo.finished}        />{todo.title}    </li>)const store = new TodoList();ReactDOM.render(<TodoListView todoList={store} />, document.getElementById('mount'));

MobX 会确保组件总是在须要的时从新渲染。

下面例子中的 onClick 解决办法会强制对应的 TodoView 进行渲染,如果未实现工作的数量(unfinishedTodoCount)曾经扭转,它将导致 TodoListView 进行渲染。

可是,如果移除 Tasks left 这行代码(或者将它放到另一个组件中),当点击 checkbox 的时候 TodoListView 就不再从新渲染。

自定义 reactions

应用autorunreactionwhen 函数即可简略的创立自定义 reactions,以满足你的具体场景。

autorun(() => {    console.log("Tasks left: " + todos.unfinishedTodoCount)})

最简实现

1. 定义状态并使其可察看

import {observable} from 'mobx';var appState = observable({    timer: 0});

2. 创立视图以响应状态的变动

appState 中相干数据产生扭转时视图会自动更新。 MobX 会以一种最小限度的形式来更新视图。

通常来说,任何函数都能够成为能够察看本身数据的响应式视图,MobX 能够在任何合乎ES5的JavaScript环境中利用。

import {observer} from 'mobx-react';@observerclass TimerView extends React.Component {    render() {        return (            <button onClick={this.onReset.bind(this)}>                Seconds passed: {this.props.appState.timer}            </button>        );    }    onReset() {        this.props.appState.resetTimer();    }};ReactDOM.render(<TimerView appState={appState} />, document.body);

3. 更改状态

不像一些其它框架,MobX 不会命令你如何如何去做。 这是最佳实际,但要害要记住一点: *MobX 帮忙你以一种简略直观的形式来实现工作*

无论是在扭转状态的控制器函数中,还是在应该更新的视图中,都没有明确的关系定义。 应用 observable 来装璜你的状态视图,这足以让 MobX检测所有关系了。

appState.resetTimer = action(function reset() {    appState.timer = 0;});setInterval(action(function tick() {    appState.timer += 1;}), 1000);

了解Mobx怎么作出响应

MobX 会对在追踪函数执行过程读取现存的可察看属性做出反馈。
  • “读取” 是对象属性的间接援用。
  • “追踪函数”computed 表达式、observer 组件的 render() 办法和 whenreactionautorun 的第一个入参函数。
  • “过程(during)” 意味着只追踪那些在函数执行时被读取的 observable 。这些值是否由追踪函数间接或间接应用并不重要。

换句话说,MobX 不会对其作出反应:

  • 从 observable 获取的值,然而在追踪函数之外
  • 在异步调用的代码块中读取的 observable

MobX 追踪属性拜访,而不是值

假如你有如下的 observable 数据结构(默认状况下 observable 会递归利用,所以本示例中的所有字段都是可察看的)。

const message = observable({    title: "Foo",    author: {        name: "Michel"    },    likes: [        "John", "Sara"    ]})

绿色框示意可察看属性。 请留神, 自身是不可察看的!

当初 MobX 基本上所做的是记录你在函数中应用的是哪个箭头。之后,只有这些箭头中的其中一个扭转了(它们开始援用别的货色了),它就会从新运行。

间接援用

// 正确,在追踪函数内进行const disposer = autorun(() => {    console.log(message.title)    // 追踪过程    trace()})// 输入:// [mobx.trace] 'Autorun@2' tracing enabledmessage.title = "Hello"// [mobx.trace] 'Autorun@2' is invalidated due to a change in: 'ObservableObject@1.title'// 谬误,在追踪函数外进行间接援用const title = message.title;autorun(() => {    console.log(title)})message.title = "Bar"

扭转了非 observable 的援用

autorun(() => {    console.log(message.title)})message = observable({ title: "Bar" })

这将不会作出反应。message 被扭转了,但它不是 observable,它只是一个援用 observable 的变量,然而变量(援用)自身并不是可察看的。

存储 observable 对象的本地援用而不对其追踪

const author = message.author;autorun(() => {    console.log(author.name)})// 失效message.author.name = "Sara";// 生效message.author = { name: "John" };

常见陷阱: console.log

const message = observable({ title: "hello" })autorun(() => {    console.log(message)})// 不会触发从新运行message.title = "Hello world"

因为没有在 autorun 内应用。autorun 只依赖于 message,它不是 observable,而是常量。

事实上 console.log 会打印出 messagetitle,这是让人费解的,console.log 是异步 API,它只会稍后对参数进行格式化,因而 autorun 不会追踪 console.log 拜访的数据。所以,请确保始终传递不变数据 ( immutable data ) 或进攻正本给 console.log

autorun(() => {    console.log(message.title) // 很显然, 应用了 `.title` observable})autorun(() => {    console.log(mobx.toJS(message)) // toJS 创立了深克隆,从而读取音讯})autorun(() => {    console.log({...message}) // 创立了浅克隆,在此过程中也应用了 `.title`})autorun(() => {    console.log(JSON.stringify(message)) // 读取整个构造})

拜访数组

// 失效// 留神这会对数组中的任何更改做出反馈。 数组不追踪每个索引/属性(如 observable 对象和映射),而是将其作为一个整体追踪。// 但前提条件必须是提供的索引小于数组长度。autorun(() => {    console.log(message.likes.length);})message.likes.push("Jennifer");// 生效// MobX 不会追踪还不存在的索引或者对象属性(当应用 observable 映射(map)时除外)autorun(() => {    console.log(message.likes[0]);})message.likes.push("Jennifer");

应用对象的非 observable 属性

MobX 4

// 生效// MobX 只能追踪 observable 属性,下面的 postDate 还未被定义为 observable 属性。autorun(() => {    console.log(message.postDate)})message.postDate = new Date()// 能够应用 MobX 提供的 get 和 set 办法来使其工作:autorun(() => {    console.log(get(message, "postDate"))})set(message, "postDate",  new Date())

MobX 5

能够追踪还不存在的属性。留神,这只实用于由 observableobservable.object 创立出的对象。 对于类实例上的新属性,还是无奈主动将其变成 observable 的。

MobX 4 和 MobX 5 的不同之处在于后者应用了 ES6 的 proxy 来追踪属性。因而,MobX 5 只能运行在反对 proxy 的浏览器上,而 MobX 4能够运行在任何反对 ES5 的环境中。

MobX 4 的重要局限性:

  • Observable 数组并非真正的数组,所以它们无奈通过 Array.isArray() 的查看。最常见的解决办法是在传递给第三方库之前,你常常须要先对其进行 .slice() 操作,从而失去一个浅拷贝的真正数组。
  • 向一个已存在的 observable 对象中增加属性不会被主动捕捉。要么应用 observable 映射来代替,要么应用工具函数 中办法来对想要动静增加属性的对象进行读/写/迭代。

MobX 只追踪同步地拜访数据

function upperCaseAuthorName(author) {    const baseName = author.name;    return baseName.toUpperCase();}autorun(() => {    console.log(upperCaseAuthorName(message.author))})message.author.name = "Chesterton"

只管 author.name 不是在 autorun 自身的代码块中进行间接援用的。 MobX 会追踪产生在 upperCaseAuthorName 函数里的间接援用,因为它是在 autorun 执行期间产生的。

autorun(() => {    setTimeout(        () => console.log(message.likes.join(", ")),        10    )})message.likes.push("Jennifer");

autorun 执行期间没有拜访到任何 observable,而只在 setTimeout 执行期间拜访了。 通常来说,这是相当显著的,很少会导致问题。

MobX 只会为数据是间接通过 render 存取的 observer 组件进行数据追踪

一个应用 observer 的常见谬误是它不会追踪语法上看起来像 observer 父组件的数据,但实际上是由不同的组件渲染的。当组件的 render 回调函数在第一个类中传递给另一个组件时,常常会产生这种状况。

const MyComponent = observer(({ message }) =>    <SomeContainer        title = {() => <div>{message.title}</div>}    />)message.title = "Bar"

起初看上去所有仿佛都是没问题的,除了 <div> 实际上不是由 MyComponent(有追踪的渲染) 渲染的,而是 SomeContainer。 所以要确保 SomeContainer 的 title 能够正确对新的 message.title 作出反应,SomeContainer 应该也是一个 observer

如果 SomeContainer 来源于内部库的话,这通常不在你的掌控之中。在这种场景下,你能够用本人的无状态 observer 组件来包裹 div 解决此问题,或通过利用 <Observer>组件:

const MyComponent = observer(({ message }) =>    <SomeContainer        title = {() => <TitleRenderer message={message} />}    />)const TitleRenderer = observer(({ message }) =>    <div>{message.title}</div>})message.title = "Bar"

另外一种办法能够防止创立额定组件,它同样实用了 mobx-react 内置的 Observer 组件,它不承受参数,只须要单个的 render 函数作为子节点:

const MyComponent = ({ message }) =>    <SomeContainer        title = {() =>            <Observer>                {() => <div>{message.title}</div>}            </Observer>        }    />message.title = "Bar"

防止在本地字段中缓存 observable

@observer class MyComponent extends React.component {    author;    constructor(props) {        super(props)        this.author = props.message.author;    }    render() {        return <div>{this.author.name}</div>    }}

组件会对 author.name 的变动作出反应,但不会对 message 自身的 .author 的变动作出反应!因为这个间接援用产生在 render() 之外,而render()observer 组件的惟一追踪函数。 留神,即使把组件的 author 字段标记为 @observable 字段也不能解决这个问题,author 依然是只调配一次。 这个问题能够简略地解决,办法是在 render() 中进行间接援用或者在组件实例上引入一个计算属性:

@observer class MyComponent extends React.component {    @computed get author() {        return this.props.message.author    }// ...