mobx用法

Mobx

关于Mobx

1.关于Mobx和Redux

Mobx和Redux一样,是React状态管理比较流行的解决方案之一。和Redux的原理不一样的是,Mobx早期参考了vue,使用了响应式编程,而React+Mobx的选型就是一个复杂版的Vue。
Mobx工作流程:在这里插入图片描述
Redux工作流程:
在这里插入图片描述
Mobx的流程相比Redux而言,非常简洁也很容易上手,性能也相对更好。
react以及redux的各种hooks本身的特性以及hooks带来的各种新的状态管理库确实写起来非常顺手,但是hooks不能完全代替class组件,在大型项目里,Mobx依然是个不错的选择。

2.关于Mobx版本

mobx6版本之前大量使用了装饰器语法,但是在6.x之后抛弃了装饰器语法。目前最高版本就是6.x版本。
原因是当时装饰器语法还没有纳入到ES标准之中,标准化的过程还需要很长时间,而且未来制定的标准可能与现在的方案有所不同。处于兼容性的考虑,Mobx6中放弃了装饰器语法。
目前在ES7中已经加入了装饰器语法,装饰器语法确实非常简洁。
Mobx响应式对象的原理参考了VUE,在6.x之后使用了Proxy代理,在6.0之前使用的是Object.defineProperty API,Mobx在6.x之后提供了降级方案,可以在configure配置中设定。

Mobx5最好配合React16.x使用,搭配React17及以上会报错。

3.Mobx的核心概念
概念用法
observable state被mobx跟踪的状态
computed根据应用程序状态派生的新值,计算值
action允许修改状态的方法,严格模式下只有action方法被允许修改状态
observer视图组件必须通过observer方法包裹

三个常用的库

功能
mobx核心库
mobx-react支持函数式组件以及class组件
mobx-react-litemobx-react的轻量版,只支持hooks

二 Mobx6.x的用法

1. 基于Mobx6.x以及react的useContext的例子。

这里用的是mobx核心库以及mobx-react,项目结构如下:
在这里插入图片描述
Counter.ts

import { makeAutoObservable } from 'mobx';
 
class Counter {
  count: number = 0;
 
  constructor() {
    makeAutoObservable(this);
  }
 
  increase() {
    this.count += 1;
  }
 
  decrease() {
    this.count -= 1;
  }
 
  get double() {
    return this.count * 2;
  }
 
}
 
export default Counter;

stores/index.ts

import React from 'react';
 
import Counter from './Counter';
import Themes from './Themes';
 
const stores = React.createContext({
  counter: new Counter(),
  themes: new Themes(),
});
 
export default stores;

hooks/useStores.ts

import React from 'react';
import stores from '../stores';
 
const useStores = () => React.useContext(stores);
 
export default useStores;

Pane.tsx

import React, { useEffect } from 'react';
import { Row, Col, Button } from 'antd';
import { PlusOutlined, MinusOutlined } from '@ant-design/icons';
import { observer } from 'mobx-react-lite';
 
import useStores from './hooks/useStores';
 
type PaneProps = React.HTMLProps<HTMLDivElement> & {
  name?: string;
}
 
const Pane: React.FC<PaneProps> = ({ name, ...props }) => {
  const stores = useStores();
 
  return (
    <div {...props}>
      {name && <h2>{name}</h2>}
      <Row align="middle">
        <Col span="4">Count</Col>
        <Col span="4">{stores.counter.count}</Col>
        <Col>
          <Button
            type="text"
            icon={<PlusOutlined />}
            onClick={() => stores.counter.increase()}
          />
          <Button
            type="text"
            icon={<MinusOutlined />}
            onClick={() => stores.counter.decrease()}
          />
 
          <Button
            type="text"
            onClick={() => stores.counter.calcCount()}
          >异步</Button>
        </Col>
      </Row>
    </div>
  );
};
 
Pane.defaultProps = { name: undefined };
 
export default observer(Pane);
2.常用的API

以上的代码只涉及到两个API,Mobx的makeAutoObservable和mobx-react-lite的observer。

Mobx通过makeAutoObservable方法来构造响应式对象,传入的对象属性会通过Proxy进行代理。

makeAutoObservable是makeObservable的智能版,能自动推断所有属性,规则如下:

  • 所有属性都成为observable;
  • 所有getters都成为computed;
  • 所有setters都成为action;
  • 所有prototype中的functions都成为autoAction;

除了通过makeAutoObservable简化处理之外,Mobx还有其他api可以逐个定义。

Mobx常用的API:

  • observable
  • action
  • computed
  • makeObservable

stores/Themes.ts

import { action, makeObservable, observable, computed } from 'mobx';
 
export default class Themes {
    color = 'red';
    constructor() {
        makeObservable(this, {
            color: observable,
            changeColor: action,
            isDefaultTheme: computed,
        });
    }
    changeColor() {
        this.color = `#${Math.random()
            .toString(16)
            .slice(-6)}`;
    }
    get isDefaultTheme() {
        return this.color === 'red';
    }
 
}

注意:

(1)cpmputed计算值是有缓存的。
(2)如果修改行为时直接修改状态而不是通过action修改,那么在非严格模式下控制台会有提示信息,严格模式下编译报错。

3.异步方法

Vuex中将状态的修改分为mutation和action,同步修改放到mutation中,异步操作放到action中。

在Mobx中,同步或者异步操作都可以放在action里,但是在异步操作修改属性时,需要将赋值操作放到runInAction中。如果不调用runInAction,也可以用本身存在的action。

