Mobx 与 Hook 的摸索

4,911 阅读6分钟

一.运用Mobx的步骤

mobx看上去api非常丰富,但使用 MobX 将一个应用(不仅仅是react应用)变成响应式的可归纳为以下三个步骤:

1.定义状态并使其可以观察

使用observable定义一个观察的对象,该对象可以是数据类型、引用类型、普通对象、类实例、数组和映射。例如观察一个对象,它包含属性值和方法:

let appState = observable({
    timer: 10,
    get getTimer() {
      return this.timer
    },
    setTimer(timer: number) {
      this.timer = timer
    },
    resetTimer() {
      this.timer = 0
    }
  }
)

mobx还提供了装饰器的方式@observable,它适用于ES7或TS类中的属性使用。例如:

@observable name = 0;
@observable obj = {
  name1: 'bobo',
  name2: 'lili'
}

基于以上两种方式,一个被观察的内容就定义出来了。

2.创建视图以响应状态的变化

(1)使用@computed响应

@computed能自动的对现有状态做出响应(类似与vue的computed),例如下面代码能自动对appState.timer的变化做出响应:

@computed get getTimer () {
    return this.props.appState.timer * -1
}

(2)使用(@)observer响应

(a)在react class component中

@computed仅仅可以作用在类属性的方法上,如果我想将一个组件转换为响应式组件,那么 (@)observer能在依赖的数据改变后强制刷新组件(注意:observermobx-react独立提供)。@observerobserver(class Timer ... { })的效果是一致的,但是前者更简明。下面这个例子首先定义了一个可观察对象timerData, 然后将Timer组件作为观察者对timerData做出响应。

import {observer} from "mobx-react";

var timerData = observable({
    secondsPassed: 0
});

setInterval(() => {
    timerData.secondsPassed++;
}, 1000);

@observer class Timer extends React.Component {
    render() {
        return (<span>Seconds passed: { this.props.timerData.secondsPassed } </span> )
    }
};

ReactDOM.render(<Timer timerData={timerData} />, document.body);
(b)在无状态组件中

在无状态组件中,@observer不再适用,但observer仍然适用:

import {observer} from "mobx-react";

const Timer = observer(({ timerData }) =>
    <span>Seconds passed: { timerData.secondsPassed } </span>
);

3.更改状态

使用(@)action能够显示的把更改操作标记出来,当然不使用(@)action也可以对状态做出更改。使用(@)action具有更高的性能,因为在更改动作完成之前,状态的改变在外界是不可见的。

二.mobx+hook如何工作

结合mobx与hook,你需要使用上mobx-react-lite

1.跑通mobx+hook

先看一个错误的例子:

import {observer} from 'mobx-react-lite'
import {observable} from "mobx";
import React from 'react'

export default observer(() => {
    const store = observable({
       timer: 0
    });
    function add() {
        store.timer += 1
        console.log('当前的timer---->', store.timer)
    }
    return (
        <div className="App">
            <h2 onClick={() => add()}>Now time is {store.timer}</h2>
        </div>
    )
})

执行上面的代码,当点击h2标签后,发现页面内容不发生改变,控制台输出相同的内容:

页面内容
控制台信息
当看见上述的错误之后,就应该明白是hook function 的特性:capture Value :每次render的内容都会形成一个快照保持下来,每个render 的props 与 State固定不变。

mobx-react-lite里面提供了一个useObserable方法,使用它能对视图进行更新。按下列的写法就能得到一个正确的结果:

import {observer, useObservable} from 'mobx-react-lite'
import React from 'react'

export default observer(() => {
    const store = useObservable({
       timer: 0
    });
    function add() {
        store.timer += 1
        console.log('当前的timer---->', store.timer)
    }
    return (
        <div className="App">
            <h2 onClick={() => add()}>Now time is {store.timer}</h2>
        </div>
    )
})

但是,使用useObserable不是唯一的解决方式,mobx-react-lite里面提供了一个Obserable组件也能更新所包裹的内容:

import {observer, Observer} from 'mobx-react-lite'
import {observable} from 'mobx'
import React from 'react'

