# react_mobx_todoList
**Repository Path**: cheerqjy/react_mobx_todo-list
## Basic Information
- **Project Name**: react_mobx_todoList
- **Description**: react-mobox的基本使用及一个小案例todoList
- **Primary Language**: JavaScript
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 0
- **Created**: 2021-03-11
- **Last Updated**: 2022-06-07
## Categories & Tags
**Categories**: Uncategorized
**Tags**: React, Redux
## README
## mobx
随着页面逻辑的复杂度提升,各种状态库也层次不穷,业内比较成熟的解决方案有 Redux、但是Redux使用相对繁琐,对于新手来说不友好。所以今天特意推荐大家可以去尝试使用另一个比较成熟的状态管理工具Mobx,mobx 的核心理念是 简单、可扩展的状态管理库。
个人感觉它和vuex有很多相似之处,如果你会vuex十分钟就可以搞定。
Mobx它通过透明的函数响应式编程(transparently applying functional reactive programming - TFRP)使得状态管理变得简单和可扩展。
MobX用于简单、可扩展的状态管理。通常搭配 React 使用,但不只限于 React。如何你厌烦了 Redux 繁杂的模板代码和 API,那么可以尝试下 MobX。网上好像流传: Redux 是谁用谁加班😂。
## 基础配置
mobx 需要使用decorator 修饰器语法 但项目默认是不支持的 此时我们需要配置以下
创建项目后 先使用 `npm run eject `弹出webpack配置 否则我们的packge.json 没有我们需要的配置项
```shell
yarn run eject
```
安装支持装饰器所需依赖:
```
yarn add @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties
```
安装mobx
```
yarn add mobx mobx-react
```
配置 package.json
```json
... eslint的配置
"eslintConfig": {
"parserOptions": {
"ecmaFeatures": {
"legacyDecorators": true
}
},
"extends": [
"react-app",
"react-app/jest"
]
},
...
"babel": {
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
[
"@babel/plugin-proposal-class-properties",
{
"loose": true
}
]
],
"presets": [
"react-app"
]
}
```
## Mobx主要概念:
- `observable`:用来声明可观察的数据
- `action`:动作,建议是唯一可以修改状态的方式;
- `computed`:计算值 是声明可观察数据的演变数据,是根据observable推导计算出来的值;
- `reaction`:响应,受state影响,会对state的变化做出一些更新ui、打印日志等反应;
结合单向数据流管理方式和上述概念,Mobx的主要流程如下图所示:

`observable` 可被观察的,用于定义可共享的数据,相当于是Vuex中的 state
`action` 相当于Vuex中的 mutations + actions,用于定义改变state的方法
特点强调:装饰器只是用来修饰class类及其属性和方法的
`MobX `为现有的数据结构(如对象,数组和类实例)添加了可观察的功能。 通过使用 `@observable` 装饰器(ES.Next)来给你的类属性添加注解就可以简单地完成这一切。
## 项目中使用mobx流程

