一步一步学Vue(四)

接上篇。上篇中给出了代码框架,没有具体实现,这一篇会对上篇定义的几个组件进行分别介绍和完善:

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 @οnsearch="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组件中使用 @οnsearch="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中的表单组件,编辑和新增公用,我们需要考虑的是,我们的初始化数据由外部传入,首先看第一版代码,考虑有什么坑?

 /*** 表单组件*/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="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="OK" v-on:click="ok()" class="btn btn-primary offset-md-10"  /></div></div>
    `,props: ['initItem'],
     data: function {return {todo: this.initItem}
},methods: {ok: function () {this.$emit('onsave', this.todo);}}}

这段代码有什么问题呢?我们把传入的初始化参数给了我们的todo对象,这样导致的直接问题是:新增的时候没问题,但是编辑的时候无法绑定数据,原因是,编辑操作实际上就是修改外部传入的initItem对象,但是todo只在组件初始化的时候被赋值,其它时候是不响应initItem变化的,如何才能响应initItem变化,很明显是我们的computed属性,computed属性会响应其封装对象的变化;代码第二版修改如下:

 /*** 表单组件*/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="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="OK" v-on:click="ok()" class="btn btn-primary offset-md-10"  /></div></div>
    `,props: ['initItem'],computed: {todo: function () {
          //这里不直接返回this.initItem 是因为会导致双向绑定,因为传递的是引用
return { id: this.initItem.id, title: this.initItem.title, desc: this.initItem.desc };}},methods: {ok: function () {this.$emit('onsave', this.todo);}}}

 

 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);}}}

这两个数据渲染组件就没什么好说名的了;但是大家发现一个很不爽的问题:由于我们在容器中统一管理了业务逻辑(更逼格高一些,叫状态),所以在todoitem组件中触发的事件没办法直接到TodoContainer组件中,只能通过一级一级的往上传递,所以在todolist中也有和todoitem中类似的触发事件的代码this.$emit('onremove', $e);这里组件层级才2级,如果多了状态管理就是灾难了,幸好vuex的出现,就是专门处理这种问题的,后期用到vuex的时候会详细介绍。

5、小结

  todolist这个demo,就暂时告一段落了,下一片会以一个稍微复杂的demo(信息管理)来介绍vue-router,当然在一步一步学习的过程中,我还是没能做到把所有基本概念过一遍,我个人觉得还是用到再解释吧,否则还不如直接看文档来的方便。。。

