Mobx在ReactNative中的使用

MobX

简单、可扩展的状态管理
Github 仓库: https://github.com/SangKa/mobx-docs-cn

入门

MobX 是一个经过战火洗礼的库,它通过透明的函数响应式编程(transparently applying functional reactive programming - TFRP)使得状态管理变得简单和可扩展。MobX背后的哲学很简单:

任何源自应用状态的东西都应该自动地获得。

其中包括UI、数据序列化、服务器通讯,等等。

这里写图片描述

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

对于应用开发中的常见问题,React 和 MobX都提供了最优和独特的解决方案。React 提供了优化UI渲染的机制, 这种机制就是通过使用虚拟DOM来减少昂贵的DOM变化的数量。MobX 提供了优化应用状态与 React 组件同步的机制,这种机制就是使用响应式虚拟依赖状态图表,它只有在真正需要的时候才更新并且永远保持是最新的。

和Redux是一个性质的东西,但是使用上比redux要简单许多,就连redux的作者也曾经向大家推荐过它,基本可以使用Mobx来替代掉redux。

核心概念

MobX 的核心概念不多。 下面的代码片段可以在 JSFiddle (或者 不使用 ES6 和 JSX)中在线试用。

Observable state(可观察的状态)

MobX 为现有的数据结构(如对象,数组和类实例)添加了可观察的功能。 通过使用 @observable 装饰器(ES.Next)来给你的类属性添加注解就可以简单地完成这一切。

class Todo {
id = Math.random();
@observable title = “”;
@observable finished = false;
}

使用 observable 很像把对象的属性变成excel的单元格。 但和单元格不同的是,这些值不只是原始值,还可以是引用值,比如对象和数组。 你甚至还可以自定义可观察数据源。

插曲: 在ES5、ES6和ES.next环境下使用 MobX

这些 @ 开头的东西对你来说或许还比较陌生,它们是ES.next装饰器。 在 MobX 中使用它们完全是可选的。参见装饰器文档详细了解如何使用或者避免它们。 MobX 可以在任何ES5的环境中运行,但是利用像装饰器这样的ES.next的特性是使用 MobX 的最佳选择。 本自述文件的剩余部分都会使用装饰器,但请牢记,它们是可选的。

例如,上面一段代码的ES5版本应该是这样:

function Todo() {
this.id = Math.random()
extendObservable(this, {
title: “”,
finished: false
})
}

Computed values(计算值)

使用 MobX, 你可以定义在相关数据发生变化时自动更新的值。 通过@computed 装饰器或者利用 (extend)Observable 时调用 的getter / setter 函数来进行使用。

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

当添加了一个新的todo或者某个todo的 finished 属性发生变化时,MobX 会确保 unfinishedTodoCount 自动更新。 像这样的计算可以类似于 MS Excel 这样电子表格程序中的公式。每当只有在需要它们的时候,它们才会自动更新。

Reactions(反应)

Reactions 和计算值很像,但它不是产生一个新的值,而是会产生一些副作用,比如打印到控制台、网络请求、递增地更新 React 组件树以修补DOM、等等。 简而言之,reactions 在 响应式编程和命令式编程之间建立沟通的桥梁。

React 组件

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

import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import {observer} from 'mobx-react';
@observer
class 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'));

observer 会将 React (函数)组件转换为它们需要渲染的数据的衍生。 使用 MobX 时没有所谓的智能和无脑组件。 所有的组件都会以巧妙的方式进行渲染,而只需要一种简单无脑的方式来定义它们。MobX 会确保组件总是在需要的时重新渲染,但仅此而已。所以上面例子中的 onClick 处理方法会强制对应的 TodoView 进行渲染,如果未完成任务的数量(unfinishedTodoCount)已经改变,它将导致 TodoListView 进行渲染。 可是,如果移除 Tasks left 这行代码(或者将它放到另一个组件中),当点击 checkbox 的时候 TodoListView 就不再重新渲染。你可以在 JSFiddle 中自己动手来验证这点。

自定义 reactions

使用autorun、reaction 和 when 函数即可简单的创建自定义 reactions,以满足你的具体场景。

例如,每当 unfinishedTodoCount 的数量发生变化时,下面的 autorun 会打印日志消息:

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

MobX 会对什么作出反应?

为什么每次 unfinishedTodoCount 变化时都会打印一条新消息?答案就是下面这条经验法则:

