Fork me on github

关于 Vue3 setup 语法糖

Vue3 setup

参考:https://juejin.cn/post/7009282373476941831#comment

新的 setup 是组件创建之前, props 被解析之后执行,是组合式 API 的入口

在 setup 中应该避免使用 this, 因为它不会找到示例。setup 的调用发生在 data property,computed property 或 methods 被解析之前,所以它们无法在 setup 中被获取

setup 是一个接收 props 和 context 的函数,setup 返回的所有内容都暴露给组件的其余部分(计算属性、方法、生命周期钩子等等)以及组件的模版,所有 ES 模块导出都被认为是暴露给上下文的值,并包含在 setup()返回对象中。

使用后,script 标签中的内容相当于原本组件声明中 setup() 的函数体,不过也有一定的区别。

使用 script setup 语法糖,组件只需引入不用注册,属性和方法也不用返回,也不用写 setup 函数,也不用洗 export default,甚至是自定义指令也可以在我们的 template 中自动获得。

调用时机

创建组实例,然后初始化 props,紧接着就调用 setup 函数。从生命周期钩子的视角来看,它会在 beforeCreate 钩子之前被调用。

模板中使用

如果 setup 返回(以count例子来看,可以暂时理解为创建)一个对象,则对象的属性将会被合并到组件模板的渲染上下文。

setup 参数

第一个参数接收一个响应式的 props ,这个 props 指向的是外部的 props。如果没有定义 props 选项,setup 中的第一个参数将为 undefined。

  • 不要在自组件中修改 props,如果尝试修改,会得到警告甚至报错
  • 不要解构 props,解构的 props 会失去响应式

第二个参数提供了一个上下文对象

组件自动注册

导入 component 或 directive 直接 import 即可,无需额外声明

import { MyButton } from "@/components"
import { directive as clickOutside } from 'v-click-outside'

在 script setup 中,引入的组件可以直接使用,无需再通过 components 进行注册,并且无法指定当前组件的名字,它会自动以文件名为主,也就是无法再写 name 属性了。

如果需要定义类似 name 的属性,可以再加个平级的 script 标签,在里面实现即可

组件核心 API 的使用

定义组件的 props

通过 defineProps 指定当前的 props 类型,获得上下文的 props 对象。

<script setup>
  import {defineProps} from 'vue'
  
  const props = defineProps({
    title: String
  })
</script>

或者

<script setup lang="ts">
  import {ref, defineProps} from 'vue'
  
  type Props = {
    msg: string
  }
  
  defineProps<Props>()
</script>

定义 emit

使用 defineEmit 定义当前组件含有的事件,并通过返回的上下文去执行 emit

<script setup>
  import { defineEmit } from 'vue'
  
  const emit = defineEmit(['change', 'delete'])
</script>

父子组件通信

defineProps 用来接收父组件传来的 props;defineEmits 用来声明触发的事件

父组件

<template>
  <my-son :foo="count" @childClick="childClick"></my-son>
</template>

<script setup lang="ts">
  import MySon from "./MySon.vue";
  import {ref} from "vue";

  let count = ref(0)

  let childClick = (e: any): void => {
    count.value++
    console.log("from son", e)
  }
</script>

自组件

<template>
  <span @click="sonToFather">信息: {{props.foo}}</span>
</template>

<script lang="ts" setup>
  import {defineEmits, defineProps} from "vue";

  const emit = defineEmits(["childClick"]); // 声明触发事件
  const props = defineProps({foo: Number})      // 获取 props

  const sonToFather = () => {
    emit("childClick", props.foo)
  }
</script>

<style scoped></style>

自组件通过 defineProps 接收父组件传过来的数据,自组件通过 defineEmits 定义事件发送信息给父组件

增强的 props 类型定义

  const props2 = defineProps<{
    foo: number,
    bar?: number
  }>()
  
  const emit2 = defineEmits<(
    e: 'update' | 'delete',
    id: number
  ) => void>()

采用这种方法将无法使用 props 默认值

定义响应变量、函数、监听、计算属性 computed

watchEffect

<script setup lang="ts">
import {ref, computed, unref, watchEffect} from 'vue';

const count = ref(0) // 不用 return ,直接在 template 中使用

const addCount = () => {
  count.value ++
}

// 定义计算属性,使用同上
const howCount = computed(() => "现在count值为: " + count.value)

// 定义监听,使用同上,用于有副作用的监听,会自动收集依赖,和 watch 区别:无需区分deep,immediate,只要依赖的数据发生变化就会调用
watchEffect(() => console.log(count.value))

</script>

reactive

reactive 是一个响应式对象,ref 是一个 { value: 'xxx' }的结构

<script setup lang="ts">
import {reactive, onUnmounted} from "vue";

const state = reactive({
  counter: 0
})

// 定时器,每秒都会更新数据
const timer = setInterval(() => {
  state.counter ++
}, 1000)

onUnmounted(() => {
  clearInterval(timer)
})
</script>

<template>
  <div>{{state.counter}}</div>
</template>

ref 暴露变量到模板

无需 export 声明,编译器会自动寻找模板中使用的变量。

生命周期方法

因为 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地定义它们。
换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。

可以在生命周期钩子前面加上 "on" 来访问组件的声明周期钩子。

image

获取 slots 和 attrs

useAttrsuseSlots

useAttrs 得到的结果
image

defineExpose API

传统的写法,我们可以在父组件中,通过 ref 实例的方式去访问自组件的内容,但在 script setup 中,该方法就不能用了,setup 相当于是一个闭包,除了内部的 template 模板,谁都不能访问内部的数据和方法。

如果需要对外暴露 setup 中的数据和方法,需要使用 defineExpose API

const a = 1
const b = ref(2)
defineExpose({a,b})

未亲自测试!!

支持 async await 异步

在 vue3 的源代码中,setup 执行完毕,函数 getCurrentInstance 内部的有个值会释放对 currentInstance 的引用
await 语句会导致后续代码进入异步执行的情况。

所以上述例子中最后一个 getCurrentInstance() 会返回 null,建议使用变量保存第一个 getCurrentInstance() 返回的引用

上述结论还没有亲自实验证实

定义组件其它配置

配置项的缺失,有时候我们需要更改组件选项,在 setup 中我们目前是无法做到的。
我们需要在上方再引入一个 script ,在上方写入对应的 export 即可,需要单开一个 script

import HelloWorld from '../components/HelloWorld.vue' // 在 setup() 作用域中执行 (对每个实例皆如此) // your code ```

关于 TS 和 ESLint 的不完美

  1. 与@typescript-eslint/no-unused-vars规则不兼容,影响不大可以关闭
  2. 与导入的类型声明不兼容,当通过解构的方式去导入类型,setup sugar 会进行自动导出。这时候会收到 TS 的一条报错:此为类型,但被当作值使用。解决办法:类型导出使用 export default 导出或者引入时使用 import * as xx 来进行导入,也可以使用 import type {test} from "./test"解决

使用习惯

推荐分区方式:

  1. 相关引入
  2. 响应式数据,props,emit定义
  3. 生命周期以及 watch 书写
  4. 方法定义
  5. 方法、属性暴露

组件抽离:将页面拆成两个文件夹,一个为views ,另一个为components。
views 目录为页面入口,掌管数据,而components则为页面中一些组件抽离。如果是公共组件,再抽离到 components目录下其它位置。

hook 抽离:尽可能将逻辑抽离,并不一定要进行复用。

posted @ 2023-04-29 14:39  zjy4fun  阅读(157)  评论(0编辑  收藏  举报