Vue 状态管理 & Vue CLI

lin-lins 2024-08-03 09:37:02 阅读 58

Vue 状态管理 & Vue CLI

1、状态管理2、集中状态管理2.1 Vuex2.1.1 Vuex核心概念2.1.2 Vuex Store实例2.1.3 Vuex Getter2.1.4 Vuex Mutation2.1.4 Vuex Actions2.1.4 Vuex Module

2.2 Pinia2.2.1功能增强

3、Vuex 实现原理4、Pinia 实现原理5、CLI5.1 实现

1、状态管理

将一个数据变为状态的方法:

ref

reactive

provide + inject,深层状态传递

Vuex、Pinia,集中状态管理

eventbus,状态管理,响应式数据解决不了的问题,我们尝试使用发布订阅模式来解决

怎么选择:

如果是组件内部状态、父子组件状态管理,选择 ref、reactive、computed如果是深层组件状态,选择 provide + inject 来处理

换肤、主题国际化 跨组件、跨模块的状态,选择 vuex、Pinia

2、集中状态管理

在 Vue 最重要就是 数据驱动 和 组件化,每个组件都有自己 data ,template 和 methods。data是数据,我们也叫做状态,通过 methods 中方法改变状态来更新视图,在单个组件中修改状态更新视图是很方便的。

当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:

多个视图依赖于同一状态。来自不同视图的行为需要变更同一状态。

2.1 Vuex

2.1.1 Vuex核心概念

Action:响应在视图上的用户输入导致的状态变化。State:驱动应用的数据源;Mutation(在pinia中被取消)View:以声明方式将状态映射到视图;Module:数据模块

ps: react:action -> reducer -> state -> view

在这里插入图片描述

Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

2.1.2 Vuex Store实例

<code>import { createStore } from 'vuex';

const defaultState = {

count: 0,

};

// Create a new store instance.

export default createStore({

state() {

return defaultState;

},

mutations: {

increment(state) {

state.count++;

},

},

actions: {

increment(context) {

context.commit('increment');

},

},

});

import { createApp } from 'vue';

import App from './App.vue';

import store from './store';

createApp(App).use(store).mount('#app');

Vue 组件中展示状态

// 创建一个 Counter 组件

const Counter = {

template: `<div>{ { count }}</div>`,

computed: {

count () {

return store.state.count

}

}

}

mapState 辅助函数

当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性:

// 在单独构建的版本中辅助函数为 Vuex.mapState

import { mapState } from 'vuex'

export default {

// ...

computed: mapState({

// 箭头函数可使代码更简练

count: state => state.count,

// 传字符串参数 'count' 等同于 `state => state.count`

countAlias: 'count',

// 为了能够使用 `this` 获取局部状态,必须使用常规函数

countPlusLocalState (state) {

return state.count + this.localCount

}

})

}

对象展开运算符

对象展开运算符(opens new window),将多个对象合并为一个,将最终对象传给 computed 属性

computed: {

localComputed () { /* ... */ },

// 使用对象展开运算符将此对象混入到外部对象中

...mapState({

// ...

})

}

2.1.3 Vuex Getter

有时候从 store 中的 state 中派生出一些状态,例如对列表进行过滤并计数:(使用计算属性)

computed: {

doneTodosCount () {

return this.$store.state.todos.filter(todo => todo.done).length

}

}

如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想。

Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。

Getter 接受 state 作为其第一个参数:

const store = createStore({

state: {

todos: [

{ id: 1, text: '...', done: true },

{ id: 2, text: '...', done: false }

]

},

getters: {

doneTodos (state) {

return state.todos.filter(todo => todo.done)

}

}

})

通过属性访问

Getter 会暴露为 store.getters 对象,你可以以属性的形式访问这些值:

store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]

通过方法访问

getters: {

// ...

getTodoById: (state) => (id) => {

return state.todos.find(todo => todo.id === id)

}

}

store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }

mapGetters 辅助函数

mapGetters 辅助函数:将 store 中的 getter 映射到局部计算属性:

用法类似mapState

import { mapGetters } from 'vuex'

export default {

// ...

computed: {

// 使用对象展开运算符将 getter 混入 computed 对象中

...mapGetters([

'doneTodosCount',

'anotherGetter',

// ...

])

}

}

如果你想将一个 getter 属性另取一个名字,使用对象形式:

...mapGetters({

// 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`

doneCount: 'doneTodosCount'

})

2.1.4 Vuex Mutation

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。

每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)

回调函数:实际进行状态更改的地方,并且它会接受 state 作为第一个参数

事件类型:store.commit(type)

定义:

const store = createStore({

state: {

count: 1

},

mutations: {

increment (state) {

// 变更状态

state.count++

}

}

})

要唤醒一个 mutation 处理函数,你需要以相应的 type 调用 store.commit 方法:

store.commit('increment')

Payload

你可以向 store.commit 传入额外的参数,即 mutation 的载荷(payload):

// ...

mutations: {

increment (state, n) {

state.count += n

}

}

// ..

store.commit('increment', 10)

在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:

// ...

mutations: {

increment (state, payload) {

state.count += payload.amount

}

}

// ...

store.commit('increment', {

amount: 10

})

对象风格的提交方式

store.commit({

type: 'increment',

amount: 10

})

重要:Mutation 必须是同步函数

2.1.4 Vuex Actions

Actions存在的意义是假设你在修改state的时候有异步操作,vuex作者不希望你将异步操作放在Mutations中,所以就给你设置了一个区域,让你放异步操作,这就是Actions。

