友情链接:
https://www.jianshu.com/p/bc0f212d39d4
https://zhuanlan.zhihu.com/p/146097763
https://blog.csdn.net/qq_39773416/article/details/114274275
源码剖析文章
https://blog.csdn.net/gaoqiang1112/article/details/102619184
1.ref
ref可以代理字符串、数字、boolean等基本类型值
ref声明的值需要通过.value去改变
ref目的是为了引用原始类型值,但仍然可以引用非基本类型值例如对象
ref 本质也是reactive 可以简单地把 ref(1) 理解为这个样子 reactive({value: 1})
//用法一 代理基本类型
import { ref } from 'vue'
const refVal = ref(1)
const add = () => {
refVal.value++ //值改变,视图更新
}
//用法二
const refObj = ref({ foo: 1 })
const add = () => {
//需要通过.value去访问
refObj.value.foo = 2 //值改变,触发视图更新
}
//用法三
//可以通过ref代理某个对象下面的值,复制修改响应式数据不影响原对象
const obj = { foo: 1 }
const refVal = ref(obj.foo)
const add = () => {
refVal.value++
console.log(refVal.value) //值改变,视图更新
console.log(obj.foo) //值不变
}
2.reactive
reactive接受一个可代理的对象,但不能是字符串、数字、boolean等基本类型值
reactive不需要通过.value去访问属性值
reactive解构会造成响应式丢失
import { reactive, toRefs } from 'vue'
const obj = reactive({ foo: 1 })
const add = () => {
obj.foo++ //值改变,视图更新
}
// reactive解构会造成响应式丢失
const book = reactive({
author: 'Vue Team',
year: '2020',
title: 'Vue 3 Guide',
description: 'You are reading this book right now ;)',
price: 'free'
})
let { author, title } = book
title = 'vue3 good' // 响应式丢失
// 解决办法
let { author, title } = toRefs(book)
title.value = 'vue3 good' // 通过.value 因为现在title为ref
console.log(book.title) // 成功
3.shallowReactive
用法同reactive,用于定义一个浅响应数据只代理第一层,当数据结构比较复杂时,每层都用proxy代理消耗性能
import { shallowReactive } from 'vue'
const obj = shallowReactive({ foo: { bar: 1 } })
const add = () => {
obj.foo.bar = 2 //值改变,不触发视图更新
obj.foo = { bar: 2 } //值改变,视图更新
}
4.readonly
用于定义一个只可读数据,接受一个Object对象
import { readonly } from 'vue'
const obj = readonly({ text: 'hi' })
const add = () => {
obj.text = 'hello' //报错
}
5.shallowReadonly
import { shallowReadonly } from 'vue'
const obj = shallowReadonly({ foo: { bar: 1 } })
const add = () => {
obj.foo = { bar: 2 } //报错
obj.foo.bar = 2 //有效
}
6.toRef
创建一个ref类型数据, 并和以前的数据关联
相当于引用, 修改响应式数据会影响原始数据
第一个参数为 obj 对象;第二个参数为对象中的属性名
应用场景:如果想让响应式数据和原始的数据关联起来, 并且更新响应式数据之后还不想更新UI, 那么就可以使用toRef
//数据发生改变, 视图也不会自动更新
const obj = { foo: 1 }
//const obj = reactive({ foo: 1}) //reactive创建的对象会触发视图更新
const refVal = toRef(obj, 'foo')
const add = () => {
refVal.value++
console.log(refVal.value) //值改变,视图不更新
console.log(obj.foo) //值改变,视图不更新
}
7.toRefs
//用法一
<template>
<p @click="add">{{ foo }}</p>
</template>
import { reactive, toRefs } from 'vue'
const obj = reactive({ foo: 1 })
return{
...toRefs(obj) //将obj里的每个属性转化为ref响应式数据
}
//用法二
//批量创建ref类型数据, 并和以前数据关联,不触发视图更新
import { reactive, toRefs } from 'vue'
const obj = { foo: 1, num: 1 }
//const obj = reactive({ foo: 1, num: 1 }) //reactive创建的对象会触发视图更新
const state = toRefs(obj)
const add = () => {
state.foo.value = 2
state.num.value = 2
console.log(state.foo.value) // 2 值改变,视图不更新
console.log(obj.foo) // 2 值改变,视图不更新
}
8.shallowRef
这是一个浅层的 ref ,只代理.value 可用于优化性能
import { shallowRef, triggerRef } from 'vue'
const obj = shallowRef({ foo: 1 })
//shallowRef只代理 ref 对象本身,也就是说只有 .value 是被代理的,而 .value 所引用的对象并没有被代理
const add = () => {
obj.value.foo = 2 //值改变,视图不更新
triggerRef(obj) // 可通过修改值后立即驱动视图更新
obj.value = { foo: 2 } //值改变,视图更新
}
9.unref
unref接收一个值,如果这个值是 ref 就返回 .value,否则原样返回
//vue3 unref 源码
export function unref<T>(ref: T): T extends Ref<infer V> ? V : T {
return isRef(ref) ? (ref.value as any) : ref
}
const val = 1
const refVal = ref(1)
const unrefVal = unref(val) //输出1
const unrefObj = unref(refVal) //输出1
10.markRaw
markRaw 方法可以将原始数据标记为非响应式的,即使用 ref 或 reactive 将其包装,仍无法实现数据响应式,其接收一个参数,即原始数据,并返回被标记后的数据
markRaw 函数所做的事情,就是在数据对象上定义 __v_skip 属性,从而跳过代理
import { markRaw, reactive } from 'vue'
//通过markRow代理过的对象,不会触发视图更新
//markRow可用来数据改变但不需要视图改变的情况,用于提升性能。
const obj = { foo: 1 }
const obj2 = markRaw(obj)
const state = reactive(obj2)
const add = () => {
state.foo = 2 //值改变,视图不更新
console.log(state) //2
console.log(obj) //2
}
11.toRaw
toRaw方法用于拿到原始数据,对原始数据进行修改,不会更新UI界面,
与markRow()方法类似可用于提升性能,不同的是 markRow接收的不是被代理过的响应式数据
toRaw 方法是用于获取 ref 或 reactive 对象的原始数据的
import { toRaw, reactive, ref } from 'vue'
//代理reactive对象
const obj = reactive({ foo: 1 })
const obj2 = toRaw(obj)
const add = () => {
obj2.foo = 2 //值改变,视图不会更新
}
//代理ref创建的对象
const obj = ref({ foo: 1 })
const obj2 = toRaw(obj.value) //与reactive不同的是,需要用.value去获取原始数据,因为经过Vue处理之后,.value中保存的才是当初创建时传入的那个原始数据
const add = () => {
obj2.foo = 2 //值改变,视图不会更新
console.log(obj.value) //输出 { foo: 2 }
}
12.isRef
用于判断数据是否是ref创建的,Vue3创建ref的时候会增加__v_isRef: true属性来标识ref数据
import { ref, isRef } from 'vue'
const val = ref(1)
console.log(isRef(val)) //true
13.isReactive
判断数据对象是否是 reactive
import { ref, isReactive } from 'vue'
const obj = reactive({ foo: 1 })
console.log(isReactive(obj)) //true
14.isReadonly
判断数据对象是否是readonly只可读
import { readonly, isReadonly } from 'vue'
const val = readonly({ foo: 1 })
console.log(isReadonly(val)) //true
15.isProxy
用于判断对象是否是reactive 或 readonly 创建的代理对象
import { readonly, reactive, isProxy } from 'vue'
const obj = reactive({ foo: 1 })
const val = readonly({ foo: 1 })
console.log(isProxy(obj)) //true
console.log(isProxy(val)) //true
16.computed
用法与Vue2中的computed一样
import { ref, computed } from 'vue'
//写法-
const val = ref(1)
//vue3中计算属性的函数中如果只传入一个回调函数,表示的是get
const doubule = computed(() => val.value * 2)
const add = () => {
val.value = 3
console.log(doubule.value) // 6 需要通过.value去访问
}
//写法2
<template>
<input type="text" v-model="doubule">
</template>
const val = ref(1)
const doubule = computed(() => val.value * 2)
const doubule = computed({
get() {
//dobule的返回值
return obj.foo * 2
},
set(value) {
//写你的逻辑代码
val.value++
obj.foo = val.value
}
})
17.watch
watch监听数据变化,需手动传入监听的数据,返回新值和旧值
与vue2不同的是 vue2需要通过computed计算才会返回新值和旧值,否则返回的都是新值。
watch 与 watchEffect共享停止侦听,清除副作用 (相应地 onInvalidate 会作为回调的第三个参数传入)、副作用刷新时机和侦听器调试行为。
import { ref, reactive, watch } from 'vue'
//监听ref创建的数据
const val = ref(0)
watch(val,(newVal, oldVal) => {
console.log(newVal) //1 输出新值
console.log(oldVal) //0 输出旧值
},
{
immediate: false, //是否在初始化监听
deep: false //是否开启深度监听
}
)
const add = () => {
val.value = 1 //值改变
}
//监听reactive创建的数据,与ref不同的是需要用箭头函数指向要监听的数据
const obj = reactive({ foo: 0 })
watch(() => obj.foo,(newVal, oldVal) => {
console.log(newVal) //1 输出新值
console.log(oldVal) //0 输出旧值
},
{
immediate: false, //是否在初始化监听
deep: false //是否开启深度监听
}
)
const add = () => {
obj.foo = 1 //值改变
}
//监听多个数据源
const val = ref(1)
const obj = reactive({ foo: 1 })
watch([() => obj.foo, val], ([newFoo, newVal], [oldFoo, oldVal]) => {
console.log(newFoo, oldFoo)
console.log(newVal, oldVal)
})
const add = () => {
val.value += 2
obj.foo++
}
//watch 接受一个stop
const stop = watch(() => obj.foo,(newVal, oldVal) => {})
stop() //停止监听
// 使用侦听器来比较一个数组或对象的值,这些值是响应式的,要求它有一个由值构成的副本。
const numbers = reactive([1, 2, 3, 4])
watch(
() => [...numbers],
(numbers, prevNumbers) => {
console.log(numbers, prevNumbers)
}
)
numbers.push(5) // logs: [1,2,3,4,5] [1,2,3,4]
18.watchEffect
watchEffect也是监听数据变化。
当 watchEffect 在组件的 setup() 函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。
与watch不同的是:
1.不需要手动传入依赖
2.每次初始化都会执行
3.无法获取到原值,只能得到变化后的值
import { ref, reactive, watchEffect } from 'vue'
const obj = reactive({ foo: 1 })
const val = ref(0)
watchEffect(() => {
console.log(obj.foo)
console.log(val.value)
})
const stop = watchEffect(() => {
/* ... */
})
// 手动停止依赖
stop()
const add = () => {
val.value++
obj.foo++
}
//watchEffect还接受一个函数作为参数 ,可用于清除副作用
watchEffect(async () => {
const data = await fetch(obj.foo)
})
//当 obj.foo 变化后,意味着将会再次发送请求,那么之前的请求怎么办呢?是否应该将之前的请求标记为 invalidate
watchEffect(async (onInvalidate) => {
let validate = true
onInvalidate(() => {
validate = false
})
const data = await fetch(obj.foo)
if (validate){
/* 正常使用 data */
} else {
/* 说明当前副作用已经无效了,抛弃即可 */
}
})
watchEffect((onInvalidate)=>{
let timer = getList(title.value)
// 清除副作用
onInvalidate(()=>{
clearTimeout(timer)
})
})
// 在组件更新后触发,这样你就可以访问更新的 DOM。
// 注意:这也将推迟副作用的初始运行,直到组件的首次渲染完成。(默认pre(更新前),post (更新后), sync (同步,低效不建议使用))
watchEffect(
() => {
/* ... */
},
{
flush: 'post'
}
)
19.defineComponent & PropType
两者都是为了更好的推断TS类型
import { defineComponent, PropType } from 'vue'
interface Mylist {
name: string
age: number
}
export default defineComponent({
props: {
list: Object as PropType<Mylist[]>
},
setup(){}
})
20.生命周期函数
improt{
onBeforeMount
onMounted
onBeforeUpdate
onUpdated
onBeforeUnmount
onUnmounted
onActivated
onDeactivated
onErrorCaptured
} from 'vue'
vue3 新增的两个钩子
onRenderTracked
onRenderTriggered
export default {
onRenderTriggered(e) {
debugger
// 检查哪个依赖项导致组件重新呈现
}
}
21.customRef
自定义 ref,常用来定义需要异步获取的响应式数据
//可以用customRef实现一个搜索框防抖
<template>
<input type="text" v-model="text">
</template>
const useDebouncedRef = (value: string, delay = 1000) => {
let timeout: any
/**
* customRef回调接受两个参数
* track用于追踪依赖
* trigger用于触发响应
* 回调需返回一个包含get和set方法的对象
*/
return customRef((track, trigger) => {
return {
get() {
track() //追踪该数据
return value
},
set(newVal: string) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newVal
trigger() // 数据被修改,更新ui界面
}, delay)
}
}
})
}
const text = useDebouncedRef('')
watch(text, async (newText) => {
if (!newText) return void 0
console.log(newText) //停止输入1秒后输出。
})
return{ text }
22.withDefaults&defineProps & defineEmits
//在setup中直接用于接受props和emit,可做ts类型推导
<script setup lang="ts">
interface Props {
/** banner类型 */
type?: '1' | '2' | '3';
/** 主题颜色 */
color?: string;
}
withDefaults(defineProps<Props>(), {
type: '1',
color: '#409eff'
});
// Emits
interface Emits {
(e: 'close', id: number): void
(e: 'show', name: string, age: number): void
}
const emit = defineEmits<Emits>();
emit('close', 1)
emit('show', '1', 2)
//参数相同时
const emit = defineEmits<(e: 'close' | 'show', id: number) => void>()
emit('close', 1)
emit('show', 1)
</script>
23.defineAsyncComponent
//用于引入组件
<script setup lang="ts">
import { defineAsyncComponent } from 'vue'
const AsyncShow = defineAsyncComponent(
() => import('@/components/AsyncShow.vue')
)
</script>
24.script vars
支持将组件状态驱动的 CSS 变量注入到“单个文件组件”样式中。
<template>
//颜色改变
<p class="text">hello</p>
</template>
<script setup lang="ts">
const color = '#3b6af9'
</script>
<style lang="scss" scoped>
.text {
color: v-bind(color);
}
</style>
25.provide && inject
与 Vue2中的 provide 和 inject 作用相同,只不过在Vue3中需要手动从 vue 中导入
这里简单说明一下这两个方法的作用:
provide :向子组件以及子孙组件传递数据。接收两个参数,第一个参数是 key,即数据的名称;第二个参数为 value,即数据的值
inject :接收父组件或祖先组件传递过来的数据。接收一个参数 key,即父组件或祖先组件传递的数据名称
// A.vue
<script>
import {provide} from 'vue'
export default {
setup() {
const obj= {
name: '前端印象',
age: 22
}
// 向子组件以及子孙组件传递名为info的数据
provide('info', obj)
}
}
</script>
// B.vue
<script>
import {inject} from 'vue'
export default {
setup() {
// 接收A.vue传递过来的数据
inject('info') // {name: '前端印象', age: 22}
}
}
</script>
// C.vue
<script>
import {inject} from 'vue'
export default {
setup() {
// 接收A.vue传递过来的数据
inject('info') // {name: '前端印象', age: 22}
}
}
</script>
//在ts中需要类型检查
//新建context.ts
import { InjectionKey } from "vue";
exprot interface UserInfo {
id: number,
name: string
}
export const injectKeyUser: InjectionKey<UserInfo> = Symbol()
//parent.vue
import { provide } from 'vue'
import { injectKeyUser } from './context'
export default {
setup(){
provide(injectKeyUser, {
id: 7,
name:'小燕'
})
}
}
//child.vue
import { inject } from 'vue'
import { injectKeyUser } from './context'
export default {
setup(){
const user = inject(injectKeyUser)
//UserInfo | undefined
if(user){
console.log(user.name) //小燕
}
}
}
26.getCurrentInstance
//获取当前实例,和vue2中的this相同,用于setup函数中(不建议使用)
import { getCurrentInstance } from 'vue'
const { ctx } = getCurrentInstance()
console.log(ctx)
27.vue-router里的hooks
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
console.log(route.params.id)
router.push('/xxx/xxx')
28.vuex 里的 hooks
import { useStore } from 'vue-router'
const store = useStore()
29.在script setup lang="ts"语法糖中引入组件以及自定义指令
//组件引入
<script setup lang="ts">
import HelloWord from '@/components/HelloWord.vue'
</script>
<template>
<HelloWord />
</template>
//自定义指令引入
//focus.ts
import type { Directive } from 'vue';
const focus:Directive = {
mounted(el:HTMLDivElement) {
el.focus()
}
}
export default focus
//用法
<script setup lang="ts">
import focus from '@/direvtive/focus'
</script>
30.Suspense和Teleport新增组件
//Suspense
/*
1.default插槽 用于成功后显示
2.fallback插槽 用于加载之前和加载失败显示
3.Suspense下的子组件setup可以使用 async
4.子组件返回一个promise
*/
<Suspense>
<template #default>
<div :key="1">
click here
</div>
</template>
<template #fallback>
<h1>Waiting for server data</h1>
</template>
</Suspense>
//Teleport
/*
1.可以挂载到任意div下,不受父级影响
*/
<Teleport to="#app">
</Teleport>
31.注册自己的store仓库
// 创建自己的store
import { InjectionKey, App, inject } from 'vue'
interface MyState {
id: number
}
export const myStateKey: InjectionKey<MyState> = Symbol()
export function createMyStore() {
const state = {}
return {
install(app: App) {
app.provide(myStateKey, state)
},
}
}
export function useMyStore() {
return inject(myStateKey)!
}
// 用法
// main.js const App = createApp(App) app.use(createMyStore())
// 任意页面 const state = useMyStore()
32.defineExpose
使用 script setup 的组件是默认关闭的,也即通过模板 ref 或者 $parent 链获取到的组件的公开实例
比如你需要 通过ref拿到子组件的方法,则需要通过 defineExpose将方法暴露出来。
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
</script>
33. useSlots和 useAttr
<script setup>
// 可以访问到$slots和$attrs
import { useSlots, useAttrs } from 'vue'
const slots = useSlots()
const attrs = useAttrs()
</script>