mobx学习笔记

参考视频链接:https://www.bilibili.com/video/BV1tL4y1h7ND?p=14&spm_id_from=pageDriver

一、前期准备

在初次使用React 的装饰器时,第一次在项目中使用 @ 会报错,原因是react默认是不支持装饰器的,所以才会报错,所以是需要做一些配置来支持装饰器。

【报错显示:Parsing error: This experimental syntax requires enabling one of the following parser plugin(s): “decorators-legacy”, “decorators”.
bug描述

  1. 创建项目
npm install -g create-react-app
create-react-app mobx-study
  1. 安装插件 —— 改变 create-react-app 中 webpack 配置

    yarn add -D react-app-rewired customize-cra
    yarn add -D @babel/core @babel/plugin-proposal-decorators @babel/preset-env

  2. 修改package.json文件中 scripts 脚本

// package.json
"scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-scripts eject"
  }
  1. 在项目根目录下创建config-overrides.js 并写入以下内容
const path = require('path')
const { override, addDecoratorsLegacy } = require('customize-cra')

function resolve(dir) {
    return path.join(__dirname, dir)
}

const customize = () => (config, env) => {
    config.resolve.alias['@'] = resolve('src')
    if (env === 'production') {
        config.externals = {
            'react': 'React',
            'react-dom': 'ReactDOM'
        }
    }

    return config
};


module.exports = override(addDecoratorsLegacy(), customize())
  1. 在项目根目录下创建.babelrc并写入以下内容
{
    "presets": [
        "@babel/preset-env"
    ],
    "plugins": [
        [
            "@babel/plugin-proposal-decorators",
            {
                "legacy": true
            }
        ]
    ]
}

基本完成以上步骤就可以正常使用装饰器了,再也不会报 @ 的错误了。同时Support for the experimental syntax ‘decorators-legacy’ isn’t currently enabled这个错误也将消失。

二、计数器 小demo

  1. 安装依赖
    yarn add mobx@5 mobx-react@6
  2. 小demo
import React from 'react';
import ReactDOM from 'react-dom';
// import App from './App';
import { observable, action} from 'mobx'
import { observer } from 'mobx-react'

// 1. 初始化 mobx 容器仓库
class Store {
  @observable count = 0
  @action.bound increment() {
    this.count++
  }
}
// 2. 在组件中使用 mobx容器状态
@observer
class App extends React.Component {
  render () {
    const  {store} = this.props
    return (
      <div>
        <h1>App Component</h1>
        <p>{store.count}</p>
        <button onClick={store.increment}>1</button>
      </div>
    )
  }
}

ReactDOM.render(<App store={new Store()}/>,document.getElementById('root'));

三、es6装饰器

// 装饰器是一个对类进行处理的函数

// 基本用法
function fn1(target) {
    target.foo = 'bar'
}

// 返回函数,为装饰器传参
function fn2(value) {
    return function (target) {
        target.count = value
    }
}

// 添加实例属性
function fn3(target) {
    target.prototype.foo = 'baz'
}

// 装饰类的成员
function readOnly(target, name, descriptor) {
    // console.log(target);  //目标类的.prototype
    // console.log(name);   // 被修饰的类成员的名称
    // console.log(descriptor);  // 被修饰的类成员的描述对象,包括对象是否可枚举(enumerable)、可写(writable)等等
    descriptor.writable = false
}

function unEnumerable(target, name, descriptor) {
    descriptor.enumerable = false
}

@fn1
@fn2(10)
@fn3
class MyClass {
    @readOnly message = 'hello';
    @unEnumerable sayHello() {
        console.log('hello decorator');
    }
}

const cl = new MyClass()
// console.log(cl.foo);
// console.log(cl.count);
// console.log(cl.foo);

console.log(cl.message);
// cl.message = "hello world" // 【报错】该属性只读,不可以修改
for(let key in cl) {
    console.log(key + ":" + cl[key]);
}
cl.sayHello()
// decorator-mixins.js 
function mixins(...list) {
    return function (target) {
      	// Object.assign(target, source)方法用于对象的合并,
      	// 将源对象(source)的所有可枚举属性,复制到目标对象(target) 【浅复制】
        Object.assign(target.prototype, ...list)
    }
}

const obj = {
    name:'lfy',
    sayName() {
        console.log('My name is lfy');
    }
}

@mixins(obj)
class MyClass {}

let person = new MyClass();
console.log(person.name);
person.sayName()

四、mobx知识补充

1. observable, autorun

const store = new Store()
// 检测@observable count的值的改变,
// 默认执行一次,然后每次它的依赖关系改变时会再次被触发
autorun(()=>{
  console.log('autorun', store.count);
})

