Mobx 我的理解

作者: 水落斜阳 | 来源:发表于2018-11-29 13:40 被阅读1次

    什么是MobX
    官方图


    image.png

    网图


    安装

    1.安装MobX:yarn add mobx --save

    2.安装React绑定库:yarn add mobx-react --save

    3.启用装饰器语法(能够使用@标签)

    第一步:

    yarn add babel-plugin-transform-decorators-legacy babel-preset-react-native-stage-0 --save-dev
    

    第二步:在.babelrc文件中修改为

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

    可以理解为被观察者对象,其值可以是JS基本数据类型、引用类型、普通对象、类实例、数组和映射。
    注意:如果value是没有原型的对象(例如:{key: 'key', value: 'value'}普通对象是指不是使用构造函数创建出来的对象,而是以 Object 作为其原型,或者根本没有原型。 ),则该对象会被克隆,并且其所有的属性都会被转换为可观察的;如果value是有原型的对象(例如:new MyClass()),MobX 不会将一个有原型的对象自动转换成可观察的,在该类中的构造函数中使用extendObservable或在类定义上使用 @observable / decorate

    对observable的变化做出响应

    类似于Vue中的计算属性,根据现有被观察的对象计算衍生出新的值。

    import {observable, computed} from "mobx"
    
    class Cart {
        @observable price = 0;
        @observable amount = 1;
    
        constructor(price) {
            this.price = price;
        }
    
        @computed get total() {
            return this.price * this.amount;
        }
    }
    

    若前一个计算中使用的数据没有更改,计算属性将不会重新运行;当其有观察者的时候才会重新计算;如果一个计算属性不在被观察了(使用它的UI被销毁了),Mobx会自动将其回收。

    2.autorun

    不需要被观察者观察,创建的时候被触发一次,然后每次它的依赖关系改变时会再次被触发 ,常用作打印日志,更新UI等。 autorun 中的值必须要手动清理才行 ,传递给 autorun 的函数在调用后将接收一个参数,即当前 reaction(autorun),可用于在执行期间清理 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'
    
    // 清理autorun 
    disposer();
    numbers.push(5);
    // 不会再输出任何值。`sum` 不会再重新计算。
    
    3.observer

    observer 是由单独的 mobx-react 包提供的 ,observer 函数/装饰器可以用来将 React 组件转变成响应式组件

    import {
      observable,
      computed,
      autorun,
      action
    } from 'mobx'
    
    class AppState {
    
      @observable timer = 0
    
      // 注意这里不能调用super(props),否则报错
      constructor (props) {
        setInterval(() => {
          this.timerIncreat(this.timer)
        }, 1000)
      }
    
      @action timerIncreat (time) {
        this.timer = time += 1
      }
    
      // 重置计数器
      @action resetTimer () {
        this.timer = 0
      }
    }
    
    export default AppState
    
    import React, {Component} from 'react'
    import {View, StyleSheet} from 'react-native'
    import {Button, Label} from 'teaset'
    
    import AppState from '../mobx/AppState'
    import { observer } from 'mobx-react'
    
    // 实例化,也可在导出的时候实例化
    const appState = new AppState()
    
    // 这里必须要写,不然监听不到值的变化
    @observer
    export default class MobxDemo1 extends Component {
    
      static navigationOptions = ({navigation, screenProps}) => ({
          headerTitle: 'Mobx练习一'
      })
    
      render () {
        return (
          <View style={styles.container}>
            <Label style={styles.welcome} text="计数器的一个Mobx例子"/>
            <View style={{flexDirection: 'row', justifyContent: 'space-around', marginTop: 40}}>
              <Label style={styles.welcome} text={`当前的值是:${appState.timer}`}/>
              <Button type='primary' size='md' title='重置' onPress={this.onReset}/>
            </View>
          </View>
        )
      }
    
      onReset () {
        appState.resetTimer()
      }
    }
    
    const styles = StyleSheet.create({
      container: {
          flex: 1,
          // justifyContent: 'center',
          backgroundColor: 'white',
          alignItems: 'center'
      },
      welcome: {
          marginTop: 20,
          fontSize: 20, 
      }
    })
    
    改变observable的值
    1. action

    顾名思义就是动作,所有被观察的对象被修改都应该通过动作函数来进行修改

    用法有以下几种:
    action(fn)
    action(name, fn)
    @action classMethod() {}
    @action(name) classMethod () {}
    @action boundClassMethod = (args) => { body }
    @action(name) boundClassMethod = (args) => { body }
    @action.bound classMethod() {}
    

    2.异步action
    action只能影响正在运行的函数,而无法影响当前函数调用的异步操作 。action 包装/装饰器只会对当前运行的函数作出反应,而不会对当前运行函数所调用的函数(不包含在当前函数之内)作出反应 ,也就是说promise的then或async语句,并且在回调函数中某些状态改变了,这些回调函数也应该包装在action中。
    (1)第一种方案,使用action关键字来包装promises的回调函数。
    // 第一种写法

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

    // 第二种写法

     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 function来处理业务,那么我们可以使用runInAction这个API来解决之前的问题 。

    import {observable, action, useStrict, runInAction} from 'mobx';
    useStrict(true);
    
    class Store {
      @observable name = '';
      @action load = async () => {
        const data = await getData();
        // await之后,修改状态需要动作
        runInAction(() => {
          this.name = data.name;
        });
      }
    }
    
    1. flows
      然而,更好的方式是使用 flow 的内置概念。它们使用生成器。一开始可能看起来很不适应,但它的工作原理与 async / await 是一样的。只是使用 function * 来代替 async,使用 yield 代替 await 。 使用 flow 的优点是它在语法上基本与 async / await 是相同的 (只是关键字不同),并且不需要手动用 @action 来包装异步代码,这样代码更简洁。

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

    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"
            }
        })
    }
    
    实际中的运用

    跨组件交互
    在不使用其它框架、类库的情况下,React要实现跨组件交互这一功能相对有些繁琐。通常我们需要在父组件上定义一个state和一个修改该state的函数。然后把state和这个函数分别传到两个子组件里,在逻辑简单,且子组件很少的时候可能还好,但当业务复杂起来后,这么写就非常繁琐,且难以维护。而用Mobx就可以很好地解决这个问题。来看看以下的例子:

    class MyState {
      @observable num1 = 0;
      @observable num2 = 100;
    
      @action addNum1 = () => {
        this.num1 ++;
      };
      @action addNum2 = () => {
        this.num2 ++;
      };
      @computed get total() {
        return this.num1 + this.num2;
      }
    }
    
    const newState = new MyState();
    
    const AllNum = observer((props) => <div>num1 + num2 = {props.store.total}</div>);
    
    const Main = observer((props) => (
      <div>
        <p>num1 = {props.store.num1}</p>
        <p>num2 = {props.store.num2}</p>
        <div>
          <button onClick={props.store.addNum1}>num1 + 1</button>
          <button onClick={props.store.addNum2}>num2 + 1</button>
        </div>
      </div>
    ));
    
    @observer
    export default class App extends React.Component {
    
      render() {
        return (
          <div>
            <Main store={newState} />
            <AllNum store={newState} />
          </div>
        );
      }
    }
    
    

    有两个子组件,Main和AllNum (均采用无状态函数的方式声明的组件)
    在MyState中存放了这些组件要用到的所有状态和函数。
    之后只要在父组件需要的地方实例化一个MyState对象,需要用到数据的子组件,只需要将这个实例化的对象通过props传下去就好了。

    那如果组件树比较深怎么办呢?
    我们可以借助React15版本的新特性context来完成。它可以将父组件中的值传递到任意层级深度的子组件中。
    总结
    Mobx想要入门上手可以说非常简单,只需要记住少量概念并可以完成许多基础业务了。但深入学习下去,也还是要接触许多概念的。例如Modifier、Transation等等。
    最后与Redux做一个简单的对比

    Mobx写法上更偏向于OOP
    对一份数据直接进行修改操作,不需要始终返回一个新的数据
    对typescript的支持更好一些
    相关的中间件很少,逻辑层业务整合是一个问题

    相关文章

      网友评论

        本文标题:Mobx 我的理解

        本文链接:https://www.haomeiwen.com/subject/xjpxcqtx.html