手書きの Vuex4.x コア (Vuex ソース実装)

vuex を使用して TodoList を実装します。

最初に小さな TodoList ケースを作成し、vuex を適用してそれを実現し、ロジックが実行された後、これに基づいて独自の vuex を少しずつ作成し、以前と同じ効果を達成しましょう。

vite を使用してプロジェクトを作成します。

yarn create vite vuex-core-dev --template vue

vuex をインストールします。

yarn add vuex@next --save

hellowolrd.vue を削除し、src ディレクトリの下に新しいストア フォルダーを作成し、vuex 構造を改善します。

main.js で導入:

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import store from './store'

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

store ディレクトリの下の index.js で返されるオブジェクトには、install(app) が含まれている必要があります。これは vue のプラグインによって決まり、使用後にインストールする必要があります。これが vuex 内で実装されているだけですが、独自の vuex で注意を払う必要があります。main.js で訴える場合は、返されるオブジェクトに install を含める必要があります。

TodoList のロジックに従って、vuex の実装を書きましょう。

state.js:

export default {
    todos: [],
    filter: 'all', //all finished unfinished
    id: 0
}

状態では、todos はすべての情報を保存し、フィルターは現在の全体的な TodoList の状態を示します. all の場合はすべてのリストを表示し、完了した場合は完了したすべてのリストを表示します。未完成の場合、すべての未完成リストが表示されます。また、各リストには対応する ID があります。

actions.js:

export default {
    addTodo ({ commit }, text) {
        commit('addTodo', text)
    },
    toggleTodo ({ commit }, id) {
        commit('toggleTodo', id)
    },
    removeTodo ({ commit }, id) {
        commit('removeTodo', id)
    }
}

理解するのは難しいことではありません。つまり、状態のデータを変更するアクションを介して指定されたミューテーションを送信することです。

Mutation.js:

export default {
    addTodo (state, text) {
        state.todos.push({
            id: state.id++,
            text,
            isFinished: false
        })
    },
    toggleTodo (state, id) {
        state.todos = state.todos.map(item => {
            if (item.id === id) {
                item.isFinished = !item.isFinished
            }
            return item
        })
    },
    removeTodo (state, id) {
        state.todos = state.todos.filter(item => {
            if (item.id !== id) {
                return true
            }
        })
    },
    setFilter (state, filter) {
        state.filter = filter
    }
}

getters.js:

export default {
    finishedTodos (state) {
        return state.todos.filter(todos => todos.isFinished)
    },
    unfinishedTodos (state) {
        return state.todos.filter(todos => !todos.isFinished)
    },
    filteredTodos (state, getters) {
        switch (state.filter) {
            case 'finished':
                return getters.finishedTodos
            case 'unfinished':
                return getters.unfinishedTodos  
            default:
                return state.todos     
        }
    }
}

ゲッターは計算されたプロパティに似ています。ゲッターでfilteredTodosを使用して、現在の状態に従ってデータをフィルタリングします。

最後に、index.js でこれらをオブジェクトに渡し、エクスポートします。

import state from "./state";
import getters from "./getters";
import mutations from "./mutations";
import actions from "./actions";

import { createStore } from 'vuex'

export default createStore ({
    state,
    getters,
    mutations,
    actions
})

集中:

独自の vuex を実装するためです。そのため、送信するミューテーション、ディスパッチされたアクション、vuex のアクションとミューテーションについて明確にする必要があります。

ミューテーション ディスパッチ アクションを送信する

ミューテーション -> commit(type, payload) タイプはミューテーションの名前です

アクション -> ディスパッチ (タイプ、ペイロード)

アクションを実行する

アクション -> (ストア、ペイロード)

ミューテーションを実行する

突然変異 -> (状態、ペイロード)

TodoList ビューを記述します。

components ディレクトリに新しい TodoList フォルダーを作成し、Form.vue、index.vue、Tab.vue、および Todos.vue を作成します。これら 4 つのコンポーネントの内容を別々に記述します。

フォーム.ビュー:

<template>
    <div>
       <input
       type="text"
       placeholder="Please input something"
       v-model="inputRef"
       />
       <button @click="addTodo">ADD TODO</button>
    </div>
</template>

