# vue2 遇到的难题
- 随着功能的增长,复杂组件的代码变得难以维护。
- 随着复杂度的上升,每个功能实现的逻辑很乱,很难按照逻辑进行分类(分散在不同的声明周期中)。
- 虽然 mixin 可以解决但是存在命名冲突、不清楚暴露出来变量的作用、重用到其他 component 经常会遇到很多问题。
- vue2 对于 typescript 的支持非常有限。
# vue3 新特性
# ref
可以通过 ref 定义一个响应式的值:跟 react 的 useRef 类似。 使用 ref 一般定义原始类型数据。将原始类型转换为响应式对象。
<template>
<h1>{{count}}</h1>
<h2>{{double}}</h2>
<button @click="increase">赞</button>
</template>
<script lang="ts">
import { defineComponent, ref, computed } from 'vue';
interface DataProps {
count: number;
}
export default defineComponent({
name: 'App',
// 无法访问 this,在生命周期之前运行
setup() {
// 响应式数据定义
const count = ref(0)
// 计算属性
const double = computed(() => {
return count.value * 2
})
// 定义方法
const increase = () => {
count.value ++
}
return {
count,
double,
increase
}
}
});
</script>
# reactive
reactive 与 ref 非常类似,接收一个 Object 参数:
<template>
<h1>{{count}}</h1>
<h2>{{double}}</h2>
<button @click="increase">赞</button>
</template>
<script lang="ts">
import { defineComponent, computed, reactive } from 'vue';
interface DataProps {
count: number;
increase: () => void;
double: number;
}
export default defineComponent({
name: 'App',
// 无法访问 this,在生命周期之前运行
setup() {
const data: DataProps = reactive({
count: 0,
increase: () => {
// 注意在 reactive 中不需要使用 value 属性去访问数据
data.count ++
},
// 使用 computed 定义会造成类型推论的循环--解决方法就是给 data 指定类型
double: computed(() => {
return data.count * 2
})
})
// 这里注意不能使用展开运算符 ...data
return {
data
}
}
});
</script>
需要注意的事项:
在 setUp 函数返回中,不能直接将 data 进行解构返回:
// 这样返回是获取不到响应式数据,因为结构后得到的是基本类型数据
return {
...data
}
// 可以直接返回
return {
data
}
直接返回 data 在模板展示中需要 data.count 这样写;也可以通过 toRefs API 来将传入的对象的每一项转换为响应式对象。
const refData = toRefs(data)
// 这里注意不能使用展开运算符 ...data
return {
...refData
}
# 响应式数据监听优化
vue3 中可以监听数组等对象的属性修改监听,比如: 数组可以直接通过索引进行赋值、以及监听对象属性变化进行渲染,不在使用 $set 去改变属性值。
import { defineComponent, computed, reactive, toRefs } from 'vue';
interface DataProps {
numbers: number[];
person: {
name?: string;
};
}
export default defineComponent({
name: 'App',
// 无法访问 this,在生命周期之前运行
setup() {
const data: DataProps = reactive({
numbers: [0, 1, 2],
person: {}
})
data.numbers[0] = 5
data.person.name = 'jiegiser'
const refData = toRefs(data)
// 这里注意不能使用展开运算符 ...data
return {
...refData
}
}
});
# 生命周期的变化
主要是修改了 beforeDestory 以及 destroyed 添加了对应的两个别名:beforeUnmount 以及 unmounted;其他
下面是在 setup 函数中使用的声明周期 API;
主要是声明周期都添加了 on 前缀,以及 beforeCreate 以及 created 可以在 setup 中直接写。新增了调试相关(观察数据变化) api :onRenderTRacked、onRenderTriggered
// mapping vue2 to vue3
beforeCreate -> use setup()
created -> use setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdatae -> onBeforeUpdate
updated -> onUpdated
beforeDestory -> onBeforeUnmount
destroyed -> onUnmounted
acticated -> onActivated
deactivated -> onDeactivated
errorCaptured -> onErrorCaptured
// added 调试作用
onRenderTRacked
onRenderTriggered
例子:
import {
defineComponent,
computed, reactive,
toRefs,
onRenderTriggered,
onMounted,
onUpdated
} from 'vue';
interface DataProps {
count: number;
increase: () => void;
double: number;
numbers: number[];
person: {
name?: string;
};
}
export default defineComponent({
name: 'App',
// 无法访问 this,在生命周期之前运行
setup() {
onMounted(() => {
console.log('mounted')
})
onUpdated(() => {
console.log('updated')
})
onRenderTriggered((event) => {
console.log(event)
// effect: ƒ reactiveEffect()
// key: "count"
// newValue: 1
// oldTarget: undefined
// oldValue: 0
// target: {count: 1, double: ComputedRefImpl, numbers: Array(3), person: {…}, increase: ƒ}
// type: "set"
// __proto__: O
})
const data: DataProps = reactive({
count: 0,
increase: () => {
// 注意在 reactive 中不需要使用 value 属性去访问数据
data.count ++
},
// 使用 computed 定义会造成类型推论的循环--解决方法就是给 data 指定类型
double: computed(() => {
return data.count * 2
}),
numbers: [0, 1, 2],
person: {}
})
data.numbers[0] = 5
data.person.name = 'jiegiser'
const refData = toRefs(data)
// 这里注意不能使用展开运算符 ...data
return {
...refData
}
}
});
# watch
import {
defineComponent,
reactive,
toRefs,
watch
} from 'vue';
interface DataProps {
count: number;
}
export default defineComponent({
name: 'App',
// 无法访问 this,在生命周期之前运行
setup() {
const data: DataProps = reactive({
count: 0,
})
const refData = toRefs(data)
// 监听的值为响应式对象 - 使用 ref 定义的变量或者使用 toRefs 转换的变量。
watch(refData.count, (newValue, oldValue) => {
console.log('update count', newValue, oldValue)
})
// 这里注意不能使用展开运算符 ...data
return {
...refData
}
}
});
注意监听多个值,newValue,oldValue 也是对应一个数组;如果监听的值是一个 reactive 创建的变量的属性,需要通过函数返回;
watch([refData.count, () => data.count], (newValue, oldValue) => {
console.log(newValue, oldValue)
})
# 自定义 hooks 替换 mixin
可以通过自定义 hooks 来实现 mixin 的功能:
<template>
<h1>x:{{x}}-y:{{y}}</h1>
</template>
<script lang="ts">
import { ref, defineComponent, onMounted, onUnmounted } from 'vue';
export default defineComponent({
name: 'App',
// 无法访问 this,在生命周期之前运行
setup() {
const x = ref(0)
const y = ref(0)
const updateMouse = (e: MouseEvent) => {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => {
document.addEventListener('click', updateMouse)
})
onUnmounted(() => {
document.removeEventListener('click', updateMouse)
})
return {
x,
y
}
}
});
</script>
可以将里面的功能进行封装 hooks ,然后引用:
新建 useMousePosition.ts 文件,封装的 hooks
import { ref, onMounted, onUnmounted } from 'vue'
function useMousePositioin() {
const x = ref(0)
const y = ref(0)
const updateMouse = (e: MouseEvent) => {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => {
document.addEventListener('click', updateMouse)
})
onUnmounted(() => {
document.removeEventListener('click', updateMouse)
})
return {
x,
y
}
}
export default useMousePositioin
使用:
<template>
<h1>x:{{x}}-y:{{y}}</h1>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import useMousePositioin from './hooks/useMousePosition'
export default defineComponent({
name: 'App',
// 无法访问 this,在生命周期之前运行
setup() {
const { x, y } = useMousePositioin()
return {
x,
y
}
}
});
</script>
可以使用泛型给 hooks 增加类型注释:
<script lang="ts">
import { defineComponent } from 'vue';
import useMousePositioin from './hooks/useMousePosition'
interface DataProps {
data: string;
error: string;
}
export default defineComponent({
name: 'App',
// 无法访问 this,在生命周期之前运行
setup() {
const { x, y, result } = useMousePositioin<DataProps>()
}
});
</script>
hooks:
import { ref, onMounted, onUnmounted } from 'vue'
function useMousePositioin<T>() {
const result = ref<T | null>(null)
const x = ref(0)
const y = ref(0)
const updateMouse = (e: MouseEvent) => {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => {
document.addEventListener('click', updateMouse)
})
onUnmounted(() => {
document.removeEventListener('click', updateMouse)
})
return {
x,
y,
result
}
}
export default useMousePositioin
阅读量:
评 论:
← 源码相关