首页 前端知识 Mobx6的基本使用

Mobx6的基本使用

2024-08-04 00:08:14 前端知识 前端哥 247 802 我要收藏

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

Mobx6的基本使用

  • Mobx概述
    • Mobx是什么?
    • Mobx和Redux的区别
    • Mobx的优点和缺点
    • Mobx的使用场景和适用范围
  • Mobx基本使用流程
    • 安装
      • 安装 mobx6
      • 安装 React 的集成库
    • 使用流程
      • 创建`Store`
      • 创建`RootStore`
      • 在组件中使用
  • Mobx的核心概念
    • Observable:可观察对象
    • Action:操作
    • Computed:计算值
    • Reactions:响应式副作用
  • Observable
    • 使用 observable 函数
    • 使用 makeObservable 函数
    • 使用 makeAutoObservable 函数
  • Action
    • Action中没有异步操作
    • Action中含有异步操作
  • Computed
  • Reactions
    • autorun
    • reaction
    • when
  • Mobx模块化
  • 总结


Mobx概述

Mobx是什么?

Mobx是一个简单易用的状态管理库,它提供了一种简洁的方法来管理JavaScript应用程序的状态。通过使用Mobx,您可以轻松地将应用程序状态分解为可观察的部分,并在状态发生变化时自动更新UI。

mobx官网:

https://zh.mobx.js.org/installation.html

Mobx和Redux的区别

Mobx与Redux一样,都是用于管理应用程序状态的库。但是,两者在设计理念和用法上有很大的不同。

Redux是一个严格的状态容器,它使用单向数据流来管理应用程序状态。Redux的设计是基于函数式编程的思想,它通过纯函数来改变状态,然后通过订阅机制来更新UI。

相比之下,Mobx的设计更加灵活,它支持直接修改状态,并且能够自动检测状态的变化并更新UI。与Redux相比,Mobx更加易于使用和理解。

Mobx的优点和缺点

优点

  • 易于学习和使用。
  • 基于响应式编程的设计,能够自动更新UI。
  • 支持直接修改状态,更加灵活。
  • 可以在任何JavaScript应用程序中使用,不限于React。

缺点

  • 可能会导致性能问题,特别是在处理大型数据集时。
  • 不适用于复杂的应用程序状态。
  • 可能会导致代码结构变得混乱和难以维护

Mobx的使用场景和适用范围

Mobx适用于任何需要管理JavaScript应用程序状态的场景。特别是在以下情况下,Mobx的优点将更加明显:

  • 在React应用程序中管理状态。
  • 处理需要实时更新UI的数据。
  • 需要处理可变数据集。

Mobx基本使用流程

本文计算器的案例为例

安装

安装 mobx6

npm install mobx

安装 React 的集成库

npm install mobx-react-lite

npm install mobx-react

使用流程

创建Store

//store/CounterStore.ts
import { makeAutoObservable } from 'mobx'

class CounterStore {
    //定义state
    //使用 makeObservable 或 makeAutoObservable 绑定之后才是真的state
    count = 10;

    constructor() {
        //参数2:排除属性和方法,{reset:false}
        //参数3:自动绑定,不指定就只能用 ()=>
        makeAutoObservable(this, {}, { autoBind: true })
    }

    //定义action
    increment() {
        this.count++;
    }
}

创建RootStore

//store/RootStore.ts
import { createContext, useContext } from "react";
import CounterStore from "./CounterStore";

class RootStore {
    counterStotre: CounterStore;

    constructor() {
        this.counterStotre = new CounterStore();
    }
}

const store = new RootStore();

//提供初始值,不需要context.Provider
const context = createContext(store)

//自定义hook
function useStore() {
    return useContext(context);
}

export default useStore;

在组件中使用

组件必须使用observer进行包裹,mobx才能正常响应

import React from 'react'
import { observer } from 'mobx-react'
import useStore from '../../store/RootStore';

function Counter() {
  let { counterStotre } = useStore();

  return (
    <div>
      <h3>计数器</h3>
      <h4>{counterStotre.count}</h4>
      <button onClick={() => counterStotre.increment()}>+1</button>
    </div>
  )
}

export default observer(Counter);

Mobx的核心概念

