vue3 的新用法
前言:目前在使用 vue3 来写新的项目,因为是第一次使用这个 vue3,所以有很多不熟练的地方,所以写下这个文章来加深一下自己的印象。
setup 的使用?
setup是 vue3 的新的 API,官方的解释是作为一个组件选项,在创建组件之前执行。
setup 接受两个参数,props和context两个参数,前者是用来接收父组件的传值。后者是一个普通的 JavaScript 对象(非响应式),它暴露组件的三个属性attrs、slots、emit。
生命周期的更新
beforeCreate -> 使用 setup()
created -> 使用 setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeUnmount -> onBeforeUnmount
unmounted -> onUnmounted
errorCaptured -> onErrorCaptured
renderTracked -> onRenderTracked
renderTriggered -> onRenderTriggered
activated -> onActivated
deactivated -> onDeactivated
props 的解构
因为props是使用proxy来代理的,所以不能进行解构,需要使用vue的toRefs来完成属性的解构
eg:
import { toRefs } from 'vue';
// 还是要声明props的具体属性的,不然解构个寂寞。
props: {
title: String
},
setup(props) {
const { title } = toRefs(props)
console.log(title.value)
}
当前,如果传的值是可选的话,toRefs 是不会解构出来的,这个时候需要用到toRef来做替代:
import { toRef } from 'vue'
props: {
title?: String // 这样设置可选项?
},
setup(props) {
const title = toRef(props, 'title')
console.log(title.value)
}
官方的万金油类型声明示例
interface Data {
[key: string]: unknown;
}
interface SetupContext {
attrs: Data;
slots: Slots;
emit: (event: string, ...args: unknown[]) => void;
}
function setup(props: Data, context: SetupContext): Data;
如何创建响应式 data?
vue3 新增了ref和reactive来创建响应式对象。
ref
接受一个内部值并返回一个响应式且可变的ref对象,ref 对象具有指向内部值的 property 的.value
eg:
const num = ref(0);
console.log(num.value); // 0
num.value = 6; // 赋值操作
console.log(num.value); // 6 取值
有的时候可能需要指定复杂的类型,那么就可以在定义的时候传值一个泛型参数,操作如下:
const foo = ref<string | number>('foo'); // foo 的类型:Ref<string | number>
foo.value = 123; // ok!
watch 以及 watchEffect 的区别?在什么时候用更好?
watch
官方文档上watch API 与选项式 APIthis.$watch完全等效,watch 需要监听特定的数据源,并且在单独的回调函数中执行副作用(我理解副作用也就是自己的业务逻辑)。默认情况下,watch 是惰性的--即回调仅仅在监听的数据源发生更改的时候被调用。
watch 监听一个数据源
// 侦听一个 getter,官方给的例子,目前未遇到使用场景。看到router监听params有用到。
const state = reactive({count: 0});
watch(
() => state.count,
(count, prevCount) => {
/* ... */
}
);
// 直接侦听一个 ref
const count = ref(0);
watch(count, (count, prevCount) => {
/* ... */
});
watch 监听多个数据源
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
/* ... */
});
watchEffect
官方解释:响应式的跟踪其依赖项时立即运行一个函数,并在更改依赖项时重新运行它。
我是没有看懂,首先依赖项来源在哪里,如何标记依赖项?其次在依赖项有更改的时候重新运行,那么多个依赖项在更改都会重新运行吗?
先看下官方给的例子
// 首先创建一个响应式数据源
const count = ref(0);
// 立即执行一次,此时肯定打印的是0.
watchEffect(() => console.log(count.value));
setTimeout(() => {
// 数据变化的时候也会执行一次
count.value++;
}, 100);
自己测试的一个例子
const count = ref(0);
const num = ref(8);
watchEffect(() => {
console.log(count.value);
console.log(num.value);
});
setTimeout(() => {
count.value++;
}, 1000);
setTimeout(() => {
num.value++;
}, 1000);
先看下结果:
==第一次打印==:0,8
==第二次打印==:1, 8, 1,9(1s 以后打印四个)
解释:第一次打印是 watchEffect 的特性,它会立即执行一次,所以这里会打印两个数据源的初始值。第二次打印四个是因为两个数据源都进行了赋值操作,count 在累加之后值有变动触发了一次副作用,同理 num 的值有变化的时候也出发了一次副作用。
假设只打印 count 会发生什么事情
const count = ref(0);
const num = ref(8);
watchEffect(() => {
console.log(count.value);
});
setTimeout(() => {
count.value++;
}, 1000);
setTimeout(() => {
num.value++;
}, 1000);
==第一次打印==: 0
==第二次打印==: 1
解释:这个就很明显了,这个依赖项的来源就是只有写在 watchEffect 中才会产生副作用.
:muscle: 这节很大,你忍一下
多个数据源对数据的影响
直接看下面的代码
const count = ref(0);
const num = ref(8);
watchEffect(() => {
console.log(count.value);
num.value++;
});
setTimeout(() => {
count.value++;
}, 1000);
setTimeout(() => {
num.value++;
}, 1000);
setTimeout(() => {
console.log(num.value);
}, 4000);
==第一次==:0 也就是初始化的时候打印的内容
==第二次==:1 1 两次 timeout 在 1s 以后同时打印出 count 的值为 1
==第三次==:12
:question: 为什么会是 12
:bell: 首先明确第三次打印也就是 4s 之后打印的是 num 的值,在初始化的时候因为会执行一次 watchEffect,所以 num 变成了 9,然后 1s 之后 num 的值发生了变化使得 num 又变化了一次变成了 10,同时 num 的值因为定时器也加了一次变成了 11,但是这次仍然触发了副作用所以 num 累加了一次变成了最终的 12.
延伸,如果写的是一个复杂的数据呢?
const state = reactive({
count: 0,
num: 1,
});
watchEffect(() => {
// 如果是这么写,count或者num变化都会触发副作用
console.log(state.count);
console.log(state.num);
// 如果这么写,只会在开始的时候执行一次,打印state的初始值
console.log(state);
// 如果只写这一个,count变化才会打印的
console.log(state.count);
});
setTimeout(() => {
state.count++;
}, 1000);
setTimeout(() => {
state.num++;
}, 1000);
watchEffect 执行的时机?
看了上面之后就会发现,这个 watchEffect 的执行时机就是在其内部写入的数据源在外部(赋值操作不写在 Effect 中,可以参考上面例子 num.value++写在了副作用中但是没有重复执行,这么做事合理的不然会造成无限互相调用的情况。)有发生变化就会触发。
computed 如何使用呢?
首先看下官方给的解释:接受一个 getter 函数,并为从 getter 返回的值返回一个不变的响应式 ref 对象。
官方例子:
const count = ref(1);
const plusOne = computed(() => count.value + 1);
console.log(plusOne.value); // 2
plusOne.value++; // 错误
这个例子给出的信息有以下两点:
-
computed 返回的也是一个响应式的对象,你可以通过内置 value 获取它的值;
-
computed 创建的对象是无法主动赋值的,这个跟之前是保持一致的;
通过其内置的get和set来创建可写的 ref 对象
const count = ref(1);
const plusOne = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1;
},
});
// 在赋值的时候可以触发其内部的set函数
plusOne.value = 1;
console.log(count.value); // 0
slot 在 vue3 废弃了,那应该如何使用?
略
router 如何使用?
因为 vue3 是没有 this,所以就无法使用this.$router或者this.$route来进行路由操作。所以我们需要新的 API 来满足需求,官方当然能想到这种问题,看下如何实现。
// 类似react的操作,
import {useRouter, useRoute} from 'vue-router';
export default {
setup() {
const router = useRouter();
const route = useRoute();
function pushWithQuery(query) {
router.push({
name: 'search',
query: {
...route.query,
},
});
}
},
};
剩下的其他操作感觉没有什么变化,例如路由守卫,以及懒加载相关的,就不在赘述。
why Composition API?
个人觉得优雅的创建响应式 data 的方式
setup() {
// 可以把需要的变量都放在这里
const state = reactive({
count: 0,
num: 10
})
// toRefs把响应式的对象给解构,这样在template中就可以直接使用count作为数据了。
return {
...toRefs(state)
}
}
参考文档: