学习 Vue.js:用 Vuex 重构 Todo 项目

上一篇 中,vue.js 与 Laravel 结合实现了 Todo 项目的数据持久化,下面用 Vuex 重构。

安装 Vuex

$ npm install vuex --save

修改 main.js,引入 Vuex。

import Vue from 'vue'
import axios from 'axios'
import VueAxios from 'vue-axios'
import Vuex from 'vuex'
import App from './App'
import router from './router'

Vue.use(VueAxios, axios)
Vue.use(Vuex)

Vue.config.productionTip = false

const store = new Vuex.Store({
  state: {
    todos: [],
    newTodo: { id: null, title: '', completed: false }
  },
  mutations: {
    // ...
  },
  actions: {
    // ...
  }
})

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,
  template: '<App/>',
  components: { App }
})

Vuex 仅以一个全局对象(store)的形式呈现,就可以管理整个项目里所有的组件状态。state 对象里的 todosnewTodo 可简单理解为全局变量,这两个变量之所以对项目中所有组件可见,是因为在 new Vue 的时候注册入项目里了。

new Vue({
  ...
  store,
  ...
})

App.vue

<template>
  <div id="app">
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'app',
  mounted() {
    this.$store.dispatch('getTodos')
  }
}
</script>

在组件里使用 this.$store 取得注入的 store 对象。我们把获得 Todo 列表的工作 dispatch 给了 store 对象的 getTodos action。

// main.js
const store = new Vuex.Store({
  state: {
    todos: [],
    newTodo: { id: null, title: '', completed: false }
  },
  mutations: {
    get_todos (state, todos) {
      state.todos = todos
    }
  },
  actions: {
    getTodos (store) {
      Vue.axios.get('http://localhost:8000/api/todos').then((response) => {
        store.commit('get_todos', response.data)
      })
    }
  }
}

getTodos action 取得 Todo 列表,把结果 response.data commit 给了 get_todos mutation; get_todos mutation 接收的第二个参数就是传递过去的 Todo 列表,然后赋值给了 state.todos。接下来在 Todos.vue 可直接使用 todos 变量了。

Todos.vue

// Todos.vue
<script>
export default {
  name: 'todos',
  computed: {
    todos () {
      return this.$store.state.todos
    },
    newTodo () {
      return this.$store.state.newTodo
    }
  }
}
</script>

通过 this.$store.state 就能取得 todosnewTodo,然后就可以在组件 <template> 中直接使用了。

增删改查

与 Todo 项目增删改查的操作,与实现显示 Todo 列表的流程相似,这里直接贴出代码。共涉及三个文件 main.jsTodos.vueApp.vue

main.js

import Vue from 'vue'
import axios from 'axios'
import VueAxios from 'vue-axios'
import Vuex from 'vuex'
import App from './App'
import router from './router'

Vue.use(VueAxios, axios)
Vue.use(Vuex)

Vue.config.productionTip = false

const store = new Vuex.Store({
  state: {
    todos: [],
    newTodo: { id: null, title: '', completed: false }
  },
  mutations: {
    get_todos (state, todos) {
      state.todos = todos
    },
    add_todo (state, todo) {
      state.todos.push(todo)
      state.newTodo.title = ''
    },
    delete_todo (state, index) {
      state.todos.splice(index, 1)
    },
    toggle_done (state, todo) {
      todo.completed = !todo.completed
    }
  },
  actions: {
    getTodos (store) {
      Vue.axios.get('http://localhost:8000/api/todos').then((response) => {
        store.commit('get_todos', response.data)
      })
    },
    addTodo (store) {
      if (! store.state.newTodo.title.trim()) { return ; }
      Vue.axios.post('http://localhost:8000/api/todos/create', { 'title': store.state.newTodo.title })
        .then((response) => {
          store.commit('add_todo', response.data)
        });
    },
    deleteTodo (store, payload) {
      Vue.axios.delete('http://localhost:8000/api/todo/' + payload.todo.id + '/delete')
        .then((response) => {
          store.commit('delete_todo', payload.index)
        });
    },
    toggleDone (store, todo) {
      Vue.axios.patch('http://localhost:8000/api/todo/' + todo.id + '/toggleComplete')
        .then((response) => {
            store.commit('toggle_done', todo)
        });
    }
  }
})

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,
  template: '<App/>',
  components: { App }
})

App.vue

<template>
  <div id="app">
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'app',
  mounted() {
    this.$store.dispatch('getTodos')
  }
}
</script>

Todos.vue

<template>
  <div class="container">
    <div class="row">
      <div class="col-md-6 col-md-offset-3">
        <div class="panel panel-default">
          <div class="panel-heading text-center">
              计划要做的事情,共 {{ todos.length }} 件
          </div>
          <div class="panel-body">
            <div class="list-groups">
              <a class="list-group-item" v-bind:class="{ 'completed': todo.completed }" v-for="(todo, index) in todos">

                <router-link :to="{ name: 'Todo', params: { id: todo.id }}">{{ todo.title }}</router-link>

                <button class="btn btn-xs btn-danger pull-right" v-on:click='destroy(todo, index)'title="删除">✘</button>
                <button class="btn btn-xs pull-right" v-on:click='toggleDone(todo)' v-bind:class="[todo.completed ? 'btn-success': '']" v-bind:title="[todo.completed ? '点击,标记为未完成': '点击,标记为已完成']">✔</button>
              </a>
            </div>
          </div>
          <div class="panel-footer">
            <form v-on:submit.prevent="add">
              <div class="form-group">
                <input type="text" class="form-control text-center" v-model="newTodo.title">
              </div>
              <button class="btn btn-default btn-block" type="submit">添加</button>
            </form>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'todos',
  computed: {
    todos () {
      return this.$store.state.todos
    },
    newTodo () {
      return this.$store.state.newTodo
    }
  },
  methods: {
    add() {
      this.$store.dispatch('addTodo')
    },
    destroy(todo, index) {
      this.$store.dispatch('deleteTodo', { todo, index })
    },
    toggleDone(todo) {
      this.$store.dispatch('toggleDone', todo)
    }
  }
}
</script>
本作品采用《CC 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 3

好眼熟 这不是教主的教程么... @jellyBool

7年前 评论

@Rekkles 这就是教主的,我记下来,方便看的。

7年前 评论

表示还没用过 Vuex , mark一下.

7年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!