前言
本小册是《千锋大前端小册》系列之 Mobx 部分。通过本小册,可以系统学习 Mobx 基础知识,为将来 Mobx 在大前端项目的应用打下坚实的基础。
—— 作者:千锋教育·古艺散人
mobx搭建环境
mkdir my-app
cd my-app
npm init -y
npm i webpack webpack-cli webpack-dev-server -D
npm i html-webpack-plugin -D
npm i babel-loader @babel/core @babel/preset-env -D
npm i @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties -D
npm i @babel/plugin-transform-runtime -D
npm i @babel/runtime -S
npm i mobx -S
mkdir src
mkdir dist
touch index.html
touch src/index.js
touch webpack.config.js
编写webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: path.resolve(__dirname, 'src/index.js'),
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: [
//支持装饰器
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose" : true }],
['@babel/plugin-transform-runtime']
]
}
}
}
]
},
plugins: [new HtmlWebpackPlugin()],
devtool: 'inline-source-map'
}
编写index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
mobx入门
observable可观察的状态
1. map
import {observable} from 'mobx'
// 声明
const map = observable.map({a: 1, b: 2});
// 设置
map.set('a', 11);
// 获取
console.log(map.get('a'));
console.log(map.get('b'));
// 删除
map.delete('a');
console.log(map.get('a'));
// 判断是否存在属性
console.log(map.has('a'));
2. object
import {observable} from 'mobx'
// 声明
const obj = observable({a: 1, b: 2});
// 修改
obj.a = 11;
// 访问
console.log(obj.a, obj.b);
3. array
import {observable} from 'mobx'
const arr = observable(['a', 'b', 'c', 'd']);
// 访问
console.log(arr[0], arr[10]);
// 操作
arr.pop();
arr.push('e');
4. 基础类型
import {observable} from 'mobx'/
const num = observable.box(10);
const str = observable.box('hello');
const bool = observable.box(true);
// 获得值
console.log(num.get(), str.get(), bool.get());
// 修改值
num.set(100);
str.set('hi');
bool.set(false);
console.log(num.get(), str.get(), bool.get());
observable装饰器
import {observable} from 'mobx'
// observable这个函数可以识别当成普通函数调用还是装饰器调用
// 如果是装饰器,会自动识别数据类型,使用不同的包装转换方案。
class Store{
@observable arr = [];
@observable obj = {a: 1};
@observable map = new Map();
@observable str = 'hello';
@observable num = 123;
@observable bool = false;
}
const store = new Store();
console.log(store);
console.log(store.obj.a);
注意:vscode编译器中,js文件使用装饰器会报红。解决方式:
在根目录编写jsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"experimentalDecorators": true
},
"include": ["src/**/*"]
}
对 observables 作出响应
0. 基础代码:
import {observable} from 'mobx'
class Store{
@observable arr = [];
@observable obj = {a: 1};
@observable map = new Map();
@observable str = 'hello';
@observable num = 123;
@observable bool = false;
}
const store = new Store();
1. computed
计算值是可以根据现有的状态或其它计算值衍生出的值, 跟vue中的computed非常相似。
const result = computed(()=>store.str + store.num);
console.log(result.get());
// 监听数据的变化
result.observe((change)=>{
console.log('result:', change);
})
//两次对store属性的修改都会引起result的变化
store.str = 'world';
store.num = 220;
computed可作为装饰器, 将result的计算添加到类中:
class Store{
@observable arr = [];
@observable obj = {a: 1};
@observable map = new Map();
@observable str = 'hello';
@observable num = 123;
@observable bool = false;
@computed get result(){
return this.str + this.num;
}
}
2. autorun
当你想创建一个响应式函数,而该函数本身永远不会有观察者时,可以使用 mobx.autorun
所提供的函数总是立即被触发一次,然后每次它的依赖关系改变时会再次被触发。
经验法则:如果你有一个函数应该自动运行,但不会产生一个新的值,请使用autorun。 其余情况都应该使用 computed。
//aotu会立即触发一次
autorun(()=>{
console.log(store.str + store.num);
})
autorun(()=>{
console.log(store.result);
})
//两次修改都会引起autorun执行
store.num = 220;
store.str = 'world';
3. when
when(predicate: () => boolean, effect?: () => void, options?)
when 观察并运行给定的 predicate,直到返回true。 一旦返回 true,给定的 effect 就会被执行,然后 autorunner(自动运行程序) 会被清理。 该函数返回一个清理器以提前取消自动运行程序。
对于以响应式方式来进行处理或者取消,此函数非常有用。
when(()=>store.bool, ()=>{
console.log('when function run.....');
})
store.bool = true;
4. reaction
用法: reaction(() => data, (data, reaction) => { sideEffect }, options?)。
autorun 的变种,对于如何追踪 observable 赋予了更细粒度的控制。 它接收两个函数参数,第一个(数据 函数)是用来追踪并返回数据作为第二个函数(效果 函数)的输入。 不同于 autorun 的是当创建时效果 函数不会直接运行,只有在数据表达式首次返回一个新值后才会运行。 在执行 效果 函数时访问的任何 observable 都不会被追踪。
// reaction
reaction(()=>[store.str, store.num], (arr)=>{
console.log(arr.join('/'));
})
//只要[store.str, store.num]中任意一值发生变化,reaction第二个函数都会执行
store.num = 220;
store.str = 'world';
改变 observables状态
1. action
接上面案例,添加action到类中:
class Store{
@observable arr = [];
@observable obj = {a: 1};
@observable map = new Map();
@observable str = 'hello';
@observable num = 123;
@observable bool = false;
@computed get result(){
return this.str + this.num;
}
@action bar(){
this.str = 'world';
this.num = 40;
}
}
const store = new Store();
//调用action,只会执行一次
store.bar();
2. action.bound
action.bound 可以用来自动地将动作绑定到目标对象。
class Store{
@observable arr = [];
@observable obj = {a: 1};
@observable map = new Map();
@observable str = 'hello';
@observable num = 123;
@observable bool = false;
@computed get result(){
return this.str + this.num;
}
@action bar(){
this.str = 'world';
this.num = 40;
}
//this 永远都是正确的
@action.bound foo(){
this.str = 'world';
this.num = 40;
}
}
const store = new Store();
setInterval(store.foo, 1000)
3. runInAction
action 只能影响正在运行的函数,而无法影响当前函数调用的异步操作。如果你使用async function来处理业务,那么我们可以使用 runInAction 这个API来解决这个问题。
@action async fzz() {
await new Promise((resolve) => {
setTimeout(() => {
resolve({
num: 220,
str: 'world'
})
}, 1000)
})
runInAction(()=>{
store.num = 220
store.str = 'world'
})
}
在react中使用mobx
在react中使用mobx,需要借助mobx-react。
它的功能相当于在react中使用redux,需要借助react-redux。
首先来搭建环境:
create-react-app react-app
cd react-app
npm run eject
npm i @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties -D
npm i mobx mobx-react -S
修改package.json中babel的配置:
"babel": {
"presets": [
"react-app"
],
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
[
"@babel/plugin-proposal-class-properties",
{
"loose": true
}
]
]
}
注意:vscode编译器中,js文件使用装饰器会报红。解决方式:
在根目录编写写jsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"experimentalDecorators": true
},
"include": ["src/**/*"]
}
React + Mobx 案例
修改index.js
应用 react-mobx-app 创建完项目基本环境,修改项目根目录下的 index.js:
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(
<App/>,
document.querySelector('#root')
)
创建 store
在项目根目录下创建store目录,在store目录下创建index.js,内容如下:
import { observable, action, computed } from 'mobx'
class AppStore {
@observable title = 'mobx'
@observable todos = []
@computed get desc(){
return `一共${this.todos.length}条`
}
@action addTodo(todo) {
this.todos.push(todo)
}
@action deleteTodo() {
this.todos.pop()
}
@action resetTodo() {
this.todos = []
}
}
const store = new AppStore()
export default store
修改App.js
修改项目根目录下的 App.js
import React, { Component } from 'react'
import Home from './pages/Home'
import { Provider } from 'mobx-react'
import store from './store/'
export default class App extends Component {
render() {
return (
<Provider store={store}>
<Home></Home>
</Provider>
)
}
}
创建 Home.js
在项目根目录下创建 pages 文件夹,在 pages文件夹下创建 Home.js 组件,内容如下:
import React, { Component } from 'react'
import {inject, observer} from 'mobx-react'
@inject('store')
@observer
class Home extends Component {
render() {
const {store} = this.props
return (
<div>
<div>{store.title}</div>
<button onClick={this.handleTodos.bind(this, 'add')}>添加</button>
<button onClick={this.handleTodos.bind(this, 'delete')}>删除</button>
<button onClick={this.handleTodos.bind(this, 'reset')}>重置</button>
<h6>{store.length}</h6>
{
store.todos.map((ele, index)=>(
<div key={index}>
{ele}
</div>
))
}
</div>
)
}
handleTodos(type){
const {store} = this.props
switch (type) {
case 'add':
store.addTodo('这是一条新内容')
break
case 'delete':
store.deleteTodo()
break
case 'reset':
store.resetTodo()
break
default:
break
}
}
}
export default Home;