深入探讨Vue.js组件和组件通信


Posted in Javascript onSeptember 12, 2016

基本是按照官网的 Guide 全部梳理了一遍:http://vuejs.org/guide/index.html 这里我们以一个 Todo List 应用为例来把相关的只是都串起来,这篇里面的全部代码都在github上 https://github.com/lihongxun945/vue-todolist

 Vue 实例

一个 Vue 应用是由一个 root vue instance 引导启动的,而 Vue instance 是这么创建的:

var vm = new Vue({
 // options
})

 一个 instance 实际上就是 MVVM 中的一个 VM。 传入的配置对象中data里的所有属性都会被挂载到 instance上,而为了避免命名冲突,Vue 内置方法都会以 $ 开头的属性挂载到 instance 上。

instance 从创建到销毁会经历如下生命周期:

在初始化的时候大致经过三步:
 •绑定数据监听,即对 data 的监听
 •编译模板
 •插入document或者替换对应dom
 # Vue 基本语法

数据绑定

Vue 使用的是一种 类 mastache 语法。常用绑定语法分这么几类:
 •mastache 语法,比如 {{ data }} {{ data | filter}}
 •v-bind 绑定属性,比如 v-bind: href, v-bind:class
 •v-on 绑定事件, 比如 v-on:click, v-on:submit 

其中 v-* 都是 directive

例子:
<div v-bind:class="[classA, isB ? classB : '']">

 属性计算

Vue 支持一个很有意思的属性计算语法,可以指定一个属性由其他属性计算出来,这样就不用通过 $watch 来实现了:

var vm = new Vue({
 el: '#example',
 data: {
 a: 1
 },
 computed: {
 // a computed getter
 b: function () {
 // `this` points to the vm instance
 return this.a + 1
 }
 }
})

 ## 流程控制和列表相关的语法 包括 `v-if`, `v-show`, `v-else`, `v-for`

表单

双向数据绑定:

<input type="text" v-model="message" placeholder="edit me">

<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">

 ## 动画 动画的实现方式和 Angular 以及 React 都是一样的,都是通过添加和删除 class 来实现的。 # Component

组件的基本用法

Component 的定义包括两部分:

1 创建component类:

var Profile = Vue.extend({
 template: "<div> Lily </div>"
});

 2 注册一个 tagname:

Vue.component("me-profile", Profile);

 这样我们就可以通过 tagname 来使用这么组件了:

<div id="todo">
 <my-profile></my-profile>
 <form v-on:submit="add" v-on:submit.prevent>
 <input type="text" v-model="input"/>
 <input type="submit" value='add' />
 </form>
 ...
</div>

 Vue.component("me-profile", Profile); 属于全局注册,如果只是在某一个页面内使用,可以通过局部注册的方式:

var vm = new Vue({
 el: "#todo",
 components: {
 "my-profile": Profile
 },
 ...
}

 其中因为我们的 Vue 实例是绑定在 todo 元素上的,所以如果把 my-profile 放在这个元素外面是无效的,只有放在这个里面才会被 Vue 的这个实例引导初始化。

注意事项:

Vue 构造函数可以传的参数基本都可以用在 Vue.extend 上,但是对 el 和 data 两个参数需要注意,为了避免不同实例间共享同一个对象,总是要通过 function 返回一个新的对象比较靠谱:

var MyComponent = Vue.extend({
 data: function () {
 return { a: 1 }
 }
})

 因为参数都一样,其实他们俩就是同一个东西,不过一个是组件,一个是用来引导Vue启动的。

模板注意事项

因为 Vue 就是原生的DOM,所以有些自定义标签可能不符合DOM标准,比如想在 table 中自定义一个 tr,如果直接插入 my-component 不符合规范,所以应该这样写:

<table>
 <tr is="my-component"></tr>
</table>

 Props 传递数据

在 Vue 中每个组件都是独立的,不能也不应该直接访问父类的data。所以我们通过 props 来向子组件传递数据,是不是和 React 的方式很像?

不同于 React,在 Vue 中子组件需要先声明自己的 props 才行:

var Profile = Vue.extend({
 props: ["name"],
 template: `
 <h2>{{name}}'s Todo List</h2>
 <h4>{{name}} is a good girl</h4>
 `
});

 然后我们可以在使用 Profile 的时候这样传递参数:
<my-profile name='Lily'></my-profile>

 这种是通过字面量传递参数,所以传递的值一定是字符串。还有一种方式是动态传参,通过 v-bind 来传递参数,可以双向绑定数据或者传非字符串参数:
<my-profile v-bind:name=‘input'></my-profile>

 v-bind 如果是一个字符串,则是绑定父组件的data中对应的字段,比如上面就是双向绑定了 input 的值。如果是一个数字则就是绑定了一个数字。

Vue 还可以显式指定单向还是双向的数据绑定:

<!-- default, one-way-down binding -->
<child :msg="parentMsg"></child>

<!-- explicit two-way binding -->
<child :msg.sync="parentMsg"></child>

<!-- explicit one-time binding -->
<child :msg.once="parentMsg"></child>

 Props 校验

一个好的组件总是应该先验证参数是否正确,另外可能还需要设置一些参数的默认值:

var Profile = Vue.extend({
 input: {
 type: String
 }
});

 父子组件通信

上面讲到的 props 其实就是父组件向子组件传递消息的一种方式。
在子组件中有一个 this.$parent 和 this.$root 可以用来方法父组件和根实例。不过,现在我们应该避免这么做。因为组件本身就是为了封装独立的逻辑,如果又去直接访问父组件的数据就破坏了组件的封装性。
所以我们应该还是应该通过父组件向子组件传递 props 的方式来通信。

当然 props 其实只能做回调。在 React 中就探讨过这个问题,React 的做法就是通过 props 来做,传一个回调函数给子组件。其实我不是很喜欢这种把回调函数传来传去的方式,我更喜欢的是事件的方式。Vue 中子组件可以通过通过事件和父组件进行通信的。向父组件发消息是通过 this.$dispatch,而向子组件发送消息是通过 this.$boardcast,这里都是向所有的父亲和孩子发送消息,但是一旦执行一个回调之后就会停止,除非这个回调函数显式返回了 true。

我们把之前的Todo List拆成不同的组件来实现,这样可以体验下如何进行组件的双向通信,我们拆分出两个组件,分别是 List 和 Form 。

Form 负责处理用户输入,并在提交表单的时候向父组件发送一个 add 消息,代码如下:

var Form = Vue.extend({
 props: {
 username: {
 type: String,
 default: "Unnamed"
 }
 },
 data: function() {
 return {
 input: "",
 };
 },
 template: `
 <h1>{{username}}'s Todo List</h1>
 <form v-on:submit="add" v-on:submit.prevent>
 <input type="text" v-model="input"/>
 <input type="submit" value='add' />
 </form>
 `,
 methods: {
 add: function() {
 this.$dispatch("add", this.input); //这里就是向父组件发送消息
 this.input = "";
 }
 }
});

 List 只负责展示列表和处理用户勾选操作,它接收到 add 消息之后会在自己上添加一个条目:

var List = Vue.extend({
 template: `
 <ul>
 <li v-for='todo in list'>
 <label v-bind:class="{ done : todo.done }" >
  <input type="checkbox" v-model="todo.done"/>
  {{todo.title}}
 </label>
 </li>
 </ul>`,
 props: {
 initList: {
 type: Array
 }
 },
 data: function() {
 return {
 list: []
 }
 },
 events: {
 add: function(input) {
 if(!input) return false;
 this.list.unshift({
 title: input,
 done: false
 });
 }
 }
});

 然后,因为这是两个组件,当然需要一个 Vue 实例来引导启动,我们的实例如下:

var vm = new Vue({
 el: "#todo",
 components: {
 "todo-form": Form,
 "todo-list": List
 },
 events: {
 add: function(input) {
 this.$broadcast("add", input);
 }
 }
});

 注意,其实 Form 和 List 在逻辑上是平级的组件,所以他们没有父子关系,他们共同都是 vm 的孩子。这里 vm 接到 Form 的消息之后会转发给 List。

html 代码就更简单了:

<div id="todo">
 <todo-form username='Lily'></todo-form>
 <todo-list></todo-list>
 </div>

 Slot

通过 Slot 可以实现把父组件渲染出来的HTML插入到子组件中,目前还不清楚什么时候会需要这样做,而且这么做对子组件的侵入性太大。

动态切换组件

这个功能感觉有点多余,感觉很多情况下我们应该是通过逻辑代码来实现切换,而不是通过Vue内置的动态组件来切换。不过用来实现一个类似 tab 切换的功能还是很方便的。

我们这里给 Todo List 增加一个 about 页面。那么首先我们需要把 vm 改成一个组件,这个组件叫 Todo,它就是整个 Todo 页面:

var Todo = Vue.extend({
 template: `
 <div id="todo">
 <todo-form username='Lily'></todo-form>
 <todo-list></todo-list>
 <slot>not show</slot>
 </div>
 `,
 components: {
 "todo-form": Form,
 "todo-list": List
 },
 events: {
 add: function(input) {
 this.$broadcast("add", input);
 }
 }
});

 其实改动就第一行。

然后我们需要创建一个 About 组件:

var About = Vue.extend({
 template: `
 <div id="about">
 <p>About Todo List V0.1.0</p>
 <p>Content here</p>
 </div>`
});

 接下来是重点了,我们要创建一个实例 vm,这vm要负责切换这两个页面:

var vm = new Vue({
 el: "body",
 data: {
 currentView: "todo"
 },
 components: {
 "todo": Todo,
 "about": About
 }
});

 这里我们定义了一个 currentView 字段,当然可以是任意名称,然后通过特殊的 component 标签来进行组件切换:

<component :is="currentView"></component>
 <ul>
 <li><label><input type="radio" name='page' value='todo' v-model='currentView'> Home</label></li>
 <li><label><input type="radio" name='page' value='about' v-model='currentView'> About</label></li>
 </ul>

 上面的代码有两处需要注意:
 •通过 component这个特殊标签,然后用 :is 属性来进行组件的切换。
 •radio 通过双向绑定来修改 currentView 字段,从而实现点击之后就可以进行切换。

数据绑定的实现原理

Vue 把双向绑定称作 reactive,可以翻译为响应式数据绑定。内部是通过 ES5 定义的 getter 和 setter 方法实现的,所以不支持 IE8 及以下浏览器,这种实现方式有两个容易犯错的地方:
 •如果在 data 上直接添加和删除属性是无法被检测到的,一般删除是不会的,但是可能会动态添加,这个时候应该通过 vm.$set(“name”, value) 的方式来添加。
 •无法检测到对象内部的变化,也就是只能检测 data 的属性变化,如果 data.a 是一个对象,那么 data.a.b = 1 这种变化是无法被检测到的。这种情况下应该创建一个新的对象并赋值给 data.a 就行了。 

异步更新机制

Vue 对DOM的更新是异步的! 这个异步是在一个异步队列中进行的,不过这个异步队列会在当前的 Event Loop 中执行完,所以如果修改了 Data 立刻去DOM中做查询操作是不对的,这个时候DOM还没有更新,正确的做法是这样做:

vm.msg = 'new message' // change data
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
 vm.$el.textContent === 'new message' // true
})

 或者这样:

vm.$nextTick(function () {
 this.$el.textContent === 'new message' // true
})

 花了半天时间才看完组件,下面应该去看一下另一个重点: Directive

本文已被整理到了《Vue.js前端组件学习教程》,欢迎大家学习阅读。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascrpt绑定事件之匿名函数无法解除绑定问题
Dec 06 Javascript
jQuery 过滤方法filter()选择具有特殊属性的元素
Jun 15 Javascript
原生js实现日期联动
Jan 12 Javascript
JavaScript中Date对象的常用方法示例
Oct 24 Javascript
js制作支付倒计时页面
Oct 21 Javascript
jQuery中table数据的值拷贝和拆分
Mar 19 Javascript
js禁止表单重复提交
Aug 29 Javascript
JS实现可视化文件上传
Sep 08 Javascript
从零开始搭建vue移动端项目到上线的步骤
Oct 15 Javascript
vue实现倒计时获取验证码效果
Apr 17 Javascript
vue穿梭框实现上下移动
Jan 29 Vue.js
vue.js 使用原生js实现轮播图
Apr 26 Vue.js
前端框架Vue.js中Directive知识详解
Sep 12 #Javascript
前端框架Vue.js构建大型应用浅析
Sep 12 #Javascript
jQuery过滤选择器用法示例
Sep 12 #Javascript
强大Vue.js组件浅析
Sep 12 #Javascript
超详细的JS弹出窗口代码大全
Apr 18 #Javascript
使用JS实现图片展示瀑布流效果的实例代码
Sep 12 #Javascript
关于javascript的一些知识以及循环详解
Sep 12 #Javascript
You might like
php 伪静态之IIS篇
2014/06/02 PHP
PHP实现数组转JSon和JSon转数组的方法示例
2018/06/14 PHP
文字幻灯片
2006/06/26 Javascript
jquery选择器排除某个DOM元素的方法(实例演示)
2014/04/25 Javascript
JavaScript动态修改背景颜色的方法
2015/04/16 Javascript
jQuery插件bgStretcher.js实现全屏背景特效
2015/06/05 Javascript
直接拿来用的15个jQuery代码片段
2015/09/23 Javascript
跟我学习javascript的函数和函数表达式
2015/11/16 Javascript
Webpack 实现 AngularJS 的延迟加载
2016/03/02 Javascript
你不需要jQuery(三) 新AJAX方法fetch()
2016/06/14 Javascript
jQuery实现select下拉框获取当前选中文本、值、索引
2017/05/08 jQuery
jQuery 实现鼠标画框并对框内数据选中的实例代码
2017/08/29 jQuery
解决Angular2 router.navigate刷新页面的问题
2018/08/31 Javascript
在webstorm开发微信小程序之使用阿里自定义字体图标的方法
2018/11/15 Javascript
微信小程序使用wx.request请求服务器json数据并渲染到页面操作示例
2019/03/30 Javascript
JS实现简单移动端鼠标拖拽
2020/07/23 Javascript
解决vuex数据页面刷新后初始化操作
2020/07/26 Javascript
SpringBoot在yml配置文件中配置druid的操作
2020/11/16 Javascript
[11:12]2018DOTA2国际邀请赛寻真——绿色长城OpTic
2018/08/10 DOTA
Python自定义scrapy中间模块避免重复采集的方法
2015/04/07 Python
Django中传递参数到URLconf的视图函数中的方法
2015/07/18 Python
Django如何配置mysql数据库
2018/05/04 Python
python 弹窗提示警告框MessageBox的实例
2019/06/18 Python
pandas.read_csv参数详解(小结)
2019/06/21 Python
Python for循环搭配else常见问题解决
2020/02/11 Python
Python3 利用face_recognition实现人脸识别的方法
2020/03/13 Python
Selenium启动Chrome时配置选项详解
2020/03/18 Python
简单介绍一下pyinstaller打包以及安全性的实现
2020/06/02 Python
HTML5实现QQ聊天气泡效果
2017/06/26 HTML / CSS
HearthSong官网:儿童户外玩具、儿童益智玩具
2017/10/16 全球购物
国际商务系学生个人的自我评价
2013/11/26 职场文书
《听鱼说话》教学反思
2014/02/15 职场文书
保护环境建议书300字
2014/05/13 职场文书
美术学专业求职信
2014/07/23 职场文书
用人单位的规章制度,怎样制定才是有效的?
2019/07/09 职场文书
python 实现定时任务的四种方式
2021/04/01 Python