使用Vuex
會使代碼變得繁瑣冗餘(代碼太多了)「被忽略的官方說明」,而大多時候咱們並無用到所謂的狀態追蹤,只是簡單的進行getter
、setter
、mutations
;當狀態多的時候,Vuex
真的能夠有利於咱們對代碼便捷的閱讀嗎?javascript
不利於編輯器和typescript
中代碼查找,每次要找某個變量的時候,要在store
文件全局搜,各類代碼片斷去反覆跳;如今這種狀態管理方式解決了這個痛點:像一些標準的多語言編輯器vscode
、atom
等是能夠檢查到靜態代碼的,Ctrl
+點擊能夠跳到對應位置、鼠標放上去某個字段時,會獲得定義時聲明的類型提示和代碼片斷(js中須要用jsDoc註釋聲明),一開始我也以爲這東西沒用,後面寫過typescript
才發現,原來javascript
也同樣能夠經過jsDoc註釋
來提供typescript
同樣的類型提示,真香。php
這裏我推薦自行定義class
做爲分模塊數據管理會更加好,理由就是數據龐大的時候能夠拆分爲各class
來便於對狀態的管理,爲何不使用普通object
單例對象?由於普通object
沒有繼承
、沒有自身調用的構造函數,在ts
中類型定義會比較麻煩,並且沒有class
中好用的private
、protected
和class
能夠做爲interface
使用等;一句話總結就是:「沒有class
靈活」,這個我也是從遊戲編程那邊借鑑來的習慣。css
寫這篇文章的時候,我早就在2017年開始實際項目中使用class
替代vuex
了,因此並不用擔憂有其餘問題,並且Vue 3.x
中也能夠複用,並且這只是一個編程的設計模式,並非我我的發明的方言html
const a = {
data: {
value: 10
}
}
const b = {
data: a.data
};
b.data.value = 20;
console.log(a, b); // 輸出 {data: { value: 20 }}, {data: { value: 20 }}
複製代碼
先來講下原理:由於javascript
變量對等賦值的時候,指針指向同一個內存,因此依賴這個特性和Vue
中賦值是直接對等,就能夠自行定義一些全局的狀態屬性,依次注入到須要同步更新的組件中。廢話很少說,直接看代碼結構:vue
src/store/index.js
// 自行定義一個class做爲數據管理
class ModuleStore {
/** 訂單信息 */
orderInfo = {
/** 訂單名 */
name: "訂單" + Math.random().toString(36).substr(2),
/** 訂單日期 */
date: "2018/12/12 12:12:12",
/** * 訂單狀態 * @type {"ok"|"fail"|"invalid"|"wait"} 完成 | 失敗 | 無效 | 待支付 */
state: "ok"
}
}
/** 狀態管理模塊 */
const store = new ModuleStore;
export default store;
複製代碼
src/goods.vue
<script> import store from "../store"; export default { data () { return { // 當前組件響應式用到的數據,這裏對等以後,修改 store.orderInfo 的值就是響應式了 pageData: store.orderInfo } } } </script>
複製代碼
src/list.vue
<script> import store from "../store"; export default { data () { return { // 當前組件響應式用到的數據,這裏對等以後,修改 store.orderInfo 的值就是響應式了 listData: store.orderInfo } }, methods: { modifyState() { // 這裏修改了,其餘引用到 store.orderInfo 全部組件都會同步修改 this.listData.state = "wait"; // 或者 store.orderInfo.state = "wait"; } } } </script>
複製代碼
<script> import store from "../store"; export default { ...more, methods: { modifyState() { // 注意不要這樣去修改整個屬性,由於會致使對象內全部的屬性都失去了指針(參照一開始那個段代碼片斷) this.listData = { name: "修改訂單名", date: new Date().toLocaleString() } // 可是我不想 // this.listData.name = "修改訂單名"; // this.listData.date = new Date().toLocaleString() // 咋整? } } } </script>
複製代碼
ModifyObject.js
,能夠分代碼多方式去使用:默認導出給其餘模塊繼承使用
、做爲普通單例工具使用
;在typescript
中這兩個方法我會用泛型去約束傳參,那麼就確保了狀態字段的可靠性。// 由於可能存在多個狀態模塊,因此定義一個基類,導出讓其餘模塊繼承使用
// 這裏 export 是給其餘模塊繼承用
export class ModuleModifyObject {
/** * 修改屬性值-只修改以前存在的值 * @param {object} target 修改的目標 * @param {object} value 修改的內容 */
modifyData(target, value) {
for (const key in value) {
if (Object.prototype.hasOwnProperty.call(target, key)) {
target[key] = value[key];
}
}
}
/** * 設置屬性值-以前不存在的值也根據傳入的`value`值去設置 * @param {object} target 設置的目標 * @param {object} value 設置的內容 */
setData(target, value) {
for (const key in object) {
target[key] = value[key];
}
}
}
const modifyObject = new ModuleModifyObject();
// 這裏 export default 是做爲單例用
export default modifyObject;
複製代碼
src/store/index.js
import {
ModuleModifyObject
} from "./utils/ModifyObject"
class ModuleStore extends ModuleModifyObject {
...more
}
...more
複製代碼
src/list.vue
中<script> import store from "../store"; export default { ...more, methods: { modifyState() { const obj = { name: "修改訂單名", date: new Date().toLocaleString(), // 下面是不在 store.orderInfo 的屬性,上面的基類中我已經作了處理,因此自己不存在的屬性是不會更改到的 a: "php", b: "java" } // 寫法一 store.modifyData(this.listData, obj); // 寫法二 store.modifyData(store.orderInfo, obj); // 這樣就不須要 this.listData.name = "修改訂單名"; this.listData.date = new Date().toLocaleString() 這種逐個屬性去修改了 } } } </script>
複製代碼
store.xxx
,而是經過mutations
去修改狀態1. 開始改用typescript
,首先要定義兩個核心類型工具,一會用到java
/** 深層遞歸全部屬性爲可選 */
export type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
}
/** 深層遞歸全部屬性爲只讀 */
export type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
}
複製代碼
2. 改造一下store
模塊;注意這裏不是要使用vuex
的mutations
,而是經過對應方法去約束使用react
interface UrderInfoType {
/** 用戶名 */
name: string
/** 用戶出生日期 */
date: string
/** 性別 */
gender: "man" | "woman"| ""
/** 登陸信息 */
api: {
/** 登陸憑據 */
token: string
/** 登陸`id` */
id: string
}
}
class ModuleStore extends ModuleModifyObject {
/** 用戶信息 */
readonly userInfo: DeepReadonly<UrderInfoType> = {
name: "",
date: "",
gender: "",
api: {
token: "",
id: "",
}
}
/** * 更新用戶信息,並緩存到`sessionStorage`中 * @param value 要更新的值 */
updateUserInfo(value: DeepPartial<UserInfoType>) {
// `modifyData`方法在裏面作了遞歸處理,能夠看下面的代碼倉庫有寫,這裏不作解釋
this.modifyData(this.userInfo, value);
// or
// this.modifyData(this.userInfo as DeepPartial<UserInfoType>, value);
sessionStorage.setItem("user-info", JSON.stringify(this.userInfo));
}
}
複製代碼
3. 回到使用場景git
<script> import store from "../store"; export default { data () { return { // 掛載響應式狀態 userInfo: store.userInfo } }, methods: { setToken() { // 如今不能直接修改了,編輯器會報錯提示:沒法分配到 "token" ,由於它是隻讀屬性。 // this.userInfo.api.token = "" // 如今只能經過`updateUserInfo`去更改要更改的屬性,並且有類型約束和代碼提示,不再會擔憂狀態出錯, // 反正我是不會去記住每個屬性的,這些就應該交給機器去記住而後提示出來 store.updateUserInfo({ api: { token: "adfsdf2sd4f5s4d" } }) } } } </script>
複製代碼
4. 對比下vuex
中的mutations
github
const store = new Vuex.Store({
state: {
userInfo: {
...more
}
},
mutations: {
updateUserInfo(state, value) {
state.userInfo = value;
sessionStorage.setItem("user-info", JSON.stringify(state.userInfo));
}
}
})
// 使用時,官方給出的例子是:若是不想覆蓋舊屬性的值去改某個屬性,那麼這樣寫
this.$store.commit(updateUserInfo, {
...this.$store.state.userInfo,
...{
api: {
token: "asfdf45s4d54s5d4f5",
id: vuexStore.state.userInfo.api.id
}
}
})
// 實在太醜陋了,並且屬性多的時候(對象屬性很深層),不必定能保證你能夠百分百沒寫錯,懂我要說什麼吧?
// 或者說,你在外面設置好修改以後的數據再傳進去不就行了嗎?那來看看是怎麼樣的
const value = JSON.parse(JSON.stringify(this.$store.state.userInfo)); // 這裏必需要深拷貝,否則直接修改到狀態了
value.api.token = "asfdf45s4d54s5d4f5"
this.$store.commit(updateUserInfo, value);
// 經過對比,應該能看出來有什麼區別了
複製代碼
以上這個就是基本的狀態管理實現,說下我在項目中用到的狀態監聽處理:在ModuleStore
這個類裏面使用Object.defineProperty
或者new Proxy
做爲更復雜的操做,自定義的class
做爲狀態管理更容易理解,並且擴展性也高。vuex
最後對比優缺點,優勢:代碼編輯器(以vscode爲例)靜態代碼追蹤提示很是友好(Vuex
沒法實現,並且代碼多的時候找某個屬性太費力了,因此我才放棄使用),數據龐大時尤爲明顯,若是你是使用ts
,配合 readonly
、private
、enum
等關鍵字使用,可維護、閱讀性簡直再舒服不過。缺點:沒法使用瀏覽器的vuex插件,不過有了靜態代碼分析檢測,也不須要調試插件了。
稍做改動,在store
模塊中把對應的屬性加上reactive()
便可:
import { reactive } from "vue";
class ModuleStore {
/** 訂單信息 */
orderInfo = reactive({
/** 訂單名 */
name: "訂單" + Math.random().toString(36).substr(2),
/** 訂單日期 */
date: "2018/12/12 12:12:12",
/** * 訂單狀態 * @type {"ok"|"fail"|"invalid"|"wait"} 完成 | 失敗 | 無效 | 待支付 */
state: "ok"
})
}
...代碼省略
複製代碼
仍是用上面兩個文件爲例子
src/list.vue
<template>
<div class="list card">
<h1 class="title">{{ orderInfo.name }}</h1>
<code class="code_box">{{ orderInfo }}</code>
<button class="button button_blue" @click="changeName()">設置`orderInfo.name`爲"訂單"</button>
</div>
</template>
<script> import store from "../store"; export default { setup() { // 直接引用便可 const orderInfo = store.orderInfo; function changeName() { orderInfo.name = "訂單" + Math.random().toString(36).substr(2); } return { changeName, orderInfo } } } </script>
<style> .list{ width: 100%; margin-bottom: 20px; padding: 8px; } .list .title { font-size: 22px; margin-bottom: 14px; } .list .code_box { font-size: 14px; margin-bottom: 10px; display: block; } </style>
複製代碼
src/goods.vue
<template>
<div class="goods card">
<h1 class="title">{{ orderInfo.name }}</h1>
<code class="code_box">{{ orderInfo }}</code>
<button class="button button_green" @click="changeName()">設置`orderInfo.name`爲隨機字符串</button>
<button class="button button_red" @click="changeState()">修改`orderInfo.state`</button>
</div>
</template>
<script> import store from "../store"; export default { setup() { // 直接引用便可 const orderInfo = store.orderInfo; function changeName() { orderInfo.name = Math.random().toString(36).substr(2); } function changeState() { orderInfo.state = state.orderInfo.state == "wait" ? "ok" : "wait"; } return { changeName, changeState, orderInfo } } } </script>
<style> .goods{ width: 100%; margin-bottom: 20px; padding: 8px; } .goods .title { font-size: 22px; margin-bottom: 14px; } .goods .code_box { font-size: 14px; margin-bottom: 10px; display: block; } </style>
複製代碼
能夠看到,vue 3.x
以後提供了reactive()
這個api
使得響應式變量能夠直接提取到其餘地方去聲明,相比vue 2.x
的操做簡單了一些,代碼更直觀一點;以上這個設計模式在二者中,實現的功能都是相同的。
最後附上使用到的項目:vue-admin-template、uni-app-template
博客地址:Hjs' blog
有問題歡迎提出~