Vue学习笔记
Vue生命周期执行顺序以及时机
// 当前组件层级
<Parent>
<Child></Child>
</Parent>
// 初始化阶段
parent beforeCreate
parent created
parent beforeMount
child beforeCreate
child created
child beforeMount
child mounted
parent mounted
// parent组件更新阶段,同时影响子组件变化
parent beforeUpdate
child beforeUpdate
child updated
parent updated
// child组件自身改变,以及parent组件自身变化没有影响到child组件时自身组件beforeUpdate updated方法会执行
// 和react中不同的是,parent组件更新时,child组件没有经过pure处理子组件也会进行更新
列表渲染注意事项 v-for
使用v-for时,为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一的 key
。
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
-
push()
-
pop()
-
shift()
-
unshift()
-
splice()
-
sort()
-
reverse()
对于不修改原数组的API如:filter()
、concat()
和 slice()
可以用新数组替换旧数组。
事件处理
- 简易事件触发
<button @click="counter += 1">Add 1</button>
data() {
return {
counter: 0
}
}
- 调用method中事件,不含参数
<button @click="click">click</button>
// 默认带有当前点击的click事件
click(event: Event) {
console.log(event);
},
- 调用method中事件,含参数
<button @click="click('test', $emit)">click</button>
// 默认带有当前点击的click事件
click(param: string, event: Event) {
console.log(string);
console.log(event);
},
- 多事件处理
<button @click="click1(1, $event), click2($event)">
Submit
</button>
click1(param: number, event: Event) {
console.log(string);
console.log(event);
},
click2(event: Event){
console.log(event);
}
- 子组件调用父组件方法
// index.vue
<demo @click-test="ClickTest"></demo>
ClickTest(payload: { param: string }) {
console.log(payload. param);
},
// demo.vue
<button @click="click"> ClickTest</button>
click() {
this.$emit("ClickTest", {
param: 'demo'
});
}
Vue自定义事件,在事件触发之前可以验证事件有效性
动态组件 & 异步组件
动态组件、keep-alive
// 动态组件
<component :is="currentTabComponent"></component>
// 结合keep-alive的动态组件
<keep-alive>
<component :is="currentTabComponent"></component>
</keep-alive>
设置 keep-alive 的组件,会增加两个生命钩子(activated / deactivated)。
首次进入组件:beforeCreate -> created -> beforeMount -> mounted -> activated 离开组件触发
deactivated,因为组件缓存不销毁,所以不会触发 beforeDestroy 和 destroyed 生命钩子。再次进入组件后直接从 activated 生命钩子开始。
keep-alive参数:
-
include
-string | RegExp | Array
。字符串或正则表达式,名称匹配的组件会被缓存。 -
exclude
-string | RegExp | Array
。字符串或正则表达式,名称匹配的组件不会被缓存。 -
max
-number | string
。最多可以缓存多少组件实例。
异步组件
可以实现组件的按需加载,结合webpack的对组件进行分包,理由浏览器对文件并行加载。
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
import('./components/Demo.vue')
)
同时他还提供相比React.lazy和Suspense更全的用法:
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent({
// 工厂函数
loader: () => import('./Foo.vue'),
// 加载异步组件时要使用的组件
loadingComponent: LoadingComponent,
// 加载失败时要使用的组件
errorComponent: ErrorComponent,
// 在显示 loadingComponent 之前的延迟 | 默认值:200(单位 ms)
delay: 200,
// 如果提供了 timeout ,并且加载组件的时间超过了设定值,将显示错误组件
// 默认值:Infinity(即永不超时,单位 ms)
timeout: 3000,
// 定义组件是否可挂起 | 默认值:true
suspensible: false,
/**
*
* @param {*} error 错误信息对象
* @param {*} retry 一个函数,用于指示当 promise 加载器 reject 时,加载器是否应该重试
* @param {*} fail 一个函数,指示加载程序结束退出
* @param {*} attempts 允许的最大重试次数
*/
onError(error, retry, fail, attempts) {
if (error.message.match(/fetch/) && attempts <= 3) {
// 请求发生错误时重试,最多可尝试 3 次
retry()
} else {
// 注意,retry/fail 就像 promise 的 resolve/reject 一样:
// 必须调用其中一个才能继续错误处理。
fail()
}
}
})
组件通信
父子组件通讯
父组件传值给子组件
// parent.vue
<div>
<child :lists="lists"></child>
</div>
data() {
return {
lists: [1, 2, 3],
};
}
// child.vue
<div class="child">
<ul>
<li v-for="list in lists">{{list}}</li>//遍历传递过来的值,然后呈现到页面
</ul>
</div>
props:{
lists:{ //这个就是父组件中子标签自定义名字
type:Array,
required:true
}
}
子组件向父组件传值
通过$emit回传给父组件
隔代组件通讯
- Provide / Inject
// index.vue
provide: {
title: 'vue',
listLength: computed(() => this.lists.length)
}
// child.vue
inject: ['title', 'listLength']
- Vuex
父子、兄弟、隔代组件通讯
- 抽象出最高的父组件进行个子组件之前通信
- Vuex
Slot(插槽)
在React中实现自定义组件引入自定义组件:
// index.jsx
<Parent>
<Child></Child>
</Parent>
// parent.jsx
<div>{children}</div>
在Vue中可以使用Slot实现类似功能:
普通插槽
// index.vue
<slot-demo> <div>slot-child</div> </slot-demo>
// slot.vue
<div class="slot">
<slot></slot>
</div>
具名插槽
// index.vue
<slot-demo>
<template #header>
<div>header</div>
</template>
<template #body>
<div>body</div>
</template>
</slot-demo>
// slot-demo.vue
<div class="slot">
<slot name="header"></slot>
<slot name="body"></slot>
</div>
作用域插槽
// index.vue
<slot-demo>
<template #footer="{item}">
<span class="green">{{ item }}</span>
</template>
</slot-demo>
// slot-demo.vue
<ul>
<li v-for="(item, index) in items" :key="index">
<slot name="footer" :item="item" :index="index"></slot>
</li>
</ul>
composition-api
setup
参数:
- props: 组件传入的属性
- context
setup中接受的props
是响应式的, 当传入新的props 时,会及时被更新。由于是响应式的, 所以不可以使用ES6解构,解构会消除它的响应式。
ref
ref
我们用来将基本数据类型
定义为响应式数据;。
ref并不只是具有对基本数据类型的响应式处理能力,他也是可以处理对象的。
<template>
<div>
{{ num }}
</div>
</template>
export default defineComponent({
setup() {
const num = ref(1);
return { num };
},
});
reactive
reactive
用来将引用类型
定义为响应式数据,其本质是基于Proxy
实现对象代理
返回一个obj对象可以实现数据的更新
<template>
<div>
{{ obj.a }}
</div>
<button @click="obj.foo">test</button>
</template>
setup() {
const obj = reactive({
a: 1,
b: 2,
foo() {
this.a = 2;
},
});
return { obj };
},
返回解雇赋值时无法更新试图
<template>
<div>
{{ a }}
</div>
<button @click="foo">test</button>
</template>
setup() {
const obj = reactive({
a: 1,
b: 2,
foo() {
this.a = 2;
},
});
return { ...obj };
},
可以通过toRefs做处理
<template>
<div>
{{ a }}
</div>
<button @click="foo">test</button>
</template>
setup() {
const obj = reactive({
a: 1,
b: 2,
foo() {
this.a = 2;
},
});
return { ...toRefs(obj) };
},
toRef
可以用来为源响应式对象上的某个 property 新创建一个 ref
。然后,ref 可以被传递,它会保持对其源 property 的响应式连接。
const state = reactive({
foo: 1,
bar: 2
})
const fooRef = toRef(state, 'foo')
fooRef.value++
console.log(state.foo) // 2
state.foo++
console.log(fooRef.value) // 3
toRefs
toRefs
会将我们一个响应式
的对象转变为一个普通
对象,然后将这个普通对象
里的每一个属性变为一个响应式的数据
const state = reactive({
foo: 1,
bar: 2
})
const stateAsRefs = toRefs(state)
/*
stateAsRefs 的类型:
{
foo: Ref<number>,
bar: Ref<number>
}
*/
// ref 和原始 property 已经“链接”起来了
state.foo++
console.log(stateAsRefs.foo.value) // 2
stateAsRefs.foo.value++
console.log(state.foo)
computed
接受一个 getter 函数,并根据 getter 的返回值返回一个不可变的响应式 ref 对象。
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 错误
watchEffect
立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
watch
侦听多个数据源
const firstName = ref('')
const lastName = ref('')
watch([firstName, lastName], (newValues, prevValues) => {
console.log(newValues, prevValues)
})
const changeValues = () => {
firstName.value = 'John';
lastName.value = 'Smith';
// 打印 ["John", "Smith"] ["", ""]
};
注意多个同步更改只会触发一次侦听器。
通过更改设置 flush: 'sync'
,我们可以为每个更改都强制触发侦听器,尽管这通常是不推荐的
watch([firstName, lastName], (newValues, prevValues) => {
console.log(newValues, prevValues)
}, { flush: 'sync' })
-
与
watchEffect
比较,
watch
允许我们:
- 懒执行副作用;
-
更具体地说明什么状态应该触发侦听器重新运行;
-
访问侦听状态变化前后的值。
setUp中执行方法
方式一
<template>
<button @click="foo">test</button>
</template>
setup() {
const obj = reactive({
a: 1,
foo() {
this.a = 2;
},
});
return { ...toRefs(obj) };
},
方式二
<template>
<button @click="foo">test</button>
</template>
setup() {
const foo = (data) => {
}
return { foo };
},
方式三
<template>
<button @click="foo">test</button>
</template>
const foo = (data) => {
}
setup() {
return { foo };
},
Vue Router
创建路由
const router = createRouter({
history: createWebHistory(),
routes,
});
参数history:
- createWebHashHistory --> html5 history 实现
- createWebHistory --> window onhashchange 实现
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave
守卫。 - 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫 (2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 调用
beforeRouteEnter
守卫中传给next
的回调函数,创建好的组件实例会作为回调函数的参数传入。
导航守卫
全局前置守卫
在路由跳转前触发,可在执行 next 方法前做一些身份登录验证的逻辑。
router.beforeEach((to, from, next) => {
// 必须执行 next 方法来触发路由跳转
next()
})
全局解析守卫
与 beforeEach 类似,也是路由跳转前触发,区别是还需在所有组件内守卫和异步路由组件被解析之后
,也就是在组件内 beforeRouteEnter 之后被调用。
router.beforeResolve(
(to, from, next) => {
// 必须执行 next 方法来触发路由跳转
next()
})
全局后置钩子
和守卫不同的是,这些钩子不会接受 next
函数也不会改变导航本身。
router.afterEach((to, from) =>{
})
路由独享守卫
可在路由配置上直接定义 beforeEnter
{
path: '/about',
name: 'About',
beforeEnter(to, from, next) {},
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
}
组件内的守卫
beforeRouteEnter(to, from, next) {
// 不能获取组件实例 this
// 当守卫执行前,组件实例还没被创建
next();
},
beforeRouteUpdate(to, from, next) {
next();
// 当前路由改变,但是组件被复用时调用
// 可访问实例 this
},
beforeRouteLeave(to, from, next) {
next();
// 导航离开组件时被调用
},
路由匹配、路由传参、命名路由
路由匹配方式和react相似
命名路由
支持创建路由时给路由设置name属性命名,在跳转时可以指定跳转name进行跳转。
{
path: '/user/:userId',
name: 'user',
component: User
}
router.push({ name: 'user', params: { userId: 123 } })
传参
- 方式一
router.push({ path: '/detail/${id}'})
// 获取参数
route.params.id
- 方式二
和react传递state参数相似,刷新会丢失数据
router.push({ name: 'Detail', params: { id: id } })
// 获取参数
route.params.id
- 方式三
router.push({ name: 'Detail', query: { id: id } })
// 获取参数
route.query.id