Mobx

Mobx

Mobx介绍

(1) Mobx是一个功能强大,上手非常容易的状态管理工具。

(2) Mobx背后的哲学很简单: 任何源自应用状态的东西都应该自动地获得。

(3) Mobx利用getter和setter来收集组件的数据依赖关系,从而在数据发生变化的时候精确知道哪些组件需要重绘,在界面的规模变大的时候,往往会有很多细粒度更新。

Mobx与redux区别

  • Mobx写法上更偏向于OOP
  • 对一份数据直接进行修改操作,不需要始终返回一个新的数据并非单一store,可以多store。
  • Redux默认以JavaScript原生对象形式存储数据,而Mobx使用可观察对象

优点:

  • 学习成本小
  • 面向对象编程, 而且对 TS 友好

缺点:

  • 过于自由:Mobx提供的约定及模版代码很少,代码编写很自由,如果不做一些约定,比较容易导致团队代码风格不统一
  • 相关的中间件很少,逻辑层业务整合是问题。

Mobx使用

1.observable和autorun

通过observable.box对属性进行观察监听,当属性发生改变时,autorun中可以监听获取。

注意:autorun中监听某个属性a的时候,其他属性b发生改变,监听a属性的autorun方法不会再执行。也就是说,在Mobx中,需要进行属性一对一的监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import {observable,autorun} from  'mobx'

var observableNumber = observable.box(10)
var observableName = observable.box('name')
autorun(() => {
console.log(observableNumber.get());
})
autorun(() => {
console.log(observableName.get());
})

setTimeout(() => {
observableNumber.set(20)
observableName.set('jerry')
},1000)

// 观察对象,通过Map
const map = observable.map({key:"value"})
// map.set("key","new value")
// map.get("key")

//观察对象,不通过map
const map = observable({ key: "value"});
// map.key map.key="xiaoming"
//观察数组
const list = observable([1, 2, 4]);
list[2] = 3;

2.action,runInAction和严格模式

  • action普通派发行为
  • runInAction 解决异步派发行为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// mobx/store.js
import {observable, action, configure,runInAction} from 'mobx';
configure({enforceActions:'always'})
//严格模式, 必须写action,
//如果是never,可以不写action,
//最好设置always, 防止任意地方修改值, 降低不确定性。

class Store {
@observable isTabbarShow = true
@observable list = []

@action changeShow(){
this.isTabbarShow = true
}
// 同步
@action changeHide(){
this.isTabbarShow = false
}
// 异步
@action async getList(){
var list = await axios({
url: 'https://m.maizuo.com/gateway?cityId=110100&ticketFlag=1&k=878555',
headers: {
'X-Client-Info': '{"a": "3000", "ch": "1002", "v": "5.2.0", "e": "1646462402616989231939585"}',
'X-Host': 'mall.film-ticket.cinema.list'
},
method:'get'
}).then(res => {
return res.data.data.cinemas
// runInAction解决Mobx中的异步
// runInAction(() => {
// this.list = res.data.data.cinemas
// })

})
// runInAction解决Mobx中的异步
runInAction(() => {
this.list = list
})
}
}
const store = new Store()
export default store

// App.js
class App extends Component {

state = {
isShow:false
}

componentDidMount() {
// 监听Mobx状态的改变,从而驱动react中的状态
autorun(() => {
console.log(store.isTabbarShow);
this.setState({
isShow:store.isTabbarShow
})
})
console.log(this.props);
}

render() {
return (
<div>
<IndexRouter>
{this.state.isShow && <Tabbar/>}
</IndexRouter>

</div>
);
}
}

export default App;

