• 手写 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 目录下新建 store 文件夹,完善 vuex 结构:

    在 main.js 中引入:

    1. import { createApp } from 'vue'
    2. import './style.css'
    3. import App from './App.vue'
    4. import store from './store'
    5. createApp(App).use(store).mount('#app')

    我们在 store 目录下的 index.js 中返回的对象,里面一定包含一个 install(app)。这是根据 vue 的 plugin 来决定的,你用 use 了就一定得对应 install。只不过这是 vuex 内部已经帮我们实现好的,但是在我们自己的 vuex 中要注意,如果在 main.js 中 sue了,那我们必须在返回的对象中包含 install。

    根据 TodoList 的逻辑,我们来编写 vuex 的实现:

    state.js:

    1. export default {
    2. todos: [],
    3. filter: 'all', //all finished unfinished
    4. id: 0
    5. }

    在 state 中 todos 存储着所有信息,filter 则是当前整体的 TodoList 处于什么状态,是 all 的话就显示所有 list,是 finished 就显示全部已完成的 list,unfinished 就显示全部未完成的 list。并且每条 list 都有对应的 id。

    actions.js:

    1. export default {
    2. addTodo ({ commit }, text) {
    3. commit('addTodo', text)
    4. },
    5. toggleTodo ({ commit }, id) {
    6. commit('toggleTodo', id)
    7. },
    8. removeTodo ({ commit }, id) {
    9. commit('removeTodo', id)
    10. }
    11. }

    这应该不难理解,就是通过 actions 来提交指定的 mutation 进而改变 state 中的数据将。

    mutations.js:

    1. export default {
    2. addTodo (state, text) {
    3. state.todos.push({
    4. id: state.id++,
    5. text,
    6. isFinished: false
    7. })
    8. },
    9. toggleTodo (state, id) {
    10. state.todos = state.todos.map(item => {
    11. if (item.id === id) {
    12. item.isFinished = !item.isFinished
    13. }
    14. return item
    15. })
    16. },
    17. removeTodo (state, id) {
    18. state.todos = state.todos.filter(item => {
    19. if (item.id !== id) {
    20. return true
    21. }
    22. })
    23. },
    24. setFilter (state, filter) {
    25. state.filter = filter
    26. }
    27. }

    getters.js:

    1. export default {
    2. finishedTodos (state) {
    3. return state.todos.filter(todos => todos.isFinished)
    4. },
    5. unfinishedTodos (state) {
    6. return state.todos.filter(todos => !todos.isFinished)
    7. },
    8. filteredTodos (state, getters) {
    9. switch (state.filter) {
    10. case 'finished':
    11. return getters.finishedTodos
    12. case 'unfinished':
    13. return getters.unfinishedTodos
    14. default:
    15. return state.todos
    16. }
    17. }
    18. }

    getters 就类似于计算属性,我们在 getters 中通过 filteredTodos 来根据当前状态筛选数据。

    最后在 index.js 中我们把这些传入一个对象再导出出去:

    1. import state from "./state";
    2. import getters from "./getters";
    3. import mutations from "./mutations";
    4. import actions from "./actions";
    5. import { createStore } from 'vuex'
    6. export default createStore ({
    7. state,
    8. getters,
    9. mutations,
    10. actions
    11. })

    重点:

    因为我们要实现自己的 vuex 。所以我们要非常清楚我们提交的 mutation ,派发的 action 和 vuex 中的 actions,mutations 他们的参数都是什么

    提交 mutation 派发 action

    mutations -> commit(type, payload) type就是 mutaion 的名字

    actions -> dispatch(type, payload)

    执行 actions

    action -> (store, payload)

    执行 mutations

    mutation -> (state, payload)

    编写 TodoList 视图:

    在 components 目录下新建 TodoList 文件夹,创建 Form.vue,index.vue,Tab.vue,Todos.vue。分别别编写这四个组件的内容:

    Form.vue:

    1. <script>
    2. import { ref } from 'vue'
    3. import { useStore } from 'vuex'
    4. export default {
    5. setup () {
    6. const store = useStore()
    7. const inputRef = ref('')
    8. const addTodo = () => {
    9. store.dispatch('addTodo', inputRef.value)
    10. inputRef.value = ''
    11. }
    12. return {
    13. inputRef,
    14. addTodo
    15. }
    16. }
    17. }
    18. script>
    19. <style lang="scss" scoped>
    20. style>

    Tab.vue:

    1. <script>
    2. import { useStore } from 'vuex'
    3. export default {
    4. setup () {
    5. const store = useStore()
    6. const setFilter = (filter) => {
    7. store.commit('setFilter',filter)
    8. }
    9. return {
    10. store,
    11. setFilter
    12. }
    13. }
    14. }
    15. script>
    16. <style lang="scss" scoped>
    17. a {
    18. margin-right:15px;
    19. }
    20. .active {
    21. text-decoration: none;
    22. color: #000;
    23. }
    24. style>

    Todos.vue: