mobx使用教程

2021-08-25  本文已影响0人  易路先登

一、 简介

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 <span>{queue.length}</span>
    }
}

class Foo extends Component {
    // static propTypes = {
    //     cache:observableTypes.observableObject
    // }
    render(){
        const cache = this.props.cache;
        return <div><button onClick={()=>{
            this.props.refresh()
        }}>refresh</button><Bar queue={cache.queue} /></div>
    }
}

ReactDOM.render(<Foo cache = {store.cache} refresh={store.refresh} />,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 <>
        <input 
        type="checkbox" 
        className="toggle" 
        checked={todo.finished}
        onChange={(e)=>{
            console.log(e.target.checked)
            todo.toggle(e.target.checked)
        }}
        ></input>
        <span className={["title",todo.finished && 'finished'].join(' ')}>{todo.title}</span></>
    }
}

@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 <div className="todo-list">
            <header>
                <form onSubmit={this.handleSubmit}>
                    <input 
                    type="text" 
                    onChange = {this.handleChange} 
                    value={this.state.inputValue}
                    className="input"
                    placeholder="What needs to finished?" />
                </form>
            </header>
            <ul>
                {
                    todos.map(todo=>{
                        return <li key={todo.id} className = "todo-item">
                            <TodoItem todo={todo} />
                            <span 
                            className="delete"
                            onClick={()=>{
                                store.removeTodo(todo)
                            }}>delete</span>
                        </li>
                    })
                }
            </ul>
            <footer>{store.left} item(s) unfinished</footer>
        </div>
    }
}
ReactDOM.render(<TodoList store={store}/>,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 <>
        <input 
        type="checkbox" 
        className="toggle" 
        checked={todo.finished}
        onChange={(e)=>{
            console.log(e.target.checked)
            todo.toggle(e.target.checked)
        }}
        ></input>
        <span className={["title",todo.finished && 'finished'].join(' ')}>{todo.title}</span></>
    }
}

@observer
class TodoFooter extends Component{
    render(){
        trace()
        let store = this.props.store;
        return <footer>{store.left} item(s) unfinished</footer>
    }
}
@observer
class TodoView extends Component{
    render(){
        trace()
        let todos = this.props.todos
        return todos.map(todo=>{
            return <li key={todo.id} className = "todo-item">
                <TodoItem todo={todo} />
                <span 
                className="delete"
                onClick={()=>{
                    store.removeTodo(todo)
                }}>delete</span>
            </li>
        })
    }
}
@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 <header>
                    <form onSubmit={this.handleSubmit}>
                        <input 
                        type="text" 
                        onChange = {this.handleChange} 
                        value={this.state.inputValue}
                        className="input"
                        placeholder="What needs to finished?" />
                    </form>
                </header>
    }
}

@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 <div className="todo-list">
            <TodoHeader store={store} />
            <ul>
                <TodoView todos={todos} />
            </ul>
            <TodoFooter store={store} />
        </div>
    }
}
ReactDOM.render(<TodoList store={store}/>,document.querySelector('#root'))
上一篇下一篇

猜你喜欢

热点阅读