export default observer(() => {
    const store = observable({
        timer: 0
    });
    function add() {
        store.timer += 1
        console.log('当前的timer---->', store.timer)
    }
    return (
      <div className="App">
          <Observer>
              {() => <h2 onClick={() => add()}>Now time is{store.timer}</h2>}
          </Observer>
      </div>
    )
})

也许你注意到了useObserver了,它也同样能给你提供一个正确的结果:

import {observer, useObserver,useObservable} from 'mobx-react-lite'
import {observable} from 'mobx'
import React from 'react'

export default (() => {
    const store = useObservable({
        timer: 0
    });

    function add() {
        store.timer += 1
        console.log('当前的timer---->', store.timer)
    }

    return (
      useObserver(() => (
        <div className="App">
            <h2 onClick={() => add()}>Now timer is{store.timer}</h2>
        </div>
      ))
    )
})

接下来再看一个错误的例子,大家看看得到的结果是什么,为什么会这样?

import {useObservable} from 'mobx-react-lite'
import React from 'react'

export default (() => {
    const store = useObservable({
        timer: 0
    });
    function add() {
        store.timer += 1
        console.log('当前的timer---->', store.timer)
    }
    return (
      <div className="App">
          <h2 onClick={() => add()}>Now time is {store.timer}</h2>
      </div>
    )
})

当点击页面内容后发现控制台能正确输入:

但是页面展示内容却一直为:

这有点出乎意料,但是思考之后应该能发现问题。useobervable能正确的得到当前的结果,但是没有一个渲染机制能将结果重新渲染到页面之上。这个错误的例子和本节第一个错误的例子有类似之处,第一个是没有得到正确的当前值,这个例子是没有将正确结果渲染出来。 为了证明我的说法,使用useEffect能证明页面有没有重新更新,因为useEffect能在每次更新的时候执行,下列代码中useEffect的函数并没有每次点击后执行:

import {useObservable} from 'mobx-react-lite'
import React, {useEffect} from 'react'

export default (() => {
    const store = useObservable({
        timer: 0
    });
    useEffect(() => {
        console.log('页面进行了重新渲染') //点击后没有执行
    })
    function add() {
        store.timer += 1
        console.log('当前的timer---->', store.timer)
    }
    return (
      <div className="App">
          <h2 onClick={() => add()}>Now time is {store.timer}</h2>
      </div>
    )
})

那么怎么修改上方代码才能让页面渲染呢,你可能已经想出了一系列方法。但是万变不离其宗,你需要一个创建响应视图,如上方的正确代码,下面是另一种写法,使用observer响应timer的变化:

import {useObservable, observer} from 'mobx-react-lite'
import React from 'react'

export default (() => {
    const store = useObservable({
        timer: 0
    });
    function add() {
        store.timer += 1
        console.log('当前的timer 是---->', store.timer)
    }
    const ObserverH2 = observer(() =>
      <h2 onClick={() => {add()}}>Now timer is {store.timer}</h2>
    )
    return (<ObserverH2/>)
})

2.useComputed

与computed类似,作为计算属性,它能自动监听依赖的变化:

 let computedTimer = useComputed(() => {
        return store.timer
    }, [store])

3.useLocalStore

使用useLocalStore可以将一些内容封装在一个根对象中,借助React Context将其传递给应用程序。

import {useObserver,useLocalStore} from 'mobx-react-lite'
import React from 'react'

export default (() => {
    const store = useLocalStore(() => ({
        timer: 0
    }));
    function add() {
        store.timer += 1
        console.log('当前的timer---->', store.timer)
    }
    return (
      useObserver(() => (
        <div className="App">
            <h2 onClick={() => add()}>Now timer is{store.timer}</h2>
        </div>
      ))
    )
})

上面的useLocalStoreuseObservable用法完全一致,但是这不是useLocalStore的合理用法,借助react的contex,可以将store的内容共享。这里有一份官方的迁移文档,里面有更详细的用法。

总结

本文先是介绍Mobx的使用方法,然后又在react hook中尝试使用,由于初次使用Mobx,错误之处,敬请原谅。

上一篇:react hook 初体验