<script>
import { ref } from 'vue'
import { useStore } from 'vuex'
    export default {
        setup () {
            const store = useStore()
            const inputRef = ref('')
        
            const addTodo = () => {
                store.dispatch('addTodo', inputRef.value)
                inputRef.value = ''
            }
            return {
                inputRef,
                addTodo
            }
        }
        
    }
</script>

<style lang="scss" scoped>

</style>

タブビュー:

<template>
    <div>
        <a href="javascript:;"
        @click="setFilter('all')"
        :class="{ active: store.state.filter === 'all' }"
        >All</a>
        <a href="javascript:;"
        @click="setFilter('finished')"
        :class="{ active: store.state.filter === 'finished' }"
        >Finished</a>
        <a href="javascript:;"
        @click="setFilter('unfinished')"
        :class="{ active: store.state.filter === 'unfinished' }"
        >unFinished</a>
    </div>
</template>

<script>
import { useStore } from 'vuex'
    export default {
        setup () {
            const store  = useStore()

            const setFilter = (filter) => {
                store.commit('setFilter',filter)
            }
            return {
                store,
                setFilter
            }
        }
    }
</script>

<style lang="scss" scoped>
    a {
        margin-right:15px;
    }
    .active {
        text-decoration: none;
        color: #000;
    }
</style>

すべてのビュー:

<template>
    <div>
        <div v-for="item of store.getters.filteredTodos"
          :key="item.id"
        >
            <input type="checkbox"
            :checked="item.isFinished"
            @click="toggleTodo(item.id)"
            >
            <span :class="{ finished: item.isFinished }">{
   
   {item.text}}</span>
            <button @click="removeTodo(item.id)">DELETE</button>
        </div>
    </div>
</template>

<script>
import { useStore } from 'vuex'
    export default {
        setup () {
            const store = useStore()

            const toggleTodo = (id) => {
                store.dispatch('toggleTodo',id)
            }

            const removeTodo = (id) => {
                store.dispatch('removeTodo',id)
            }
            return {
                store,
                toggleTodo,
                removeTodo
            }
        }
    }
</script>

<style lang="scss" scoped>
.finished {
    text-decoration: line-through;
}
</style>

index.vue は、次の 3 つのコンポーネントをエントリ ファイルとしてインポートします。

<template>
    <div>
        <todo-tab></todo-tab>
        <todo-form></todo-form>
        <todos></todos>
    </div>
</template>

<script>
import TodoTab from './Tab'
import TodoForm from './Form'
import Todos from './Todos'
    export default {
        components: {
            TodoTab,
            TodoForm,
            Todos
        }
    }
</script>

<style lang="scss" scoped>

</style>

voite.config.js を変更します。そうしないと、実行時にエラーが報告されます。

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    extensions: ['.vue', '.js'],
    alias: {
      '@': resolve(__dirname, 'src')
    }
  }
})

TodoList の準備ができたら、プロジェクトを開始します。

すべてが選択されている場合は、[食べる] をクリックします。

選択が行われます:

選択が行われていません:

独自の vex を実装する

独自の vuex を実装する前に、store オブジェクトの出力を作成して、vuex の store d が何を持っているかを確認しましょう。

ここでのアクション、ミューテーション、および状態はすべて「_」で始まります。これは、作成したアクション、ミューテーション、および状態を vuex がラップすることを意味します。

_state では、データは data 属性に保存されます. このデータはどこから来るのでしょうか? これは、vuex が私たちのために行うことでもあります. 実際、オブジェクトを保存するのはリアクティブであり、このオブジェクトにはデータ = 私たちの状態が含まれています.

前に非常に重要な問題について言及しました。つまり、対応するミューテーションとアクションを呼び出すとき、渡されるパラメーターはタイプとペイロードです。そして、ミューテーションとアクションの 2 つの関数は、それぞれ state ペイロードと store ペイロードで渡されます。この場合、パラメーターの受け渡しは均一ではなく、変換する必要があります。最初にペイロードを渡す関数を定義し、次にミューテーションを呼び出し、その this ポイントをストアに変更してから、独自の状態、ペイロード、またはストア、ペイロード。

これが、ミューテーションとアクションを別々に保存する必要がある理由です。最初に関数を定義し、コミットとディスパッチのペイロードを渡し、次にミューテーションとアクションを呼び出し、状態、ペイロードまたはストア、およびペイロードをそれらに渡す必要があるためです。 .

