强大的状态管理工具-Mobx

本文深入探讨MobX在React和ReactNative应用中的优势,包括其API如observable、computed、observer等,以及如何优化React组件,减少不必要的更新,提高性能。

前言

Mobx是一款精准的状态管理工具库,如果你在 React 和 React Native 应用中使用过 Redux ,那毫不犹豫地说,MobX 的简单性将成为你状态管理的不二之选,本文主要讲的是关于Mobx在React-native下的使用和常见问题。

常见API

在使用Mobx之前,首先介绍几个常见的API

1. observable

Mobx如此简单的原因之一,就是使用了可观察数据(observable Data),简单来说,可观察数据就是可以观察到数据的读取,写入,并进行拦截
Mobx中提供了observable接口来定义可观察数据,可观察数据的类型可以使基本数据类型,object,array或者ES6中的Map类型,
注意:数组经过observable包装之后,就不是Array类型了,而是Mobx中的一个特殊类型,observable类型。
虽然数据类型不一样,但是使用方式和原来使用方式一致(原始数据类型除外)

const Array =  observable([1,2,3]);
const object =  observable({name: 'Jay'});
const Map =  observable(new Map([['name','ding']]));

console.log(Array[0])  // 1
console.log(object.name)  // Jay
console.log(Map.get('name'))  // ding

@observable

装饰器可以再ES7或者TypeScript类属性中使用,将其转换成可观察的。 @observable可以再字段和属性getter上使用,对于对象的哪部分需要成为可观察对象,@observable 提供了细粒度的控制。

import { observable, computed } from "mobx";

class Order {
    @observable price = 0;
    @observable amount = 1;

    @computed get total() {
        return this.price * this.amount;
    }
}

observer

observer接收一个React-native组件作为参数,并将其转换成响应式组件


@observer export default class App extends Component  {
    render() {
        return (
            <View></View>
        )
    }
}

响应式组件,即当且仅当组件依赖的可观察对象数据发生改变时,组件才会自动相应并且重新渲染,而在传统的react-native应用中,当状态属性变化后会先调用shouldComponentUpdate,该方法会深层对比前后状态和属性是否发生改变,再确定是否更新组件。

shouldComponentUpdate是很消耗性能的,Mobx通过可观察数据,精确地知道组件是否需要更新,减少了利用shouldComponentUpdate这个方法,这是Mobx性能好的原因之一
陷阱:Mobx可以做很多事,但是它还是无法将原始数据类型转换成可观察的,所以值是不可观察的,但是对象的属性可以被观察,这意味着 @observer 实际上是对间接引用(dereference)值的反应。

computed

计算值(computed values)是可以根据现有状态或其它计算值衍生出的值,计算的耗费是不可低估的,computed尽可能帮你减少其中的耗费,它们是高度优化的。
computed values是自动帮你从你的状态(state)值和其他计算辅助值来计算的。MobX做了很多的优化。当参与计算的值没有发生改变,Computed是不会重新运行。如果参与计算的值没有被使用,Computed values是暂停的。如果Computed values不再是观察者(observed),那么在UI上也会把它除掉,MobX能自动做垃圾回收,用法如下:

class foo {
    @observable length: 2,
    @computed get squared() {
        return this.length * this.length;
    }
}

Autorun

Autorun是用在一些你想要产生一个不用观察者参与的被动调用函数里面。当autorun被使用的时候,一旦依赖项发生变化,autorun提供的函数就会被执行。与之相反的是,computed提供的函数只会在他有自己的观察员(observers)的时候才会评估是否重新执行,否则它的值被认为是无用的
综上所述:如果你需要一个自动运行但确不会产生任何新的值的结果的函数,就可以使用Autorun,其他情况可以使用computed,Autorun只是作用于如果达到某个效果或者功能,而不是计算某些值
就像 @ observer 装饰器/函数,autorun 只会观察在执行提供的函数时所使用的数据。

var numbers = observable([1,2,3]);
var sum = computed(() => numbers.reduce((a, b) => a + b, 0));
var disposer = autorun(() => console.log(sum.get()));
// 输出 '6'
numbers.push(4);
// 输出 '10'

disposer();
numbers.push(5);
// 不会再输出任何值。`sum` 不会再重新计算

action

  • 任何应用程序都有操作(action),action是任何改变状态的事物,使用Mobx
    ,可以通过标记他们在你的代码中显式的显示你的操作(action),它会更好的组织你的代码,它们用于修改可观察量或具有副作用的任何函数中。
    需要注意的是:action是用在strict mode 中的

    用法:

  • action(fn)
  • action(name, fn)
  • @action classMethod() {}
  • @action(name) classMethod () {}
  • @action boundClassMethod = (args) => { body }
  • @action(name) boundClassMethod = (args) => { body }
  • @action.bound classMethod() {}
  // 添加图片
  @action addImg = () => {
    ImagePicker.openPicker({
      multiple: true,
      waitAnimationEnd: false,
      includeExif: true,
      forceJpg: true,
      maxFiles: 9 - this.imgs.length,
      compressImageQuality: 0.5,
    }).then((images) => {
      console.log(images)
    }).catch((err) => {
      console.warn(err)
    })
  }

2.Action仅仅作用于当前运行的函数,而不是作用于当前函数调用的函数,这意味着在一些定时器或者网络请求,异步处理的情况下,它们的回调函数无法对状态改变,这些回调函数都应该包裹在action里面,但是,如果你使用了async / await的话,最好的方式应该是使用 runInAction 来让它变得更加简单