MobX 会对在执行跟踪函数期间读取的任何现有的可观察属性做出反应。

想深入了解 MobX 是如何知道需要对哪个可观察属性进行反应,请查阅 理解 MobX 对什么有反应。

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 这个可选的内置概念。 使用 actions 是有优势的: 它们可以帮助你把代码组织的更好,还能在状态何时何地应该被修改这个问题上帮助你做出明智的决定。

MobX: 简单且可扩展

MobX 是状态管理库中侵入性最小的之一。这使得 MobX的方法不但简单,而且可扩展性也非常好:

使用类和真正的引用

使用 MobX 不需要使数据标准化。这使得库十分适合那些异常复杂的领域模型(以 Mendix 为例: 一个应用中有大约500个领域类)。

保证参照完整性

因为数据不需要标准化,所以 MobX 会自动跟踪状态和衍生之间的关系,你可以免费获得参照完整性。渲染通过三级间接寻址访问的数据?

没有问题,MobX 会跟踪它们,一旦其中一个引用发生了变化,就会重新渲染。作为回报,陈年的老bug已不复存在。作为一个程序员,你可能记不住修改的一些数据可能会影响到的某个角落里看起来毫不相关的组件,但 MobX 不会。

更简单的 actions 更便于维护

正如上面所演示的,使用 MobX 修改状态是非常简单的。你只需简单的写出你的目的。MobX 会替你处理好剩下的事情。

细粒度的可观测性是高效的

MobX 构建应用中所有衍生的图形,以找到保持最新状态所需的重新计算的最少次数。“衍生一切”或许听上去开销很昂贵,但 MobX 构建虚拟衍生图以保持衍生与状态同步所需的重计算的数量最小化。

事实上,在 Mendix 测试 MobX 时我们发现使用这个库跟踪代码中的关系通常会更有效,然后通过使用手写事件或基于容器组件的“智能”选择器来推送更改。

原因来说,是因为 MobX 会在数据上建立更细粒度的“监听器”,然后你可以通过程序来控制。

其次, MobX 看到衍生之间的因果关系,因此它可以为衍生排序,使得衍生不会运行多次或引入缺陷。

想了解这是如何工作的? 请参见 深入剖析 MobX。

易操作性

MobX 使用原生 javascript 。由于它的侵入性不强,它可以和绝大部分 javascript 库共同使用,而不需要特定的 MobX 风格库。

所以你可以继续使用你的路由,数据获取和工具库,比如react-router、 director、 superagent、 lodash,等等。

出于同样的原因,你可以在服务器端和客户端使用它,也可以在 react-native 这样的同构应用中使用。

结论就是: 相比其它状态管理解决方案,当使用 MobX 时通常只需学习更少的新概念。

用法 结合reactnative

这里写图片描述

MObX环境配置:

1.npm i mobx mobx-react –save //引入mobx
2.npm i babel-plugin-transform-decorators-legacy babel-preset-react-native-stage-0 –save-dev //能够使用@标签
3.在.babelrc文件中修改为{
“presets”: [“react-native”],
“plugins”: [“transform-decorators-legacy”]
}

mobx常用的标签

@observable : 使用此标签监控要检测的数据;
@observer: 使用此标签监控当数据变化是要更新的Component(组件类)
autorun : 当观测到的数据发生变化的时候,如果变化的值处在autorun中,那么autorun就会自动执行。
@action : 使用此标签监控数据改变的自定义方法(当在需要数据改变的时候执行此自定义方法,那么View层也会跟着自动变化,默认此View层已经使用@observer标签监控)
useStrict : 使用严格模式
@computed : 计算值(computed values)是可以根据现有的状态或其它计算值衍生出的值,可以定义在相关数据发生变化时自动更新的值.

新建一个AppState的js文件,用来管理app中的数据.

 * Created by 卓原 on 2017/11/30.
 * zhuoyuan93@gmail.com
 */


import {observable, autorun, computed, action, useStrict, comparer} from 'mobx';
import {Toast} from 'teaset';

useStrict(true);

class AppState {

    @observable timer = 0;   //数字 observable可以用来观测一个数据