完整代码:

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"></head><body class="container"><div id="app"><todo-container></todo-container></div><script src="./todolist.js"></script>
</body></html>
View Code

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="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="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="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="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);}}}/*** 容器组件* 说明:容器组件包括三个字组件*/var TodoContainer = {template: `<div class="container"><search-bar @οnsearch="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>*/
View Code

 

转载于:https://www.cnblogs.com/Johnzhang/p/7223064.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/400436.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【51单片机快速入门指南】4.3: I2C读取MPU6050陀螺仪的原始数据

目录硬知识特性参数MPU6050 简介模块重要寄存器简介电源管理寄存器 1陀螺仪配置寄存器加速度传感器配置寄存器FIFO 使能寄存器陀螺仪采样率分频寄存器配置寄存器电源管理寄存器 2陀螺仪数据输出寄存器加速度传感器数据输出寄存器温度传感器示例程序MPU6050.cMPU6050.hmain.c实验…

OSM数据的获取及格式转换

转自 &#xff1a;http://blog.sina.com.cn/s/blog_72f0b6080102w39z.html 前言&#xff1a;本篇博文将介绍如何对OSM数据进行获取&#xff0c;以及格式的转换&#xff08;转为shapefile格式&#xff09;。以供OSM数据获取失败、OSM editor操作失败的朋友参考。由于并不是多么高…

再读TCP/IP网络7层协议

随着工作的深入&#xff0c;每次读这7层协议&#xff0c;每次都有不同的理解。 分层名 分层号 描述 比喻 应用层Application La…

LVS之一:基本命令和调度方法

实验环境&#xff1a;fedora_server最新版本1.查看内核是否支持ipvsgrep -i vs /boot/config-3.17.4-301.fc21.i686PAE2.安装ipvsadmyum install ipvsadm3.ipvsadm命令1、管理集群服务1.添加&#xff1a;-Aipvsadm -A|E -t|u|f service-address-t&#xff1a;tcp协议集群 -u&am…

windows下安装mysql教程

下载mysql压缩包 我的系统是windows10&#xff0c;64位的&#xff0c;我下载了最新版的MySQL Community Server。这是社区版的mysql服务器。自己根据自己系统酌情下载。 解压和检查包内容 用你喜欢的软件将你下载来的压缩包解压&#xff0c;解压位置随意&#xff0c;因为可以配…

oracle复习笔记

2019独角兽企业重金招聘Python工程师标准>>> 1.oracle相关认证&#xff1a;OCA:Oracle认证专员&#xff0c;OCP:Oracle专家认证&#xff0c;OCM:Oracle认证大师。 2.1979年,Oracle2发布。 3.Oracle数据库特点&#xff1a;支持多用户&#xff0c;大事务量的事务处理&…

依据地图上的经纬度坐标计算某个点到多边形各边的距离

http://www.th2w.com/article/85 依据地图上的经纬度坐标计算某个点到多边形各边的距离 最近公司有一个需求&#xff1a;依据地图上的经纬度坐标计算某个点到多边形各边的距离。 主要原理&#xff1a; 依据当前点p和多边形相邻两点(pb, pe)组成三角形由于是坐标系&#xff0c;…

Redis持久化(转载)

原文地址&#xff1a;http://www.jianshu.com/p/2f14bc570563?fromjiantop.com 数据持久化 Redis提供了将数据定期自动持久化至硬盘的能力&#xff0c;包括RDB和AOF两种方案&#xff0c;两种方案分别有其长处和短板&#xff0c;可以配合起来同时运行&#xff0c;确保数据的稳…

linux内核中send与recv函数详解

Linux send与recv函数详解 1.简介 #include <sys/socket.h> ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags); ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags); send和recv的前3个参数等同于read和write&#xff1b;flags参数值…

【51单片机快速入门指南】4.3.1: MPU6050调用DMP库获取四元数和欧拉角

目录相关介绍DMP库相关DMP加载步骤&#xff1a;DMP设置数据写入更新DMPDMP数据包结构程序实现DMP.cDMP.h测试程序四元数实验现象欧拉角的获取普中51-单核-A2 STC89C52 Keil uVision V5.29.0.0 PK51 Prof.Developers Kit Version:9.60.0.0 上位机&#xff1a;Vofa 1.3.10 相关…

cardsui-for-android

https://github.com/Androguide/cardsui-for-android cardsui-for-android-master.zip

spoj 2 Prime Generator

题目&#xff1a;Prime Generator 思路&#xff1a;分段筛素数 #include <cstdio> #include <iostream> #include <cmath> #include <algorithm> #include <cstring> #include <map> using namespace std; #define maxn 40000 int n_prime…

LTDC/DMA2D—液晶显示

本章参考资料&#xff1a;《STM32F4xx 参考手册 2》、《STM32F4xx 规格书》、库帮助文档《stm32f4xx_dsp_stdperiph_lib_um.chm》。关于开发板配套的液晶屏参数可查阅《5.0 寸液晶屏数据手册》配套资料获知。 显示器简介显示器属于计算机的 I/O 设备&#xff0c;即输入输出设备…

【51单片机快速入门指南】4.3.2: MPU6050:一阶互补滤波、二阶互补滤波和卡尔曼滤波获取欧拉角

目录源码MPU6050_Filter.cMPU6050_Filter.h使用方法测试程序一阶互补滤波效果二阶互补滤波效果卡尔曼滤波效果总结普中51-单核-A2 STC89C52 Keil uVision V5.29.0.0 PK51 Prof.Developers Kit Version:9.60.0.0 上位机&#xff1a;Vofa 1.3.10 参考资料&#xff1a; MPU6050…

Mac 运行 psql postgres 报错

psql: could not connect to server: No such file or directoryIs the server running locally and accepting用brew卸载postgresql&#xff1a; brew uninstall postgresqlbrew doctor &#xff08;修正这里的一切&#xff09;brew prune删除所有Postgres文件夹&#xff1a;r…

NGUI基础-三大基础组件之Root组件

NGUI NGUI&#xff08;Next-Gen UI&#xff09;是一款用于Unity游戏引擎的UI插件&#xff0c;它提供了一套功能强大、灵活易用的界面开发工具。在NGUI中&#xff0c;Root&#xff08;根节点&#xff09;是一个重要的概念。 基础组件之Root Root是NGUI中的最高层级节点&#…

【转】android 中如何限制 EditText 最大输入字符数

原文网址&#xff1a;http://blog.csdn.net/fulinwsuafcie/article/details/7437768 方法一&#xff1a; 在 xml 文件中设置文本编辑框属性作字符数限制 如&#xff1a;android:maxLength"10" 即限制最大输入字符个数为10 方法二&#xff1a; 在代码中使用InputFilte…

ibatis 中 $与#的区别

在sql配置中比如in(#rewr#) 与in ($rewr$) 在Ibatis中我们使用SqlMap进行Sql查询时需要引用参数&#xff0c;在参数引用中遇到的符号#和$之间的区分为&#xff0c;#可以进行与编译&#xff0c;进行类型匹配&#xff0c;而$不进行数据类型匹配&#xff0c;例如&#xff1a; sele…

【51单片机快速入门指南】4.3.3: MPU6050使用Mahony AHRS算法实现六轴姿态融合获取四元数、欧拉角

目录源码Mahony_6.cMahony_6.h使用方法测试程序main.c效果STC89C516 32MHz Keil uVision V5.29.0.0 PK51 Prof.Developers Kit Version:9.60.0.0 上位机&#xff1a;Vofa 1.3.10 移植自MPU6050姿态解算——Mahony互补滤波 —— 大写的小写字母 加入了输入数据范围的自动处理…

linux文件系统及bash基础特性

linux文件系统 一、根文件系统 linux被识别的第一个被称为根之间关联的文件系统叫做根文件系统&#xff08;rootfs&#xff09;&#xff0c;其他分区要想被读到&#xff0c;需要挂载到根目录的某个挂载点&#xff08;根的子目录&#xff09;上。根文件系统至关重要&#xff0c;…