@action /*optional*/ updateDocument = async () => {
    const data = await fetchDataFromUrl();
    /* required in strict mode to be allowed to update state: */
    runInAction("update state after fetching data", () => {
        this.data.replace(data);
        this.isSaving = true;
    })
}

常见可观察类型

Observable 对象

observable.object方法将对象变为可观察的,它实际上是把对象的所有属性转换为可观察的,存放到一个代理对象上,以减少对原对象的污染,默认情况下,observable是递归应用的,所以如果对象的某个值是一个对象或数组,那么该值也将通过 observable 传递

import {observable, autorun, action} from "mobx"

var person = observable({
    name : 'jack',
    age:24,
    sex:'男'
})

Observable 数组

与对象类似,可以使用Observable.array(array)或者将数组传给 observable,可以将数组转换成可观察的,这也是递归的,所以数组中的所有(未来的)值都会是可观察的。

import {observable, autorun} from "mobx";
var todos = observable([
    { title: "Spoil tea", completed: true },
    { title: "Make coffee", completed: false }
]);
autorun(() => {
    console.log("Remaining:", todos
        .filter(todo => !todo.completed)
        .map(todo => todo.title)
        .join(", ")
    );
});
// 输出: 'Remaining: Make coffee'

todos[0].completed = false;
// 输出: 'Remaining: Spoil tea, Make coffee'

todos[2] = { title: 'Take a nap', completed: false };
// 输出: 'Remaining: Spoil tea, Make coffee, Take a nap'

todos.shift();
// 输出: 'Remaining: Make coffee, Take a nap'

除了可以使用所有的内置函数,observable数组还提供了好多方法供我们使用

  • clear() -- 从数组中删除所有项
  • replace(newItems) -- 用新元素替换数组中所有已存在的元素
  • remove(value) -- 通过值从数组中移除一个单个的元素。

注意:

不同于sort和reverse函数的实现,observableArray.sort 和 observableArray.reverse 不会改变数组本身,而只是返回一个排序过/反转过的拷贝,在 MobX 5 及以上版本中会出现警告。推荐使用 array.slice().sort() 来替代。

Observable Map

与数组的处理方式类似,Mobx也实现了一个ObservableMap类,不过只支持字符串。数字或Bool值作为键,ObservableMap在可观察对象的基础上,还要使键的增删可观察。它可以看做两个可观察映射和一个可观察数组的组合:

import {observable, autorun} from "mobx"
const map = observable(new Map());

autorun(() => {
  console.log(map.get('key'));
});

map.set('key', 'value'); // 新增 key-value 键值对,输出 value
map.set('key', 'anotherValue'); // 修改为 key-anotherValue,输出 anotherValue
map.set('prop', 'value'); // 不输出
map.delete('prop'); // 不输出

优化React 组件

避免在父组件中访问子组件的属性

在文档中也有提到过这个问题,Mobx对于一个observer组件,是通过访问属性来访问以来的,所以哪怕父组件里没有用到这个属性,只是为了作为props传给子组件,Mobx还是会算它依赖了这个属性,于是会产生不必要的更新,最好的方式是把数据统一放到Store中,子组件通过 inject store 方式获取数据。

小组件

由于React的机制,Mobx只能在组件层发光发热,对于组件内部就是无能为力了,所以大组件很容易卡死,小组件才能真正发挥Mobx的优势。

在专用组件中渲染列表

React在渲染大型数据集合的时候处理的很不好,因为协调器必须评估每个集合变化的集合所产生的组件。因此,建议使用专门的组件来映射集合并渲染这个组件,且不再渲染其他组件:

官方提供的demo如下:

不妥的处理方式:

@observer class MyComponent extends Component {
    render() {
        const {todos, user} = this.props;
        return (<div>
            {user.name}
            <ul>
                {todos.map(todo => <TodoView todo={todo} key={todo.id} />)}
            </ul>
        </div>)
    }
}

在示例中,当user.name发生变化时React 会不必要地协调所有的 TodoView 组件。尽管TodoView 组件不会重新渲染,但是协调的过程本身是非常昂贵的。

正确的处理方式:

@observer class MyComponent extends Component {
    render() {
        const {todos, user} = this.props;
        return (<div>
            {user.name}
            <TodosView todos={todos} />
        </div>)
    }
}

@observer class TodosView extends Component {
    render() {
        const {todos} = this.props;
        return <ul>
            {todos.map(todo => <TodoView todo={todo} key={todo.id} />)}
        </ul>)
    }
}

在react-native使用Mobx常见的问题

observable 数组的类型是对象

observable 数组类型其实是个对象,所以它遵循propTypes.object ,如果使用propTypes.array 会报错。mobx-react 为 observable 数据结构提供了明确的 PropTypes。

在 React Native 中渲染 ListView

React Native 的的DataSource只能接收真正的数组,但是observable 数组是个对象,所以在传给ListView之前使用.slice方法,此外,ListView.DataSource 本身可以移到 store 之中并且使用 @computed 自动地更新,这步操作同样可以在组件层完成

class ListStore {
  @observable list = [
    'Hello World!',
    'Hello React Native!',
    'Hello MobX!'
  ];

  ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });

  @computed get dataSource() {
    return this.ds.cloneWithRows(this.list.slice());
  }
}

const listStore = new ListStore();

@observer class List extends Component {
  render() {
    return (
      <ListView
        dataSource={listStore.dataSource}
        renderRow={row => <Text>{row}</Text>}
        enableEmptySections={true}
      />
    );
  }
}

原文http://techblog.sishuxuefu.com/atricle.html?5bb9f17a0b6160006f5988bb

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值