Vue3+TS学习实践(一)

409 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 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的实例,通过查看源码,可以看出,该实例中包含了很多的方法:

1.png 从上图我们可以看到很多常见的方法,例如usemixincomponentdirectivemount等等. 我们仔细观察可以发现,像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是指传向一个组件,但是该组件并没有相应propsemits定义的 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 对象仅有一个 .value property,指向该内部值。 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 上一个APIshallowRef只能跟踪自身.value的变化,我们可以通过triggerRef强制更新页面DOM
const changeName = () => {
  person.value.name = "李四...."; //修改器属性是非响应式的,这样是不会改变的
  triggerRef(person);//这样也是可以改变值的
};
  • customRef: 自定义ref 创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收 track 和 trigger 函数作为参数,并且应该返回一个带有 get 和 set 的对象。4

具体可以看官网的例子:Vue官网-customRef