一、Vue2
1.1 模板语法
1.1.1 模板的理解
html 中包含了一些 JS 语法代码,语法分为两种,分别为:
- 插值语法(双大括号表达式)
- 指令(以 v-开头)
1.1.2. 插值语法
- 功能: 用于解析标签体内容
- 语法: {{xxx}} ,xxxx 会作为 js 表达式解析
1.1.3. 指令语法
- 功能: 解析标签属性、解析标签体内容、绑定事件
- 举例:v-bind:href = 'xxxx' ,xxxx 会作为 js 表达式被解析
- 说明:Vue 中有有很多的指令,此处只是用 v-bind 举个例子
1.1.4 单向数据绑定
- 语法:v-bind:href ="xxx" 或简写为 :href
- 特点:数据只能从 data 流向页面
1.1.5. 双向数据绑定
- 语法:v-mode:value="xxx" 或简写为 v-model="xxx"
- 特点:数据不仅能从 data 流向页面,还能从页面流向 data
- v-mode.number 指定类型为数字类型
- v-mode.lazy 失去焦点之后在收集改变数据
- v-mode.trim 清除前后空格
1.1.6 MVVM 模型
- M:模型(Model) :对应 data 中的数据
- V:视图(View) :模板
- VM:视图模型(ViewModel) : Vue 实例对象
1.1.7 Object.defineProperty (vue中data属性原理【数据代理】)
const person = {
name: 'zangh',
sex: '男'
}
Object.defineProperty(person, 'age', { // 对象增加一个属性 (对追加的属性设置限制)
value: 18,
enumerable: true, // 控制属性是否可以枚举,默认值是false
writable: true, // 控制属性是否可以被修改,默认值是false
configurable: true, // 控制属性是否可以被删除,默认值是false
})
// 需求:对象中的添加一个属性age = number,当number改变时,person中的age也会改变
let number = 34
Object.defineProperty(person, 'age',
// 当读取person的age属性时候,get(getter)函数会被调用,且返回值就是age的值
get () {
return number
},
// 当修改person的age属性时, set函数(setter)就会被调用,且会收到修改的具体值
set (value) {
console.log('age属性值被修改了,值为' + value)
number = value // 修改 person 中的age同时修改 number 参数值
}
})
1.2 事件处理
1.2.1 绑定监听
- v-on:xxx="fun"
- @xxx="fun"
- @xxx="fun(参数)"
- 默认事件形参: event 5. 隐含属性对象: $event
1.2.2 事件修饰符
示例:@click.prevent="testClick"
- .prevent : 阻止事件的默认行为 event.preventDefault()
- .stop : 停止事件冒泡 event.stopPropagation()
- .once: 事件只触发一次
- .capture:使用时间的捕获模式
- .self:只有event.target是当前操作的元素时才触发
- .passive:事件的默认行为立即执行,无需等待事件回调执行完毕 (如weel鼠标滚轮事件,打开之后不等待函数执行完成直接产生默认行为【页面滚动】)
- 键盘事件(@keyup.enter=“test”):
- enter 回车
- delete 删除
- esc 退出
- space 空格
- tab
- up
- down
- left
- Right
1.2.3 按键修饰符
- keycode : 操作的是某个 keycode 值的键
- .keyName : 操作的某个按键名的键(少部分)
1.3 计算属性与监视
1.3.1 计算属性-computed
- 要显示的数据不存在,要通过计算得来。
- 在 computed 对象中定义计算属性。
- 在页面中使用{{方法名}}来显示计算的结果。
1.3.2. 监视属性-watch
- 通过通过 vm 对象的$watch()或 watch 配置来监视指定的属性
- 当属性变化时, 回调函数自动调用, 在函数内部进行计算
1.3.3 watch 深度监视
numbers: {
a: 1,
b: 1
}
watch: {
// 监视多集结构中某个属性的变化
'numbers.a': {
handler () {
console.log('a被改变了')
}
},
// 监视多集结构中所有属性的变化
numbers: {
deep: true, // 默认是false
handler () {
console.log('numbers被改变了')
}
}
}
1.4 class 与 style 绑定
1.4.1 class 绑定
- :class='xxx'
// 适用于: 样式的类名不确定,需要动态绑定
<div class="basic" :class="mood"></div>
data () { // 当前div的class为 ‘basic normal’
mood: 'normal'
}
表达式是字符串: 'classA'
表达式是对象: {classA:isA, classB: isB}
// 适用于:要绑定的样式个数确定、名字也确定,但要动态绑定决定用不用
<div class="basic" :class="mood"></div>
data () { // 当前div的class为 ‘basic normal1’
mood: {
normal: true,
normal2: false
}
}
- 表达式是数组: ['classA', 'classB']
// 适用于:要绑定的样式个数不确定、名字也不确定
<div class="basic" :class="mood"></div>
data () { // 当前div的class为 ‘basic normal normal2 normal3’
mood: ['normal', 'normal2', 'normal3']
}
1.4.2. style 绑定
- :style="{ color: activeColor, fontSize: fontSize + 'px' }"
// 适用于:要绑定的样式个数不确定、名字也不确定
<div :style="styleObj"></div>
data () { // 当前div的class为 ‘basic normal normal2 normal3’
styleObj: {
fontSize: '100px',
color: "#999"
}
}
1.5 条件渲染
1.5.1. 条件渲染指令
- v-if 与 v-else
- v-show
1.5.2. 比较 v-if 与 v-show
- 如果需要频繁切换 v-show 较好
- 当条件不成立时, v-if 的所有子节点不会解析(项目中使用)
1.6 列表渲染
1.6.1 列表显示指令
遍历数组: v-for / index
遍历对象: v-for / key
1.7 过滤器
1.7.1. 理解过滤器
- 功能: 对要显示的数据进行特定格式化后再显示
- 注意: 并没有改变原本的数据, 是产生新的对应的数据
1.7.2 过滤器使用
<div>{{ time | timeFormater }}</div>
// 全局过滤器
Vue.filter('myscene', function (value) {
return value.slice(1,4)
})
new Vue({
el: '#root',
data: {
time: 12312313
},
methods: {},
// 局部过滤器
filters: {
timeFormater (value) {
return new Date(value)
}
}
})
1.8 内置指令与自定义指令
1.8.1. 常用内置指令
- v-text : 更新元素的 textContent
- v-html : 更新元素的 innerHTML
- v-if : 如果为 true, 当前标签才会输出到页面
- v-else: 如果为 false, 当前标签才会输出到页面
- v-show : 通过控制 display 样式来控制显示/隐藏
- v-for : 遍历数组/对象
- v-on : 绑定事件监听, 一般简写为@
- v-bind : 绑定解析表达式, 可以省略 v-bind
- v-model : 双向数据绑定
- v-cloak : 防止闪现, 与 css 配合: [v-cloak] { display: none }
- v-once: 所在节点在初次动态渲染后,就视为静态内容了,以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
- v-pre: 跳过所在节点的编译过程,可以利用他跳过:没有指令语法,没有插值语法的节点,会加快编译。
1.8.2 自定义指令
1. 注册全局指令
Vue.directive('my-directive', function(el, binding){
el.innerHTML = binding.value.toupperCase()
})
2. 注册局部指令
directives : {
// 简写
'my-directive':function (el, binding) {
el.innertext = binding.value * 10
},
// 对象写法
'my-directive': {
// 指令与元素成功绑定时 (这时结构还没渲染到页面上)
bind (el, binding) {
el.innerHTML = binding.value.toupperCase()
},
// 指令所在元素被插入页面时
inserted () {
console.log('inserted')
},
// 指令所在的模板被重新解析时
update () {
console.log('update')
}
}
}
使用指令: v-my-directive='xxx'
1.9. Vue 实例生命周期
1.9.1 vue 生命周期分析
-
初始化显示
* beforeCreate() * created() * beforeMount() // vue 完成模板的解析并且把真实的DOM元素放入页面后(挂在完毕)调用mounted * mounted()
- 更新状态: this.xxx = value
* beforeUpdate()
* updated()
- 销毁 vue 实例: vm.$destory()
* beforeDestory()
* destoryed()
1.9.2 常用的生命周期方法
- mounted(): 发送 ajax 请求, 启动定时器等异步任务
- beforeDestory(): 做收尾工作, 如: 清除定时器
1.10 使用 Vue 脚手架
1.10.1 ref 与 props
ref
- 作用: 用于给节点打标识
- 读取方式: this.$refs.xxxxxx
props
作用:用于父组件给子组件传递数据
-
读取方式一: 只指定名称
props: ['name', 'age', 'setName']
-
读取方式二: 指定名称和类型
props: { name: String, age: Number, setNmae: Function }
-
读取方式三: 指定名称/类型/必要性/默认值
props: { name: { type: String, required: true, default:xxx } }
1.10.2 mixin 混入
可以吧多个组件公用的配置提取成一个混入对象
// mixin.js
export const hunhe = {
methods: {
showName () {
alert(this.name)
}
},
mounted () {
console.log('hello')
}
}
export const hunhe2 = {
methods: {
test () {
alert(this.test)
}
}
}
// 局部引入 template.vue
<script>
import { hunhe, hunhe2 } from '../mixin'
export default {
data() {
return {
name: 'name',
test: 'test'
}
},
mixins: [hunhe, hunhe2]
}
</script>
// 全局引入 main.js
Vue.mixin(hunhe)
Vue.mixin(hunhe2)
1.10.3 插件 Vue.use()
// pluhins.js
export default {
install (Vue) {
// 全局过滤器
Vue.filter('mySlice', function (value) {
return value + 'test'
})
// 混入
Vue.mixin({
data () {
test: 'test'
}
})
// 给vue原型增加方法
Vue.prototype.hello = () => { alert('hello') }
}
}
// main.js
import pluhins from 'pluhins.js'
Vue.use(pluhins)
1.11 Vue 中的自定义事件
绑定事件监听
<Header @addTodo="addTodo"/>
或者
<Header ref="header"/>
触发事件
this.$emit('addTodo', todo)
或者
this.$refs.header.$on('addTodo', this.addTodo)
解绑事件
this.$off('addTodo')
this.$off(['addTodo', 'demo']) // 解绑多个
this.$off() // 所有的自定义事件都解绑
- 子组件传至给父组件:通过父组件给子组件传值为一个函数、通过自定义事件、通过ref去监听子组件的事件
注:当在自定义组件上定义原生事件时候事件名后加native关键字,如:<Student @click.native="show"></Student>
1.12 全局事件总线
1.12.1 理解
- Vue 原型对象上包含事件处理的方法
- $on(eventName, listener): 绑定自定义事件监听
- $emit(eventName, data): 分发自定义事件
- $off(eventName): 解绑自定义事件监听
- $once(eventName, listener): 绑定事件监听, 但只能处理一次
- 所有组件实例对象的原型对象的原型对象就是 Vue 的原型对象
- 所有组件对象都能看到 Vue 原型对象上的属性和方法
- Vue.prototype.bus 这个属性 对象
- 全局事件总线
- 包含事件处理相关方法的对象(只有一个)
- 所有的组件都可以得到
1.12.2 指定事件总线对象
new Vue({
beforeCreate () { // 尽量早的执行挂载全局事件总线对象的操作
Vue.prototype.$globalEventBus = this
},
}).$mount('#root')
1.12.3 绑定事件
this.$globalEventBus.$on('deleteTodo', this.deleteTodo)
1.12.4 分发事件
this.$globalEventBus.$emit('deleteTodo', this.index)
1.12.5 解绑事件
this.$globalEventBus.$off('deleteTodo')
1.13 消息订阅与发布
1.13. 1 理解
这种方式的思想与全局事件总线很相似
-
它包含以下操作:
- 订阅消息--对应绑定事件监听
- 发布消息--分发事件
- 取消消息订阅--解绑事件监听
需要引入一个消息订阅与发布的第三方实现库: PubSubJS
1.13.2 使用 PubSubJS
- import PubSub from 'pubsub-js' // 引入
- PubSub.subscribe(‘msgName’,functon(msgName,data){})
- PubSub.publish(‘msgName’,data):发布消息,触发订阅的回调函数调用
- PubSub.unsubscribe(token):取消消息的订阅
1.14 $nextTick
在下一次DOM更新结束后执行指定的回调
this.$nextTick(function () {
this.$refs.inputTitle.focus() // 让 ref=inputTitle的输入框获取焦点
})
1.15 过度与动画
1.15.1 vue 动画的理解
- 操作 css 的 trasition 或 animation
- vue 会给目标元素添加/移除特定的 class
- 过渡的相关类名:
- xxx-enter-active: 指定显示的 transition
- xxx-leave-active: 指定隐藏的 transition
- xxx-enter/xxx-leave-to: 指定显示隐藏时的样式
1.15.2 基本过渡动画的编码
- 在目标元素外包裹<transition name="xxx"></transition>
- 定义 class 样式
- 指定过渡样式: transition
- 指定隐藏时的样式: opacity/其它
当多个元素需要动画效果时候使用<transition-group name="xxx"></transition-group>
第三方动画库:animate.css
1.16 slot 插槽
父组件向子组件传递带数据的标签,当一个组件有不确定的结构时, 就需要使用 slot 技术,注意:插槽内容是在父组件中编译后, 再传递给子组件的。
分类:
- 默认插槽
- 命名插槽
- 作用域插槽 (将子组件中的数据传给组件的使用者)
// 组件
<tempalte>
<div>
<h3>分类</h3>
<slot :games="games">我是某人的内容</slot>
</div>
</tempalte>
<script>
export default {
name: 'Category'
data () {
return {
games: ['123123', '213123213', '123123213']
}
}
}
</script>
// 使用组件
<Category>
<template scope="obj">
<div v-for="(item, index) in obj.games" :key="index">{{ item }}</div>
</template>
</Category>
1.17 vuex
vuex 是什么
- 概念:专门在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 vue 应 用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方 式,且适用于任意组件间通信
什么时候使用 Vuex
- 多个组件依赖于同一状态
- 来自不同组件的行为需要变更同一状态
1.18 vuex 核心概念和 API
1.18.1 state
- vuex 管理的状态对象
- 它应该是唯一的
const state = {
xxx: initValue
}
1.18.2 actions
值为一个对象,包含多个响应用户动作的回调函数
通过 commit( )来触发 mutation 中函数的调用, 间接更新 state
如何触发 actions 中的回调?
在组件中使用: $store.dispatch('对应的action回调名')触发
- 可以包含异步代码(定时器, ajax 等等)
const actions = {
zzz ({ commit, state }, data1) {
commit('yyy', { data1 })
}
}
1.18.3 mutations
值是一个对象,包含多个直接更新 state 的方法
-
谁能调用 mutations 中的方法?如何调用?
在 action 中使用: commit('对应的mutations方法名') 触发
mutations 中方法的特点:不能写异步代码、只能单纯的操作 state
const mutations = {
yyy (state, { data1 }) {
// 更新state的某个属性
}
}
1.18.4 getters
值为一个对象,包含多个用于返回数据的函数
-
如何使用?—— $store.getters.xxx
const getters = { mmm (state) { return state.msg + '!' } }
1.18.5 modules
- 包含多个 module
- 一个 module 是一个 store 的配置对象
- 与一个组件(包含有共享数据)对应
1.19 vue-router
路由组件通常存放在pages文件夹,一般组件通常放在components文件夹
实现切换 (active-class可配置高亮样式)
<router-link active-class=“actine” to=“/about”></router-link>
指定路由页面展示位置
<router-view></router-view>
1.19.1 嵌套(多级)路由
1.19.2 路由传参
# query携带参数
<router-link active-class=“actine” :to=`{path: '/home', query: { id: 213, name; 'test' } }`></router-link>
# params 携带参数
<router-link active-class=“actine” :to=`/user/${item.id}/${item.name`></router-link>
<router-link active-class=“actine” :to=“{name: '/user', params: { id: 234, name: 'test' }}”></router-link>
// router/index.js
const router = new VueRouter({
routes: [
{
name: 'user'
path: '/user/:id/:name', // 占位
component: User,
children: []
}
]
}]
1.19.3 路由的props
const router = new VueRouter({
routes: [
{
name: home
path: '/home',
component: Home,
children: [
name: 'test'
path: 'test/:id/:title', // 子路由前面不用写 ‘/’
component: Test,
// props的第一种写法,值为对象;改对象中的所有key--value都会以props传给 Test 组件
props: { a: 1, b: 'hello' }
// props的第二种写法,值为布尔值, 若布尔值为真,就会把该组件收到的所有params参数,以props的形式传给 Test 组件
props: true
// props的第三种写法,值为函数,
props ($route) {
return { id: $route.query.id, title: $route.query.title }
}
]
}
]
})
1.19.5 路由的router-link的replace
# 开启 replace 模式
<router-link replace active-class=“actine” to=“/about”></router-link>
浏览器的历史记录有2种写入方式:分别为 push
和 replace
,push是追加历史记录,replace是替换当前记录。路由在跳转时候默认为push
1.19.5 编程式路由导航
- this.$router.push(path): 相当于点击路由链接(可以返回到当前路由界面)
- this.$router.replace(path): 用新路由替换当前路由(不可以返回到当前路由界面)
- this.$router.back(): 请求(返回)上一个记录路由
- this.$router.go(-1): 请求(返回)上一个记录路由
- this.$router.go(1): 请求下一个记录路由
- 在新浏览器标签页打开某个路由页面
const { href } = this.$router.resolve({ path: '/home', query: { id: 23 } })
window.open(href, '_blank')
1.19.6 缓存路由组件
让不展示的路由组件保持挂载,不被销毁
include:需要呗缓存的组件名;不写则是缓存此路由显示区下的所有组件
<keep-alive include="test">
<router-view></router-view>
</keep-alive>
<keep-alive :include="['test', 'news']">
<router-view></router-view>
</keep-alive>
1.19.7 activated与deactivated生命周期钩子
在组件被设置了路由缓存才存在的钩子
activated () {} // 组件激活时
deactivated () {} // 组件失活时
1.19.8 路由守卫
1.19.8.1 全局路由守卫
const router = new VueRouter({
routes: [
{
name: 'test'
path: '/test', // 占位
component: Test,
meta: { isAuth: false, title: '测试' },
},
{
name: 'home'
path: '/home', // 占位
component: Home,
// 路由附带属性
meta: { isAuth: true, title: '主页' },
children: [
{
name: 'user'
path: 'user', // 占位
component: User,
meta: { isAuth: true, title: '用户' },
},
{
name: 'news'
path: 'news', // 占位
component: News,
meta: { isAuth: true, title: '新闻' },
// 独享路由守卫 (只有前置没有后置)
beforeEach((to, from, next) => {
if (!to.meta.isAuth) next() // 判断是否需要鉴权
if (localStorage.getitem('test') === 'test') {
next()
} else {
alert('无权限查看')
}
})
}
]
}
]
}]
// 全局前置路由守卫,初始化的时候与每次切换路由的时候被调用
router.beforeEach((to, from, next) => {
if (!to.meta.isAuth) next() // 判断是否需要鉴权
if (to.path === '/home/user' && localStorage.getitem('test') === 'test') {
next()
} else {
alert('无权限查看')
}
})
// 后置路由守卫,初始化和每次路由切换之后被调用
router.afterEach((to, from) => {
// 例:根据页面更改网页标签的标题
document.title = to.meta.title || 'vue'
})
export default router
1.19.8.2 组件内路由守卫
<scrip>
export default {
data () {},
// 通过路由规则进入该组件时被调用
beforeRouteEnter (to, from, next) {
next()
}
// 通过路由规则离开该组件时被调用
beforeRouteLeave (to, from, next) {
next()
}
}
</script>
1.19.9 history模式与hash模式
const router = new VueRouter({
mode: 'history', // 默认是hash
routes: [
{
name: 'test'
path: '/test', // 占位
component: Test,
meta: { isAuth: false, title: '测试' },
}
]
}]
export default router
1.20 Vue UI 组件库
1.20.1 移动端常用 UI 组件库
- Vant https://youzan.github.io/vant
- Cube UI https://didi.github.io/cube-ui
- Mint UI http://mint-ui.github.io
1.20.2 PC 端常用 UI 组件库
- Element UI https://element.eleme.cn
- IView UI https://www.iviewui.com
1.21 总结
data: {
// 定义参数
},
methods: {
// 定义函数
},
filters: {
// 定义过滤器
},
computed: {
// 定义计算属性
},
watch: {
// 定义监听
},
directives: {
// 定义自定义指令
},
***********生命周期函数************
beforeCreate () {
// 在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用此时组件的选项还未挂载,因此无法访问methods,data,computed上的方法或数据
},
created () {
// 实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。
// 这是一个常用的生命周期,因为你可以调用methods中的方法、改变data中的数据,并且修改可以通过vue的响应式绑定体现在页面上、获取computed中的计算属性等等。
},
beforeMount () {
// 在挂载开始之前被调用:相关的 render 函数首次被调用。
},
mounted () {
// el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。
// 1.在这个周期内,对data的改变可以生效。但是要进下一轮的dom更新,dom上的数据才会更新。
// 2.这个周期可以获取 dom。 之前的论断有误,
// 3.beforeRouteEnter的next的勾子比mounted触发还要靠后4.指令的生效在mounted周期之前
},
beforeUpdate () {
// 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
},
updated () {
// 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件 DOM 已经更新
// 所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。
},
beforeDestroy () {
// 实例销毁之前调用。在这一步,实例仍然完全可用。
// 这一步还可以用this来获取实例。2.一般在这一步做一些重置的操作。比如清除掉组件中的 定时器 和 监听的dom事件
},
destroyed () {
// Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
},
**************指令的周期*****************
bind () {
// 只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。
}
inserted () {
// 被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)。 实际上是插入vnode的时候调用。
}
update () {
// 被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。慎用,如果在指令里绑定事件,并且用这个周期的,记得把事件注销
}
componentUpdated () {
// 被绑定元素所在模板完成一次更新周期时调用。
}
unbind () {
// 只调用一次, 指令与元素解绑时调用。
}
***********keep-alive周期函数***********
activated () {
// 在组件第一次渲染时会被调用,之后在每次缓存组件被激活时调用。
},
deactivated () {
// 组件被停用(离开路由)时调用。使用 keep-alive 就不会调用 beforeDestroy(组件销毁前钩子)和 destroyed(组件销毁),因为组件没被销毁,被缓存起来了,这个钩子可以看作 beforeDestroy 的替代
}
***************全局路由钩子**************
beforeEach () {
// router.beforeEach
// 示例router.beforeEach((to,from,next)=>{ console.log('路由全局勾子:beforeEach -- 有next方法')next()})
// 一般在这个勾子的回调中,对路由进行拦截。
// 比如,未登录的用户,直接进入了需要登录才可见的页面,那么可以用next(false)来拦截,使其跳回原页面。
// 值得注意的是,如果没有调用next方法,那么页面将卡在那。
// next的四种用法
// 1.next() 跳入下一个页面
// 2.next('/path') 改变路由的跳转方向,使其跳到另一个路由
// 3.next(false) 返回原来的页面
// 4.next((vm)=>{}) 仅在beforeRouteEnter中可用,vm是组件实例。
},
afterEach () {
// 示例router.afterEach((to,from)=>{console.log('路由全局勾子:afterEach---没有next方法')})
// 在所有路由跳转结束的时候调用,和beforeEach是类似的,但是它没有next方法
}
***************组件路由勾子**************
beforeRouteEnter () {}
beforeRouteUpdate () {}
beforeRouteLeave () {}
二、Vue3
2.1.Vue3带来了什么
2.1.1.性能的提升
打包大小减少41%
初次渲染快55%, 更新渲染快133%
-
内存减少54%
......
2.1.2 源码的升级
使用Proxy代替defineProperty实现响应式
-
重写虚拟DOM的实现和Tree-Shaking
......
2.1.3 拥抱TypeScript
- Vue3可以更好的支持TypeScript
2.1.4 新的特性
-
Composition API(组合API)
- setup配置
- ref与reactive
- watch与watchEffect
- provide与inject
- ......
-
新的内置组件
- Fragment
- Teleport
- Suspense
-
其他改变
- 新的生命周期钩子
- data 选项应始终被声明为一个函数
- 移除keyCode支持作为 v-on 的修饰符
- ......
2.2 vite创建工程
## 创建工程
npm init vite-app <project-name>
## 进入工作目录
cd <project-name>
## 安装依赖
npm I
## 运行
npm run dev
2.3 常用 Composition API
2.3.1 拉开序幕的setup
- 理解:Vue3.0中一个新的配置项,值为一个函数。
- setup是所有<strong style="color:#DD5145">Composition API(组合API)</strong><i style="color:gray;font-weight:bold">“ 表演的舞台 ”</I>。
- 组件中所用到的:数据、方法等等,均要配置在setup中。
- setup函数的两种返回值:
- 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
- <span style="color:#aad">若返回一个渲染函数:则可以自定义渲染内容。(了解)</span>
- 注意点:
- 尽量不要与Vue2.x配置混用
- Vue2.x配置(data、methos、computed...)中<strong style="color:#DD5145">可以访问到</strong>setup中的属性、方法。
- 但在setup中<strong style="color:#DD5145">不能访问到</strong>Vue2.x配置(data、methos、computed...)。
- 如果有重名, setup优先。
- setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)
- 尽量不要与Vue2.x配置混用
<script>
export default {
name: 'App',
setup() {
let name = 'test'
let age = 18
function sayHello () {
alert(`hello---${name}----${age}`)
}
return { name, age, sayHello }
}
}
</script>
2.3.2 ref函数
- 作用: 定义一个响应式的数据
- 语法:
const xxx = ref(initValue)
- 创建一个包含响应式数据的<strong style="color:#DD5145">引用对象(reference对象,简称ref对象)</strong>。
- JS中操作数据:
xxx.value
- 模板中读取数据: 不需要.value,直接:
<div>{{xxx}}</div>
- 备注:
- 接收的数据可以是:基本类型、也可以是对象类型。
- 基本类型的数据:响应式依然是靠
Object.defineProperty()
的get
与set
完成的。 - 对象类型的数据:内部 <i style="color:gray;font-weight:bold">“ 求助 ”</i> 了Vue3.0中的一个新函数——
reactive
函数。
<script>
import { ref } from 'vue'
export default {
name: 'App',
setup() {
let name = ref('test')
let age = ref(18)
function setValue () {
name.value = 'hello'
age.value = 22
}
return { name, age, setValue }
}
}
</script>
2.3.3 reactive函数
- 作用: 定义一个<strong style="color:#DD5145">对象类型</strong>的响应式数据(基本类型不要用它,要用
ref
函数) - 语法:
const 代理对象= reactive(源对象)
接收一个对象(或数组),返回一个<strong style="color:#DD5145">代理对象(Proxy的实例对象,简称proxy对象)</strong> - reactive定义的响应式数据是“深层次的”。
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。
<script>
import { reactive } from 'vue'
export default {
name: 'App',
setup() {
let testObj = reactive({
name: 'test',
age: 18
})
function setTestObj () {
testObj.name = 'hello'
testObj.age = 22
}
return { testObj, setTestObj }
}
}
</script>
2.3.4.Vue3.0中的响应式原理
vue2.x的响应式
-
实现原理:
对象类型:通过
Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持)。-
数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
Object.defineProperty(data, 'count', { get () {}, set () {} })
-
存在问题:
- 新增属性、删除属性, 界面不会更新。
- 直接通过下标修改数组, 界面不会自动更新。
Vue3.0的响应式
-
实现原理:
通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
通过Reflect(反射): 对源对象的属性进行操作。
-
MDN文档中描述的Proxy与Reflect:
Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
-
Reflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
new Proxy(data, { // 拦截读取属性值 get (target, prop) { return Reflect.get(target, prop) }, // 拦截设置属性值或添加新属性 set (target, prop, value) { return Reflect.set(target, prop, value) }, // 拦截删除属性 deleteProperty (target, prop) { return Reflect.deleteProperty(target, prop) } }) proxy.name = 'tom'
2.3.5 reactive对比ref
- 从定义数据角度对比:
- ref用来定义:<strong style="color:#DD5145">基本类型数据</strong>。
- reactive用来定义:<strong style="color:#DD5145">对象(或数组)类型数据</strong>。
- 备注:ref也可以用来定义<strong style="color:#DD5145">对象(或数组)类型数据</strong>, 它内部会自动通过
reactive
转为<strong style="color:#DD5145">代理对象</strong>。
- 从原理角度对比:
- ref通过
Object.defineProperty()
的get
与set
来实现响应式(数据劫持)。 - reactive通过使用<strong style="color:#DD5145">Proxy</strong>来实现响应式(数据劫持), 并通过<strong style="color:#DD5145">Reflect</strong>操作<strong style="color:orange">源对象</strong>内部的数据。
- ref通过
- 从使用角度对比:
- ref定义的数据:操作数据<strong style="color:#DD5145">需要</strong>
.value
,读取数据时模板中直接读取<strong style="color:#DD5145">不需要</strong>.value
。 - reactive定义的数据:操作数据与读取数据:<strong style="color:#DD5145">均不需要</strong>
.value
。
- ref定义的数据:操作数据<strong style="color:#DD5145">需要</strong>
2.3.6 setup的两个注意点
-
setup执行的时机
- 在beforeCreate之前执行一次,this是undefined。
-
setup的参数
- props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
- context:上下文对象
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
this.$attrs
。 - slots: 收到的插槽内容, 相当于
this.$slots
。 - emit: 分发自定义事件的函数, 相当于
this.$emit
。
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
2.3.7 计算属性与监视
1.computed函数
与Vue2.x中computed配置功能一致
-
写法
import {computed} from 'vue' setup(){ ... //计算属性——简写 let fullName = computed(()=>{ return person.firstName + '-' + person.lastName }) //计算属性——完整 let fullName = computed({ get(){ return person.firstName + '-' + person.lastName }, set(value){ const nameArr = value.split('-') person.firstName = nameArr[0] person.lastName = nameArr[1] } }) }
2.watch函数
与Vue2.x中watch配置功能一致
-
两个小“坑”:
- 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
- 监视reactive定义的响应式数据中某个属性时:deep配置有效。
//情况一:监视ref定义的响应式数据 watch(sum,(newValue,oldValue)=>{ console.log('sum变化了',newValue,oldValue) },{immediate:true}) //情况二:监视多个ref定义的响应式数据 watch([sum,msg],(newValue,oldValue)=>{ console.log('sum或msg变化了',newValue,oldValue) }) /* 情况三:监视reactive定义的响应式数据 若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!! 若watch监视的是reactive定义的响应式数据,则强制开启了深度监视 */ watch(person,(newValue,oldValue)=>{ console.log('person变化了',newValue,oldValue) },{immediate:true,deep:false}) //此处的deep配置不再奏效 //情况四:监视reactive定义的响应式数据中的某个属性 watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{immediate:true,deep:true}) //情况五:监视reactive定义的响应式数据中的某些属性 watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{immediate:true,deep:true}) //特殊情况 watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
3.watchEffect函数
watch的套路是:既要指明监视的属性,也要指明监视的回调。
watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
-
watchEffect有点像computed:
- 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
- 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。 watchEffect(()=>{ const x1 = sum.value const x2 = person.age console.log('watchEffect配置的回调执行了') })
2.3.8 生命周期
- Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:
-
beforeDestroy
改名为beforeUnmount
-
destroyed
改名为unmounted
-
- Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
-
beforeCreate
===>setup()
-
created
=======>setup()
-
beforeMount
===>onBeforeMount
-
mounted
=======>onMounted
-
beforeUpdate
===>onBeforeUpdate
-
updated
=======>onUpdated
-
beforeUnmount
==>onBeforeUnmount
-
unmounted
=====>onUnmounted
-
2.3.9 自定义hook函数
什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。
类似于vue2.x中的mixin。
自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。
// usePoint.js
import { reactive, onMounted, onBeforeUnmount } from 'vue'
export default function () {
// 实现获取当前鼠标位置数据的保存
let point = reactive({
x: 0,
y: 0
})
// 实现鼠标打点的方法
function savePoint(event) {
point.x = event.pageX
point.y = event.pageY
}
// 实现鼠标鼠标打点的生命周期钩子
onMounted(() => {
window.addEventListener('click', savePoint)
})
onBeforeUnmount(() => {
window.removeEventListener('click', savePoint)
})
return point
}
// Demo.vue
<script>
import usePoint from 'usePoint'
export default {
name: 'Demo',
setup () {
const point = usePoint()
return { point }
}
}
</script>
2.3.10 toRef
- 作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。
- 语法:
const name = toRef(person,'name')
- 应用: 要将响应式对象中的某个属性单独提供给外部使用时。
- 扩展:
toRefs
与toRef
功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)
import { reactive, toRef, toRefs } from 'vue'
setup () {
let person = reactive({
name: '张',
age: 18,
job: {
j1: {
salary: 20
}
}
})
// return {
// name: toRef(person, 'name'),
// age: toRef(person, 'age'),
// salary: toRef(person.job.j1, 'salary')
// }
return {
// 批量创建多个ref对象
...toRefs(person)
}
}
2.4 其它 Composition API
2.4.1 shallowReactive 与 shallowRef
shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。
-
什么时候使用?
- 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
- 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。
2.4.2 readonly 与 shallowReadonly
- readonly: 让一个响应式数据变为只读的(深只读)。
- shallowReadonly:让一个响应式数据变为只读的(浅只读)。
- 应用场景: 不希望数据被修改时。
2.4.3 toRaw 与 markRaw
- toRaw:
- 作用:将一个由
reactive
生成的<strong style="color:orange">响应式对象</strong>转为<strong style="color:orange">普通对象</strong>。 - 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
- 作用:将一个由
- markRaw:
- 作用:标记一个对象,使其永远不会再成为响应式对象。
- 应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类库等。
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
2.4.4.customRef
作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。
-
实现防抖效果:
<template> <input type="text" v-model="keyword"> <h3>{{keyword}}</h3> </template> <script> import {ref,customRef} from 'vue' export default { name:'Demo', setup(){ // let keyword = ref('hello') //使用Vue准备好的内置ref //自定义一个myRef function myRef(value,delay){ let timer //通过customRef去实现自定义 return customRef((track,trigger)=>{ return{ get(){ track() //告诉Vue这个value值是需要被“追踪”的 return value }, set(newValue){ clearTimeout(timer) timer = setTimeout(()=>{ value = newValue trigger() //告诉Vue去更新界面 },delay) } } }) } let keyword = myRef('hello',500) //使用程序员自定义的ref return { keyword } } } </script>
2.4.5 provide 与 inject
<img src="https://v3.cn.vuejs.org/images/components_provide.png" style="width:300px" />
作用:实现<strong style="color:#DD5145">祖与后代组件间</strong>通信
套路:父组件有一个
provide
选项来提供数据,后代组件有一个inject
选项来开始使用这些数据-
具体写法:
-
祖组件中:
setup(){ ...... let car = reactive({name:'奔驰',price:'40万'}) provide('car',car) ...... }
-
后代组件中:
setup(props,context){ ...... const car = inject('car') return {car} ...... }
-
2.4.6 响应式数据的判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive
创建的响应式代理 - isReadonly: 检查一个对象是否是由
readonly
创建的只读代理 - isProxy: 检查一个对象是否是由
reactive
或者readonly
方法创建的代理
2.5 Composition API 的优势
2.5.1 Options API 存在的问题
使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。
[图片上传失败...(image-c3175-1663341595536)]
[图片上传失败...(image-6800e1-1663341595536)]
2.5.2.Composition API 的优势
我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。
[图片上传失败...(image-590ac0-1663341595536)]
[图片上传失败...(image-752a0-1663341595536)]
2.6 新的组件
2.6.1 Fragment
- 在Vue2中: 组件必须有一个根标签
- 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
- 好处: 减少标签层级, 减小内存占用
2.6.2.Teleport
-
什么是Teleport?——
Teleport
是一种能够将我们的<strong style="color:#DD5145">组件html结构</strong>移动到指定位置的技术。<teleport to="移动位置"> <div v-if="isShow" class="mask"> <div class="dialog"> <h3>我是一个弹窗</h3> <button @click="isShow = false">关闭弹窗</button> </div> </div> </teleport>
2.6.3.Suspense
等待异步组件时渲染一些额外内容,让应用有更好的用户体验
-
使用步骤:
-
异步引入组件
import {defineAsyncComponent} from 'vue' const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
-
使用
Suspense
包裹组件,并配置好default
与fallback
<template> <div class="app"> <h3>我是App组件</h3> <Suspense> <template v-slot:default> <Child/> </template> <template v-slot:fallback> <h3>加载中.....</h3> </template> </Suspense> </div> </template>
-
2.7 其他
2.7.1.全局API的转移
-
Vue 2.x 有许多全局 API 和配置。
-
例如:注册全局组件、注册全局指令等。
//注册全局组件 Vue.component('MyButton', { data: () => ({ count: 0 }), template: '<button @click="count++">Clicked {{ count }} times.</button>' }) //注册全局指令 Vue.directive('focus', { inserted: el => el.focus() }
-
-
Vue3.0中对这些API做出了调整:
-
将全局的API,即:
Vue.xxx
调整到应用实例(app
)上2.x 全局 API( Vue
)3.x 实例 API ( app
)Vue.config.xxxx app.config.xxxx Vue.config.productionTip <strong style="color:#DD5145">移除</strong> Vue.component app.component Vue.directive app.directive Vue.mixin app.mixin Vue.use app.use Vue.prototype app.config.globalProperties
-
2.7.2 其他改变
data选项应始终被声明为一个函数。
-
过度类名的更改:
-
Vue2.x写法
.v-enter, .v-leave-to { opacity: 0; } .v-leave, .v-enter-to { opacity: 1; }
-
Vue3.x写法
.v-enter-from, .v-leave-to { opacity: 0; } .v-leave-from, .v-enter-to { opacity: 1; }
-
<strong style="color:#DD5145">移除</strong>keyCode作为 v-on 的修饰符,同时也不再支持
config.keyCodes
-
<strong style="color:#DD5145">移除</strong>
v-on.native
修饰符-
父组件中绑定事件
<my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" />
-
子组件中声明自定义事件
<script> export default { emits: ['close'] } </script>
-
-
<strong style="color:#DD5145">移除</strong>过滤器(filter)
过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。
......