MobX - 简单的可扩展状态管理

735 阅读6分钟

笔记来源:拉勾教育 - 大前端就业集训营

文章内容:学习过程中的笔记、感悟、和经验

提示:项目实战类文章资源无法上传,仅供参考

MobX - 简单的可扩展状态管理

简介

简单、可扩展的状态管理库,和redux作用是一样的,但更简单

  • MobX是由Mendix,Coinbase,Facebook开源和众多个人赞助商所赞助的
  • React和MobX是一对强力组合,React负责渲染应用的状态,MobX负责管理应用状态供React使用

浏览器支持情况:

  • MobX5版本运行在任何支持ES6-proxy的浏览器,不支持IE11、Node.js6
  • MobX4可以运行在任何支持ES5的浏览器上
  • MobX4和5的API是相同的

课程中使用版本5进行讲解

开发前准备

启用装饰器语法支持

方法1:

  1. 弹射出项目底层配置:npm run eject

  2. 下载装饰器语法babel插件:npm install @babel/plugin-proposal-decorators

  3. 在package.json文件中加入配置

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

方法2(课程使用,不报错即可):

  1. 覆盖底层配置:npm install react-app-rewired @babel/plugin-proposal-decorators customize-cra

  2. 在项目根目录创建config-overrides.js并加入配置

    • const { override, addDecoratorsLegacy } = require("customize-cra");
      module.exports = override(addDecoratorsLegacy());
      
  3. 修改配置文件package.json

    • "scripts": {
          "start": "react-app-rewired start",
          "build": "react-app-rewired build",
          "test": "react-app-rewired test",
      }
      

解决vscode编辑器关于装饰器语法的警告

  • vscode设置中的javascript.implicitProjectConfig.experimentalDecorators设置为true
  • 如果找不到可以找JS/TS › Implicit Project Config: Experimental Decorators

MobX使用

  • 下载MobX:npm i mobx mobx-react注意版本,课程使用的是mobx - ^5.15.4、mobx-react - ^6.2.2
  • 工作流程:action(指令) => state(状态) => views(视图),视图想修改状态就必须先触发action,state会自动修改视图

创建并使用store状态

  1. store对象需要是一个类的实例对象,在类内部设置初始状态,然后把实例对象导出即可
  2. 使用modx-react的Provider将store对象传递下去,方法和redux相同
  3. 组件中引入mobx-react装饰器inject,使用装饰器获取store,然后在组件的props中即可获取store状态了

使用action修改store状态

  1. 直接把状态修改方法书写在最开始的store类中
  2. 只需要触发类中的方法即可修改状态,此时会发现修改后状态并不会同步给视图
  3. 使用mobx装饰器observable将状态数据变成可被观测数据
  4. 组件中使用mobx-react装饰器observer将组件设置为观察者
// src/stores/countStore.js  新建状态仓库

import { observable } from 'mobx'

// 创建store
class CountStore {
  // observable实现可被观察功能 创建状态数据
  @observable count = 1
  // action
  add = () => {
    // 直接修改状态
    this.count = this.count + 1
  }
  cut = () => {
    this.count = this.count - 1
  }
}
// 创建实例
const counter = new CountStore()
// 直接导出实例
export default counter
// src/index.js  底层引入并传递状态仓库

import { Provider } from 'mobx-react'
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import counter from './stores/countStore'

ReactDOM.render(
  // 使用 Provider 将数据传递下去
  <Provider counter={counter}>
    <App />
  </Provider>,
  document.getElementById('root')
)
// src/App.js  组件获取并使用状态、使用action

import { inject, observer } from 'mobx-react'
import React, { Component } from 'react'

// 接收状态
@inject('counter')
// 设置观察模式,当状态变化自动更新
@observer
// 组件
class App extends Component {
  render() {
    // 解构获取状态
    const { counter } = this.props
    return (
      <div>
        {/* 点击直接触发action */}
        <button onClick={counter.cut}>-</button>
        {/* 展示状态 */}
        {counter.count}
        <button onClick={counter.add}>+</button>
      </div>
    )
  }
}

export default App

禁止普通函数更改程序状态并引入action装饰器

为了避免任意一个函数都可以更改状态,需要禁止这些默认行为,避免不必要的错误出现

启用严格模式,只让action函数更改状态,设置action装饰器后,非action函数想要更改状态会立即报错

import { action, configure, observable } from 'mobx'

// 配置mobx,开启严格模式
configure({ enforceActions: 'observed' })

