Hello Vue .
Vue 预习 🎬
logo.png🧩Hello Vue
-
宿主文件
-
引入
vue.js
-
创建vue实例
...
<!-- 宿主文件 -->
<div id='app'>
{{title}}
</div>
...
<script src='./vue.js'></script>
<script>
const app = new Vue({
el: '#app', // 选择器
data() {
return {
title: 'Hello, vue!'
}
},
})
</script>
- 理解
vue
的核心理念(设计思想)
- 数据驱动应用(不需要直接操作
DOM
直接数据绑定) -
MVVM
模式的践行者 (Model、View、ViewModel)
-
MVVM
框架的三个要素:响应式、模板引擎及其渲染
- 响应式:数据响应式,改数据界面就会跟随变化
- 模板引擎:Vue解析视图里的表达式
- 渲染:Vue的渲染函数得到虚拟
DOM
,虚拟DOM
再转化为真正DOM
🧩Vue 核心知识 模板语法
- 插值文本
-
v-once
指令 (当数据改变时,插值处的内容不会更新) -
v-html
输出真正的 HTML -
v-bind
绑定在 HTML attribute -
JavaScript
表达式: {{ ok ? 'YES' : 'NO' }}
- 列表渲染
<div v-for="c in courses" :key="c.id">
{{ c }}
</div>
- 用户输入(双向绑定)
<p>
<input type="text" v-model='course' v-on:keydown.enter='addCourse'>
<button @click='addCourse'>新增</button>
</p>
...
...
methods: {
addCourse() {
// 1. 新增元素
this.courses.push(this.course)
// 2. 清空输入框
this.course = ''
}
},
-
class
和style
绑定
:class="{active: selectCourse === c}
- 列表渲染
<div class="course-list" v-else>
<div v-for="c in courses" :key="c.id" :class="{active: selectCourse === c}"
@click='selectCourse = c'>
{{ c }}
</div>
</div>
- 条件渲染
<!-- 条件渲染 -->
<div v-if='courses.length == 0'>没有课程</div>
注意:
v-for
和v-if
会有优先级问题,不要在同一标签中同时使用
- 模板语法底层是怎样实现的
console.log(app.$option.render)
🧩计算属性 & 监听器
原计算方式Demo:
<p>
<!-- 绑定表达式 -->
<!-- 课程总数:{{courses.length + '门'}} -->
</p>
- 计算属性
computed
<!-- 计算属性 -->
<!-- 课程总数:{{total}} -->
...
const app = new Vue({
computed: {
total() {
return this.courses.length + '门'
}
},
})
- 监听器
watch
<!-- 监听器 -->
课程总数:{{totalCount}}
...
data() {
return {
...
totalCount: 0, // 先定义值
}
},
...
// 下面这种不能生效,因为初始化时不会触发
// watch: {
// courses(newValue, oldValue) {
// this.totalCount = newValue.length + '门'
// }
// },
watch: {
courses: {
immediate: true, // 即时触发
// deep: true, // 深数据使用
handler(newValue, oldValue) {
this.totalCount = newValue.length + '门'
}
}
},
🧩核心知识 - 生命周期
Vue实例的生命周期过程中会运行一些叫做生命周期钩子的函数,这给用户在不同阶段添加自己代码 的机会
- 最常用的
created
和mounted
-
created
未加载完DOM
-
mounted
已加载完,可以直接做操作DOM
的操作
...
async created() {
const courses = await getCourses()
this.courses = courses
}
- 使用场景分析
{
beforeCreate(){} // 执行时组件实例还未创建,通常用于插件开发中执行一些初始化任务
created(){} // 组件初始化完毕,各种数据可以使用,常用于异步数据获取
beforeMount(){} // 未执行渲染、更新,dom未创建
mounted(){} // 初始化结束,dom已创建,可用于获取访问数据和dom元素
beforeUpdate(){} // 更新前,可用于获取更新前各种状态
updated(){} // 更新后,所有状态已是最新
beforeDestroy(){} // 销毁前,可用于一些定时器或订阅的取消
destroyed(){} // 组件已销毁,作用同上
}
🧩核心知识 - 组件化
组件封装注意:
- 单项数据流(一个变量由外再传出,中间不做变量再定义)
- 无状态组件封装
- 组件注册
<!-- 新增组件 -->
<component-add @add='parentAdd'></component-add>
注意:直接在
DOM
中使用注册组件时,DOM
不区分大小写(例如:<h1>/<H1>
),所以建议是中划线起名
- Prop & 组件自定义事件
Vue.component('component-add', {
data() {
return {
course: ''
}
},
// 参数父传子
props: {
courses: {
type: Array, // Prop 限制传输类型
default: []
}
},
template: `
<div>
<!-- 用户输入 -->
<p>
<input type="text" v-model='course' v-on:keydown.enter='addCourse'>
<button @click='addCourse'>新增</button>
</p>
</div>
`,
methods: {
addCourse() {
// $emit 参数子传父
this.$emit('add', this.course) // add 是注册组件绑定的方法名
this.course = '' // 清空输入值
}
},
})
···
···
// 创建vue实例
const app = new Vue({
el: '#app', // 选择器
data() {
return {
title: 'Hello, vue!',
ok: true,
course: '',
courses: [],
totalCount: 0,
}
},
methods: {
addCourse() {
// 1. 新增元素
this.courses.push(this.course)
// 2. 清空输入框
this.course = ''
},
parentAdd(value) {
this.courses.push(value)
}
},
})
- 在组件上使用
v-model
(变形上面封装的组件例子🌰)
注意:了解
v-model
的语法糖,实现无状态组件封装
// v-model
<component-add v-model='course' @add='parentAdd'></component-add>
// 语法糖
<component-add :value='course' @input='course=$event' @add='parentAdd'></component-add>
组边变动的Demo:
<!-- 新增组件 -->
<component-add v-model='course' @add='parentAdd'></component-add>
···
···
Vue.component('component-add', {
props: ['value'],
template: `
<div>
<!-- 用户输入 -->
<p>
<input type="text" v-on:keydown.enter='addCourse'
:value='value' @input='onInput'>
<button @click='addCourse'>新增</button>
</p>
</div>
`,
methods: {
addCourse() {
this.$emit('add') // 这里调用的还是外部事件 @add='parentAdd'
},
onInput(e) {
// 外部的 input,也就是 v-model 语法糖解析后的 input 事件!
this.$emit('input', e.target.value)
},
},
})
- 插槽/具名插槽/
sync
修饰
<!-- 弹窗组件 -->
<message :show.sync='isShow'>
<!-- <message @update:show='$event'> -->
<!-- 具名插槽 -->
<template v-slot:title>
<h2>恭喜</h2>
</template>
<template>
<!-- 不起名字的默认语法糖 -->
<!-- <template v-slot:default> -->
{{addValueName}}
</template>
</message>
作用域插槽:
<template v-slot:title='slotProps'>
<!-- <h2>恭喜</h2> -->
<!-- 作用域插槽 -->
{{slotProps.scopeSlot}}
</template>
···
···
<!-- 通过 slot 插槽获取传入的内容-->
<slot name='title' scopeSlot='作用域插槽'></slot>
- 组件化的理解
组件化是Vue的精髓,Vue应用就是由一个个组件构成的。Vue的组件化涉及到的内容非常多,当面试时
被问到:谈一下你对Vue组件化的理解。这时候有可能无从下手,可以从以下几点进行阐述:
-
定义:组件是可复用的 Vue 实例,准确讲它们是
VueComponent
的实例,继承自Vue -
优点:从上面案例可以看出组件化可以增加代码的复用性、可维护性和可测试性
-
使用场景:什么时候使用组件?以下分类可作为参考:
- 通用组件:实现最基本的功能,具有通用性、复用性,例如按钮组件、输入框组件、布局组件等
- 业务组件:它们完成具体业务,具有一定的复用性,例如登录组件、轮播图组件。
- 页面组件:组织应用各部分独立内容,需要时在不同页面组件间切换,例如列表页、详情页组件
- 如何使用组件
- 定义:
Vue.component()
,components
选项,sfc
- 分类:有状态组件,
functional
,abstract
- 通信:
props
,$emit()/$on()
,provide/inject
,$children/$parent/$root/$attrs/$listeners
- 内容分发:
<slot>,<template>,v-slot
- 使用及优化:
is
,keep-alive
,异步组件
- 组件的本质
vue中的组件经历如下过程
组件配置 => VueComponent实例 => render() => Virtual DOM=> DOM
所以组件的本质是产生虚拟DOM
🧩核心知识 - Vue 必回API
- 数据相关
API
-
Vue.set()
asthis.$set
-
Vue.delete()
asthis.$delete
向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新
- 事件相关
API
-
vm.$on
监听当前实例上的自定义事件。事件由vm.$emit
触发,回调函数会接收所有传入事件触发函数的额外参数 -
vm.$emit
触发当前实例上的事件。附加参数都会传给监听器回调 -
vm.$once
监听一个自定义事件,但是只触发一次,一旦触发后,监听器就会被移除 -
vm.$off
移除自定义事件监听器
vm.$off() 如果没有提供参数,则移出所有的事件监听器
vm.$off('test') 如果只提供了事件,则只移出该时间的所有监听器
vm.$off('test', callback) 如果同时提供了时间和回调,则只移出这个回调的监听器
- 事件总线 (总线设计模式)
通过在Vue原型上添加一个Vue实例作为事件总线,实现组件间相互通信,而且不受组件间关系的影响
Vue.prototype.$bus = new Vue()
这样做可以在任意组件中使用
this.$bus
访问到该Vue实例
Demo:
<!-- 派发事件:toolbar -->
<div class="toolbar">
<button @click='$bus.$emit("message-close")'>清除弹框</button>
</div>
···
···
<script src='./vue.js'></script>
<script>
// 注册事件总线
Vue.prototype.$bus = new Vue()
···
</script>
···
···
// 监听事件 $on 上面有解释
mounted() {
this.$bus.$on('message-close', () => {
// Do something...
this.$emit('update:show', false)
})
}
- 组件或元素引用 (ref 如哎服润丝)
ref
被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的$refs
对象上。如果在普通的DOM
元素上使用,引用指向的就是DOM
元素;如果用在子组件上,引用就指向组件示实例
获取焦点 Demo:
···
<input type="text" ... ref="inp">
···
···
mounted() {
// mounted之后才能访问到inp
this.$refs.inp.focus()
}
🧩扩展知识 - 过度&动画
-
CSS
过度动画 (transition
组件)
<style>
/**
* 动画相关样式
* enter 入场动画 -> 准备离场
* enter-active 入场后过度名
* leave 离场动画
* leave-to 离场前
* leave-active 离场后
**/
.fade-enter, .fade-leave-to {
opacity: 0
}
.fade-enter-active, .fade-leave-active {
transition: opacity 1.5s
}
</style>
···
···
<script>
Vue.component('message', {
// 使用 transition 组件应用过度动画
template: `
<transition name="fade">
...
</transition>
`,
})
</script>
- 使用
CSS
动画库
引入animate.css
<link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
transition
设置
<transition enter-active-class="animated bounceIn"
leave-active-class="animated bounceOut">
-
JS
动画 (JavaScript
钩子)
可以在<transition>
属性中声明JavaScript
钩子,使用JS
实现动画
<transition
v-on:before-enter="beforeEnter" // 动画开始前,设置初始状态
v-on:enter="enter" // 执行动画
v-on:after-enter="afterEnter" // 动画结束,清理工作
v-on:enter-cancelled="enterCancelled" // 取消动画
v-on:before-leave="beforeLeave"
v-on:leave="leave"
v-on:after-leave="afterLeave"
v-on:leave-cancelled="leaveCancelled"
></transition>
- 结合
transition
一起使用 Demo:
···
// 动画钩子
.fade-enter-active, .fade-leave-active {
transition: opacity 1.5s
}
···
···
<transition name='fade'
@before-enter='beforeEnter'
@enter='enter'
@before-leave='beforeLeave'
@leave='leave'
>
<div class='message-box' v-if='show'>
<!-- 通过 slot 插槽获取传入的内容-->
<slot name='title' scopeSlot='作用域插槽'></slot>
<slot></slot>
<span class='message-box-close' @click='toggle'>X</span>
</div>
</transition>
···
···
methods: {
beforeEnter(el) {
// el 指的就是 transition 子元素本身
// 动画的初始状态
el.style.opacity = 0;
},
enter(el, done) {
// 这里会有 浏览器重排/回流
// 触发回流才能激活动画
document.body.offsetHeight
// 动画结束状态
el.style.opacity = 1;
// done 函数,动画结束后的动作
// 监听动画结束事件,并执行 done 函数
el.addEventListener('transitionend', done)
},
beforeLeave(el) {
// 离开之前
el.style.opacity = 1;
},
leave(el, done) {
// 离开之后
el.style.opacity = 0;
// 监听动画结束事件,并执行 done 函数
el.addEventListener('transitionend', done)
},
},
- 纯
JS
动画,引入第三方动画Demo:
// 引入JS
<script src='https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js'></script>
···
methods: {
beforeEnter(el) {
// el 指的就是 transition 子元素本身
// 动画的初始状态
el.style.opacity = 0;
},
enter(el, done) {
// 内部帮忙做了回流,这里就不需要了
Velocity(el, { opacity: 1 }, { duration: 1500, complete: done })
},
beforeLeave(el) {
// 离开之前
el.style.opacity = 1;
},
leave(el, done) {
Velocity(el, { opacity: 0 }, { duration: 1500, complete: done })
},
},
知识点:涉及浏览器回流原理
- 列表过度
利用transition-group
可以对v-for
渲染的每个元素应用过度
transition-group 使用
transition-group
用于列表的过渡效果:
- 默认为span, 可以使用tag 转换为其他元素。
- 子元素通常使用v-for进行循环。
- 子元素必须要有唯一的key属性,且key不能为索引值。
- css过渡是应用在子元素中,而不是这个容器本身。
- 对应的js事件钩子:
before-enter、enter、after-enter、enter-cancelled、before-leave、leave、after-leave、leave-cancelled
···
.fade-enter, .fade-leave-to {
opacity: 0
}
.fade-enter-active, .fade-leave-active {
transition: opacity 1.5s
}
···
···
<!--
如果需要过滤的元素是通过v-for循环渲染出来,不能使用transition包裹,需要使用transition-group
如果要为v-for循环创建元素设置动画,必须为每一个元素设置:key属性
-->
<transition-group name="fade" class="content" tag="ul">
<div v-for="c in courses" :key="c" :class="{active: selectCourse === c}"
@click='selectCourse = c'>
{{ c }}
</div>
</transition-group>
🧩扩展知识 - 过滤器
过滤器分为:全局过滤器、局部过滤器
- 全局过滤器
···
{{ c | currency('$') }}
···
Vue.filter('currency', (value, symbol = '¥') => {
return symbol + value
})
- 局部过滤器
···
{{ c | currency('$') }}
···
···
methods: {
···
},
// 局部过滤器
filters: {
currency(value, symbol = '¥') {
return symbol + value
}
}
- 自定义指令
除了核心功能默认内置的指令 (
v-model
和v-show
),Vue 也允许注册自定义指令。注意,在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操 作,这时候就会用到自定义指令
···
<!-- toolbar -->
<div class="toolbar" v-permisstion="getPermisstion()">
<button @click='$bus.$emit("message-close")'>清除弹框</button>
</div>
···
// 获取权限的方法
getPermisstion() {
return 'admin';
}
···
···
let role = 'admin2';
// 自定义事件
Vue.directive('permisstion', {
inserted(el, binding) {
if (role != binding.value) {
el.parentElement.removeChild(el)
}
}
})
知识点:
directive
下的钩子函数