一、基础语法
1、双向数据绑定
vue2
//defineProperty 无法直接监听到元素的增加,删除
let person = {
name: 'hello'
}
let p = {}
把值通过遍历做数据劫持
Object.defineProperty(p, 'name', {
get() {
return person.name
},
set(value) {
console.log('变更后的值为:', value)
person.name = value
}
})
p.name = 'world'
vue3
let person = {
name: 'hello'
}
let p = new Proxy(person, {
get(target, propName) {
return Reflect.get(target, propName)
},
set(target, propName, value) {
Reflect.set(target, propName, value)
},
deleteProperty(target, propName) {
Reflect.deleteProperty(target, propName)
}
})
p.name = "world"
2、ref, reactive
ref:一般用在定义基本类型和引用类型,如果是引用类型底层会借助reactive形成proxy代理对象,可以直接复制整个对象,如table的数据请求回来,需要将数据整体赋值个响应对象这时如果使用的是reactive就无法进行响应。
reactive:一般用在引用类型,如{}等,不能一次性修改整个对象,如我们后端请求table的数据数据,如果想一次性赋值的整个数组的话,就行不通,此时建议使用ref来定义数组。
!!!注意
let obj=reactive([])
setTimeout(()=>{
let arr=[1,2,3]
//当页面初始化完成以后,这种直接赋值会改变obj的对象指向,是不会生效的
obj=arr
},1000)
注意:
在vue3中私用ref 等api需要手动引入,如果不想手动引入的话可以使用setup语法糖插件
1、npm i unplugin-auto-import -D
2、在vite.config.js中配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(),
AutoImport({
imports:['vue','vue-router'],
dts:"src/auto-import.d.ts"//生成auto-import.d.ts全局声明
})
]
})
3、toRef toRefs 把变量变成ref
<template>
<div>{{name}}</div>
</template>
return {
name:toRef(person,'name')
或者
...toRefs(person)
}
// 如果想在setup语法糖中使用toRefs
<script setup lang="ts">
let person = reactive({
name: "hello"
});
const { name } = toRefs(person);
</script>
4、shallowReactive,shallowRef 浅层次的响应式
shallowReactive只处理对象最外层的响应。如果一个对象数据,结构比较深,但变化时只是外层属性变化
shallowRef只处理基本数据类型的响应式,不处理对象类型的响应式。如果一个对象数据,后续不会修改该对象中的属性,而是生新的对象来替换。
5、readonly shallowReadonly
readonly:让一个响应式变为只读(深只读)(person=readonly(person))
shallowReadonly:让一个响应式变为只读(浅只读,不能更改最外层的数据)
6、toRaw markRaw
toRaw
将一个由reactive生成的响应式Proxy对象转为普通对象
适用场景:用于读取响应式对象对应的普通对象,对这个普通对象做操作不起作用
markRaw
标记一个对象使其永远不会再成为响应式对象
应用场景:
①、有些值不应被设置为响应式,例如复杂的第三方类库
②、当渲染具有不可变数据源的大列表时,跳过响应式转换可提高性能
③、当添加的元素不想做响应式,但值会正常发生改变:person.car=markRaw('ffff')
7、customRef
创建自定义的ref,并对其依赖项跟踪和更新触发进行显示控制
let custom = myRef('hello')
//自定义ref名称为myRef
function myRef(value) {
return customRef((track, trigger) => {
return {
get() {
console.log('获取最新的值')
track()//追踪当前数据
return value
},
set(newValue) {
console.log('收到最新的值', value)
value = newValue
//通知vue重新解析模板
trigger()
}
}
})
}
8、provide inject
祖孙组件传值
//祖组件
setup(){
let obj={}
provide('obj,obj)
}
//孙组件
setup(){
let obj=inject('obj)
}
9、props
//需要在props中定义
props: {
msg: String,
},
setup(props, context) {
//context为执行上下文
let name = ref(props.msg)
}
//如果是使用了setup语法糖(仅限类型的 props 声明)
1、 `defineProps` 可以使用运行时声明和类型声明两种方式(详见[官方文档](https://link.juejin.cn/?target=https%3A%2F%2Fv3.cn.vuejs.org%2Fapi%2Fsfc-script-setup.html%23%25E4%25BB%2585%25E9%2599%2590-typescript-%25E7%259A%2584%25E5%258A%259F%25E8%2583%25BD "https://v3.cn.vuejs.org/api/sfc-script-setup.html#%E4%BB%85%E9%99%90-typescript-%E7%9A%84%E5%8A%9F%E8%83%BD")),但是不能同时使用。为了使 TS 更好的流转,实践中建议使用上例中的**类型声明**方式。
2、withDefaults 用来声明 props 的默认值,使用方式与运行时声明定义 default 的格式一样
interface Props {
msg?: string
labels?: string[]
}
const props = withDefaults(defineProps<Props>(), {
msg: 'hello',
labels: () => ['one', 'two']
})
10、emit
需要在emits中定义
emits: ['hello'],
//setup中
function sayHello() {
context.emit('hello')
}
//在setup语法糖中
// e 为 emit 的名字,第二个参数的 key 可以随意定义
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
//获取子组件的实例
defineExpose({
sayHello
})
11、computed
let fullName = computed(() => {
return `${props.msg}${props.age}`
})
12、watch watchEffect
首次加载不会监听,只会被监听数据发生变化时才监听到。(惰性)
可以拿到新的值以前的值
可以同时监听多个数据的变化
①、监听ref定义的变量 1个或多个
watch(job, (value) => {
console.log('值变化了', value)
})
//多个时,返回值为数组
watch([job, hobby], (value) => {
console.log('值变化了', value)
}, { immediate: true})
②、监听relative所定义的响应式数据的全部属性
注意1、此处无法正确获取到oldValue
2、强制开启了深度监听(deep配置无效)
watch(obj, (newValue,oldValue) => {
console.log('值变化了', value)
})
③、监听relative所定义的响应式数据的某些属性,props中的数据的监听同理
watch(() => props.msg, (value) => {
console.log('值变化了', value)
}, { immediate: true })
//监听某些具体的属性为对象,deep是起作用的
watch([() => obj.name,() => obj.age], (value) => {
console.log('值变化了', value)
},{deep:true})
watchEffect的特性
watchEffect 也是一个帧听器,是一个副作用函数。
它会监听引用数据类型的所有属性,不需要具体到某个属性,一旦运行就会立即监听,组件卸载的时候会停止监听
不需要手动传入依赖(不用指定监听对象)
无法获取原始值,只能获取更新后的值
立即执行(在onMounted前调用)
一些异步操作放里面更加的合适
只能拿到最新的值
不需要指定监听的数据,组件初始化的时候就会执行一次用以收集依赖,而后收集到的依赖发生变化,这个回调才会再次执行
有点像computed,但computed更注重计算出来的值,必须写返回值。watchEffect更注重过程,不用写返回值。
watchEffect(() => {
//会监听在里面使用的值
console.log('发生变化',job.value) // ref 类型的需要加.value
})
onInvalidate 解析
①、停止监听
let stop= watchEffect(() => {
console.log('发生变化',job.value) // ref 类型的需要加.value
})
stop()
②、配置项:
副作用刷新时机:flush 一般使用post
pre:组件更新前执行
aync:强制效果始终同步触发
post:组件更新后执行
onTrigger 用来调试watchEffect
③、清除副作用
有时副作用函数会执行一些异步的副作用,这些响应需要在其失效时清除 (场景:有一个页码组件里面有5个页码,点击就会异步请求数据。于是做一个监听,监听当前页码,只要有变化就请求一次。问题:如果点击的比较快,从1到5全点了一遍,那么会有5个请求,最终页面会显示第几页的内容?第5页?那是假定请求第5页的ajax响应的最晚,事实呢?并不一定。于是这就会导致错乱。还有一个问题,连续快速点5次页码,等于我并不想看前4页的内容,那么是不是前4次的请求都属于带宽浪费?这也不好。
于是官方就给出了一种解决办法:
侦听副作用传入的函数可以接收一个 onInvalidate 函数作入参,用来注册清理失效时的回调。
13、生命周期及对应写入setup中的表示
生命周期 setup中
beforeCreate setup()
created setup()
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmounted
unmounted onUnmounted
13、自定义hook函数
把setup中使用的composition API进行封装,类似于mixin
//hook->usePoint.ts
import { reactive,onMounted } from 'vue';
export default function(){
//实现鼠标打点的数据
let point = reactive({
x: 0,
y: 0
})
onMounted(() => {
window.addEventListener('click',(event) => {
point.x = event.pageX
point.y = event.pageY
})
})
return point
}
//使用时
import usePoint from './hooks/usePoint'
let point=usePoint()
14、响应式数据的判断
isRef
isReactive
isReadonly
isProxy:检查一个对象是否由reactive或者readonly方法创建的代理
fragment(片段)
组件没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中
15、teleport(传送)
能够将我们的模板移动到 DOM 中 Vue app 之外的其他位置的技术
<button>显示弹窗</button>
<teleport to="body">
<div>弹窗内容</div>
</teleport>
16、defineAsyncComponent
等待异步组件时渲染一些额外的内容,让应用有更好的用户体验。
缺点:在网速慢的时候会有抖动的效果。使用Suspense进行占位(这个是实验属性)。
<Suspense>
<template v-slot:default>
<Child></Child>
</template>
<template v-slot:fallback>占位内容</template>
</Suspense>
import { defineAsyncComponent } from 'vue';
const Child=defineAsyncComponent(()=>import('./components/Child'))//异步引入
export default defineComponent({
name: 'App',
components: {
Child
},
}
二、差异调整
api调整
2.X全局api 3.X实例api
Vue.config.xxx app.config.xxx
Vue.config.productionTip 移除
Vue.component app.component
Vue.directive app.directive
Vue.mixin app.mixin
Vue.use app.use
Vue.prototype app.config.globalProperties
其他调整
①、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;
}
事件修饰符(不变此处记录一下)
.prevent:阻止默认事件
.stop:阻止事件冒泡
.once:事件只触发一次
.capture:使用事件的捕获模式
.self:只有event.target是当前操作的元素时才触发事件
.passive:事件的默认行为立即执行,无需等待事件回调执行完毕
③、移除keyCode作为v-on的修饰符,同时也不再支持config.keyCode
不能使用@keyup.13这种使用方法
Vue.config.keyCode.huiche=13 //不支持这样定义别名按键
④、移除v-on.native修饰符
父组件中绑定事件
<Child
v-on:close="handleClose"
v-on:click="handleClick"
>
</Child>
子组件中声明自定义事件
export default{
//如果不声明就是原生事件,声明了就是自定义事件
emits:['close']
}
⑤、移除过滤器filter