// 创建store
class CountStore {
  // observable实现可被观察功能 创建状态数据
  @observable count = 1
  // action,添加action装饰器,没有装饰器的函数一旦尝试修改状态就会报错
  @action add = () => {
    // 直接修改状态
    this.count = this.count + 1
  }
  @action cut = () => {
    this.count = this.count - 1
  }
}
// 创建实例
const counter = new CountStore()
// 直接导出实例
export default counter

更正类中的普通函数this指向

上面例子中,我们定义的action韩式都是一个箭头函数,这避免了this指向错误的问题,但在大多数情况下,我们更愿意使用普通函数作为对象action函数,这就要求我们需要设置一下普通函数中this指向的问题

// 箭头函数不需要修正this指向
@action add = () => {
  // 直接修改状态
  this.count = this.count + 1
}
// 修改普通函数的this指向问题
@action.bound cut() {
  this.count = this.count - 1
}

异步更新状态

当action函数中存在异步操作的时候,我们不能直接修改状态数据

  • 修改方法1:使用mobx中的runInAction()方法修改,runInAction函数接受一个函数,在此函数内修改数据
  • 修改方法2:使用mobx的flow函数,函数接收一个函数作为参数(function*),在参数内部直接修改即可
import { action, configure, flow, observable, runInAction } from 'mobx'
import axios from 'axios'

// 配置mobx,开启严格模式
configure({ enforceActions: 'observed' })

// 创建store
class CountStore {
  // observable实现可被观察功能 创建状态数据
  @observable count = 1
  @observable users = []
  // action,添加action装饰器,没有装饰器的函数一旦尝试修改状态就会报错
  @action add = () => {
    // 直接修改状态
    this.count = this.count + 1
  }
  // 修改普通函数的this指向问题
  @action.bound cut() {
    this.count = this.count - 1
  }

  // 异步请求处理方案1
  // @action.bound async getUsers() {
  //   const { data } = await axios.get('https://api.github.com/users')
  //   // 异步请求直接使用runInAction修改状态
  //   runInAction(() => (this.users = data))
  // }

  // 异步请求处理方案2,使用flow处理异步函数,此时就需要书写装饰器了
  getUsers = flow(function* () {
    const { data } = yield axios.get('https://api.github.com/users')
    this.users = data
  })
}
// 创建实例
const counter = new CountStore()
// 直接导出实例
export default counter

数据监测

computed计算值

计算值是指可以根据现有的状态或其他计算值衍生出的值

当模版中有复杂逻辑的时候,可将复杂逻辑从模版中抽离出来

使用mobx的computed装饰器可做计算值的处理,并使用get修饰,用法类似于Vue的计算属性

import { computed } from 'mobx'

// 使用修饰器把函数变成计算值,并设置为get方法
@computed get getRes() {
  // 返回计算值即可
  return this.count * 10
}

// 使用计算值,直接调用即可,不需要加()执行
{counter.getRes}

autorun方法

当监测的状态发生变化时,想根据状态产生相应的效果

会在初始化的时候执行一次,然后再梅西状态变化时执行

例如当用户输入用户名的时候实时检测这个用户名是否存在

// src/stores/countStore.js mobx

import { action, autorun, configure, observable } from 'mobx'

// 配置mobx,开启严格模式
configure({ enforceActions: 'observed' })

// 创建store
class CountStore {
  constructor() {
    // autorun要写在constructor中
    // 参数1:执行方法,参数2:配置参数
    autorun(
      () => {
        try {
          // 引入判断方法判断当前用户名是否已存在
          UserNameDistinguish(this.userName)
          // 用户名不存在显示可用
          console.log('用户名可用')
        } catch (e) {
          // 用户名存在报错
          console.log(e.message)
        }
      },
      // 设置延时检测时间2s
      { delay: 2000 }
    )
  }
  @observable userName = ''

  // 修改用户名action
  @action.bound changeValue(value) {
    this.userName = value
  }
}
// 创建实例
const counter = new CountStore()

// 判断用户名是否存在的函数
function UserNameDistinguish(value) {
  return new Promise((resolve, reject) => {
    // 判断用户名
    if (value === 'admin') {
      reject('用户名已存在')
    } else {
      resolve()
    }
  })
}
// 直接导出实例
export default counter
// src/App.js  组件

import { inject, observer } from 'mobx-react'
import React, { Component } from 'react'

// 接收状态
@inject('counter')
// 设置观察模式,当状态变化自动更新
@observer
// 组件
class App extends Component {
  render() {
    // 解构获取状态
    const { counter } = this.props
    return (
      <div>
        {/* 文本框,用来输入用户名 */}
        <input
          type='text'
          value={counter.userName}
          onChange={e => counter.changeValue(e.target.value)}
        />
      </div>
    )
  }
}

export default App