2. computed计算属性

class Store {
  @observable count = 0
  @observable price = 10
  @action.bound increment() {
    this.count++
  }
  // 会进行缓存,多次重复使用不会再次触发计算,当计算依赖的值改变时才再次进行计算
  @computed get totalPrice () {
    // console.log('计算totalPrice');
    return this.price * this.count
  }
}

3. action

知识点:

  1. @action.bound this的绑定
  2. 尽量使用装饰了@action的方法来修改容器中的数据
  3. 配置mobx的configure
  4. runInAction
  5. 异步action的三种方法
// 强制必须使用装饰了@action的方法来修改容器中的数据
configure({
  enforceActions: 'observed'
})

class Store {
  @observable count = 0
  @observable price = 10
  
  @action.bound change () {
    console.log(this);
    this.count = 20
    this.price = 50
    this.price = 100
  }
}

const store = new Store()
// 检测@observable count\price的值的改变,
// 总是立即被触发一次,然后每次它的依赖关系改变时会再次被触发
autorun(()=>{
  console.log('autorun', store.count, store.price);
})

// 每一次修改都会触发autorun,过于频繁
// store.count = 20
// store.price = 50
// store.price = 100

// 尽量使用装饰了@action的方法来修改容器中的数据,这样子autorun只会执行一次
store.change()

// @action.bound 使用bound后绑定this指向类Store 防止this丢失
const changeMethod = store.change
changeMethod()  // 如果没有使用.bound绑定this,this输出为undefined

// 使用runInAction也可以修改容器中的数据
runInAction(()=>{
  store.count = 999
})
// 异步action的三种方法
class Store{
  @observable count = 0
  
  @action.bound asyncChange() {
    setTimeout(() => {
      // this.count = 100  // 报错

      // 方法1  定义action函数【若业务逻辑比较复杂,推荐使用】
      // this.changeCount()

      // 方法2  直接调用action函数【不推荐】
      // action('changeFoo', ()=>{
      //   this.count = 100
      // })()

      // 方法3 runInAction【简单场景 推荐使用】
      runInAction(()=>{
        this.count = 100
      })

    },500)
  }
  
  @action.bound changeCount() {
    this.count = 100
  }
  
}

4. 数据监测

监测使用 @observable 装饰的属性

方法一:computed

**执行时机/次数:**会进行缓存,多次重复使用不会再次触发计算,当计算依赖的值改变时才再次进行计算
经验法则:如果你想响应式的产生一个可以被其它 observer 使用的值,请使用 @computed

class Store {
  @observable count = 100
  @observable price = 10
  @computed get totalPrice () {
    // console.log('计算totalPrice');
    return this.price * this.count
  }
}

方法二:autorun

执行时机/次数:总是立即被触发一次,然后每次它的依赖关系改变时会再次被触发
经验法则:如果你有一个函数应该自动运行,但不会产生一个新的值,而是想要达到一个
效果
,请使用autorun。

const store = new Store()
autorun(()=>{
  console.log('autorun', store.count);
})

方法三:when

执行时机/次数:该方法只执行一次;若初始化时 条件成立也是会执行的

// 第一个参数为when函数执行的条件函数,第一个参数返回true时,执行第二个函数参数
when(
  () => store.count >= 100,
  () => {
    console.log('when', store.count);
  }
)

方法四:reaction

**执行时机/次数:**初始化时不会执行,当监测的数据改变时才执行

reaction(
  () => {
    // 执行一些业务逻辑操作,返回数据给下一个函数使用
    return store.count/100
  },
  (data, reaction) => {
    console.log('reaction', data);   // data是第一个函数参数的返回值

    reaction.dispose()  // 手动停止当前reaction的监听
  }
)

五、购物车案例

代码:https://gitee.com/lfy__fly/learn-mobx.git
运行截图:
在这里插入图片描述

1. 模拟后台数据

⭕ src / api / shop.js

const _products = [
    {'id':1, 'title':'ipad 9','price':500.01,'inventory':2},
    {'id':2, 'title':'ipad 8','price':300.01,'inventory':12},
    {'id':3, 'title':'ipad 10','price':1500.01,'inventory':62},
]

export const getAllProducts = callback => {
    setTimeout(function () {
        callback(_products)
    }, 100)
}

export const buyProducts = (callback, errorCallback) => {
    setTimeout(()=>{
        Math.random() > 0.5 ? callback() : errorCallback()
    }, 100)
}

2. mobx 管理数据

⭕ src / stores / index.js

主文件,进行整合,并通过构造器让productsStore和cartStore联系起来,可以互相访问

import productsStore from "./products";
import cartStore from "./cart";

