1、TodoContainer组件
TodoContainer组件,用来组织其它组件,这是react中推荐的方式,也是redux中高阶组件一般就是用来包装成容器组件用的,比如redux中的connect函数,返回的包装组件就是一个容器组件,它用来处理这样一种场景:加入有A、B两个组件,A组件中需要通过Ajax请求和后端进行交互;B组件也需要通过Ajax请求和后端交互,针对这种场景有如下代码:
//组件A定义var CompA={ template:'<div>A 渲染 list</div>', data:function(){ return { list:[] } }, methods:{ getListA:function(){ $.ajax({ url:'xxx', dataType:'json', success:r=>this.list=r.data }) } } };//组件B定义var CompB={ template:'<div>B 渲染list</div>', data:function(){ return { list:[] } }, methods:{ getListB:function(){ $.ajax({ url:'xxx', dataType:'json', success:r=>this.list=r.data }) } } }
可以看出,A组件和B组件中都会存在Ajax访问重复代码,有重复代码就要提取出来;第一种方式,提取公共方法,使用mixin混入到两个组件中,所谓混入就是动态把方法注入到两个对象中;
第二种方法使用外部传入,这是react中推荐的方式,使用props传入;其实我们仔细分析我们的两个组件,都是为了渲染列表数据,至于是在组件外请求还是在组件内请求,它是不关注的,这样我们可以进一步考虑,把AB组件重构成只用来渲染数据的pure组件,数据由外部传入,而vue正好提供了这种props父传子的机制,把Ajax操作定义到父组件中(就是我们这里提到的容器组件),也起到了重复代码提取的作用,基于此请看我们的第二版代码:
//A组件var CompA={ template:'<div>A</div>', props:['list'] };//B组件var CompB={ template:'<div>B</div>', props:['list'] }//容器组件var Container={ template:` <comp-a :list="listA" ></comp-a> <comp-b :list="listB" ></comp-b> `, components:{ 'comp-a':CompA, 'comp-b':CompB }, methods:{ getListA:function(){ //TODO:A 逻辑 }, getListB:function(){ //TODO:B 逻辑 } } }
这样A、B组件变成了傻白甜组件,只是负责数据渲染,所有业务逻辑由容器组件处理;容器组件把A、B组件需要的数据通过props的方式传递给它们。
已经明白了容器组件的作用,那么我们来实现一下前几篇中todolist的容器组件吧,上篇已有基本结果,这里先出代码后解释:
/** * 容器组件 * 说明:容器组件包括三个字组件 */ 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); } } }
我们把所有业务逻辑也放在容器组件中处理,其它组件都是傻白甜,这样的好处是,其它组件容易重用,因为只是数据渲染,并不涉及业务操作,这种组件没有业务相关性,比如一个list组件我可以用它显示用户信息,当然也可以用它显示角色信息,根据传入的数据而变化。
对上述代码,需要简单解释一下的是,Vue中父子event传递是通过$emit和$on来实现的,但是写法和angular中有一些差异;在angular中我们一般这样写:
//事件发射$scope.$emit("onxxx",data);//事件监听$scope.$on("onxxx",function(e,data){ //TODO: })
但是在vue中$on是直接使用v-on:onxxx或@onxxx来写的,所以一般存在的是这样的代码:
<todo-list :items="items" @onremove="remove($event)" @onedit="edit($event)"></todo-list> <todo-form :init-item="initItem" @onsave="save($event)" ></todo-form>
其它代码中加入了很多注释,也比较简单,就不做过多解释了,有疑问可提交。
2、SearchBar组件
SearchBar组件比较简单,只是简单触发查询按钮,发射(触发)onsearch事件,然后TodoContainer组件中使用 @onsearch="search($event)" 进行监听。
/** * 搜索组件 */ 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="search" class="btn btn-primary" /> </div> </div> `, data: function () { return { keyword: '' } }, methods: { search: function () { this.$emit('onsearch', this.keyword); } } }
3、TodoForm组件
TodoForm组件,是我们Todolist中的表单组件,编辑和新增公用,我们需要考虑的是,我们的初始化数据由外部传入,首先看第一版代码,考虑有什么坑?
TodoForm =<div class="col-md-6"> <div class="form-inline"> <label ="title" class="control-label col-md-4">title:</label> <input type="hidden" v-bind:value="todo.id" /> <input type="text" v-model="todo.title" class="form-control col-md-8"> </div> <div class="form-inline"> <label ="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="OK" v-on:click="ok()" class="btn btn-primary offset-md-10" /> </div> </div>'initItem'.$emit('onsave',
这段代码有什么问题呢?我们把传入的初始化参数给了我们的todo对象,这样导致的直接问题是:新增的时候没问题,但是编辑的时候无法绑定数据,原因是,编辑操作实际上就是修改外部传入的initItem对象,但是todo只在组件初始化的时候被赋值,其它时候是不响应initItem变化的,如何才能响应initItem变化,很明显是我们的computed属性,computed属性会响应其封装对象的变化;代码第二版修改如下:
TodoForm =<div class="col-md-6"> <div class="form-inline"> <label ="title" class="control-label col-md-4">title:</label> <input type="hidden" v-bind:value="todo.id" /> <input type="text" v-model="todo.title" class="form-control col-md-8"> </div> <div class="form-inline"> <label ="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="OK" v-on:click="ok()" class="btn btn-primary offset-md-10" /> </div> </div>'initItem' { id: .initItem.id, title: .initItem.title, desc: .$emit('onsave',
4、TodoList && TodoItem组件
TodoList组件是数据列表组件,它的每一个列表项我们进行了一次封装,每一个list中的列表项,就是一个TodoItem组件,所以在TodoItem组件中,只需要引入todoitem数据即可,唯一需要关注的就是todoItem组件中会触发onremove和onedit事件。
/** * 列表项组件 */ var TodoItem = { template: ` <tr> <th>{{todo.id}}</th> <td>{{todo.title}}</td> <td>{{todo.desc}}</td> <td> <input type="button" value="remove" @click="remove()" class="btn btn-danger" /> <input type="button" value="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); } } }
http://www.cnblogs.com/Johnzhang/p/7223064.html