首页 > 代码库 > 一步一步学习Vue(十一)
一步一步学习Vue(十一)
本篇继续学习vuex,还是以实例为主;我们以一步一步学Vue(四)中讲述的例子为基础,对其改造,基于vuex重构一遍,这是原始的代码:
todolist.js
; (function () { var list = []; var Todo = (function () { var id = 1; return function (title, desc) { this.title = title; this.desc = desc; this.id = id++; } })(); /** * 搜索组件 */ var SearchBar = { template: ` <div class="row toolbar"> <div class="col-md-8"> keyword: <input type="text" v-model="keyword" /> <input type="button" @click="search()" value="http://www.mamicode.com/search" class="btn btn-primary" /> </div> </div> `, data: function () { return { keyword: ‘‘ } }, methods: { search: function () { this.$emit(‘onsearch‘, this.keyword); } } } /** * 表单组件 */ var TodoForm = { template: ` <div class="col-md-6"> <div class="form-inline"> <label for="title" class="control-label col-md-4">title:</label> <input type="hidden" v-bind:value="http://www.mamicode.com/todo.id" /> <input type="text" v-model="todo.title" class="form-control col-md-8"> </div> <div class="form-inline"> <label for="desc" class="control-label col-md-4">desc</label> <input type="text" v-model="todo.desc" class="form-control col-md-8"> </div> <div class="form-inline"> <input type="button" value="http://www.mamicode.com/OK" v-on:click="ok()" class="btn btn-primary offset-md-10" /> </div> </div> `, props: [‘initItem‘], computed: { todo: function () { return { id: this.initItem.id, title: this.initItem.title, desc: this.initItem.desc }; } }, methods: { ok: function () { this.$emit(‘onsave‘, this.todo); } } } /** * 列表项组件 */ var TodoItem = { template: ` <tr> <th>{{todo.id}}</th> <td>{{todo.title}}</td> <td>{{todo.desc}}</td> <td> <input type="button" value="http://www.mamicode.com/remove" @click="remove()" class="btn btn-danger" /> <input type="button" value="http://www.mamicode.com/edit" @click="edit()" class="btn btn-info" /> </td> </tr> `, props: [‘todo‘], methods: { edit: function () { console.log(this.todo); this.$emit(‘onedit‘, this.todo.id); }, remove: function () { this.$emit(‘onremove‘, this.todo.id); } } } /** * 列表组件 */ var TodoList = { template: ` <div class="col-md-6"> <table class="table table-bordered"> <tr> <th></th> <th>title</th> <th>desc</th> <th></th> </tr> <todo-item v-for="item in items" :todo="item" :key="item.id" @onedit="edit($event)" @onremove="remove($event)"></todo-item> </table> </div> `, props: [‘items‘], components: { ‘todo-item‘: TodoItem }, methods: { edit: function ($e) { this.$emit(‘onedit‘, $e); }, remove: function ($e) { this.$emit(‘onremove‘, $e); } } } /** * 容器组件 * 说明:容器组件包括三个字组件 */ var TodoContainer = { template: ` <div class="container"> <search-bar @onsearch="search($event)"></search-bar> <div class="row"> <todo-list :items="items" @onremove="remove($event)" @onedit="edit($event)"></todo-list> <todo-form :init-item="initItem" @onsave="save($event)" ></todo-form> </div> </div> `, data: function () { return { /** * Todolist数据列表 * 说明:通过props传入到Todolist组件中,让组件进行渲染 */ items: [], /** * TodoForm初始化数据 * 说明:由于TodoForm包括两种操作:新增和编辑;新增操作无需处理,编辑操作需要进行数据绑定,这里通过传入initItem属性进行编辑时数据的初始化 * 如果传入的值为空,说明为新增操作,由initItem参数的Id是否为空,来确认是更新保存还是新增保存 */ initItem: { title: ‘‘, desc: ‘‘, id: ‘‘ } } }, components: { ‘search-bar‘: SearchBar,/**SearchBar组件注册 */ ‘todo-form‘: TodoForm,/**TodoForm组件注册 */ ‘todo-list‘: TodoList/**TodoList组件注册 */ }, methods: { /** * 模拟保存数据方法 * 辅助方法 */ _mock_save: function (lst) { list = lst; }, /** * 根据id查询对象 * 辅助方法 */ findById: function (id) { return this.items.filter(v => v.id === id)[0] || {}; }, /** * 查询方法 * 由SearchBar组件触发 */ search: function ($e) { this.items = list.filter(v => v.title.indexOf($e) !== -1); }, /** * 保存方法 * 响应新增和更新操作,由TodoForm组件触发 */ save: function ($e) { //id存在则为编辑保存 if (this.initItem.id) { var o = this.findById($e.id); o.title = $e.title; o.desc = $e.desc; } else { this.items.push(new Todo($e.title, $e.desc)); } this.initItem = { id: ‘‘, title: ‘‘, desc: ‘‘ }; this._mock_save(this.items); }, /** * 删除方法 * 响应删除按钮操作 * 由TodoItem组件触发 */ remove: function ($e) { this.items = this.items.filter(v => v.id !== $e); this._mock_save(this.items); }, /** * 编辑按钮点击时,进行表单数据绑定 */ edit: function ($e) { this.initItem = this.findById($e); } } } var app = new Vue({ el: ‘#app‘, components: { ‘todo-container‘: TodoContainer } });})();/** * * * <div id="app"> * <todo-container></todo-container> * </app> */
index.html
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>demo1</title> <script src="https://cdn.bootcss.com/vue/2.4.1/vue.js"></script> <link href="https://cdn.bootcss.com/bootstrap/4.0.0-alpha.6/css/bootstrap.css" rel="stylesheet"> <script src="https://cdn.bootcss.com/vuex/2.3.1/vuex.js"></script></head><body class="container"> <div id="app"> <todo-container></todo-container> </div> <script src="./todolist.js"></script></body></html>
注意要在html页面引入vuex的路径,我们这里直接使用cdn上的库,开始我们的重构
第一步:创建全局store
在vuex中,store是全局唯一的,我们在上一篇文章中也介绍了其基本创建方式,修改todolist.js,添加如下代码:
var store=new Vuex.Store({ state:{ //TODO:状态 }, mutations:{ //TODO:改变状态的方法 } }) var app = new Vue({ store:store, el: ‘#app‘, components: { ‘todo-container‘: TodoContainer } });
上述代码表示store已经创建,并且注入到注册组件中了,在任何组件中都可以通过this.store来访问state和提交mutation,这里再简单说一下mutaiton,其实我们可以把mutation当成事件来理解,在store定义的时候,创建mutation,我们可以认为是mutation的注册,就如我们去注册普通的事件一样,内容都是key和value,其中key是事件的全局表示,value是事件的回调函数,类比mutation,定义是注册,模式还是func:function(){}的模式,在我们做commit(“mutation”)的时候相当于触发事件,这时候就会执行我们注册的回调函数。
第二步,我们把共享状态进行提取:
var store=new Vuex.Store({ state:{ items:[] // todoContainer中items,
}, mutations:{ //TODO:改变状态的方法 } })
就下来,我们把所有改变state的方法,都通过注册mutation的方式来重构,在vuex中,一定要通过mutation来改变状态:
mutations: { search: function (state, payload) { state.items = list.filter(v => v.title.indexOf(payload.title) !== -1); }, save: function (state, payload) { if (state.initItem.id) { var o = list.filter(v => v.id === payload.id); o.title = payload.title; o.desc = payload.desc; state.items=state.items.map(v=>{ if(v.id==payload.id){ return payload; } return v; }); } else { state.items.push(new Todo(payload.title, payload.desc)); } list = state.items; }, remove: function (state, payload) { state.items = state.items.filter(v => v.id !== payload.id); }, edit: function (state, payload) { state.initItem = state.items.filter(v => v.id === payload.id)[0]; } }
我们添加的上述几个mutations,包括search、save、remove、edit,由于在每一个组件中都可以访问到this.$store,那么我们就不用对事件一层一层的传递啦,我们只需要在需要调用的地方,commit对应的mutation即可,比如search操作就是在searchbar组件中,那么我们没必要传递到父组件中来触发,基于此,我们修改SearchBar组件:
/** * 搜索组件 */ var SearchBar = { template: ` <div class="row toolbar"> <div class="col-md-8"> keyword: <input type="text" v-model="keyword" /> <input type="button" @click="search()" value="http://www.mamicode.com/search" class="btn btn-primary" /> </div> </div> `, data: function () { return { keyword: ‘‘ } }, methods: { search: function () { this.$store.commit("search", { title: this.keyword }); } } }
这里看起来没有简单好多,但是我们至少不用把我们的事件往上级送了,可以对比最初代码,同理对我们的其它组件都进行重构:
/** * 表单组件 */ var TodoForm = { template: ` <div class="col-md-6"> <div class="form-inline"> <label for="title" class="control-label col-md-4">title:</label> <input type="hidden" v-bind:value="http://www.mamicode.com/todo.id" /> <input type="text" v-model="todo.title" class="form-control col-md-8"> </div> <div class="form-inline"> <label for="desc" class="control-label col-md-4">desc</label> <input type="text" v-model="todo.desc" class="form-control col-md-8"> </div> <div class="form-inline"> <input type="button" value="http://www.mamicode.com/OK" v-on:click="ok()" class="btn btn-primary offset-md-10" /> </div> </div> `, props: [‘initItem‘], computed: { todo: function () { return { id: this.initItem.id, title: this.initItem.title, desc: this.initItem.desc }; } }, methods: { ok: function () { this.$store.commit("save",this.todo); } } } /** * 列表项组件 */ var TodoItem = { template: ` <tr> <th>{{todo.id}}</th> <td>{{todo.title}}</td> <td>{{todo.desc}}</td> <td> <input type="button" value="http://www.mamicode.com/remove" @click="remove()" class="btn btn-danger" /> <input type="button" value="http://www.mamicode.com/edit" @click="edit()" class="btn btn-info" /> </td> </tr> `, props: [‘todo‘], methods: { edit: function () { this.$store.commit(‘edit‘,this.todo); }, remove: function () { this.$store.commit(‘remove‘,{id:this.todo.id}); } } } /** * 列表组件 */ var TodoList = { template: ` <div class="col-md-6"> <table class="table table-bordered"> <tr> <th></th> <th>title</th> <th>desc</th> <th></th> </tr> <todo-item v-for="item in items" :todo="item" :key="item.id" ></todo-item> </table> </div> `, props: [‘items‘], components: { ‘todo-item‘: TodoItem } } /** * 容器组件 * 说明:容器组件包括三个字组件 */ var TodoContainer = { template: ` <div class="container"> <search-bar></search-bar> <div class="row"> <todo-list :items="items" ></todo-list> <todo-form :init-item="initItem" ></todo-form> </div> </div> `, components: { ‘search-bar‘: SearchBar,/**SearchBar组件注册 */ ‘todo-form‘: TodoForm,/**TodoForm组件注册 */ ‘todo-list‘: TodoList/**TodoList组件注册 */ }, computed: { initItem: function () { return this.$store.state.initItem; }, items: function () { return this.$store.state.items; } } }
首先看一下我们的TodoContainer组件,里面已经清爽了好多,原来所有的逻辑,所有的属性,都汇集在这里,现在每个组件的逻辑都是它自己负责,表单组件负责保存操作,所以在其中提交commit(“save”);todo组件负责编辑和删除,所以在其方法中封装了remove和edit的mutaiton的访问。至此,我们的代码可以正常运行,由于只是对前文demo的重构,这里不再贴出运行效果图。
小结,在store中定义的状态,是响应式的,对其中状态的改变会导致view的重新渲染,改变状态只能通过提交mutation。由于其状态的响应式,所以我们在访问时一般定义成计算属性,如TodoContainer组件中的initItem和items;一般来说,不是所有状态都要定义到vuex的store中,每个组件都会有自己私有状态,只有全局或者共享状态才适合定义在store中,所以在实际开发中,需要好好斟酌;本篇就到此为止,其实算是上篇的一个延伸,下一篇介绍Actions,会继续在本篇demo的基础上进行延伸,敬请期待。
一步一步学习Vue(十一)