Observable:可观察对象

可观察对象是MobX的核心概念之一。它是一个包装了数据的对象,当数据发生变化时会自动通知相关的观察者。可以使用observablemakeObservablemakeAutoObservable 等方式来创建可观察对象。

import { observable } from 'mobx';

const person = observable({
  name: '张三',
  age: 18,
});
import { observable } from 'mobx';

const numbers = observable([1, 2, 3]);

// 添加元素
numbers.push(4);

// 删除元素
numbers.pop();

Action:操作

Actions是一组用于修改可观察数据的函数。在Mobx中,只有在Actions中修改可观察数据,才能够自动触发更新。

import { observable, action } from 'mobx';

const counter = observable({
    count: 0,
    increment: action(function () {
        this.count++;
    }),
    decrement: action(function () {
        this.count--;
    }),
});

Computed:计算值

Computed是一种在可观察数据的基础上进行计算的方式。Computed值是基于可观察数据自动计算得出的值,当可观察数据发生变化时,计算值会自动更新。

import { observable, computed } from 'mobx';

const shoppingCart = observable({
    items: [],
    get total() {
        return this.items.reduce((total, item) => total + item.price * item.quantity, 0);
    },
});

Reactions:响应式副作用

Reaction是一种响应可观察对象变化的方法,当可观察对象发生变化时,会自动执行Reaction中定义的函数。可以使用autorunwhenreaction等方式来创建Reaction。

import { observable, reaction } from 'mobx';

const person = observable({
  name: '张三',
  age: 18,
});

reaction(
  () => person.name,
  (name) => console.log(`我的名字是 ${name}`)
);

person.name = '李四'; // 输出:我的名字是李四。

Observable

在 MobX6 中,Observable 是一个非常重要的概念,它可以用来包装 JavaScript 对象、数组、Map、Set、原始类型等数据结构,使其成为可观察对象。当可观察对象中的数据发生变化时,所有使用该数据的部分都会自动更新。

在 MobX6 中,创建 Observable 的方式主要有三种:

使用 observable 函数

import { observable } from "mobx";

const store = observable({
    todos: [],
    filter: "all",
    get filteredTodos() {
        return this.todos.filter(todo => {
            if (this.filter === "completed") {
                return todo.completed;
            } else if (this.filter === "active") {
                return !todo.completed;
            } else {
                return true;
            }
        });
    }
});

在这个例子中,我们使用 observable 函数来创建一个可观察对象 store。它有两个属性:todos 是一个空数组,filter 是一个字符串 “all”。还有一个计算属性 filteredTodos,它根据 todosfilter 进行计算

使用 makeObservable 函数

在Mobx6中,可以使用 makeObservable 函数来定义可观察对象。这个函数可以将装饰器的功能与普通的类定义结合起来,从而使Mobx更加易于使用。

import { makeObservable, observable, computed } from "mobx";

class TodoStore {
    todos = [];
    filter = "all";

    constructor() {
        makeObservable(this, {
            todos: observable,
            filter: observable,
            filteredTodos: computed,
        });
    }

    get filteredTodos() {
        return this.todos.filter(todo => {
            if (this.filter === "completed") {
                return todo.completed;
            } else if (this.filter === "active") {
                return !todo.completed;
            } else {
                return true;
            }
        });
    }
}

使用 makeAutoObservable 函数

使用用 makeAutoObservable 可以进一步对其进行简化

import { makeAutoObservable } from "mobx";

class TodoStore {
    todos = [];
    filter = "all";

    constructor() {
        makeAutoObservable(this, {}, { autoBind: true });
    }

    get filteredTodos() {
        return this.todos.filter(todo => {
            if (this.filter === "completed") {
                return todo.completed;
            } else if (this.filter === "active") {
                return !todo.completed;
            } else {
                return true;
            }
        });
    }
}

Action

在 MobX6 中,Action 是一种用于修改状态的方法。使用 Action,我们可以确保在状态修改过程中,所有相关的观察者都能够正确地被通知到,并且 MobX 会自动地运行相关的 Reaction,从而保证应用程序的状态与用户界面保持同步。

Action中没有异步操作

import { action, makeObservable, observable } from "mobx";