在Counter.ts中添加一个异步方法calcCount:

import { makeAutoObservable, runInAction } from 'mobx';
 
class Counter {
  count: number = 0;
 
  constructor() {
    makeAutoObservable(this);
  }
 
  increase() {
    this.count += 1;
  }
 
  decrease() {
    this.count -= 1;
  }
 
  get double() {
    return this.count * 2;
  }
 
  setCount(count: number) {
    this.count = count;
  }
 
  async calcCount() {
    const count: number = await new Promise(resolve => {
      setTimeout(() => {
        resolve(10);
      }, 2000)
    })
 
    // 用runInAction或者调用内部setCount方法都可以
    // runInAction(() => {
    //   this.count = count;
    // })
 
    this.setCount(count)
  }
 
}
 
export default Counter;
4.Mobx提供的其他一些API

(1) autoRun监听对象变更
autoRun接收一个函数作为参数,函数用来执行副作用。

当函数内部使用使用的observable state,computed发生变化时函数会运行,初始运行autoRun方法时函数函数也会运行一次。

import React, { useEffect } from 'react';
import { Row, Col, Button } from 'antd';
import { PlusOutlined, MinusOutlined } from '@ant-design/icons';
import { observer } from 'mobx-react-lite';
import { autorun } from 'mobx';
 
import useStores from './hooks/useStores';
 
type PaneProps = React.HTMLProps<HTMLDivElement> & {
  name?: string;
}
 
const Pane: React.FC<PaneProps> = ({ name, ...props }) => {
  const stores = useStores();
 
  useEffect(() => {
    autorun(() => {
      console.log('counter--', stores.counter.count)
    })
  }, [])
 
  return (
    <div {...props}>
      {name && <h2>{name}</h2>}
      <Row align="middle">
        <Col span="4">Count</Col>
        <Col span="4">{stores.counter.count}</Col>
        <Col>
          <Button
            type="text"
            icon={<PlusOutlined />}
            onClick={() => stores.counter.increase()}
          />
          <Button
            type="text"
            icon={<MinusOutlined />}
            onClick={() => stores.counter.decrease()}
          />
 
          <Button
            type="text"
            onClick={() => stores.counter.calcCount()}
          >异步</Button>
        </Col>
      </Row>
    </div>
  );
};
 
Pane.defaultProps = { name: undefined };
 
export default observer(Pane);

注意:

  • 这里在autoRun外包了useEffect,这是因为每次检测到变化时,autoRun自己会多执行一次。
  • 对于基本数据类型,mobx跟踪不到复制后的值;引用数据类型,只要引用地址不发生变化,Mobx就可以跟踪。

(2)reaction, when
除了autoRun之外,Mobx还提供了更精细的监听方法。

reaction(
    () => stores.counter.count,
    (value, prevValue) => {
      console.log('diff', value - prevValue)
    }
  );

reaction的用法类似于vue中的watch。

when(() => stores.counter.count > 5, () => {
    console.log(stores.counter.count)
  })

三 Mobx装饰器写法

tsconfig.json中开启装饰器的使用:

"experimentalDecorators": true,
"useDefineForClassFields": true,

引入babel进行转码:

npm install @babel/plugin-proposal-decorators

babel.config.js中添加这个插件:

"plugins": [
      [
        "@babel/plugin-proposal-decorators",
        {
          "legacy": true
        }
      ]
    ]

新建Counter1.ts

import { makeObservable, observable, action, computed } from 'mobx';
 
class Counter1 {
  @observable count: number = 0;
 
  constructor() {
    makeObservable(this);
  }
 
  @action increase() {
    this.count += 1;
  }
 
  @action decrease() {
    this.count -= 1;
  }
 
  @computed get double() {
    return this.count * 2;
  }
 
  @action setCount(count: number) {
    this.count = count;
  }
 
  @action async calcCount() {
    const count: number = await new Promise(resolve => {
      setTimeout(() => {
        resolve(10);
      }, 2000)
    })
 
    this.setCount(count)
  }
 
}
 
export default Counter1;

mobx-react-lite的useObserver api的用法

import React, { useEffect } from 'react';
import { Row, Col, Button } from 'antd';
import { PlusOutlined, MinusOutlined } from '@ant-design/icons';
import { useObserver } from 'mobx-react-lite';
import { autorun } from 'mobx';

import useStores from './hooks/useStores';

type PaneProps = React.HTMLProps<HTMLDivElement> & {
 name?: string;
}

const Pane: React.FC<PaneProps> = ({ name, ...props }) => {
 const stores = useStores();

 useEffect(() => {
   autorun(() => {
     console.log('counter--', stores.counter.count)
   })
 }, [])

 return useObserver(() => 
   <div {...props}>
     {name && <h2>{name}</h2>}
     <Row align="middle">
       <Col span="4">Count</Col>
       <Col span="4">{stores.counter.count}</Col>
       <Col>
         <Button
           type="text"
           icon={<PlusOutlined />}
           onClick={() => stores.counter.increase()}
         />
         <Button
           type="text"
           icon={<MinusOutlined />}
           onClick={() => stores.counter.decrease()}
         />

         <Button
           type="text"
           onClick={() => stores.counter.calcCount()}
         >异步</Button>
       </Col>
     </Row>
   </div>
 ) 
};

Pane.defaultProps = { name: undefined };

export default Pane;

useObserver是包裹着render中的内容。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值