次に、最初に src ディレクトリに新しい my-vuex フォルダーを作成し、以前 vuex が使用されていたすべての場所を my-vuex に置き換えます。

最初に実装する必要があるのは、ストア内の index.js の createStore 関数です。

options パラメータを受け取ります。最初にユーザーが書いたものを取り込み、ユーザーが vuex を使用するときに useStore からストア オブジェクトをスローする必要があります。

 次に、最初にユーザー用の 2 つのメソッド createStore と useStore を記述します。これら 2 つのメソッドを実装するために、my-vuex の下に新しい store.js を作成します。

createStore の実装:

class Store {
    constructor (options) {
        const {
            state,
            getters,
            mutations,
            actions
        } = options
    }
}


export function createStore (options) {
    return new Store(options)
}

createStore はユーザーから渡されたオプションを受け取るため、Store クラスを記述して、Store のインスタンス オブジェクトを返すことができます。これは、拡張と保守が非常に簡単です。

次に、main.js で導入したのはこのインスタンスであり、このインスタンスも使用時に渡されます。前に使用と述べたので、インストールする必要があります。したがって、このメソッドを Store クラスに追加する必要があります 

class Store {
    constructor (options) {
        const {
            state,
            getters,
            mutations,
            actions
        } = options
    }

    install (app) {
        
    }
}

このインストールは最初にインスタンス オブジェクトで検索されるため、そうでない場合はプロトタイプ オブジェクトで検索され、インストールはクラスのプロトタイプ オブジェクトでハングします。

プリントアウトした store インスタンスには、_mutations と _actions で定義した関数のみがあり、チェーンはありません。彼は継承を望んでいません。したがって、オブジェクトを直接作成できます。

constructor (options) {
        const {
            state,
            getters,
            mutations,
            actions
        } = options

        this._state = reactive({ data: state })
        this._mutations = Object.create(null)
        this._actions = Object.create(null)
    }

ユーザーがミューテーションとアクションを作成すると、それらは対応する _mutations と _actions に追加されるため、新しい Creator.js を作成して、ユーザーから渡されたミューテーションとアクションを _mutations と _actions に追加します。

ユーザーから渡されたオブジェクトをトラバースし、次のロジックを実行するためにそのキーと値を取得する必要があるため、新しい utils.js を作成します。

// 对 mutations,actions 做一个循环。传入一个回调函数,参数就是我们获取的键值对,然后在里面做一些事情
export function forEachValueKey (obj, callback) {
    Object.keys(obj).forEach(key => callback(obj[key], key))
}

Creator.js に戻り、ミューテーションを処理するコードを見てみましょう。

export function createMutations (store, mutations) {
    forEachValueKey(mutations, (mutationFn, mutationKey) => {
        store._mutations[mutationKey] = (payload) => {
            mutationFn.apply(store, [ store.state, payload ])
        }
    })
}

ここで、utils で定義された forEachValueKey を呼び出して着信ミューテーションをトラバースし、独自に定義された _mutations をストアに取得します。mutationKey は、コミットを送信することによってユーザーによって渡されるタイプであり、ペイロードはコミットの 2 番目のパラメーターです。次に、コールバックで MutationFn を呼び出します。ここでは、apply を介して this のポイントを変更する必要があります。これは、mutationFn の this が、定義したクラスのインスタンスを指すようにするためです。次に、渡されるパラメーターは状態とペイロードです。

次の createActions は同じロジックです。

export function createActions (store, actions) {
    forEachValueKey(actions, (actionFn, actionKey) => {
        store._actions[actionKey] = (payload) => {
            actionFn.apply(store, [ store, payload ])
        }
    })
}

次の createGetters に注意してください。

export function createGetters (store, getters) {
    store.getters = {}
    forEachValueKey(getters, (getterFn, getterKey) => {
        Object.defineProperty(store.getters, getterKey, {
            get: () => computed(() =>  getterFn(store.state, store.getters)).value
        })
    })
}

getter を介して状態のデータにアクセスしたいので、プロキシのレイヤーとして Object.defineProperty を使用できます。

次に store クラスに戻り、定義した 3 つの関数を呼び出します。

