序文
月曜日に Meituan の面接を受けました。面接官はとても親切で、基本的に履歴書に関する質問をしてくれました。以下は Vuex の簡易版の実装方法を再整理したものです。Vuex の一般的な原理がわかります。
vuexの基本的な使い方
//index.js
import { createStore } from './gvuex.js'
const store = createStore({
// 定义store的状态
state() {
return {
count: 1
}
},
// 定义获取state状态数据的计算属性getters,以下的值都被认为是state的派生值
getters: {
double(state) {
return state.count * 2
}
},
// 定义修改state状态数据的方法mutations
mutations: {
add(state) {
state.count++
}
},
// 定义异步操作的方法actions
actions: {
asyncAdd({ commit }) {
setTimeout(() => {
commit('add')
}, 1000)
}
}
})
// App.vue
<script setup>
import { useStore } from '../store/gvuex'
import { computed } from 'vue'
let store = useStore();
let count = computed(()=>{ store.state.count })
let double = computed(()=>{ store.getters.double })
function add() {
store.commit('add')
}
function asyncAdd() {
store.dispatch('asyncAdd')
}
</script>
<template>
<div class="">
{
{ count }} * 2 = {
{ double }}
<button @click="add">add</button>
<button @click="asyncAdd">async add</button>
</div>
</template>
<style scoped>
</style>
vuex の使用法を理解した上で、次の質問をしてみませんか。
イベントの実行をトリガーする必要があるのはなぜですか
store.commit('add')
?mutation
関数を直接呼び出して操作することは可能でしょうか?関数を呼び出すことによってのみ、
state
保存された状態を直接変更できないのはなぜですか?mutation
store.dispatch('asyncAdd')
非同期的に呼び出される関数には、完了する関数が必要なのはなぜですか? 直接呼び出すことはできるのでしょうかstore.commit('asyncAdd')
?そうでない場合は、なぜですか?createStore()
そしてuseStore()
一体何が起こったのですか?
それでは一つ一つ解読していきましょう。
vueにグローバルコンポーネントを登録する
import { createApp } from 'vue'
import store from './store'
import App from './App.vue'
const app = createApp(App)
app
.use(store)
.mount('#app')
app.use()
これはプラグインをインストールするために使用され、パラメータ (通常はプラグイン オブジェクト) を受け入れます。このパラメータは、呼び出されたときに自動的に実行されるinstall
メソッドを公開する必要があります。app.use()
install()
Storeクラスのプロセスを解析する
import { reactive, inject } from 'vue'
// 定义了一个全局的 key,用于在 Vue 组件中通过 inject API 访问 store 对象
const STORE_KEY = '__store__'
// 用于获取当前组件的 store 对象
function useStore() {
return inject(STORE_KEY)
}
// Store 类,用于管理应用程序状态
class Store {
// 构造函数,接收一个包含 state、mutations、actions 和 getters 函数的对象 options,然后将它们保存到实例属性中
constructor(options) {
this.$options = options;
// 使用 Vue.js 的 reactive API 将 state 数据转换为响应式对象,并保存到实例属性 _state 中
this._state = reactive({
data: options.state()
})
// 将 mutations 和 actions 函数保存到实例属性中
this._mutations = options.mutations
this._actions = options.actions;
// 初始化 getters 属性为空对象
this.getters = {};
// 遍历所有的 getters 函数,将其封装成 computed 属性并保存到实例属性 getters 中
Object.keys(options.getters).forEach(name => {
const fn = options.getters(name);
this.getters[name] = computed(() => fn(this.state));
})
}
// 用于获取当前状态数据
get state() {
return this._state.data
}
// 获取mutation内定义的函数并执行
commit = (type, payload) => {
const entry = this._mutations[type]
entry && entry(this.state, payload)
}
// 获取actions内定义的函数并返回函数执行结果
// 简略版dispatch
dispatch = (type, payload) => {
const entry = this._actions[type];
return entry && entry(this, payload)
}
// 将当前 store 实例注册到 Vue.js 应用程序中
install(app) {
app.provide(STORE_KEY, this)
}
}
// 创建一个新的 Store 实例并返回
function createStore(options) {
return new Store(options);
}
// 导出 createStore 和 useStore 函数,用于在 Vue.js 应用程序中管理状态
export {
createStore,
useStore
}
vuex の基礎となる実装がわずか数十行のコードで実現されていることには驚きましたか? それは、 vue から と を導入し、ソースreactive
コードの大部分を省略しており、inject
これよりもはるかに複雑だからです。リアクティブの実装を理解することに興味がある場合は、私の他の記事「VUE ソース コードの学習: 手書き最小バージョン レスポンシブ プロトタイプ \- Nuggets\(juejin.cn\) [1] 」を参照してください。上記の質問に答えましょう。computed
dispatch
commit
答え
質問 1:store.commit('add')
イベントの実行をトリガーする必要があるのはなぜですか? mutation
関数を直接呼び出して操作することは可能でしょうか?
回答: ストア クラスにはミューテーション メソッドがまったくなく、ミューテーション内の関数リストは commit メソッドを呼び出すことによってのみ実行できます。
state
質問 2:関数を呼び出すことによってのみ、保存された状態を直接変更できないのはなぜですか?
回答: Vuex は、ストアの変更方法に制限を適用することで、状態のトレーサビリティを確保します。ストア内の状態は、状態の変化を簡単に追跡できるミューテーション機能を介してのみ変更できます。また、さまざまなコンポーネントからストアを意図せずに直接変更することも回避できるため、コードの保守とデバッグが困難になります。
質問 3: 非同期で呼び出される関数をstore.dispatch('asyncAdd')
完了するために関数が必要なのはなぜですか? 直接呼び出すことはできるのでしょうかstore.commit('asyncAdd')
?そうでない場合は、なぜですか?
回答: 実際には、ディスパッチ メソッドとコミット メソッドは単純なものではありません。これら 2 つのメソッドに関する vuex のソース コード部分の一部を投稿しましょう。
Store.prototype.dispatch = function dispatch (_type, _payload) {
var this$1$1 = this;
// check object-style dispatch
var ref = unifyObjectStyle(_type, _payload);
var type = ref.type;
var payload = ref.payload;
var action = { type: type, payload: payload };
var entry = this._actions[type];
if (!entry) {
if ((process.env.NODE_ENV !== 'production')) {
console.error(("[vuex] unknown action type: " + type));
}
return
}
try {
this._actionSubscribers
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
.filter(function (sub) { return sub.before; })
.forEach(function (sub) { return sub.before(action, this$1$1.state); });
} catch (e) {
if ((process.env.NODE_ENV !== 'production')) {
console.warn("[vuex] error in before action subscribers: ");
console.error(e);
}
}
var result = entry.length > 1
? Promise.all(entry.map(function (handler) { return handler(payload); }))
: entry[0](payload);
return new Promise(function (resolve, reject) {
result.then(function (res) {
try {
this$1$1._actionSubscribers
.filter(function (sub) { return sub.after; })
.forEach(function (sub) { return sub.after(action, this$1$1.state); });
} catch (e) {
if ((process.env.NODE_ENV !== 'production')) {
console.warn("[vuex] error in after action subscribers: ");
console.error(e);
}
}
resolve(res);
}, function (error) {
try {
this$1$1._actionSubscribers
.filter(function (sub) { return sub.error; })
.forEach(function (sub) { return sub.error(action, this$1$1.state, error); });
} catch (e) {
if ((process.env.NODE_ENV !== 'production')) {
console.warn("[vuex] error in error action subscribers: ");
console.error(e);
}
}
reject(error);
});
})
};
Store.prototype.commit = function commit (_type, _payload, _options) {
var this$1$1 = this;
// check object-style commit
var ref = unifyObjectStyle(_type, _payload, _options);
var type = ref.type;
var payload = ref.payload;
var options = ref.options;
var mutation = { type: type, payload: payload };
var entry = this._mutations[type];
if (!entry) {
if ((process.env.NODE_ENV !== 'production')) {
console.error(("[vuex] unknown mutation type: " + type));
}
return
}
this._withCommit(function () {
entry.forEach(function commitIterator (handler) {
handler(payload);
});
});
this._subscribers
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
.forEach(function (sub) { return sub(mutation, this$1$1.state); });
if (
(process.env.NODE_ENV !== 'production') &&
options && options.silent
) {
console.warn(
"[vuex] mutation type: " + type + ". Silent option has been removed. " +
'Use the filter functionality in the vue-devtools'
);
}
};
ソース コードでは、dispatch メソッドが Promise オブジェクトを返すのに対し、commit メソッドは値を返さず、同期コードの操作を完全に実行することがわかります。戻り値は多くのシナリオには当てはまらないかもしれませんが、この設計の主な目的は、ステータスのトレーサビリティを確保することです。
質問 4: createStore()
とuseStore()
に何が起こったのですか?
createStore()
関数を 呼び出すと 、そのコンストラクターはstate
、mutations
、actions
および function を含むgetters
オブジェクトを 受け取りoptions
、それらをインスタンスのプロパティに保存します。このとき、state
その中の値は応答性の高いオブジェクトに変換され、すべてのgetters
関数は次の時点で走査されます。それを属性にカプセル化してcomputed
インスタンス属性に保存しgetters
、main.js の app.use() を呼び出すと、自動的に install メソッドが実行され、現在のストア インスタンスが Vue.js に登録されます。アプリケーションを呼び出すだけで取得できます。useStore()
グローバル状態管理用のストア インスタンスは、グローバルに信頼しinject
てprovide
共有できます。
要約する
単純な vuex を実装し、基本的な機能を完成させることで、vuex についての理解が変わりますが、vuex のソース コードに関して言えば、上記のデモは依存関係を大まかに整理しただけであり、モジュール関数はありません。プラグイン機能を提供する、厳密モードを提供しない、ヘルパー関数を提供しない、シングルトン パターンを実装するなどの実装。
参考文献
[1]
https://juejin.cn/post/7222928881215881274
この記事について
著者: スーパーマンは飛べない
https://juejin.cn/post/7237479703167483961