const store = createStore({

state: {

count: 0

},

mutations: {

increment (state) {

state.count++

}

},

actions: {

increment (context) {

context.commit('increment')

}

}

// actions: {

// increment ({ commit }) {

// commit('increment')

// }

// }

})

Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。

分发 Action:

Action 通过 store.dispatch 方法触发:

store.dispatch('increment')

我们直接分发 mutation 岂不更方便?实际上并非如此,还记得 mutation 必须同步执行这个限制么?

Action 就不受约束!我们可以在 action 内部执行异步操作:

actions: {

incrementAsync ({ commit }) {

setTimeout(() => {

commit('increment')

}, 1000)

}

}

Actions 支持同样的载荷方式和对象方式进行分发:

// 以载荷形式分发

store.dispatch('incrementAsync', {

amount: 10

})

// 以对象形式分发

store.dispatch({

type: 'incrementAsync',

amount: 10

})

2.1.4 Vuex Module

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

const moduleA = {

state: () => ({ ... }),

mutations: { ... },

actions: { ... },

getters: { ... }

}

const moduleB = {

state: () => ({ ... }),

mutations: { ... },

actions: { ... }

}

const store = createStore({

modules: {

a: moduleA,

b: moduleB

}

})

store.state.a // -> moduleA 的状态

store.state.b // -> moduleB 的状态

2.2 Pinia

核心概念

State

Getter

Action

第一种是选项式写法

export const useCounterStore = defineStore("counter", {

state: () => ({ count: 0 }),

getters: {

doubleCount: (state) => state.count * 2,

},

actions: {

async increment() {

await sleep(1000);

this.count++;

},

},

});

第二种是 composition api 写法

// 推荐用这种方式

export const useCounterStore = defineStore("counter", () => {

const count = ref(0);

const doubleCount = computed(() => count.value * 2);

const increment = async () => {

await sleep(1000);

count.value++;

};

return { count, doubleCount, increment };

});

在 Setup Store 中

ref() 就是 state 属性computed() 就是 gettersfunction() 就是 actions

2.2.1功能增强

插件化机制

可以增强 Pinia 能力可以在 Pinia 加载时初始化一些数据,提供给应用使用

本质是一个函数,参数是 Pinia 实例内容,返回一个对象

function loggerPlugin() {

console.log("hello");

}

export function piniaLogger(context: PiniaPluginContext) {

// console.log('🚀 ~ piniaLogger ~ context:', context)

context.store.$onAction((...args) => {

console.log(`[🍍] ${ name} with payload`, args);

});

// return {

// logger: "I am pinia logger",

// };

}

插件既可以给定额外初始化 pinia 状态,也可以增强 Pinia 功能(日志)

3、Vuex 实现原理

各模块在核心流程中的主要功能:

Vue Components:Vue组件。HTML页面上,负责接收用户操作等交互行为,执行dispatch方法触发对应action进行回应。dispatch:操作行为触发方法,是唯一能执行action的方法。actions:操作行为处理模块。负责处理Vue Components接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他action以及提交mutation的操作。该模块提供了Promise的封装,以支持action的链式触发。commit:状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。mutations:状态改变操作方法。是Vuex修改state的唯一推荐方法,其他修改方式在严格模式下将会报错。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些hook暴露出来,以进行state的监控等。state:页面状态管理容器对象。集中存储Vue components中data对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新。getters:state对象读取方法。图中没有单独列出该模块,应该被包含在了render中,Vue Components通过该方法读取全局state对象。

不管是 Vue 还是 React,全局状态的方案基本都是基于发布订阅模式实现

4、Pinia 实现原理

Pinia 的实现原理核心内容与 Vuex 类似,发布订阅模式机制以及状态管理逻辑。除此以外,Pinia 比 Vuex 多了一些对于 Composition API 的支持机制

看源码的顺序:

createPinia.ts,入口

app.provide(piniaSymbol, pinia)

app.config.globalProperties.$pinia = piniavue-demi,轻量的 Vue3 钩子库,一般你去封装插件,都会用它store.tssubscriptions.ts

5、CLI

三种方案可以选择

Vue2 CLI,https://cli.vuejs.org/

Vue3 create,https://github.com/vuejs/create-vue

Vite Vue,https://vitejs.dev/guide/,pnpm create vite my-vue-app --template vue-ts

Vue CLI 创建应用:npm install -g @vue/cli,vue create hello

vue-create 创建:npm create vue@3

我们说的 Vue CLI 其实包含两个,一个是早期的 Vue CLI,打包基于 webpack,另一个是最新的 CLI,打包基于 vite

5.1 实现

模板库,用于创建新项目的模板,https://github.com/vuejs/create-vue/tree/main/template/basecli.ts (配置脚手架)

cli.ts的部分配置

// 用来人机交互完成后选择的变量

let result: {

projectName?: string;

shouldOverwrite?: boolean;

packageName?: string;

needsTypeScript?: boolean;

needsJsx?: boolean;

needsRouter?: boolean;

needsPinia?: boolean;

needsVitest?: boolean;

needsE2eTesting?: false | "cypress" | "nightwatch" | "playwright";

needsEslint?: boolean;

needsPrettier?: boolean;

needsDevTools?: boolean;

} = { };

CLI:通过命令行获得用户的意愿,根据这些意愿参数,组装模板,然后生成项目

补充资料:monorepo template:https://github.com/NiGhTTraX/ts-monorepo

pnpm workspace + turborepo



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。