美文网首页
mobx使用教程

mobx使用教程

作者: 易路先登 | 来源:发表于2021-08-25 16:43 被阅读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'))
    

    相关文章

      网友评论

          本文标题:mobx使用教程

          本文链接:https://www.haomeiwen.com/subject/usceiltx.html