一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第6天,点击查看活动详情。
前面我们已经将环境和代码校验、以及打包构建环境搭建完成,接下来我们要开启全新的篇章,学习并使用Vue3和TypeScript。
往期vite项目搭建过程如下:
Vue3学习小结
createApp函数
在Vue3中,通过使用createApp函数创建一个新的应用实例。
createApp函数接收两个参数:第一个参数是根组件的选项对象(通常是App组件),第二个参数是根prop(几乎不用).
并且通过createApp生成的实例可以进行链式调用
import { createApp } from "vue";
const app = createApp(
{
//选项对象
},
{
//prop
}
);
//进行链式调用
app
.use(xxx)
.use(xxx)
.mount('#app');
它的类型声明如下(引用自Vue官网中对createApp的描述):
interface Data {
[key: string]: unknown
}
export type CreateAppFunction<HostElement> = (
rootComponent: PublicAPIComponent,
rootProps?: Data | null
) => App<HostElement>
可以看出,createApp函数第一个参数接收的就是根组件,而第二个参数是可选属性,是传递给根组件的prop。返回的是一个App的实例,通过查看源码,可以看出,该实例中包含了很多的方法:
从上图我们可以看到很多常见的方法,例如
use、mixin、component、directive、mount等等. 我们仔细观察可以发现,像use方法返回值是this,而this就是调用该use方法的实例(app),所以就可以解释为什么createApp函数生成的app实例可以进行链式调用. 妙啊👍。
组件props和非Prop的attribute
- 组件props
常规的传递props给这边就不赘述了,这里介绍一下如何将一个对象的所有property传递给子组件.
如果想要将一个对象的所有property都作为prop传入,可以使用不带参数的 v-bind (用 v-bind 代替 :prop-name)-----来自Vue官网-传入一个对象
post: {
id: 1,
title: 'My Journey with Vue'
}
如何传入:
<blog-post v-bind="post"></blog-post>
//等价于
<blog-post v-bind:id="post.id" v-bind:title="post.title"></blog-post>
-
单向数据流: 所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定
简单来说,就是不允许在子组件中直接修改父组件中传递过来的值,因为这可能导致你的数据流向变得难以理解。
-
Prop的大小写命名 正常我们使用驼峰命名法命名的属性名在template中作为prop传递时,需要将其转成kebab-case写法:
<text post-name="hello" :current-page="1"></test>
接收
props: ['postName', 'currentPage']
- 非Prop的attribute
一个非prop的attribute是指传向一个组件,但是该组件并没有相应props或emits定义的 attribute。常见的示例包括 class、style 和 id attribute。可以通过 $attrs property 访问那些 attribute。
具体使用方法看Vue官网-
attrs
这里介绍一下v-bind="$attrs"在项目中可能用到的使用场景:我们在封装一些自定义的组件时,比如svg图标组件,可以通过使用v-bind="$attrs"将属性放到我们想要它存在的地方:
<!-- svg图标组件: svg-icon是我自己封装的组件,通过全局注册的方法变成全局组件 -->
<svg-icon name="smile" width="1.5rem" height="1.5rem"></svg-icon>
在svg-icon组件的定义中,我只接收显示的name属性,但是width属性和height我却没有接收,通过在svg标签上添加v-bing="$attrs"我们就不必将每一个属性都显示的接收。
使用v-model进行父子组件通信
父组件中:
<child v-model="keywords" v-model:keywords2="keywords2"></child-comp>
//...
setup() {
const keywords = ref<string>('');
const keywords2 = ref<string>('');
return {
keywords,
keywords2
}
}
//...
在子组件上接收:
//子组件
props: {
modelValue: {
type: String as PropType<string>,
required: true
},
keywords2: {
//...
}
},
emits: ['update:modelValue', 'update:keywords2']
使用defineProps和defineEmits
在<script setup> 中必须使用 defineProps 和 defineEmits API 来声明 props 和 emits
在子组件中接收如下所示:
<script lang="ts" setup>
const props = defineProps({
modelValue: //...
keywords2: //...
});
const emits = defineEmits(['update:modelValue', 'update:keywords2'])
</script>
另外defineProps和defineEmits还可以指定接收一个泛型
type Props = {
modelValue: boolean;
};
const props = defineProps<Props>();
const emits = defineEmits(['update:modelValue']);
遇到的问题:在使用defineProps时传入泛型时,发现当我们从外部文件导入一个type别名时,传给defineProps报错!
setup的第一个参数不能解构
如果使用ts的defineComponent方法定义组件的话,使用setup语法糖时可以指定接收两个参数,第一个参数是props, 第二个参数context是一个普通的对象(可以解构).
<script lang="ts">
setup(props, context) {
// Attribute (非响应式对象,等同于 $attrs)
console.log(context.attrs);
// 插槽 (非响应式对象,等同于 $slots)
console.log(context.slots);
// 触发事件 (方法,等同于 $emit)
console.log(context.emit);
// 暴露公共 property (函数)
console.log(context.expose);
},
</script>
如果对props使用ES6的解构赋值的话,它会消除props的响应式,如果一定需要解构,我们可以在setup函数中使用toRefs函数来完成
import { toRefs } from 'vue';
setup(props) {
const { title } = toRefs(props);
}
认识ref相关API
- ref: 接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个
.valueproperty,指向该内部值。 Ref TS 对应的接口如下所示:
interface Ref<T> {
value: T;
}
通过ref定义的变量,在使用时需要使用.value来进行赋值与访问,如果在template模板中,可以省略
<script setup lang="ts">
import { ref, Ref } from "vue";
let name = ref<string>("message");
let age: Ref<number> = ref(18);
</script>
-
isRef: 判断是不是一个 ref 对象(没啥好举例的,就是字面意思😂)
-
shallowRef: 创建一个跟踪自身.value 变化的 ref,但不会使其值也变成响应式的
<template>
<span>{{ person }}</span>
<button @click="changeName">改名字</button>
</template>
<script setup lang="ts">
import { Ref, shallowRef } from "vue";
interface Test {
name: string;
};
let person: Ref<Test> = shallowRef({
name: "张三",
});
const changeName = () => {
person.value.name = "李四...."; //修改器属性是非响应式的,这样是不会改变的
person.value = { //这样是可以被监听到的,修改value(只跟踪自身.value变化)
name: '法外狂徒'
}
};
</script>
- triggerRef: 强制更新页面DOM
上一个API
shallowRef只能跟踪自身.value的变化,我们可以通过triggerRef强制更新页面DOM
const changeName = () => {
person.value.name = "李四...."; //修改器属性是非响应式的,这样是不会改变的
triggerRef(person);//这样也是可以改变值的
};
- customRef: 自定义ref
创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收
track和trigger函数作为参数,并且应该返回一个带有get和set的对象。4
具体可以看官网的例子:Vue官网-customRef