class Counter {
  count = 0;

  constructor() {
    makeObservable(this, {
      count: observable,
      increment: action,
      decrement: action,
    });
  }

  increment() {
    this.count++;
  }

  decrement() {
    this.count--;
  }
}

Action中含有异步操作

需要注意的是,如果你在一个 Action 中使用了异步操作(如 Promise 或者 setTimeout 等),你需要使用 runInAction 函数来确保异步操作的结果能够正确地触发 Reaction。

从本质上讲,异步进程在 MobX 中不需要任何特殊处理,因为不论是何时引发的所有 reactions 都将会自动更新。但mobx在异步中直接改变属性的值,会报一个警告。正常mobx处理异步大概有两种方式,个人推荐使用runInAction处理异步。

//store/CounterStore.ts
import { makeAutoObservable, runInAction } from 'mobx'

class CounterStore {
    //observable
    count = 10;

    constructor() {
        //参数2:排除属性和方法,{reset:false}
        //参数3:自动绑定,不指定就只能用 ()=>
        makeAutoObservable(this, {}, { autoBind: true })
    }

    //action
    increment() {
        this.count++;
    }

    //计算属性
    get double() {
        return this.count * 2;
    }

    // //处理异步-方式1
    decrementAsync1() {
        setTimeout(this.increment, 1000)
    }

    // //处理异步-方式2(推荐这种)
    decrementAsync2() {
        setTimeout(() => {
            runInAction(() => {
                this.count++;
            })
        }, 1000)
    }
}

下面是官网处理异步的一个经典案例,async/await + runInAction的配合使用,在实际情况中很常用。

import { action, makeObservable, observable, runInAction } from "mobx";

class TodoStore {
    todos = [];

    constructor() {
        makeObservable(this, {
            todos: observable,
            addTodo: action,
        });
    }

    async fetchTodos() {
        const response = await fetch("https://jsonplaceholder.typicode.com/todos");
        const todos = await response.json();
        runInAction(() => {
            this.todos = todos;
        });
    }

    addTodo(todo) {
        this.todos.push(todo);
    }
}

Computed

在 MobX6 中,Computed 是一种基于 Observable 创建的衍生值。Computed 可以从 Observable 中获取数据,并根据这些数据生成一个新的值,这个新的值也是一个 Observable

使用 Computed,我们可以避免在代码中手动追踪衍生值的变化,从而简化代码并提高应用程序的性能。

//store/CounterStore.ts
import { computed, makeObservable, observable } from "mobx";

class Order {
    items = [];
    discountRate = 0.1;

    constructor() {
        makeObservable(this, {
            items: observable,
            discountRate: observable,
            totalPrice: computed,
            discountedPrice: computed,
        });
    }

    get totalPrice() {
        return this.items.reduce((total, item) => total + item.price, 0);
    }

    get discountedPrice() {
        return this.totalPrice * (1 - this.discountRate);
    }
}

在这个例子中,我们定义了一个 Order 类,其中 itemsdiscountRate 属性都是可观察对象。我们使用 makeObservable 函数将它们转化为可观察对象,将 totalPricediscountedPrice 方法转化为 Computed

当我们调用 totalPrice 方法时,它会从 items 中获取数据,并根据这些数据计算出总价。当 items 中的数据发生变化时,totalPrice 会自动重新计算,从而保证总价的正确性。

同样的,当我们调用 discountedPrice 方法时,它会从 totalPricediscountRate 中获取数据,并根据这些数据计算出折扣后的价格。当 itemsdiscountRate 发生变化时,discountedPrice 会自动重新计算,从而保证折扣后的价格的正确性。

需要注意的是,如果你想要在 Computed 中访问其他 Observable,你需要在 makeObservablemakeAutoObservable 中将这些 Observable 声明为可观察对象,否则 Computed 将无法自动追踪这些 Observable 的变化

Reactions

autorun

autorun 是 MobX6 中的一个 API,它用于创建一个响应式函数,并在数据发生变化时自动执行该函数。autorun 接受一个函数作为参数,该函数中使用的任何响应式数据都会被自动收集,当这些数据发生变化时,autorun 会自动重新运行该函数。

autorun 的语法如下:

autorun(view: (r: IReactionPublic) => any, opts?: IAutorunOptions): IReactionDisposer

其中,view 是一个响应式函数,会被自动执行;options 是一个可选的配置对象,可以设置 name 和 delay 等选项。
下面是一个示例,使用 autorun 创建一个响应式函数:

import { observable, autorun } from 'mobx';

const store = observable({
    count: 0,
});

autorun(() => {
    console.log('Count:', store.count);
});

store.count = 1;
store.count = 2;

在上面的示例中,autorun 创建了一个响应式函数,会在 store.count 发生变化时自动重新执行。当 store.count 分别变为 1 和 2 时,响应式函数会分别输出 Count: 1 和 Count: 2。

需要注意的是,在使用 autorun 时,需要传入一个响应式函数作为参数,该函数中使用的任何响应式数据都会被自动收集,当这些数据发生变化时,autorun 会自动重新运行该函数。此外,autorun 返回一个 disposer 函数,可以用于取消响应式函数的自动执行。

reaction

reaction 是 MobX6 中的一个 API,它用于创建一个响应式函数,并在数据发生变化时自动执行该函数。与 autorun 不同的是,reaction 允许自定义数据变化时执行的操作,并返回一个可用于取消响应式函数的 disposer 函数。

reaction 的语法如下:

reaction<T>(
    expression: (r: IReactionPublic) => T,
    effect: (arg: T, prev: T | undefined, r: IReactionPublic) => void,
    options ?: IReactionOptions
): IReactionDisposer

其中,expression 是一个函数,用于返回一个数据源;effect 是一个函数,用于执行自定义操作;options 是一个可选的配置对象,可以设置 namefireImmediately 等选项。

下面是一个示例,使用 reaction 创建一个响应式函数:

import { observable, reaction } from 'mobx';

const store = observable({
    count: 0,
});

reaction(
    () => store.count,
    (count, prevCount) => {
        console.log(`Count changed from ${prevCount} to ${count}`);
    }
);

store.count = 1;
store.count = 2;

在上面的示例中,reaction 创建了一个响应式函数,会在 store.count 发生变化时自动执行,并输出变化前后的值。当 store.count 分别变为 1 和 2 时,响应式函数会分别输出 Count changed from 0 to 1 和 Count changed from 1 to 2。

需要注意的是,在使用 reaction 时,需要传入两个函数作为参数,一个用于返回数据源,一个用于执行自定义操作。与 autorun 不同的是,reaction 允许自定义数据变化时执行的操作,并返回一个可用于取消响应式函数的 disposer 函数。

下面是一个综合示例:

//store/CounterStore.ts
import { autorun, makeAutoObservable, reaction, runInAction } from 'mobx'

class CounterStore {
    //observable
    count = 10;

    constructor() {
        //参数2:排除属性和方法,{reset:false}
        //参数3:自动绑定,不指定就只能用 ()=>
        makeAutoObservable(this, {}, { autoBind: true })
    }

    //action
    increment() {
        this.count++;
    }

    //计算属性
    get double() {
        return this.count * 2;
    }

    // //处理异步-方式1
    decrementAsync1() {
        setTimeout(this.increment, 1000)
    }

    // //处理异步-方式2(推荐这种)
    decrementAsync2() {
        setTimeout(() => {
            runInAction(() => {
                this.count++;
            })
        }, 1000)
    }
}

let counterStore = new CounterStore();

// 监听属性-1
// 创建时,会先执行一次
autorun(() => {
    console.log("counter.count", counterStore.count)
})

// 监听属性-2
// 创建时,不执行,变化时,才执行
reaction(
    () => counterStore.count,
    () => {
        console.log("count变化了")
    }
)

export default CounterStore;
export { counterStore };
//store/RootStore.ts
import { createContext, useContext } from "react";
import CounterStore, { counterStore } from "./CounterStore";

class RootStore {
    counterStore: CounterStore;

    constructor() {
        this.counterStore = counterStore;
    }
}

const store = new RootStore();

//提供初始值,不需要context.Provider
const context = createContext(store)

//自定义hook
function useStore() {
    return useContext(context);
}

export default useStore;

when

