Vue.js 四天课程学习笔记_第4天
课程内容概要:
1. 前情回顾
2. 通过demo再次复习一下 计算属性 是如何生成 过滤后的数组
3. Vue实例的生命周期(11个钩子)
4. 介绍组件化思想
5. 介绍 Element组件库 的安装和使用
6. 全局组件 与 局部组件
7. 组件模板的几种写法
8. 以.vue为后缀的单文件组件(需结合webpack)
9. 父子组件
10. 父子组件通信之Props Down
11. prop 单向数据流 与 prop 验证
11. 父子组件通信之Events Up
12. 非父子组件通信
13. 将上次完整功能的todoMVC拆分为组件组织形式(父子组件通信形式)
插入一句: 提到了 开发中没有银弹(出自<人月神话>)
前情回顾
1. 在Vue中如何解决表达式闪烁
1.1 v-text
1.2 v-cloak 与 [cloak]{display:none;}
当Vue解析处理完后,会把v-clock属性移除,则么display就不再是none了
2. v-show 与 v-if的区别
2.1 v-show 无论如何都会渲染, 再根据条件 显示,适合于 频繁 显示和隐藏时使用
2.2 v-if 真正的根据条件 再进行渲染
3. 计算属性
3.1 计算属性的使用方式是 属性, 不能当方法用,不能作为事件处理函数,虽然它本质上是gett/setter方法
3.2 计算属性本身不存储任何值, 计算属性 缓存计算结果
3.3 计算属性 与 v-model 同时使用,可发挥巨大力量
4. 观察者watch选项
4.1 可定向观察data选项中数据的改变,从而执行相应的业务逻辑
4.2 引用类型(如数组/对象)只能监视第1层,无法监视内部子成员的改变
4.3 当数据改变时,自动调用handler(value,oldValue)方法
4.4 deep属性可以尝试监视,immediate属性可以立即执行一次(无论有无数据的改变)
5. 在Vue中,使用计算属性(过滤数组) 具体见下面的demo vue_30.html
6. 自定义指令
6.1 当需要操作DOM的时候,使用自定义指令
6.2 Vue.directive(‘beyond-show’,{})
6.3 参数2是一个对象 有5个可选的钩子函数
bind(el,binding)
bind钩子中,拿不到父元素
inserted(el,binding)
inserted钩子中,可以拿到父元素,focus必须写在inserted中
update(el,binding)
DOM里是 模板更新前的内容
componentUpdated(el,binding)
DOM里是 模板更新后的内容
unbind(el,binding)
收尾工作,例如,清除定时器
下面通过一个简单的demo再次复习一下, 如何通过 计算属性 来过滤数组的
vue_30.html效果如下:
vue_30.html代码如下:
Vue生命周期图示:
Vue实例的生命周期:
.
生命周期钩子: 11 个
每个Vue实例在被创建之前fjtb要经过一系列的初始化过程
例如:
1. 需要设置数据监听
2. 编译模板
3. 挂载实例到DOM
4. 在数据变化时更新DOM等
因此提供了11个 生命周期钩子函数,供用户添加自己的业务代码
例如:
1. created钩子可以用来在一个实例被创建之后执行业务代码
new Vue({
data: {
girlName: ‘面码’
},
// 生命周期钩子
created: function(){
NSLog(‘hello ‘ + this.girlName)
}
})
// 输出: hello 面码
也有一些其他的钩子, 在实例生命周期不同的场景下调用
例如:
beforeCreate、created、
beforeMount、mounted、
beforeUpdate、updated、
activated、deactivated、
beforeDestroy、destroyed
errorCaptured 共11个钩子
注意: 不要在选项属性或回调上使用 箭头函数,因为 this 是和上级作用域绑定
例如: created: ()=> NSLog(this)
或者: appVue.$watch(‘girlName’,newName => this.updateDB())
因为箭头函数是和父级上下文绑定的, this 不会如你所愿
常常导致 Cannot Read Property of undefined 或
this.updateDB is not a function之类的错误
下面演示一下Vue实例的生命周期(11个钩子)
vue_44.html代码如下
第一次运行时,效果如下:
注意: beforeCreated里面 还没有data数据
单击按钮一次, 效果如下:
下面开始隆重介绍 组件 Component
组件(Component)是Vue.js最强大的功能之一,也是最核心的技能之一
组件可以扩展HTML元素,封装可重用的HTML/CSS/JS代码
组件的封装,就是封装一个自定义的HTML标签
例如<beyond-rate> </beyond-rate>
组件示意图如下:
先来一下总览:
在Vue.js中,一个组件 就是一个拥有预定义选项的 Vue 实例
在Vue中,定义一个组件:
Vue.compent(‘beyond-item’,{
template: ‘<li> 未闻花名 </li>’
})
使用一个组件
<ul>
<beyond-item > </beyond-item>
</ul>
渲染结果:
<ul>
<li> 未闻花名 </li>
</ul>
在Vue中, 组件 是用来封装视图的,即HTML
组件化思想,就是把复杂的Web页面,拆分成一个个 组件视图
包括: HTML 结构
CSS样式
JS行为
优点:
开发效率高
可维护性好
可重用性好
下面介绍 一下基于Vue.js 2.0的一个桌面端的组件库:
element ui
官网: element-cn.eleme.io
第1步, 安装 (使用 npm ):
npm install element-ui –save
版本: v2.3.7
第2步, 配置 (引入css和js):
第3步, 使用: (参照官方文档)
vue_31.html效果如下:
vue_31.html代码如下:
下面是另一个element-ui的组件示例: 日期选择组件
vue_32.html效果如下:
vue_32.html代码如下:
下面再介绍一下 antd 的Web UI组件库的使用
http://ant.design/components/upload-cn/
https://mobile.ant.design/components/popover-cn/ (移动端Mobile组件库)
下面介绍一下 radon UI组件库的使用
下面介绍一下 iView组件库的使用
插入一个,与组件无关的效果:
组件的定义
1. 全局定义
1.1 全局定义的组件,在任意组件中都可以使用
1.2 注册全局组件
Vue.component(‘beyond-component’,{
template: ‘<div> 未闻花名 </div>’
})
1.3 使用组件
<div id=”id_div_container”>
<beyond-component> </beyond-component>
</div>
1.4 渲染结果:
<div id=”id_div_container”>
<div> 未闻花名 </div>
</div>
vue_33.html代码如下:
vue_33.html效果如下:
vue_34.html代码如下:
2. 局部定义
2.1 局部定义的组件,只能在 当前组件中使用
2.2 定义局部组件
var appVue = new Vue({
data: {},
methods: {},
// 组件使用 第1步: 定义一个局部组件
components: {
‘beyond-component’: {
template: ‘<div> 我们仍未知道那年夏天所见到的花的名字 </div>’
}
}
}).$mount(‘#id_div_container’)
2.3 使用局部组件
<!– 组件 第2步: 使用一个局部组件 –>
<beyond-component> </beyond-component>
vue_34.html效果如下:
注意: 1. 组件的data必须是 函数
2. 构造Vue实例时的各选项, 在组件里也一样可以有
3. 唯一例外的是 data选项, 必须是一个函数
例如:
// 全局组件
Vue.component(‘beyond-counter’,{
template: ‘<button v-on:click=”number++”>{{ number }} </button>’,
// data选项必须是函数
data: function(){
var obj = {
number: 0
}
// 组件中的data 函数, 必须返回一个对象
return obj
}
})
// 使用全局组件
<div id=”id_div_container”>
<beyond-counter></beyond-counter>
</div>
使用js中的字符串字面量来定义template模板
vue_35.html代码如下:
使用js中的字符串字面量来定义template模板
vue_35.html效果如下:
使用script标签来创建组件的模板(template)
vue_36.html代码如下:
使用script标签来创建组件的模板(template)
vue_36.html效果如下:
对低开销的静态组件(包含大量静态内容) 请使用v-once,将其缓存起来
组件的模板template 只能有一个根节点
1. DOM模板
2. JS内联字符串模板,缺点是: 没有高亮,与js代码揉杂在一起,不利于开发与维护
例如: template: ‘<h1>未闻花名</h1>’
3. 写在特定的script标签中, 可以进行高亮
但是,它将组件的模板 与 组件的其他选项分离开了
除非是在演示应用 或 特别小的应用中,否则不推荐这样写
具体做法是:
3.1 type 指定为 x-template
3.2 起一个id
3.3 在component定义中使用
template: ‘#id’
例如: <script type=”text/x-template” id=”id_template_beyond”>
<h2>那朵花</h2>
</script>
在组件定义中是这样:
Vue.component(‘beyond-component’,{
// 使用srcipt来创建模板
template: ‘#id_template_beyond’,
// data必须是一个返回对象的函数
data: function(){
var obj = {girlName: ”}
return obj
}
})
4. 以.vue作为后缀名的单文件中的 template模板(强烈推荐)
为啥推荐单独一个.vue作为后缀的单文件 为作为组件的template呢?
首先,我们来看一看传统的定义的缺点:
4.1 全局定义 强制要求每一个组件的 名字不能重复
4.2 字符串模板 没有语法高亮,特别在有多行的时候
4.3 不支持CSS样式
4.4 ???Excuse Me??? 没有构建步骤
只能使用HTML及ES5,不能使用Babel 及 Pug(曾经的Jade)
而扩展名为.vue的single-file component则完全没有上面的缺点
并且还有如下优点
4.1 完整的语法高亮
4.2 组件作用域的CSS
4.3 CommonJS模块
下面是一个componet_beyond.vue的简单示例:
再次强调:
1. 组件 是独立的作用域
2. 组件 无法访问外部 作用域的成员
3. 外部作用域也无法访问组件内部成员
组件 最常见的就是形成父子组件的关系,
即: 组件A 在其template模板 中,使用了 组件B
组件通信:
父组件 下发数组给 子组件
子组件 将其内部发生的事 告知父组件
总结就是:
Prop向下传递
事件向上传递
如图所示:
组件设计初衷就是为了配合使用
最常见的就是形成父子组件的关系
父组件给子组件下发数据
子组件则将其内部发生的事件告诉父组件
总结来说: Props向下传递数据
Event向上传递事件
组件其实就是一个特殊的Vue实例,可以有自己的选项
(例如选项 data,methods,computed,watch等, 其中data是返回一个对象的函数)
组件的 data必须是一个方法, 返回一个对象,作为组件的数据源
组件一般分为两大类:
1. 全局组件
通用的组件,例如:评分,日期选择器,轮播图等
2. 局部组件
与具体业务绑定的组件,不通用
下面用一个demo来演示一下, 组件的作用域是独立的
vue_37.html代码如下:
vue_37.html效果如下:
下面演示一下 全局组件的嵌套
vue_38.html代码如下:
全局组件的嵌套
vue_38.html效果如下:
下面再演示一下 全局组件 嵌套 局部组件
核心代码:
全局组件 嵌套 局部组件
vue_39.html 完整代码如下:
全局组件 嵌套 局部组件
vue_39.html 效果如下:
补充一下:
在Vue实例中,如果也声明了模板template选项(高优先级),那么它将覆盖掉el选项挂载的html内的节点
vue_40.html代码如下:
vue_40.html效果如下:
下面介绍 父组件 如何 向 子组件 传递数组
父子组件通信: Props Down
注意: 父组件中的数据发生变化时,所有子组件都会同步更新
注意: 子组件能修改父组件的引用数据类型里的成员的值,但不建议这样做
注意: 子组件不能对父组件中的任何数据进行重新赋值
注意: 子组件 只能使用 来自父组件的数据, 万万不可重新赋值!!!
否则报错:
因为违背了Vue的通信原则: 单向数据流
正确做法是: 在事件发生时,把数据通过自定义事件的参数上交给父组件
1. 在父组件的template模板中 通过子组件标签的属性传递数组
例如:
<beyond-footer key_author=”面码”></beyond-footer>
或者 通过v-bind 传递 reactive data
<beyond-footer v-bind:key_author=”girlName”></beyond-footer>
2. 在子组件中 显式地用 props选项 声明它预期的数据,并使用
Vue.component(‘beyond-footer’,{
// 2.1 声明props
props: [‘key_author’]
// 2.2 像data方法返回的对象一样 使用
template: ‘<span> {{ key_author }} </span>’
})
总结起来 父组件 传递数据给子组件 就是分两步走:
从父组件 传递数据给子组件, 只要两步
第1步: 在父组件的template中, 使用v-bind在子组件的属性上传值
注: girlName来自父组件的data方法返回的对象
第2步: 在子组件中的 props选项里声明,然后就可以像数组一样使用了
然后, 在子组件的template中,像使用其data方法返回的对象中的数据一样,使用来自父组件的数据
注意: 由于 html 是不区分大小写的,
所以当使用的不是字符串模板时,
camelCase(驼峰式命名)的props需要 转换成 相对应的kebab-case(短横线分隔式命名)
注意: 如果使用的是字符串模板,则没这些限制
父组件如下:
子组件如下:
.
项目组织如下:
app入口是vue_41.js
父组件是vue_41_beyond_component.js
子组件是vue_41_beyond_footer.js
vue_41.html代码如下:
app入口 vue_41.js如下:
父组件vue_41_beyond-component.js代码如下:
子组件vue_41_beyond_footer.js代码如下:
vue_41.html效果如下:
单向数据流
Prop是单向绑定的: 当父组件的属性变化时,将传给子组件
但是,反过来不会
这是为了防止 子组件无意间修改了父组件的状态造成混乱
每次父组件更新时, 子组件的所有props都会同步更新
你不应该在子组件内部改变props的值
1. 针对我们想把传递进来的值 作为局部变量使用的情况
我们可以在data返回的对象中,定义一个局部变量, 并用props的值初始化它
然后我们就可以一直使用这个局部变量了
例如:
props: [‘key_number’],
data: function(){
var obj = {
tempNumber: this.key_number
}
return obj
}
2. 针对于props传递进来的值需要处理才能使用的情况
我们可以定义一个计算属性处理props数据,并返回
例如:
props: [‘key_name’],
computed: {
tempName: function{
return this.key_name.trim().toLowerCase()
}
}
注意:
在JS中,对象和数组是 引用类型, 指向同一个内存空间
如果props是一个对象或数组,那么一旦在子组件中改变它的话,
将会影响到父组件中的状态
Props验证
我们可以为组件的props指定一个验证规则
如果传入的数据不符合要求, Vue就会发出警告
要指定验证规则 , 需要用对象的形式来定义prop(不能用字符串数组)
例如:
Vue.component(‘beyond-btn’,{
props: {
// 基础类型检测(null表示允许任何值)
propA: Number,
// 可以是多种类型
propB: [String,Number],
// 必须是字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 67
},
// 带有默认值的对象
propE: {
type: Object,
default: function(){
var obj = {
girlName: ‘面码’,
girlAge: 13
}
return obj
}
},
// 自定义验证函数
propF: {
validator: function(value){
// 这个值必须匹配下列字符串中的一个
return [‘面码’,’mathilda’,’逢坂大河’].indexOf(value) !== -1
}
}
}
})
注意: 当prop 验证失败时, Dev环境下 Vue 会报警告
注意: 上面那些prop 会在一个 组件实例 创建之前 进行验证,
所以实例的属性在default或validator函数中是不可用的
下面开始介绍 父子组件通信: Events Up
步骤1. 在子组件中,当事件发生时(如按钮被点击) 调用this.$emit()方法 发送一个事件,
参数是: 一个自定义事件,例如: beyond-click (注意: 这儿必须用 – 连接,不能用小驼峰)
Vue.component(‘beyond-button’,{
// 子组件中有一个按钮
template: ‘<button v-on:click=”btnClicked“>{{ girlNumber }}个萝莉</button>’,
// 组件data选项必须是一个返回对象的函数
data: function(){
var obj = {
girlNumber
}
return obj
},
// 响应按钮点击事件
methods: {
btnClicked: function(){
// 1.先处理自己的逻辑
this.girlNumber += 1
// 2.再告诉父组件(手动发布一个自定义事件)
// 注意: 这儿必须是 – 连接, 不能是小驼峰
this.$emit(‘beyond-click‘)
}
}
})
步骤2: 在父组件的template中的子组件上 注册监听 自定义事件(beyond-click)
在父组件(注册)监听子组件的自定义事件(beyond-click),
当子组件内部手动发出自定义事件(beyond-click)的消息,父组件就能监听到
<div id=”#id_div_vue”>
<p>{{ girlCount }}</p>
<!–
监听 自定义事件 beyond-click
当子组件内部手动发出了beyond-click自定义事件时,
父组件将执行相应的事件处理函数
注意: 这儿必须是 – 连接, 不能是小驼峰
–>
<beyond-button v-on:beyond-click=”btnClickedCallback”></beyond-button>
</div>
步骤3:
new Vue({
el: ‘#id_div_vue’,
data: {
girlCount: 0
},
methods: {
// 子组件内部事件发生时,回调处理函数
btnClickedCallback: function(){
this.girlCount += 1
}
}
})
下面演示一下子组件如何 向上传递事件 给父组件
vue_42.html效果如下:
vue_42.html代码如下:
app入口vue_42.js代码如下:
全局子组件vue_42_beyond_btn.js代码如下:
再来一个实例, 演示的是父组件props传递数据给子组件,
子组件发送自定义事件给父组件
效果如下:
vue_43.html代码如下:
vue_43.js代码如下:
vue_43_beyond_btn.js代码如下:
下面介绍非父子组件之间的通信
非父子组件通信: Event Bus
在简单场景下,可以使用一个空的Vue实例,作为事件总线
例如:
var vueBus = new Vue()
// 手动触发组件A中的自定义事件
vueBus.$emit(‘component-a-click’, ‘面码’)
// 在组件B中 创建的钩子中 监听 组件A的自定义事件
busVue.$on(‘component-a-click’,function(girlName){
console.log(‘来自组件A自定义事件中的数据: ‘ + girlName)
})
最后,隆重介绍 专业 组件通信 大杀器: Vuex
下面新写一个todoMVC, 以组件的形式进行组织起来
第1步,从官网上git clone下来 todoMVC的 样本 template
官网:todomvc.com, 右下方, 点击 template, 跳转到git去下载
github:
https://github.com/tastejs/todomvc-app-template
clone下来并重全名为todomvc-component: ( –depth=1 表示 只下载最后一次更新 )
git clone https://github.com/tastejs/todomvc-app-template.git –depth=1 todomvc-component
第2步, 因为下载下来的的样本template的文件夹里已经有了package.json, 所以直接安装
npm install
第3步, 安装 browser-sync, 方便保存时,页面立即刷新
3.1
3.2 打开package.json, 配置script命令,方便快速启动
如图:
3.3 启动 (全自动打开浏览器,并且监视本地的html、css、js文件的变动,实时更新)
npm start
效果如下:
最后再把vue安装一下
在index.html中引用vue
<!– 第1步,导入vue.js –>
<script type=”text/javascript” src=”/node_modules/vue/dist/vue.js”></script>
<!– 第2步,生成一个全新的vue对象实例 –>
<script type=”text/javascript”>
var appVue = new Vue({
data: {},
methods: {}
}).$mount(‘#id_div_container’)
</script>
<!– 第3步, vue最终渲染结果的容器 –>
<div id=”id_div_container”></div>
到此todoMVC组件化开发的准备工作已经全部就绪了
现在, 我们右键, 审查元素, 先来将视图进行 分块(组件化)
如图所示:
.
如图所示:
组件分块 示意图:
最外层大的组件是: beyond-component, 它包含4个子组件
其中:
子组件beyond-header.js包括一个<h1>, 一个输入框
子组件beyond-main.js包括一个checkbox(选择全部/取消全部), 一个ul列表
子组件beyond-footer.js包括一个<span>(剩余数),一个ul(3种状态切换),一个button(消除已完成)
子组件beyond-copyright.js包括一个p标签
项目组织如下:
1. index.html中 引入 appVue.js
2. appVue.js 生成 Vue实例, 有自己的template选项,在components选项中,有一个名字叫beyondComponent的大局部组件
3. beyond-component.js 就是那个beyondComponent大局部组件, 它也有自己的template, 同时,在自己的components选项中,又定义了4个局部组件(就是上面4个,即beyondHeader,beyondMain,beyondFooter,beyondCopyright)
如图所示:
最终效果如下:
Vue 组件版 todoMVC
index.html代码如下:
Vue 组件版 todoMVC
程序入口appVue.js代码如下:
Vue 组件版 todoMVC
大组件(父组件)beyond-component.js代码如下:
Vue 组件版 todoMVC
四个子组件之一 beyond-header.js代码如下:
Vue 组件版 todoMVC
四个子组件之一 beyond-main.js代码如下:
Vue 组件版 todoMVC
四个子组件之一 beyond-footer.js代码如下:
Vue 组件版 todoMVC
四个子组件之一 beyond-copyright.js代码如下:
最终Vue 组件版 todoMVC 效果如下:
未完待续,下一章节,つづく