export default class rootStore {
    constructor() {
        this.productsStore = new productsStore(this)
        this.cartStore = new cartStore(this)
    }
}

⭕ src / stores / products.js

import { observable, action } from "mobx";
import * as shop from '../api/shop'

export default class productsStore {
    @observable all = [];

    constructor (rootStore) {
        this.rootStore = rootStore
    }

    // 异步action
    @action.bound getAllProducts () {
        shop.getAllProducts(products => {
            this.setAll(products)
        })
    }
    @action.bound setAll(products) {
        this.all = products
    }

    @action.bound decrementInventory(product) {
        const prod = this.all.find(prodItem => prodItem.id === product.id)
        prod.inventory -= 1
    }

}

⭕ src / stores / cart.js

import { action, computed, isObservable, observable } from "mobx";
import * as shop from '../api/shop'

export default class cartStore {
    @observable items = [];
    @observable checkoutStatus = null;

    @computed get totalPrice() {
        return this.items.reduce((total, item) => {
            return total + item.price * item.quantity
        },0)
    }

    @action.bound addToCart (product) {
        // 判断购物车数据中是否已经有该商品
        // 如果有,则让购物车中的商品数量+1
        // 如果没有,则添加新的商品到购物车
        const prod = this.items.find(cartItem => cartItem.id === product.id)
        if(prod) {
            prod.quantity++
        } else {
            this.items.push({
                id: product.id,
                title: product.title,
                price: product.price,
                quantity: 1
            })
        }
        // 添加完购物车商品以后,需要让商品列表中的库存数据-1
        this.rootStore.productsStore.decrementInventory(product)
    }

    @action.bound checkout() {
        // 备份购物车数据
        const savedProducts = [...this.items]

        // 清空结算状态
        this.setCheckoutStatus(null)

        // 清空购物车
        this.setItems([])

        // 发起结算请求
            // 成功:将结算状态设置为 成功
            // 失败:将结算状态设置为 失败 ,还原购物车数据
            shop.buyProducts(
                () => {
                    this.setCheckoutStatus('成功')
                },
                () => {
                    this.setCheckoutStatus('失败')
                    this.setItems(savedProducts)
                }
            ) 
    }
    @action.bound setCheckoutStatus(status) {
        this.checkoutStatus = status
    }
    @action.bound setItems(items) {
        this.items = items
    }

    constructor (rootStore) {
        this.rootStore = rootStore
    }
}

3. 入口文件

⭕ src / index.js

Provider 为APP组件及其子组件提供mobx容器数据

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

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

4. 组件

⭕ src / App.js

import React, { Component } from 'react'
import Product from './components/Products'
import Cart from './components/Cart'

export default class App extends Component {
  render() {
    return (
      <div>
        <Product/> 
        <hr/>
        <Cart/> 
      </div>
    )
  }
}

⭕ src / components / Product.js

由于装饰器的顺序依赖问题,确保@observable需要最先依赖再注入@inject(官网详见:4.5)
@inject(‘XXX’) 用谁就先声明一下

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

@inject('productsStore', 'cartStore')
@observer
export default class Product extends Component {
    render() {
        console.log('111');
        const {productsStore, cartStore} = this.props
        return (
            <div>
                <h1>我是商品列表</h1>
                <ul>
                    {
                        productsStore.all.map( goods => (
                            <li key={goods.id}>
                                {goods.title + " ----- ¥" + goods.price + "-----库存: " + goods.inventory }
                                <br/>
                                <button
                                    disabled={!goods.inventory}
                                    onClick={ () => cartStore.addToCart(goods) }> 
                                    {!goods.inventory ? 'Sold Out' : 'Add to cart'}
                                </button>
                            </li>
                        ))
                    }
                </ul>
            </div>
        )
    }
    componentDidMount () {
        console.log('222');
        this.props.productsStore.getAllProducts();
    }
}

⭕ src / components / Cart.js

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

@inject('cartStore')
@observer
export default class Cart extends Component {
    render() {
        const {cartStore} = this.props 
        return (
            <div>
                <h1>我是购物车</h1>
                <ul>
                    {
                        cartStore.items.map( goods => (
                            <li key={goods.id}>
                                {goods.title + " ----- ¥" + goods.price + "-----购买数量: " + goods.quantity }
                            </li>
                        ))
                    }
                </ul>
                <p>Total:{cartStore.totalPrice.toFixed(2)}</p>
                <p>
                    <button disabled={!cartStore.items.length} onClick={() => cartStore.checkout()}> Checkout</button>
                </p>
                {cartStore.checkoutStatus && <p>Checkout {cartStore.checkoutStatus}</p>}
            </div>
        )
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值