3.解决react脚手架中使用ES7中的装饰器问题

  1. npm i @babel/core @babel/plugin-proposal-decorators @babel/preset-env

  2. 创建 .babelrc

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    {
    "presets": [
    "@babel/preset-env"
    ],
    "plugins": [
    [
    "@babel/plugin-proposal-decorators",
    {
    "legacy": true
    }
    ]
    ]
    }
  3. 创建config-overrides.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    const path = require('path')
    const { override, addDecoratorsLegacy } = require('customize-cra')

    function resolve(dir) {
    return path.join(__dirname, dir)
    }

    const customize = () => (config, env) => {
    config.resolve.alias['@'] = resolve('src')
    if (env === 'production') {
    config.externals = {
    'react': 'React',
    'react-dom': 'ReactDOM'
    }
    }

    return config
    };

    module.exports = override(addDecoratorsLegacy(), customize())
  4. 安装依赖:npm i customize-cra react-app-rewired

  5. 修改package.json

    1
    2
    3
    4
    5
    6
    "scripts": {
    "start": "set PORT=4000 && react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-app-rewired eject"
    },

4.mobx-react使用

npm i mobx-react@5

1.react 组件里使用 @observer

observer 函数/装饰器可以用来将 React 组件转变成响应式组件。

2.可观察的局部组件状态

@observable 装饰器在React组件上引入可观察属性。而不需要通过 React 的冗长和强制性的 setState 机制来管理。

3.Provider 组件

它使用了 React 的上下文(context)机制,可以用来向下传递 stores。 要连接到这些 stores,需要传递一个 stores名称的列表给 inject,这使得 stores 可以作为组件的 props 使用。this.props

4.使用流程

  1. index.js 使用Provider将跟组件进行包裹

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import React from "react";
    import ReactDom from 'react-dom'
    import App from "./mobx/App";
    import {Provider} from 'mobx-react'
    import store from "./mobx/store";


    ReactDom.render(
    <Provider store={store}>
    <App/>
    </Provider>
    ,
    document.getElementById('root'))
  2. 类组件使用Mobx

    通过inject装饰器注入store,observer装饰器将react变成响应式组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    import React, {Component} from 'react';
    import IndexRouter from "./router/IndexRouter";
    import Tabbar from "./components/Tabbar";
    import './views/css/App.css'
    import {autorun} from "mobx";
    import store from "../mobx/store";
    import {inject, observer} from "mobx-react";

    @inject("store")
    @observer
    class App extends Component {

    componentDidMount() {
    // console.log(this.props);
    }

    render() {
    return (
    <div>
    <IndexRouter>
    {this.props.store.isTabbarShow && <Tabbar/>}
    </IndexRouter>

    </div>
    );
    }
    }

    export default App;
  3. 函数组件使用Mobx

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    import React, {useEffect, useState} from 'react';
    import store from "../../mobx/store";
    import {autorun} from "mobx";
    import {Observer} from "mobx-react";
    export default function Cinemas(props) {

    useEffect(() => {
    if(store.list.length === 0){
    store.getList()
    }

    },[])
    return (
    <div>

    <div>
    <Observer>
    {() => { return store.list.map(item =>
    <dl key={item.cinemaId} style={{padding:"10px"}}>
    <dt>{item.name}</dt>
    <dd style={{fontSize:"12px",color:"gray"}}>{item.address}</dd>
    </dl>
    )
    }}
    </Observer>

    </div>

    </div>
    )
    }
  4. mobx/store.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    import {observable,autorun,configure,action,runInAction}  from 'mobx'
    import axios from "axios";
    configure({
    enforceActions:'always'
    })

    class Store {
    @observable isTabbarShow = true
    @observable list = []

    @action changeShow(){
    this.isTabbarShow = true
    }

    @action changeHide(){
    this.isTabbarShow = false
    }
    @action async getList(){
    var list = await axios({
    url: 'https://m.maizuo.com/gateway?cityId=110100&ticketFlag=1&k=878555',
    headers: {
    'X-Client-Info': '{"a": "3000", "ch": "1002", "v": "5.2.0", "e": "1646462402616989231939585"}',
    'X-Host': 'mall.film-ticket.cinema.list'
    },
    method:'get'
    }).then(res => {
    return res.data.data.cinemas
    // runInAction(() => {
    // this.list = res.data.data.cinemas
    // })

    })

    runInAction(() => {
    this.list = list
    })
    }
    }
    const store = new Store()
    export default store