二. Mobx5 实战案例

Mobx 学习进阶


项目代码

https://github.com/dL-hx/react-redux-guide

v1.0 mobx练习

https://github.com/dL-hx/mobx-guide/releases/tag/v1.0

v2.0 todos案例

https://github.com/dL-hx/mobx-guide/releases/tag/v2.0

Mobx官网: https://www.mobxjs.com/

二 Mobx+React

2.1 安装Mobx

注意这里安装的版本, 最新版本已经是6了,

参照: https://blog.csdn.net/weixin_38245947/article/details/120338996

$ npm install mobx@5.15.4 mobx-react@6.2.2 -S

2.2 工作流

2.3 创建Mobx工作流

@observable 可观测数据

将数据变为可观测数据, view才能看到变化

目录结构

+ store
	+ CounterStore.js

2.4 计数器案例

  1. 创建store对象,存储默认状态
    store/CounterStore.js
// 1. 创建store对象 存储默认状态0
// 2. 将store对象放在一个全局的 组件可以够的到的地方
// 3. 让组件获取store对象中的状态 并将状态显示在组件中
import { observable, action, autorun } from 'mobx';

class CounterStore {

   @action increment = () => {
     this.count = this.count + 1;
   }

  @action decrement = () => {
    this.count = this.count - 1;
  }

}

const counter = new CounterStore();

export default counter;

2.将store对象放在一个全局的组件可以得到的地方。 Provider

index.js中使用 Provider、counter

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

