一、 简介
mobx:
Simple,scalable state management简单可扩展的状态管理
核心思想: 状态变化引起的副作用应该被自动触发
二、前奏知识
1、es5实现类
function Animal(){
}
function Dog(){
}
Object.defineProperties(Animal.prototype,{
name:{
value(){
return 'Animal';
}
},
say:{
value(){
return `I'm ${this.name()}`
}
}
})
//业务需求,实现Dog继承Animal dog instanceof Animal => true
// dog.__proto__.__proto__=== Animal.prototype
Dog.prototype = Object.create(Animal.prototype,{
constructor:{
value:Dog,
enumberable:false
},
name:{
value(){
return 'Dog'
}
}
})
console.log(new Dog().say())
2、es6实现类
class Animal{
nam(){
return 'Animal'
}
}
class Dog extends Animal{
food="pisa"
name(){
return 'Dog'
}
}
let dog = new Dog()
console.log(dog.food)
3、异常处理
报错:语法错误
food="pisa"
是因为确实babel插件导致的,
npm install babel-plugin-transform-class-properties -D
加入webpack.config.js
配置
options:{
presets:['@babel/preset-env'],
plugins:['transform-class-properties']
}
报错:
Cannot read property 'bindings' of null
解决办法:
npm install @babel/preset-env -S
,然后将webpack.config.js
中的presets:["env"]
替换成presets:["@babel/preset-env"]
4、装饰器decorator:在声明阶段实现类与类成员注解的一种语法
babel支持:
npm install -D @babel/plugin-proposal-decorators
配置:
options:{
presets:['@babel/preset-env'],
plugins:[['@babel/plugin-proposal-decorators',{"legacy":true}]]
}
(1)类修饰器
function log(target){
const desc = Object.getOwnPropertyDescriptors(target.prototype)
console.log(desc,'---')
for(const key of Object.keys(desc)){
if(key === 'constructor'){
continue;
}
const func = desc[key].value;
if('function' === typeof func){
Object.defineProperty(target.prototype,key,{
value(...args){
console.log('before ' + key);
const ret = func.apply(this,args)
console.log('after ' + key)
return ret
}
})
}
}
}
@log
class Numberic{
PI = 3.1415926;
add(...nums){
return nums.reduce((p,n)=>(p+n),0)
}
}
new Numberic().add(1,2)
成员修饰器
(2)、值属性修饰器
function readonly(target,key,descriptor){
descriptor.writable = true;
// 返回这个新的描述符
return descriptor
}
class Numberic{
@readonly PI = 3.1415926;
add(...nums){
return nums.reduce((p,n)=>(p+n),0)
}
}
let numb = new Numberic()
numb.PI = 100
console.log(numb.PI)
(3)、函数属性修饰器
function validate(target,key,descriptor){
const func = descriptor.value;
descriptor.value = function(...args){
for(let num of args){
if('number' !== typeof num){
throw new Error(`${num} is not a number`)
}
}
return func.apply(this,args)
}
}
class Numberic{
PI = 3.1415926;
@validate
add(...nums){
return nums.reduce((p,n)=>(p+n),0)
}
}
let numb = new Numberic()
console.log(numb.add(1,'2'))
三、mobx常用api
安装
npm install mobx -S
1、observable
是一种让数据的变化可以被观察的方法
(1)、数组类型
import { observable,isObservableArray } from 'mobx'
const arr = observable([1,2,3])
console.log(arr[0],Array.isArray(arr),isObservableArray(arr))
(2)、对象类型
import { observable,isObservableObject,extendObservable } from 'mobx'
const obj = observable({username:'zs'})
extendObservable(obj,{age:18})//给obj添加可观察属性age
console.log(obj['username'],isObservableObject(obj),obj['age'])
(3)、基本类型
import { observable } from 'mobx'
let number = observable.box(5)
let str = observable.box('hello')
let flag = observable.box(true)
number.set(10)
console.log(number.get())
(4)、声明仓库
import { observable } from 'mobx'
class Store{
@observable username='zs'
@observable information={message:'hello'}
}
console.log(new Store())
2、对可观察数据做出反应
(1)、computed
第一种用法
在类外返回一个根据其他属性计算后所得的值,该值会因为依赖属性的变化而变化
import { observable,computed } from 'mobx'
class Store{
@observable username='zs'
@observable age = 18
@observable res={message:'hello'}
}
var store = new Store()
var information = computed(()=>{
console.log('-----')
return {name:store.username,age:store.age}
})
console.log(information.get())
store.username = 'lisi'
console.log(information.get())
或者
import { observable,computed,makeObservable } from 'mobx'
class Store{
constructor(){
makeObservable(this)
}
@observable username='zs'
@observable age = 18
@observable res={message:'hello'}
}
var store = new Store()
var information = computed(()=>{
return {name:store.username,age:store.age}
})
information.observe_((change)=>{
console.log(change,'++++')
})
store.username = 'lisi'
store.age = 19
第二种用法
const number = observable(10);
const plus = computed(() => number.get() > 0);
autorun(() => {
console.log(plus.get());
});
number.set(-19);
number.set(-1);
number.set(1);
依次输出了true,false,true。
第一个true是number初始化值的时候,10>0为true没有问题。
第二个false将number改变为-19,输出false,也没有问题。
但是当-19改变为-1的时候,虽然number变了,但是number的改变实际上并没有改变plus的值,所以没有其它地方收到通知,因此也就并没有输出任何值。
直到number重新变为1时才输出true。
(2)、autorun
import { observable, autorun } from 'mobx';
const value = observable(0);
const number = observable(100);
autorun(() => {
console.log(value.get());
});
value.set(1);
value.set(2);
number.set(101);
import {observable, autorun} from 'mobx';
var todoStore = observable({
/* 一些观察的状态 */
todos: [],
/* 推导值 */
get completedCount() {
return this.todos.filter(todo => todo.completed).length;
}
});
/* 观察状态改变的函数 */
autorun(function() {
console.log("Completed %d of %d items",
todoStore.completedCount,
todoStore.todos.length
);
});
/* ..以及一些改变状态的动作 */
todoStore.todos[0] = {
title: "Take a walk",
completed: false
};
// -> 同步打印 'Completed 0 of 1 items'
todoStore.todos[0].completed = true;
// -> 同步打印 'Completed 1 of 1 items'
(3)、when
import { observable,when } from 'mobx'
const flag = observable(false);
when(()=>flag.get(),()=>console.log("it's true"))
flag.set(true);
(4)、Reaction
三、mobx-react
安装依赖
npm install react react-dom prop-types mobx-react -S
npm install @babel/preset-react -D
报错: Plugin/Preset files are not allowed to export objects是因为babel的版本对应不上,解决方式 webpack报错/babel报错的解决方法
mobx6+版本如果需要数据响应式得做些修改和调整
import React,{Component} from 'react'
import ReactDOM from 'react-dom'
import { action,observable,makeObservable } from 'mobx'
import { observer } from 'mobx-react'
class Store {
constructor(){
makeObservable(this)
}
@observable
cache = {queue:[1,2]}
@action.bound refresh(){
console.log(this.cache.queue)
this.cache.queue.push(1)
}
}
const store = new Store();
@observer
class Bar extends Component {
// static propTypes = {
// queue:observableTypes.observableArray
// };
render(){
const queue = this.props.queue;
return {queue.length}
}
}
class Foo extends Component {
// static propTypes = {
// cache:observableTypes.observableObject
// }
render(){
const cache = this.props.cache;
return
}
}
ReactDOM.render( ,document.querySelector('#root'))
四、todolist案例
import { observable,action,computed,makeObservable } from 'mobx'
import React,{Component,Fragment} from 'react'
import ReactDOM from 'react-dom'
import { observer,PropTypes as ObservablePropTypes } from 'mobx-react'
import PropTypes from 'prop-types'
class Todo{
id = Math.random();
@observable title = '';
@observable finished = false;
@action.bound toggle(flag){
this.finished = flag;
}
constructor(title){
makeObservable(this)
this.title = title;
}
}
class Store{
constructor(){
makeObservable(this)
}
@observable todos = [];
@action.bound createTodo(title){
this.todos.unshift(new Todo(title))
console.log(this.todos)
}
@action.bound removeTodo(todo){
this.todos.remove(todo);
}
@computed get left(){
return this.todos.filter(todo => !todo.finished).length;
}
}
var store = new Store()
@observer
class TodoItem extends Component{
static propTypes = {
todo:PropTypes.shape({
id:PropTypes.number.isRequired,
title:PropTypes.string.isRequired,
finished:PropTypes.bool.isRequired
}).isRequired
}
render(){
const todo = this.props.todo;
return <>
{
console.log(e.target.checked)
todo.toggle(e.target.checked)
}}
>
{todo.title}>
}
}
@observer
class TodoList extends Component{
static propTypes = {
store:PropTypes.shape({
createTodo:PropTypes.func,
todos:ObservablePropTypes.observableArrayOf(ObservablePropTypes.observableObject).isRequired
}).isRequired
}
state = { inputValue:'' }
handleSubmit = (e)=>{
e.preventDefault();
var store = this.props.store;
var inputValue = this.state.inputValue;
store.createTodo(inputValue);
this.setState({
inputValue:''
})
}
handleChange = (e)=>{
var inputValue = e.target.value;
this.setState({
inputValue
})
}
render(){
const store = this.props.store;
const todos = store.todos;
return
{
todos.map(todo=>{
return -
{
store.removeTodo(todo)
}}>delete
})
}
}
}
ReactDOM.render(,document.querySelector('#root'))
package.json内容
{
"name": "mobxdemo",
"version": "1.0.0",
"description": "",
"main": "webpack.config.js",
"scripts": {
"start": "webpack -w",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.15.0",
"@babel/plugin-proposal-decorators": "^7.14.5",
"babel-loader": "^8.2.2",
"babel-plugin-transform-decorators-legacy": "^1.3.5",
"babel-preset-env": "^1.7.0",
"@babel/preset-react": "^7.0.0",
"webpack": "^5.51.1",
"webpack-cli": "^4.8.0"
},
"dependencies": {
"@babel/preset-env": "^7.15.0",
"mobx": "^6.3.2",
"mobx-react": "^7.2.0",
"prop-types": "^15.7.2",
"react": "^17.0.2",
"react-dom": "^17.0.2"
}
}
webpack.config.js
const path = require('path')
const config = {
mode:'development',
entry:path.resolve(__dirname,'src/index.jsx'),
output:{
path:path.resolve(__dirname,'dist'),
filename:'main.js'
},
module:{
rules:[{
test:/\.jsx$/,
exclude:/ndoe_modules/,
use:{
loader:'babel-loader',
options:{
presets:['@babel/preset-env','@babel/react'],
plugins:[['@babel/plugin-proposal-decorators',{"legacy":true}]]
}
}
}]
},
devtool:'inline-source-map'
}
module.exports = config
五、工具函数
1、observe监查
class Todo{
id = Math.random();
@observable title = '';
@observable finished = false;
@action.bound toggle(flag){
this.finished = flag;
}
constructor(title){
makeObservable(this)
this.title = title;
}
}
class Store{
constructor(){
makeObservable(this)
observe(this.todos,(change)=>{
this.disposers.forEach(disposer => disposer())
this.disposers = [];
for(let todo of change.object){
var disposer = observe(todo,changex=>{
console.log(changex);
})
this.disposers.push(disposer);
}
})
}
disposers = [];
@observable todos = [];
@action.bound createTodo(title){
this.todos.unshift(new Todo(title))
console.log(this.todos)
}
}
var store = new Store()
store.createTodo('11111')
store.createTodo('2222')
store.todos[0].toggle(true)
//即可监测到todos的变化,也可监测到todos列表中某个元素的变化
2、spy监测
该函数可监测到数据所有层级的变化,该函数在全局调用
//可监测到所有action,update,render等导致数据变化的行为,性能损耗太大,不建议在生产环境使用
syp(event=>console.log(event))
3、"toJS"将observeable对象转换为js对象。
4、trace追踪,该函数需要在副作用中调用,比如render,传入true参数时自带断点调试功能。
六 优化法则
1、细粒度拆分试图组件
2、使用专用组件处理列表
3、尽可能晚地解构可观察数据
package.json
{
"name": "mobxdemo",
"version": "1.0.0",
"description": "",
"main": "webpack.config.js",
"scripts": {
"start": "webpack -w",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.15.0",
"@babel/plugin-proposal-decorators": "^7.14.5",
"babel-loader": "^8.2.2",
"babel-plugin-transform-decorators-legacy": "^1.3.5",
"babel-preset-env": "^1.7.0",
"@babel/preset-react": "^7.0.0",
"webpack": "^5.51.1",
"webpack-cli": "^4.8.0"
},
"dependencies": {
"@babel/preset-env": "^7.15.0",
"mobx": "^6.3.2",
"mobx-react": "^7.2.0",
"prop-types": "^15.7.2",
"react": "^17.0.2",
"react-dom": "^17.0.2"
}
}
webpack.config.json
const path = require('path')
const config = {
mode:'development',
entry:path.resolve(__dirname,'src/index.jsx'),
output:{
path:path.resolve(__dirname,'dist'),
filename:'main.js'
},
module:{
rules:[{
test:/\.jsx$/,
exclude:/ndoe_modules/,
use:{
loader:'babel-loader',
options:{
presets:['@babel/preset-env','@babel/react'],
plugins:[['@babel/plugin-proposal-decorators',{"legacy":true}]]
}
}
}]
},
devtool:'inline-source-map'
}
module.exports = config
index.jsx
import { spy,observe,observable,action,computed,makeObservable, trace } from 'mobx'
import React,{Component,Fragment} from 'react'
import ReactDOM from 'react-dom'
import { observer,PropTypes as ObservablePropTypes } from 'mobx-react'
import PropTypes from 'prop-types'
class Todo{
id = Math.random();
@observable title = '';
@observable finished = false;
@action.bound toggle(flag){
this.finished = flag;
}
constructor(title){
makeObservable(this)
this.title = title;
}
}
class Store{
constructor(){
makeObservable(this)
observe(this.todos,(change)=>{
this.disposers.forEach(disposer => disposer())
this.disposers = [];
for(let todo of change.object){
var disposer = observe(todo,changex=>{
console.log(changex);
})
this.disposers.push(disposer);
}
})
}
disposers = [];
@observable todos = [];
@action.bound createTodo(title){
this.todos.unshift(new Todo(title))
console.log(this.todos)
}
@action.bound removeTodo(todo){
this.todos.remove(todo);
}
@computed get left(){
return this.todos.filter(todo => !todo.finished).length;
}
}
var store = new Store()
@observer
class TodoItem extends Component{
static propTypes = {
todo:PropTypes.shape({
id:PropTypes.number.isRequired,
title:PropTypes.string.isRequired,
finished:PropTypes.bool.isRequired
}).isRequired
}
render(){
trace()
const todo = this.props.todo;
return <>
{
console.log(e.target.checked)
todo.toggle(e.target.checked)
}}
>
{todo.title}>
}
}
@observer
class TodoFooter extends Component{
render(){
trace()
let store = this.props.store;
return
}
}
@observer
class TodoView extends Component{
render(){
trace()
let todos = this.props.todos
return todos.map(todo=>{
return
{
store.removeTodo(todo)
}}>delete
})
}
}
@observer
class TodoHeader extends Component{
state = { inputValue:'' }
handleSubmit = (e)=>{
e.preventDefault();
var store = this.props.store;
var inputValue = this.state.inputValue;
store.createTodo(inputValue);
this.setState({
inputValue:''
})
}
handleChange = (e)=>{
var inputValue = e.target.value;
this.setState({
inputValue
})
}
render(){
trace()
let store = this.props.store
return
}
}
@observer
class TodoList extends Component{
static propTypes = {
store:PropTypes.shape({
createTodo:PropTypes.func,
todos:ObservablePropTypes.observableArrayOf(ObservablePropTypes.observableObject).isRequired
}).isRequired
}
render(){
trace()
const store = this.props.store;
const todos = store.todos;
return
}
}
ReactDOM.render(,document.querySelector('#root'))