when 是 MobX6 中的一个 API,它用于在满足指定条件时执行某些操作。when 会一直等待直到条件变为 true,然后执行传入的回调函数。

when(predicate: () => boolean, effect?: () => void, options?)

其中,predicate 是一个返回布尔值的函数,表示条件;effect 是一个可选的回调函数,表示当条件满足时要执行的操作;options 是一个可选的配置对象,可以设置 timeoutname 等选项。

下面是一个示例,使用 when 来等待一个异步操作完成后再执行某些操作:

import { observable, when } from 'mobx';

const store = observable({
    data: null,
    isLoading: true,
    async fetchData() {
        const response = await fetch('/api/data');
        const data = await response.json();
        this.data = data;
        this.isLoading = false;
    },
});

when(() => !store.isLoading, () => {
    console.log('Data has been loaded:', store.data);
});

store.fetchData();

Mobx模块化

//store/Counter.ts
import {
    makeObservable,
    observable,
    action,
    computed,
    makeAutoObservable,
    autorun,
    reaction,
    runInAction
} from 'mobx'

class Counter {
    //observable
    count = 0;

    constructor() {
        // makeObservable(this, {
        //     count: observable,
        //     increment: action,
        //     reset: action,
        //     decrement:action.bound,
        //     double:computed
        // })

        //参数2:排除属性和方法,{reset:false}
        //参数3:自动绑定,不指定就只能用 ()=>
        makeAutoObservable(this, {}, { autoBind: true })
    }
    increment() {
        this.count++;
    }

    //action
    reset() {
        this.count = 0;
    }
    decrement() {
        this.count--;
    }

    //计算属性
    get double() {
        return this.count * 2;
    }

    //处理异步-方式1
    decrementAsync() {
        setTimeout(this.increment, 1000)
    }

    //处理异步-方式2
    decrementAsync2() {
        setTimeout(() => {
            runInAction(() => {
                this.count++;
            })
        }, 1000)
    }
}
let myCounter = new Counter();

// 监听属性-1
// 创建时,会先执行一次
autorun(() => {
    console.log("counter.count", myCounter.count)
})

// 监听属性-2   
// 创建时,不执行,变化时,才执行
reaction(
    () => myCounter.count,
    () => {
        console.log("count变化了")
    }
)
export default myCounter;
//store/index.ts
import { createContext, useContext } from "react";
import myCounter from "./Counter";

class RootStore {
    counter = myCounter;
}

const store = new RootStore;

//提供初始值,可以不使用context.Provider
const context = createContext(store)

//自定义hook
function useStore() {
    return useContext(context);
}

export default useStore;
import React from 'react'
import { observer } from 'mobx-react'
import useStore from './store'

function App() {
  let store = useStore();
  return (
    <div>
      <h3>计数器</h3>
      <h4>{store.counter.count}</h4>
      <h4>{store.counter.double}</h4>
      <button onClick={() => store.counter.increment()}>+1</button>
      <button onClick={store.counter.decrement}>-1</button>
      <button onClick={() => store.counter.reset()}>重置</button>
      <button onClick={() => store.counter.decrementAsync()}>异步+1</button>
      <button onClick={() => store.counter.decrementAsync2()}>异步+1</button>
    </div>
  )
}
export default observer(App);

总结

Mobx6是一个非常强大的状态管理库,它可以帮助我们轻松地管理应用程序的状态。在使用Mobx6时,我们应该遵循一些最佳实践,例如避免使用动态可观察数据、使用Computed代替派生状态、使用Reactions处理异步行为等。此外,我们还可以利用Mobx6的一些新特性,例如使用makeObservable函数、可观察对象的类型推导、使用@action.bound代替装饰器等。虽然Mobx6具有很多优点,但仍然存在一些局限性,例如不支持自动引用计数、不支持观察MapSet对象的键等。因此,在使用Mobx6时,我们应该了解这些局限性,并根据实际情况进行选择。

转载请注明出处或者链接地址:https://www.qianduange.cn//article/14692.html
标签
评论
发布的文章

RapidJSON介绍

2024-08-14 00:08:38

jQuery 4 beta 震撼发布

2024-08-14 00:08:36

cJSON的使用

2024-08-14 00:08:36

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!