ReactDOM.render(
  <Provider counter={counter}><App /></Provider>,
  document.getElementById('root')
    

3.让组件获取store对象中的状态,并将状态显示在组件中

使用定义的counter //通过inject

App.js

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

@inject("counter")
@observer
class App extends Component {
  render() {
    const { counter } = this.props;
    return (
      <div>
        <button onClick={counter.increment}>+</button>
        <span>{counter.count}</span>
        <button onClick={counter.decrement}>-</button>
      </div>
    );
  }
}

export default App;

2.5. mobx异步

store/CounterStore.js

// 1。创建store对象,存储默认状态
// 2.将store对象放在一个全局的组件可以得到的地方。 Provider
// 3.让组件获取store对象中的状态,并将状态显示在组件中

import { observable, action, runInAction, flow } from "mobx"
import axios from 'axios'


class CounterStore {
  // 可观测的数据
  @observable count = 0
  @observable users = []

  // increment = () => {
  //   this.count = this.count + 1
  //   console.log(this.count)
  // }

  // decrement = () => {
  //   this.count = this.count - 1
  // }

  // 箭头头函数改成普通函数
  @action.bound increment() {
    this.count = this.count + 1
  }

  // 如果有异步操作,不能直接在这个函数中操作数据。处理方法如下:
  // 方法1.runInAction
  // @action.bound async getData () {
  //   let { data } = await axios.get('https://api.github.com/users');
  //   runInAction(() => {
  //     this.users = data || [{id: '1', login: '1'}]
  //   })
  // }

  // 方法2:flow
  getData = flow(function * (){
    let { data } = yield axios.get('https://api.github.com/users');
    this.users = data || [{id: '1', login: '1'}]
  }).bind(this)

}

const counter = new CounterStore()
export default counter

2.6.mobx数据检测

数据检测:当某一个状态发生变化的时候我们想根据这个值得到另外一个值
计算值:可以根据现有的状态或其他计算值衍生出的值
mobx提供的方法:
1.computed
什么时候使用计算值?
将复杂的业务逻辑从模板中进行抽离。
式例:

import {observable, action,computed} from 'mobx'
class BirdStore{
	@observable count = 10
	@observable price = 25
	

+	// 调用的时候可以不加括号,以属性的形式使用它。
  @computed get totalPrice() {
  	return this.count * this.price
}

禁止普通函数更改程序状态并引入action装饰器。
默认情况下所有的函数都可以更改程序的状态,但是我们觉得这样是不好的,如果程序状态出现了问题,我们不得不排查每一个更改应用程序状态的函数,这样开发成本就比较高的,所以我们要把这个默认行为禁止掉,也就是不希望所有的函数都可以更改应用程序的状态。我们强制规定只有@action函数可以更改应用程序的状态,降低了程序的开发成本。

//  1。创建store对象,存储默认状态
// 2.将store对象放在一个全局的组件可以得到的地方。 Provider
// 3.让组件获取store对象中的状态,并将状态显示在组件中

import { observable, action, runInAction, flow, configure } from "mobx"
import axios from 'axios'

// 通过配置强制程序使用action函数更改应用程序中的状态
configure({enforceActions: 'observed'})
class CounterStore {
  // 可观测的数据
  @observable count = 0
  @observable users = []

+ @action increment = () => {
  this.count = this.count + 1
   console.log(this.count)
  }

+ @action decrement = () => {
  this.count = this.count - 1
  }

const counter = new CounterStore()
export default counter

数据检测(二):
autorun方法
当检测的状态发生变化的时候,你想根据状态产生‘效果’,请使用autorun
autorun会在初始化的时候执行一次,会在每次状态发生变化时执行。

autorun(async () => {
	let response = await uniqueUserName(this.username)
},{delay: 1500})

三 .综合案例

案例: todo

{
  "name": "todo",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@babel/plugin-proposal-decorators": "^7.8.3",
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.5.0",
    "@testing-library/user-event": "^7.2.1",
    "customize-cra": "^0.9.1",
    "mobx": "^5.15.4",
    "mobx-react": "^6.2.2",
    "react": "^16.13.1",
    "react-app-rewired": "^2.1.6",
    "react-dom": "^16.13.1",
    "react-scripts": "3.4.1"
  },
  "scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}



config-overrides.js

const { override, addDecoratorsLegacy } = require("customize-cra");

module.exports = override(addDecoratorsLegacy());

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
import todo from './stores/TodoStore';
import { Provider } from 'mobx-react';
import './index.css';

ReactDOM.render(
  <Provider todo={todo}><App /></Provider>,
  document.getElementById('root')
);


index.css

html,
body {
	margin: 0;
	padding: 0;
}

button {
	margin: 0;
	padding: 0;
	border: 0;
	background: none;
	font-size: 100%;
	vertical-align: baseline;
	font-family: inherit;
	font-weight: inherit;
	color: inherit;
	-webkit-appearance: none;
	appearance: none;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
}

body {
	font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
	line-height: 1.4em;
	background: #f5f5f5;
	color: #4d4d4d;
	min-width: 230px;
	max-width: 550px;
	margin: 0 auto;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
	font-weight: 300;
}

:focus {
	outline: 0;
}

.hidden {
	display: none;
}

.todoapp {
	background: #fff;
	margin: 130px 0 40px 0;
	position: relative;
	box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
	            0 25px 50px 0 rgba(0, 0, 0, 0.1);
}

.todoapp input::-webkit-input-placeholder {
	font-style: italic;
	font-weight: 300;
	color: #e6e6e6;
}

.todoapp input::-moz-placeholder {
	font-style: italic;
	font-weight: 300;
	color: #e6e6e6;
}

.todoapp input::input-placeholder {
	font-style: italic;
	font-weight: 300;
	color: #e6e6e6;
}

.todoapp h1 {
	position: absolute;
	top: -155px;
	width: 100%;
	font-size: 100px;
	font-weight: 100;
	text-align: center;
	color: rgba(175, 47, 47, 0.15);
	-webkit-text-rendering: optimizeLegibility;
	-moz-text-rendering: optimizeLegibility;
	text-rendering: optimizeLegibility;
}

.new-todo,
.edit {
	position: relative;
	margin: 0;
	width: 100%;
	font-size: 24px;
	font-family: inherit;
	font-weight: inherit;
	line-height: 1.4em;
	border: 0;
	color: inherit;
	padding: 6px;
	border: 1px solid #999;
	box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
	box-sizing: border-box;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
}

.new-todo {
	padding: 16px 16px 16px 60px;
	border: none;
	background: rgba(0, 0, 0, 0.003);
	box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
}

.main {
	position: relative;
	z-index: 2;
	border-top: 1px solid #e6e6e6;
}

label[for='toggle-all'] {
	display: none;
}

.toggle-all {
	position: absolute;
	top: -55px;
	left: -12px;
	width: 60px;
	height: 34px;
	text-align: center;
	border: none; /* Mobile Safari */
}

.toggle-all:before {
	content: '❯';
	font-size: 22px;
	color: #e6e6e6;
	padding: 10px 27px 10px 27px;
}

.toggle-all:checked:before {
	color: #737373;
}

.todo-list {
	margin: 0;
	padding: 0;
	list-style: none;
}

.todo-list li {
	position: relative;
	font-size: 24px;
	border-bottom: 1px solid #ededed;
}

.todo-list li:last-child {
	border-bottom: none;
}

.todo-list li.editing {
	border-bottom: none;
	padding: 0;
}

.todo-list li.editing .edit {
	display: block;
	width: 506px;
	padding: 12px 16px;
	margin: 0 0 0 43px;
}

.todo-list li.editing .view {
	display: none;
}

.todo-list li .toggle {
	text-align: center;
	width: 40px;
	/* auto, since non-WebKit browsers doesn't support input styling */
	height: auto;
	position: absolute;
	top: 0;
	bottom: 0;
	margin: auto 0;
	border: none; /* Mobile Safari */
	-webkit-appearance: none;
	appearance: none;
}

.todo-list li .toggle:after {
	content: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
}

.todo-list li .toggle:checked:after {
	content: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');
}

.todo-list li label {
	word-break: break-all;
	padding: 15px 60px 15px 15px;
	margin-left: 45px;
	display: block;
	line-height: 1.2;
	transition: color 0.4s;
}

.todo-list li.completed label {
	color: #d9d9d9;
	text-decoration: line-through;
}

.todo-list li .destroy {
	display: none;
	position: absolute;
	top: 0;
	right: 10px;
	bottom: 0;
	width: 40px;
	height: 40px;
	margin: auto 0;
	font-size: 30px;
	color: #cc9a9a;
	margin-bottom: 11px;
	transition: color 0.2s ease-out;
}

.todo-list li .destroy:hover {
	color: #af5b5e;
}

.todo-list li .destroy:after {
	content: '×';
}

.todo-list li:hover .destroy {
	display: block;
}

.todo-list li .edit {
	display: none;
}

.todo-list li.editing:last-child {
	margin-bottom: -1px;
}

.footer {
	color: #777;
	padding: 10px 15px;
	height: 20px;
	text-align: center;
	border-top: 1px solid #e6e6e6;
}

.footer:before {
	content: '';
	position: absolute;
	right: 0;
	bottom: 0;
	left: 0;
	height: 50px;
	overflow: hidden;
	box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
	            0 8px 0 -3px #f6f6f6,
	            0 9px 1px -3px rgba(0, 0, 0, 0.2),
	            0 16px 0 -6px #f6f6f6,
	            0 17px 2px -6px rgba(0, 0, 0, 0.2);
}

.todo-count {
	float: left;
	text-align: left;
}

.todo-count strong {
	font-weight: 300;
}

.filters {
	margin: 0;
	padding: 0;
	list-style: none;
	position: absolute;
	right: 0;
	left: 0;
}

.filters li {
	display: inline;
}

.filters li button {
	color: inherit;
	padding: 0 7px;
	text-decoration: none;
	border: 1px solid transparent;
	border-radius: 3px;
}

.filters li button:hover {
	border-color: rgba(175, 47, 47, 0.1);
}

.filters li button.selected {
	border-color: rgba(175, 47, 47, 0.2);
}

.clear-completed,
html .clear-completed:active {
	float: right;
	position: relative;
	line-height: 20px;
	text-decoration: none;
	cursor: pointer;
}

.clear-completed:hover {
	text-decoration: underline;
}

.info {
	margin: 65px auto 0;
	color: #bfbfbf;
	font-size: 10px;
	text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
	text-align: center;
}

.info p {
	line-height: 1;
}

.info a {
	color: inherit;
	text-decoration: none;
	font-weight: 400;
}

.info a:hover {
	text-decoration: underline;
}

/*
	Hack to remove background from Mobile Safari.
	Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
	.toggle-all,
	.todo-list li .toggle {
		background: none;
	}

	.todo-list li .toggle {
		height: 40px;
	}
	
	.toggle-all {
		-webkit-transform: rotate(90deg);
		transform: rotate(90deg);
		-webkit-appearance: none;
		appearance: none;
	}

}

@media (max-width: 430px) {
	.footer {
		height: 50px;
	}

	.filters {
		bottom: 10px;
	}

}

stores/TodoStore.js

import { observable, action, computed } from 'mobx';

class TodoStore {
  // todo 列表
  @observable todos = [];
  // 筛选条件
  @observable filter = 'All';
  // 添加任务
  @action.bound todoAdd (taskName) {
    this.todos.push({
      taskName,
      isCompleted: false
    })
  }
  // 删除任务
  @action.bound todoDelete (index) {
    this.todos.splice(index, 1);
  }
  // 更改任务的是否已完成状态
  @action.bound changeCompleted (index, flag) {
    this.todos[index].isCompleted = flag;
  }

  @computed get unfinishedTodoCount () {
    return this.todos.filter(todo => todo.isCompleted === false).length;
  }

  // 更改筛选条件
  @action.bound changeFilter(condition) {
    this.filter = condition
  }

  // 监测筛选条件的变化 根据筛选条件对任务进行筛选
  @computed get filterTodo () {
    switch (this.filter) {
      case 'All':
        return this.todos;
      case 'Active':
        return this.todos.filter(todo => todo.isCompleted === false);
      case 'Completed':
        return this.todos.filter(todo => todo.isCompleted === true);
    }
  }
}

const todo = new TodoStore();

export default todo;

components/App.js

import React, { Component } from 'react';
import AddTodo from './addTodo';
import TodoList from './todoList';
import TodoExtra from './todoExtra';

class App extends Component {
  render() {
    return <section className="todoapp">
      <AddTodo />
      <TodoList />
      <TodoExtra />
    </section>
  }
}

export default App;

components/addTodo.js

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

@inject('todo')
@observer
class AddTodo extends Component {
  // 添加任务
  addTodo (event) {
    const { todoAdd } = this.props.todo;
    // 判断用户敲击的是否是回车键
    if (event.key === 'Enter') {
      // 获取用户在文本框中输入的内容
      const taskName = event.target.value;
      // 判断用户在文本框中是否输入了内容
      if (taskName.trim().length === 0) {
        // 阻止程序向下执行
        return;
      }
      // 将任务添加到任务列表数组中
      todoAdd (taskName);
      // 清空文本框中的内容
      event.target.value = '';
    }

  }
  render() {
    return (
      <header className="header">
        <h1>todos</h1>
        <input onKeyUp={this.addTodo.bind(this)} className="new-todo" placeholder="What needs to be done?" />
      </header>
    );
  }
}

export default AddTodo;

components/todoList.js

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

@inject("todo")
@observer
class TodoList extends Component {
  render() {
    const { todoDelete, changeCompleted, filterTodo } = this.props.todo;
    return (
      <section className="main">
        <input className="toggle-all" type="checkbox" />
        <ul className="todo-list">
          {filterTodo.map((todo, index) => {
            return (
              <li className={todo.isCompleted ? 'completed': ''} key={todo.taskName}>
                <div className="view">
                  <input onChange={event => changeCompleted(index, event.target.checked)} checked={todo.isCompleted} className="toggle" type="checkbox" />
                  <label>{todo.taskName}</label>
                  <button onClick={() => todoDelete(index)} className="destroy"></button>
                </div>
                <input className="edit" />
              </li>
            );
          })}
        </ul>
      </section>
    );
  }
}

export default TodoList;

components/todoExtra.js

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

@inject("todo")
@observer
class TodoExtra extends Component {
  render() {
    const { unfinishedTodoCount, changeFilter, filter } = this.props.todo;
    return (
      <footer className="footer">
        <span className="todo-count">
          <strong>{unfinishedTodoCount}</strong> item left
        </span>
        <ul className="filters">
          <li>
            <button className={filter === 'All' ? 'selected': ''} onClick={() => changeFilter('All')}>All</button>
          </li>
          <li>
            <button className={filter === 'Active' ? 'selected': ''} onClick={() => changeFilter('Active')}>Active</button>
          </li>
          <li>
            <button className={filter === 'Completed' ? 'selected': ''} onClick={() => changeFilter('Completed')}>Completed</button>
          </li>
        </ul>

        <button className="clear-completed">Clear completed</button>
      </footer>
    );

  }
}

export default TodoExtra;

在这里插入图片描述

https://zhuanlan.zhihu.com/p/407390704

总结:
\1. Mobx5 + Mobx-React6 主要是在子Store依赖Mobx中的这几个方法 obsevable, action, computed, autorun
\2. @observeable 是用来修饰响应式变量的
\3. @action是用来修饰修改修改@aboserveable修饰的变量的
\4. @autorun是用来修饰自动运行方法的
\5. @computed是用来修饰修改@observeable修饰的响应式变量的,需要注意使用@computed修饰的方法前面必须加上get

// @computed 状态发生变化时候会自动执行

// 这样就<无需关注复杂业务逻辑>, 只要是能计算出来的值就可以使用

\6. 在根组件中使用mobx-react中的Provider组件将store注入所有组件
\7. 在组件使用的话可以使用构造器语法,回更方便使用mobx中的数据,但是装饰器语法的需要配置相应的babel插件 [“@babel/plugin-proposal-decorators”, { “legacy”: true }], [“@babel/plugin-proposal-class-properties”] 注意顺序不能反了,这样就是@inject([‘good’]) @observer 顺序也不反,数组语法也是因为可能会使用多个子store,如果没有配置装饰器语法的就是要类似于高阶组件的使用方法 @inject([‘good’]) @observer(组件)这样的语法来写

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小李科技

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值