### 1、observable 新建store文件夹
建立一个index.js文件作为store模块,存储数据 其中可以使用`@observalbe` 和` @action`来声明数据和方法
```react
import {observable,action} from 'mobx';
// https://segmentfault.com/a/1190000037619304
/* mobx4.x/5.x 与 mobx6.x 区别;
最新版store的写法已经改变,
import { makeAutoObservable } from "mobx"
constructor() {
makeAutoObservable(this)
}
需要这一句 才可以完成页面即时渲染, 实际是是自动加 obserbable 和 action 而已。
*/
import { makeAutoObservable } from "mobx"
class AppStore {
constructor() {
this.name = 'store的默认姓名'
makeAutoObservable(this)
}
// 被观察者
// @observable 变量名 = 值
// mobx6.x 可以省略 observable和action
@observable name;
@observable values = {
username : "admin",
sex : "男"
}
// @action 方法名(参数) {
// console.log(参数)
// }
@action setName(value){//改变store中的 state
this.name = value
}
}
const store = new AppStore()
export default store
```
Mobx6之后,写法更简洁,总的来说是不需要再写@observable, @computed, @action等装饰符了,只需在构造函数中引入makeAutoObservable即可。
```react
// [mobx 6]写法
// 写法1:指定可共享数据
export default class TestStore{
constructor() {
makeObservable(this, {
list: observable,
msg: observable,
addList: action
})
}
list = [
{id: 1, task: '睡觉'},
{id: 2, task: '吃饭'}
]
msg = 'hello mobx 6'
addList(payload) {
this.list.push({
id: Date.now(),
task: payload
})
}
}
// 写法2:不指定,全部可用
export default class TestStore{
constructor() {
makeAutoObservable(this)
}
msg = 'hello mobx 6'
}
```
还可以用工厂函数的方法:
```js
function createTodoStore() {
const store = makeAutoObservable({
todos: [] as Todo[],
get unfinishedTodoCount() {
return store.todos.filter(todo => !todo.done).length
},
addTodo(todo: Todo) {
store.todos.push(todo)
}
})
return store
}
```
### 2、使用`Provider`模块,将`store`作为属性值传递给后代
在整个项目的入口文件 或者`app.js`中,
```react
// src\App.js
import logo from './logo.svg';
import './App.css';
import MyCom from './components/MyCom';
import MyChange from './components/MyChange';
import {Provider} from 'mobx-react';
import store from './store'
function App() {
return (
{/* Provider提供者 传入store属性,然后每个子组件将获得store*/}
);
}
export default App;
```
### 3、`inject` 和 `observer ` 在子组件中访问store中的数据
哪个组件使用 , 就在哪个组件中 “注入” inject
```react
// src\components\MyCom.jsx
import React from 'react'
import ReactDOM from 'react-dom'
import {observer, inject} from 'mobx-react';
// 观察者
@inject("store")//将store中的数据注入当前组件
@observer
/*
在模块内用 @inject('Store'),将 Store 注入到 props 上,保证结构的一致性
使用 @observer ,将组件变为观察者,响应 name 状态变化。
当状态变化时,组件也会做相应的更新。
*/
class DemoComponent extends React.Component {
// constructor(props){
// super(props)
// }
render() {
const { store } = this.props;
console.log(this);
return (
myCom组件获取store {store.name}
);
}
}
export default DemoComponent;
```
注意 要在类的上方使用修饰器 @observer 声明这个组件是一个被监听的组件 否则 数据不能随之改变
### 4、调用`action` , 在兄弟组件中改变store数据
```react
// src\components\MyChange.jsx
import React, { Component } from 'react';
import { observer,inject } from 'mobx-react';
import store from '../store';
@inject('store')
@observer
/*
在模块内用 @inject('Store'),将 Store 注入到 props 上,保证结构的一致性
使用 @observer ,将组件变为观察者,响应 name 状态变化。
当状态变化时,组件也会做相应的更新。
*/
class MyChange extends Component {
changeName=(event)=>{
const { store } = this.props;
// this.props.store.name='张三'//最好不要直接改store,必须调用action的方法
store.setName('张三')// 调用action
}
render() {
const { store } = this.props;
return (
myChange组件获取store {store.name}
);
}
}
export default MyChange;
```
**无状态组件(函数式组件)直接通过props接收,类组件则通过this.props接收,整体写法没有区别**
```react
@inject('store')
@observer
function Test(props) {
console.log('store', props.store)
let { test } = props.store
comfirm() {
test.addList({id: 3, task: '码代码'})
}
return (
todo
总共有多少{todo.list.length}条任务
)
}
export default Test
// 注入和观察也可以在导出的时候简写成
// export default inject('store')(observer(Test))
```
## 案例:基础TodoList
```react
// store/index.js
import {makeAutoObservable} from 'mobx';
class AppStore{
constructor(){
makeAutoObservable(this)
}
todos=[] // 初始化todos
addList(val){
this.todos.push(val)
console.log(this.todos);
}
delList(){
this.todos.pop()
}
clearList(){
this.todos.length=0
}
}
export default new AppStore()
```
```react
// src\components\Home.jsx
import React, { Component } from 'react';
import {observer,inject} from 'mobx-react'
@inject("store")
@observer
class Home extends Component {
handleTodos=(type)=>{
const {store}=this.props
console.log(type);
const inp=this.refNameRef.current// 获取到input输入框
switch (type) {// 根据按钮参数 调用 store 对应的action方法
case 'add':
store.addList(inp.value) ;
inp.value='';//结束后清空输入框的值
break;
case 'del':store.delList() ; break;
case 'clear':store.clearList() ; break;
default: ; break;
}
}
refNameRef = React.createRef()// 创建input输入框的ref
render() {
const {store}=this.props
return (
TodoList
{
// 遍历todos 并展示
store.todos.map((item,index)=>
{item}
)
}
);
}
}
export default Home;
```
## computed装饰器
https://www.jianshu.com/p/4cbabaa4904a
有时候,state 并不一定是我们需要的最终数据。例如,所有的 todo 都放在 store.todos 中,而已经完成的 todos 的值(store.unfinishedTodos),可以由 store.todos 衍生而来。
对此,mobx 提供了 computed 装饰器,用于获取由基础 state 衍生出来的值。如果基础值没有变,获取衍生值时就会走缓存,这样就不会引起虚拟 DOM 的重新渲染。
通过 @computed + getter 函数来定义衍生值(computed values)。
- `computed`注解 (es规范中称之为装饰器)
- `computed(options)`
- `computed(fn, options?)`
`Computed`值可用于从其他可观测值(`observables`)中派生信息。它们惰性求值,缓存输出,并且仅在基础可观察项之一发生更改时才重新计算。如果他们没有被使用, 则不会计算。
从概念上讲,它们与`Excel表格`中的公式非常相似,因此很**重要**。它们有助于减少必须存储的状态数量,并进行了高度优化。因此请尽可能使用它们。
```react
import { computed } from 'mobx';
class Store {
@observable todos = [{
title: "todo标题",
done: false,
},{
title: "已经完成 todo 的标题",
done: true,
}];
// 定义监听器, mobx6.x 中可以省略@computed
@computed get finishedTodos () {
return this.todos.filter((todo) => todo.done)
}
```
这样在组建中就可以直接拿到finishedTodos 过滤后的值了
## 案例:完整TodoList

