目录
- vue简介
- 第一个vue程序
- 组件基础及组件注册
- 插槽
- 单文件组件
- 双向绑定和单向数据流不冲突
- 理解虚拟DOM及key属性的作用
- 如何触发组件的更新
- 合理应用计算属性和侦听器
- 生命周期的应用场景和函数式组件
- 指令的本质
- 常用高级特性provide/inject
- 如何优雅地获取跨层级组件实例(拒绝递归)
- template和JSX的对比以及它们的性质
- 为什么需要VueX
vue简介
<mark>特点</mark>
- 更加轻量20kb min + gzip
- 渐进式框架
- 响应式的更新机制
- 学习成本低
react35kb,anglar60kb;不需要学习vue的所有知识就可以用于项目开发,可以一步步慢慢学;当数据改变,视图会自动刷新;模板语法是基于html的
第一个vue程序
- { {}}是mustache的模板语法,mustache是jquery时代的模板引擎
- { {}}中只能是表达式,不支持语句{ {var test = 2}} ×
- v-if和v-show的区别,v-if的判断条件不满足的dom不会渲染,v-show的判断条件不满足的dom会渲染(已经挂载到dom节点上了),但是不显示
- v-bind 简写 :
组件基础及组件注册
- 页面可以抽象为组件树
- 用Vue.component去注册组件的时候需要保证第一个参数name是全局唯一的
插槽
- 插槽的作用:分发内容(传递复杂内容的一种方式,因为没有办法通过简单地属性去传递复杂的内容,才设计了一个这样的API去传递复杂的内容)
- 默认插槽:
- 具名插槽:
- 作用域插槽:本质上传递的是一个返回组件的函数
单文件组件
<mark>使用Vue.component方式构建组建的缺点</mark>
- 全局定义 (Global definitions) 强制要求每个 component 中的命名不得重复
- 字符串模板 (String templates) 缺乏语法高亮,在 HTML 有多行的时候,需要用到丑陋的 \
- 不支持 CSS (No CSS support) 意味着当 HTML 和 JavaScript 组件化时,CSS 明显被遗漏
- 没有构建步骤 (No build step) 限制只能使用 HTML 和 ES5 JavaScript,而不能使用预处理器,如 Pug (formerly Jade) 和 Babel
双向绑定和单向数据流不冲突
- v-model只是语法糖,是简写的形式,本质上还是单向数据流,只是帮助我们简化了代码。对于Input标签,v-model是value属性和input事件的简写
<!--简写-->
<input v-model="message">{
{
message}}
<!--非简写-->
<input :value="message" @input="handleChange">{
{
message}}
handleChange(e) {
this.message = e.target.message
}
<mark>v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:</mark>
- text 和 textarea 元素使用 value property 和 input 事件;
- checkbox 和 radio 使用 checked property 和 change 事件;
- select 字段将 value 作为 prop 并将 change 作为事件。
<mark>自定义组件的v-model</mark>
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
- 自定义组件想要多个属性的双向绑定,使用.sync修饰符
<!--简写-->
<text-document v-bind:title.sync="doc.title"></text-document>
<!--非简写-->
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>
<!--在事件中-->
handleChange(e) {
this.$emit("update:title", ... )
this.message = e.target.message
}
- 总结:vue的双向绑定本质上还是单向数据流,v-model仅仅只是一种简写形式,它的目的只是让我们写更少的代码完成同样的功能。
理解虚拟DOM及key属性的作用
- jquery绑定事件,然后通过事件操作DOM
- 不同的事件操作不同的DOM或者相同的DOM,变得越来越乱,所以造就了vue和react的诞生
- vue和react都是通过引入数据中间层直接操作DOM,在VUE中不再关注DOM元素,仅仅关注数据state,所有的事件操作的对象都是数据state,然后通过VUE底层将数据映射到DOM。
- 数据的变化会导致DOM的更新,DOM的更新也是非常耗性能的影响用户体验的,当数据变化后,如何尽可能的减少DOM的更新?
- 于是出现了虚拟DOM
- DOM树:由数据state和模板template构成、监测对象保存的树形结构的信息。
- 两个DOM树的比对:正常比例算法O(n3),如果只对同层级的进行比较,就可以降低时间复杂度到O(n)
- DOM动作:移动、新建、删除
- 有Key就可以将一些删除新建的动作简化成移动
- 有key也会影响到插入场景,可以减少DOM操作
- 使用index作为key在list会动态新增删除的时候会出问题
- vue和react的比对不一样,一个是单边比对,一个是双边比对
如何触发组件的更新
<mark>没有特殊情况不要操作DOM</mark>
数据来源(单向的)
- 来自父元素的属性
- 来自组件自身的状态data
- 来自状态管理器,如vuex,Vue.observable
状态data vs 属性props
- 状态是组件自身的数据
- 属性是来自父组件的数据
- 状态的改变未必会触发更新
- 属性的改变未必会触发更新
响应式更新
- vue在实例化的时候会对data下的数据做一个getter、setter的转化,其实就是数据代理层
- 在页面渲染render的时候,如果data中的数据被使用,就会放到watcher中去,下次更新的时候setter就会通知watcher,才去更新,没有被使用就不会到watcher中去,也就不会触发更新,这就是data或者prop变化,而组件没有更新的原因
合理应用计算属性和侦听器
<mark>计算属性computed</mark>
- 减少模板中的计算逻辑
- 数据缓存
- 依赖固定的数据类型(响应式数据)
computed是当数据变化才会执行方法,methods是当模板刷新就会执行方法
<mark>侦听器watch</mark>
- 更加灵活、通用的api
- watch中可以执行任何逻辑,如函数节流,Ajax异步获取数据,甚至操作DOM
多层级的嵌套监听就会用"b.c"这种方式,就可以独立监听b里面c的变化
深度监听:就是会对e下面所有属性进行监听
e: {
handler: function(val, oldVal) {
...
},
deep: true
}
<mark>侦听器watch</mark>
- computed能做的,watch都能做,反之则不行
- 能用computed的尽量用computed
生命周期的应用场景和函数式组件
<mark>创建阶段</mark>
- 在beforeCreate之前会执行事件的初始化包括生命周期的初始化
- 在beforeCreate之后会对数据做响应式化处理,还有属性和侦听器的配置等
- 然后执行created这个生命周期
- 在created之后到了模板编译的阶段,再模板编译到render这个阶段(如果直接写的render函数,这个阶段会被跳过。一般我们写template,就是模板到编译到render,这些对于我们是透明的)
- 接着是beforeMount,然后是render,render会给我们生成虚拟DOM,然后会去挂载真实DOM,挂载完之后执行Mounted
- 在Mounted中会执行异步请求、操作DOM还有定时器等,但是在mounted之后vue不承诺我们子组件的DOM也会真正的挂载到我们的真实DOM上,这时我们需要this.$nextTick去把操作DOM的事情放在回调中
<mark>更新阶段</mark>
- 被多次执行,数据变化时或强制更新(this.forceUpdate)时,就会去执行beforeUpdate
- 在beforeUpdate中会去移除已经添加的事件***,一般开发中并不会用到事件***,因为vue本身提供的事件绑定监听机制已经足够我们使用了(但是如果是开发组件库,有可能会用到)
- 接着执行render,生成最新的虚拟DOM,再之后更新真实DOM,完成之后调用updated
- updated和mounted一样不承诺子组件的DOM更新完操作DOM的话,也需要放在this.$nextTick回调中,这时候需要添加一些事件***
- 在beforeUpdate和updated中万万不可更改依赖数据,如果更改了,就会导致死循环,一更改就会执行更新阶段…直到浏览器爆掉
<mark>销毁阶段</mark>
- beforeDestroy是销毁之前执行的,会去移除已经添加的事件***,还有计时器(不销毁可能会导致内存泄漏)等
- destroy很少用
函数式组件
- functional: true
- 无状态、无实例、没有this上下文、无生命周期
一般用于展示
<mark>用函数式组件在模板中做临时变量</mark>(vue没有提供临时变量的功能,但是临时变量是很有必要的,不然在模板中会出现多次重复的逻辑计算,当然计算属性在很大程度上帮我们避免了这件事,但是计算属性依赖的数据必须是响应式数据)
<!--TempVar.js-->
export default {
functional: true,
render: (h, ctx) => {
return ctx.scopedSlots.default && ctx.scopedSlots.default(ctx.props || {
});
}
}
<!--index.vue-->
<TempVar
:var1="`hello ${name}`"
:var2="destroyClock ? 'hello vue' : 'hello world'"
>
<template v-slot="{var1, var2}">
{
{
var1}}
{
{
var2}}
</template>
</TempVar>
指令的本质
<mark>vue内置的指令(语法糖、标志位)</mark>
v-text
- v-text会把子元素下的所有内容替换掉,插入字符串
<div v-text="'hello vue'">hello world</div>
<!--hello vue-->
v-html
- v-html会把子元素下的所有内容替换掉,插入标签,不建议使用,xss潜在风险存在
<div v-html="'<span style="color: red">hello vue</span>'">hello world</div>
<!--hello vue-->
v-show
- v-show是否显示(隐藏)
<div v-show="'hello vue'">hello vue</div>
<button @click="show != show">change show</button>
v-if
- v-if是否显示(直接删掉DOM)
v-else
- 不能单独使用,必须和v-if配合使用
v-else-if
- 不能单独使用,必须和v-if配合使用
<div v-if="number === 1">hello vue {
{
number}}</div>
<div v-else-if="number === 2">hello world {
{
number}}</div>
<div v-else="number === 1">hello geektime {
{
number}}</div>
v-for
- for循环数组
v-bind
- 简写 :key
<div v-for="num in [1,2,3]" v-bind:key="num">hello vue {
{
num}}</div>
v-on
- 事件,简写 @click
<div v-on:click="number=number+1">number++</div>
v-model
- 双向绑定语法糖指令
v-slot
v-pre
- 绕过{ {}}编译过程直接输出字符串,不常用
<div v-pre>{
{
this will not be compiled}}</div>
v-cloak
- 使用频率最低,在单文件模式中没有任何作用,在html中直接写木板的时候才会用到
v-once
- 这里绑定的变量number只会计算一次,后面不管如何变化都不会重新去渲染,对于性能优化很有帮助,但是很少用
<div v-once>{
{
number}}</div>
- 指令其实就是语法糖,或者说标志位,模板编译到render函数的时候,会把指令编译成js代码
- 这也是为什么Jsx和render函数并不支持内置指令的原因,因为指令确实帮我们简化了很多的代码量,jsx也在慢慢的支持内置指令,jsx也是语法糖,最终也会通过编译输出真正的render函数
自定义指令
- 不是刚需,也可以通过其他方式实现
<mark>生命周期钩子(一个指令定义对象可以提供如下几个钩子函数)</mark>
- bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
- inserted: 被绑定元素插入父节点时调用
- update: 所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。
- componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
- unbind:只调用一次,指令与元素解绑时调用。
- 进去的时候执行bind->inserted
- 组件的VNode更新,update->componentUpdated
- 组件销毁unbind
- 组件重新绑定bind->inserted
常用高级特性provide/inject
- 平时开发不常用
- 但在开发底层通用组件的时候,使用频率会相当高
<mark>组件通信</mark>
- I节点和E节点如果要通信,属性prop和事件emit都需要层层传递冒泡,这样成本很高,也没有健壮性,provide和inject就是为了解决这个问题
- A节点通过provide提供数据
- E节点通过inject注入数据,通过层层冒泡的方式去A节点取数据
- 实现A和E之间的通信
<!--A组件-->
<!--provide() {-->
<!-- return {-->
<!-- theme: {-->
<!-- color: this.color这里的color是字符串,不是响应式数据,它改变不会同步到子组件变化-->
<!-- }-->
<!-- }-->
<!--}-->
provide() {
return {
theme: this
}
}
<!--this下面会挂载到data props method 等属性都可以取得到,因为data props是响应式的-->
<!--E组件-->
<h3 :style="{
color: theme.color}">E</h3>
inject: {
theme: {
default: () => ({})
}
}
<!--F组件-->
<h3 :style="{
color: theme.color}">F</h3>
inject: {
theme1: {
from: "theme"
default: () => ({})
}
}
<!--vue很多东西都挂载到this上,可能注入的theme和本身的theme有冲突,可以通过from取别名-->
<!--I组件(函数式组件)-->
<template functional>
<h3 :style="{
color: injections.theme.color}">I</h3>
</template>
inject: {
theme: {
default: () => ({})
}
}
<!--C组件-->
provide() {
return {
theme: {
color: "green"
}
}
}
<!--如果C组件也提供了一个theme,EF节点向上找颜色的时候,发现了C节点的数据,就不会再向上去找了,类似冒泡机制-->
如何优雅地获取跨层级组件实例(拒绝递归)
<mark>ref引用信息</mark>
<p ref="p">hello</p>
<!--vm.$ref.p拿到这个DOM-->
<child-component ref="child"></child-component>
<!--vm.$ref.child拿到这个子组件实例-->
- 如果要跨层级去拿组件实例,才用递归查找的话:代码繁琐、性能低效
- react的ref是一个callback回调的形式,针对这个设计了:
- 主动通知(setXxxRef)
- 主动获取(getXxxRef)
<!--A节点-->
provide() {
return {
setChildrenRef: (name,ref) => {
this[name] = ref;
},
getChildrenRef: name => {
return this[name];
},
getRef: () => {
return this;
}
}
}
<!--D节点-->
<ChildrenH v-ant-ref="c => setChildrenRef('childrenH', c)" />
// v-ant-ref是自己开发的,是回调的形式,每次节点更新的时候都会告诉上层节点已更新
// vue 1.0有v-ref的指令,且作用和v-ant-vue完全不同,所以暂且不使用v-ref,防止冲突
inject: {
// 注入
setChildrenRef: {
default: () => {
}
}
}
template和JSX的对比以及它们的性质
- JSX和react有很大的联系,JSX是伴随着react产生的,但是又是独立的个体
- 通过插件的形式,在vue中也可以使用JSX
template
- 模板语法(HTML的扩展)
- 数据绑定使用Mustache语法(双大括号)
<span>Message:{
{
msg}}</span>
JSX
- javascript的语法扩展(JSX不是模板语法)
- 数据绑定使用单引号
<span>Message:{
this.msg}</span>
template VS JSX
- 在JSX中可以写JS的各种逻辑,所以相对于template更加灵活
- template是vue的特点和优点
- template的优点:
- 学习成本低
- 大量内置指令简化开发
- 组件作用域CSS
- template的缺点:
- 灵活性低
- JSX的优点:
- 灵活
- 组件分为两类:偏视图表现和偏逻辑的,表现类的组件远多于逻辑类组件
- 推荐前者使用模板,后者使用JSX或渲染函数
- template和JSX都是语法糖
<!--JSX-->
export default {
props: {
level: {
type: Number,
default: 1
}
},
<!--JSX-->
render: function(h) {
const Tag = `h${
this.level}`;
return <Tag>{
this.$slots.default}</Tag>
},
<!--JS-->
render: function(createElement) {
return createElement(
"h" + this.level, // 标签名称
this.$slots.default // 子元素数组
)
<!--复杂的时候,像这样层层嵌套去写,复杂度就比较高了-->
}
}
生态篇
为什么需要VueX
<mark>Vuex是一种状态管理模式</mark>