    constructor(props) {

        //计算属性——computed
        this.plus = computed(() => comparer.identity(this.timer, 0));  //判断this.timer是否===0  返回 true/false

        /**
         * 当观测到的数据发生变化的时候,如果变化的值处在autorun中,那么autorun就会自动执行。
         * 每当this.plus变化的时候会调用
         * 1->∝的时候 始终为flase 所以 不会触发, -1->∝同理 ,只有在 -1 0 1 之间改变时才会触发this.plus的变化 所以会有toast弹出
         */
        autorun(() => {
            // alert(this.plus)
            Toast.message(this.plus + '', {position: 'bottom'});
        })

    }

    //计算 适用于购物车, 商品数量乘以单价
    @computed
    get count() {
        return this.timer * 2
    }


    // 重置计数器
    @action('重置计数器')
    resetTimer() {
        this.timer = 0;
    }

    @action('增加')
    addTimer() {
        this.timer++
    };

    @action('减少')
    subtractTimer() {
        this.timer--
    }


}

const appState = new AppState();
export default appState;

如上代码所示, 我们建了一个类, 里面有一个timer用来统计次数 , 有3个方法(增加,减少,重置) 来控制它的值变化.

this.plus = computed(() => comparer.identity(this.timer, 0));  //判断this.timer是否===0  返回 true/false

这个comparer是mobx提供的,用来对比两个对象是否是===的关系的, 返回true or false.
computed就是在变化的时候执行这个. 用this.plus来接收.

下面的
autorun(() => {
// alert(this.plus)
Toast.message(this.plus + ”, {position: ‘bottom’});
})
是当this.plus变化的时候会执行里面的方法. 这里是弹出一个toast.

上面有一个
@computed
get count() {
return this.timer * 2
}
适用于购物车场景, 2可以改成商品数量,this.timer就是单价, 如此可以得到总价,每次有变化的时候就会自动执行这个方法,得到最新的总价.

可以看到, 我的方法每一个前面都加了@action , 其实不加也可以运行的.
但是我上面 :
useStrict(true);
这个是用了严格模式, 不加@action 会报错
推荐使用严格模式, 在修改数据的方法前面加上@action . 能知道是哪个地方修改的.
最后导出一个appstate的实例.

然后看一下 我们的页面使用的时候:
在index或者index引入的app中写如下代码:

/**
 * Created by 卓原 on 2017/11/30.
 * [email protected]
 */
import React, {Component} from 'react';
import {
    StyleSheet,
    Text,
    View,
    TouchableOpacity
} from 'react-native';


import APPState from './mobx/AppState';
import {observer} from 'mobx-react';
import {observable,action} from 'mobx';

// 这里必须要写的,不然不能监听值的变化
@observer
export default class Appme extends Component {

    @observable b = 1;

    render() {
        return (
            <View style={styles.container}>
                <Text style={styles.welcome}>
                    计数器的一个Mobx例子
                </Text>
                <Text style={styles.welcome}>
                    当前值乘2 = {APPState.count}
                </Text>
                <View style={{flexDirection: 'row', justifyContent: 'space-around', marginTop: 40}}>
                    <Text>
                        当前的值是: {APPState.timer}
                    </Text>
                </View>
                <View style={{flexDirection: 'row', justifyContent: 'space-around', marginTop: 40}}>
                    <TouchableOpacity onPress={() => {
                        APPState.addTimer();
                        this.addb();
                    }}>
                        <Text style={{backgroundColor: 'green', color: 'white', fontSize: 20}}>
                            增加
                        </Text>
                    </TouchableOpacity>
                    <TouchableOpacity onPress={() => {
                        APPState.subtractTimer()
                    }}>
                        <Text style={{backgroundColor: 'green', color: 'white', fontSize: 20}}>
                            减少
                        </Text>
                    </TouchableOpacity>
                    <TouchableOpacity onPress={() => {
                        APPState.resetTimer()
                    }}>
                        <Text style={{backgroundColor: 'green', color: 'white', fontSize: 20}}>
                            重置
                        </Text>
                    </TouchableOpacity>
                </View>
            </View>
        )
    }


    @action addb(){
        this.b++
    }
}


const styles = StyleSheet.create({
    container: {
        flex: 1,
        // justifyContent: 'center',
        backgroundColor: 'white',
        // alignItems: 'center'
    },
    welcome: {
        marginTop: 20,
        fontSize: 20,
    }
});

引用的值是APPstate里的timer, 修改的时候也是用的appstate的方法 , 这样其他地方引用的时候就达到了统一 .

github项目地址

猜你喜欢

转载自blog.csdn.net/u011272795/article/details/78677954