ユーザーが createStore を呼び出すと、定義したストア インスタンスが返され、ユーザーから渡されたオプションが返され、ミューテーションが _mutations プールに渡され、アクションが _actions プールに渡され、createGetters が呼び出されます。 store.getters オブジェクトを定義し、プロキシのレイヤーを作成します。

このようにして、全体のロジックがより明確になり、ユーザーがサブミットしたコミット メソッドとディスパッチ メソッドを定義します。

// 用户提交 commit 就是调用指定的 mutations
    commit(type, payload) {
        this._mutations[type](payload)
    }
    dispatch(type, payload) {
        this._actions[type](payload)
    }

store.state を介して状態のデータにアクセスできるため、_state を 1 つだけ持つだけでは十分ではありません。この _state は vuex が他のことを行うのに便利であり、ユーザーは store._state を介してそれを呼び出さないからです。したがって、プロキシの別のレイヤーを実行する必要があります。

get state() {
        return this._state.data
    }

このように、返されるのは状態のデータです

プロジェクトでは、vuex が提供する useStore メソッドを呼び出すことでストア インスタンスを取得できますが、プロジェクト内の他のコンポーネントにストア インスタンスを取得させるにはどうすればよいでしょうか? 実際、これはコンポーネント間の値の受け渡しです。 store インスタンス 、 install メソッドで app.provide メソッドを呼び出してこのインスタンスを渡し、それを useStore メソッドに注入して受け取ることができます。これは、 install メソッドが mian.js での使用に対応しているためです。 vue のルート コンポーネントで use メソッドを使用することは、ルート コンポーネントでデータの一部を提供することと同じであるため、サブコンポーネントでこのデータを使用したい場合は、直接 useStore メソッドを呼び出すことができます。

install (app) {
        app.provide('store', this)
        app.config.globalProperties.$store = this
    }

ここで app.config.globalPropertries.$store は vue2 にも対応しているため、プロジェクト内で this.$store.state を使用してステート内のデータにアクセスできます。

useStore メソッドは次のとおりです。

export function useStore () {
    return inject('store')
}

ほぼすべてを書き終えたので、プロジェクトを開始します。

エラーが報告されていることがわかりました。彼は、_mutations にアクセスできない、つまり、この部分を調整できないと述べました。

次に、これをコミットで出力して、ストアが出力されるかどうかを確認しましょう。

コンソールの出力は未定義です。では、なぜ彼は未定義なのですか?

ユーザーが commit を呼び出すと、コミット内の this ポイントが変更され、現在の this インスタンスを指しなくなったため、現在の this 環境をコミットおよびディスパッチ関数にバインドするプロセスを実行する必要があり、これを作成者コード:


export function createCommitFn (store, commit) {
    store.commit = function (type, payload) {
        commit.apply(store, [ type, payload ])
    }
}

export function createDispatchFn (store, dispatch) {
    store.dispatch = function (type, payload) {
        dispatch.apply(store, [ type, payload ])
    }
}

次に、コンストラクターでストアからコミットとディスパッチを分解し、これら 2 つの関数をパラメーターとして渡します。

class Store {
    constructor (options) { //把用户传进来的 options 做初始化
        const {
            state,
            getters,
            mutations,
            actions
        } = options
        const store = this
        const { commit, dispatch } = store
        store._state = reactive({ data: state })
        // 定义两个池子装用户定义的 Mutaions 和 actions
        store._mutations = Object.create(null)
        store._actions = Object.create(null)

        createMutations(store, mutations)
        createActions(store, actions)
        createGetters(store, getters)
        createCommitFn(store, commit)
        createDispatchFn(store, dispatch)
    }
    // 用户提交 commit 就是调用指定的 mutations
    commit(type, payload) {
        console.log(this);
        this._mutations[type](payload)
    }
    dispatch(type, payload) {
        this._actions[type](payload)
    }
    get state() {
        return this._state.data
    }
    install (app) {
        app.provide('store', this)
        app.config.globalProperties.$store = this
    }
}

スタートアップ プロジェクト:

これで、プロジェクト全体が正常に実行できるようになり、mini-vuex の基本機能が実現されました。

おすすめ

転載: blog.csdn.net/qq_49900295/article/details/126563641