```react
// src\store\index.js
import {makeAutoObservable,computed} from 'mobx';
class AppStore{
constructor(){
makeAutoObservable(this)
}
todos=[// 初始化todos
{ id:0, title: "todo标题",done: false,},
{ id:1, title: "已经完成 todo 的标题", done: true,}
]
addList(val){
const id=this.todos.length
this.todos.push({id,title:val,done:false})
console.log(this.todos);
}
delList(){
this.todos.pop()
}
clearList(){
this.todos.length=0
}
changeFinished(id){
// 改变 选中状态
const list=this.todos.filter((item,index)=>item.id==id)// 通过id筛选
list[0].done=!list[0].done// 取反
}
// computed计算属性,从可观察属性 todos 衍生出来,返回没有完成的待办项的个数
get unFinished(){
console.log('computing...');
const count=this.todos.filter((item) => item.done==false )
return count.length
}
}
export default new AppStore()
```
```react
// src\components\Home.jsx
import React, { Component } from 'react';
import {observer,inject} from 'mobx-react'
@inject("store")
@observer
class Home extends Component {
handleTodos=(type)=>{
const {store}=this.props
console.log(type);
const inp=this.refNameRef.current// 获取到input输入框
switch (type) {// 根据按钮参数 调用 store 对应的action方法
case 'add':
store.addList(inp.value) ;
inp.value='';//结束后清空输入框的值
break;
case 'del':store.delList() ; break;
case 'clear':store.clearList() ; break;
default: ; break;
}
}
setFinished=(item,event)=>{//设置当前项的完成状态
const {store}=this.props
store.changeFinished(item.id)//调用store的computed方法 传入id
}
refNameRef = React.createRef()// 创建input输入框的ref
render() {
const {store}=this.props
return (
);
}
}
export default Home;
```
注意:
- this.porps里面没有找到 @action 装饰器定义的方法, 但是可以直接使用,
- this会丢失
- this.props.store.count.increment.bind(this)
## 异步请求
### 方式1:runInAction/async/await
```
import {observable, action} from 'mobx';
class MyState {
@observable data = null;
@observable state = null;
@action initData = async () => {
try {
const data = await getData("xxx");
runInAction("说明一下这个action是干什么的。不写也可以", () => {
this.state = "success"
this.data = data;
})
} catch (error) {
runInAction(() => {
this.state = "error"
})
}
};
}
```
### 方式2:flows
更好的方式是使用 flow 的内置概念。它们使用生成器。一开始可能看起来很不适应,但它的工作原理与 async / await 是一样的。只是使用 function * 来代替 async,使用 yield 代替 await 。 使用 flow 的优点是它在语法上基本与 async / await 是相同的 (只是关键字不同),并且不需要手动用 @action 来包装异步代码,这样代码更简洁。
```
class Store {
@observable data = null;
@observable state = null;
fetchProjects = flow(function * () { // <- 注意*号,这是生成器函数!
try {
const projects = yield getData() // 用 yield 代替 await
// 异步代码块会被自动包装成动作并修改状态
this.state = "success"
this.data = projects
} catch (error) {
this.state = "error"
}
})
}
```
## 大型项目中定义多个状态(模块化)
假如我们`src/store`下有两个模块分别是test.js和mast.js
```react
// src/store/test.js
import {observable, action} from 'mobx';
class TestStore {
@observable name;
@observable age;
@action
changeAge = i => {
this.age = this.age + Number(i)
}
constructor() {
this.name = 'testStore'
this.age = 25
}
}
const test = new TestStore()
export default test
```
store子模块2
```react
// src/store/mast.js
// 这里引入的是 mobx
import {observable, computed, action} from 'mobx';
class MastSotre {
@observable list;
@computed
get getList () {
return this.list.filter(v => v.id !== 1)
}
@action
addList = obj => this.list.push(obj)
constructor() {
this.list = [
{ name: '香蕉',id: 0 },
{ name: '苹果',id: 1 },
{ name: '西瓜',id: 2 }
]
}
}
const mast = new MastSotre()
export default mast
```
新建`src/store/index.js`进行汇总,实则为所有store的集合
```react
// src/store/index.js
// 汇总store ,引入store子模块
import test from './test'
import mast from './mast'
// 定义为对象
const stores = {
// 注入子store模块集合
test,
mast,
}
export default stores
```
入口文件`index.js`中全局注入数据
```react
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import RouterContainer from './router/index'
import { Provider } from "mobx-react"
import stores from './store'
import {configure} from 'mobx'; // 开启严格模式
configure({enforceActions: true}) // 开启严格模式
ReactDOM.render(
{/* 展开stores数据 ,通过props传入Provider,用扩展运算符一次性传递多个子store模块 */}
,
document.getElementById('example')
);
```
- `Provider:` 通过 Provider 渗透
- `configure` 代表开启了严格模式,因为非严格模式下,组件是直接可以通过props.action改变state数据的,当项目复杂的时候这样是非常危险的。所以要设置唯一改变state方式是通过action
组件中获取store数据
```react
// src\components\Home.jsx
import React, { Component } from 'react';
import {observer,inject} from 'mobx-react'
// 方式一:如果store/index.js是通过对象导出的,则需要inject接收对应的store模块
@inject("testStore")
@observer
class Home extends Component {
render() {
// 方式一:如果store/index.js是通过对象导出的,组件中这样获取 testStore模
const {testStore}=this.props
console.log(testStore);
return (
{testStore.name}
);
}
}
```
方式二:传统形式
store子模块
```react
// src/store/test.js
import {makeAutoObservable} from 'mobx';
class TestStore{
constructor(){
makeAutoObservable(this)
}
name='test的数据'
}
export default new TestStore()
```
主store
```react
// src/store/index.js
方式二:如果store/index.js是通过class类导出
import testStore from "./test";
class AppStore{
testStore=testStore
}
export default new AppStore()
```
入口就
```react
// src/index.js 入口文件
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import store from './store';
import {Provider} from 'mobx-react';
ReactDOM.render(
{/* 方式二:如果store/index.js是通过class类导出的, 通过props传递store集合 */}
,
document.getElementById('root')
);
reportWebVitals();
```
组件内获取store数据
```react
// src/components/Home.jsx 组件内获取testStore模块中的数据
import React, { Component } from 'react';
import {observer,inject} from 'mobx-react'
// 方式二:如果store/index.js是通过class类导出的, 组件中这样获取 testStore模块
@inject("store")
@observer
class Home extends Component {
render() {
// 方式二:如果store/index.js是通过class类导出的, 组件中这样获取 testStore模块
const {testStore}=this.props.store
console.log(testStore);
return (
{testStore.name}
);
}
}
export default Home;
```