当前位置: 首页 > news >正文

vue3入门- script setup详解上

  • 基本语法
  • 顶层的绑定会被暴露给模板
  • 使用组件
  • 动态组件
  • 递归组件
  • 命名空间组件
    • ./form-components 是一个文件
    • ./form-components 是一个目录
    • 使用说明
    • 总结
  • 使用自定义指令
  • 与普通的 `<script>` 一起使用
  • defineOptions()
  • 顶层 await
  • 导入语句

<script setup> 是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。当同时使用单文件组件与组合式 API 时该语法是默认推荐。相比于普通的 <script> 语法,它具有更多优势:

  • 更少的样板内容,更简洁的代码。
  • 能够使用纯 TypeScript 声明 props 和自定义事件。
  • 更好的运行时性能 (其模板会被编译成同一作用域内的渲染函数,避免了渲染上下文代理对象)。
  • 更好的 IDE 类型推导性能 (减少了语言服务器从代码中抽取类型的工作)。

基本语法

要启用该语法,需要在 <script> 代码块上添加 setup attribute:

<script setup>
console.log('hello script setup')
</script>

里面的代码会被编译成组件 setup() 函数的内容。这意味着与普通的 <script> 只在组件被首次引入的时候执行一次不同,<script setup> 中的代码会在每次组件实例被创建的时候执行。

<script setup> 快速创建一个简单的计数器组件:

// Counter.vue

<template><div><p>当前计数:{{ count }}</p><button @click="increment">增加</button></div>
</template><script setup>
import { ref } from 'vue'console.log('counter loaded')const count = ref(0)function increment() {count.value++
}
</script>

如果你在父组件里多次使用 <Counter />

// Parent.vue

<template><Counter /><Counter /><Counter />
</template>

在 console 控制台上 counter loaded 会出现三次。

顶层的绑定会被暴露给模板

当使用 <script setup> 的时候,任何在 <script setup> 声明的顶层的绑定 (包括变量,函数声明,以及 import 导入的内容) 都能在模板中直接使用:

<template><button @click="log">{{ msg }}</button>
</template>
<script setup>
// 变量
const msg = 'Hello!'// 函数
function log() {console.log(msg)
}
</script>

import 导入的内容也会以同样的方式暴露。这意味着我们可以在模板表达式中直接使用导入的函数,而不需要通过 methods 选项来暴露它。

<template><p>当前日期:{{ formatDate(currentDate) }}</p>
</template><script setup>
import { formatDate } from '@/utils/date'const currentDate = new Date()
</script>

使用组件

<script setup> 范围里的值也能被直接作为自定义组件的标签名使用,无需再通过 components 进行注册

<script setup>
import MyComponent from './MyComponent.vue'
</script><template><MyComponent />
</template>

动态组件

由于组件是通过变量引用而不是基于字符串组件名注册的,在 <script setup> 中要使用动态组件的时候,应该使用动态的 :is 来绑定:

<template><component :is="someCondition ? Foo : Bar" />
</template>
<script setup>
import Foo from './Foo.vue'
import Bar from './Bar.vue'
</script>

递归组件

在实际项目中,可以用递归组件渲染树形结构。以下是一个具体的实现示例:

<template><TreeNode :node="treeData" />
</template><script setup>
import TreeNode from './TreeNode.vue'const treeData = {name: '根节点',children: [{ name: '子节点 1' },{ name: '子节点 2', children: [{ name: '子节点 2.1' },{ name: '子节点 2.2' }] }]
}
</script>

TreeNode.vue 的实现如下:

<template><div class="tree-node"><p>{{ node.name }}</p><div v-if="node.children" class="children"><TreeNode v-for="(child, index) in node.children" :key="index" :node="child" /></div></div>
</template><script setup>
import { defineProps } from 'vue'const { node } = defineProps({node: {type: Object,required: true}
})
</script><style scoped>
.tree-node {margin-left: 20px;
}.children {margin-top: 10px;
}
</style>

在这个实现中,TreeNode 组件通过递归调用自身来渲染树形结构的每个节点及其子节点。node 对象包含了每个节点的 name 和可选的 children 数组,用于表示子节点。

命名空间组件

可以使用带 . 的组件标签,例如 <Foo.Bar> 来引用嵌套在对象属性中的组件。这在需要从单个文件中导入多个组件的时候非常有用:

<template><Form.Input><Form.Label>label</Form.Label></Form.Input>
</template>
<script setup>
import * as Form from './form-components'
</script>

./form-components 在这个例子中是一个模块,它可以是一个包含多个组件的文件,也可以是一个目录。通过 import * as Form from './form-components' 的语法,Form 作为一个命名空间对象,包含了 ./form-components 中导出的所有内容。

以下是对这个例子的详细展开,包括 ./form-components 的可能实现方式:

./form-components 是一个文件

如果 ./form-components 是一个文件,那么它可能是一个 JavaScript 或 TypeScript 文件,导出多个组件。以下是一个可能的实现:

import { defineComponent, h } from 'vue';export const Input = defineComponent({name: 'Input',render() {return h('input', { type: 'text' });},
});export const Label = defineComponent({name: 'Label',render() {return h('label', {}, this.$slots.default ? this.$slots.default() : 'label');},
});

在这种情况下,import * as Form from './form-components' 会将 InputLabel 作为 Form 对象的属性导入,使用时可以通过 Form.InputForm.Label 访问。

./form-components 是一个目录

如果 ./form-components 是一个目录,那么它通常包含一个 index.js 文件,用于汇总导出目录中的所有组件。目录结构可能如下:

form-components/
├── Input.vue
├── Label.vue
└── index.js
  • Input.vue

    <!-- filepath: ./form-components/Input.vue -->
    <template><input type="text" />
    </template>
    
  • Label.vue

    <!-- filepath: ./form-components/Label.vue -->
    <template><label><slot /></label>
    </template>
    
  • index.js

    import Input from './Input.vue';
    import Label from './Label.vue';export { Input, Label };
    

在这种情况下,import * as Form from './form-components' 会从 index.js 中导入所有导出的组件。

使用说明

无论 ./form-components 是文件还是目录,最终在 Vue 组件中使用时,代码如下:

<template><Form.Input><Form.Label>label</Form.Label></Form.Input>
</template>
<script setup>
import * as Form from './form-components';
</script>

总结

  • 如果是文件:直接导出多个组件。
  • 如果是目录:通过 index.js 汇总导出组件。
  • 这种命名空间的方式非常适合组织多个相关组件,避免命名冲突,同时提高代码的可读性和可维护性。

使用自定义指令

全局注册的自定义指令将正常工作。本地的自定义指令在 <script setup> 中不需要显式注册,但他们必须遵循 vNameOfDirective 这样的命名规范:

<template><h1 v-my-directive>This is a Heading</h1>
</template><script setup>
const vMyDirective = {beforeMount: (el) => {// 在元素上做些操作}
}
</script>

如果指令是从别处导入的,可以通过重命名来使其符合命名规范:

<script setup>
import { myDirective as vMyDirective } from './MyDirective.js'
</script>

如下用自定义指令实现拖拽功能:

<template><div v-draggable>拖动我</div>
</template><script setup>
const vDraggable = {beforeMount(el) {el.style.position = 'absolute'el.onmousedown = (e) => {const shiftX = e.clientX - el.getBoundingClientRect().leftconst shiftY = e.clientY - el.getBoundingClientRect().topconst moveAt = (pageX, pageY) => {el.style.left = pageX - shiftX + 'px'el.style.top = pageY - shiftY + 'px'}const onMouseMove = (event) => moveAt(event.pageX, event.pageY)document.addEventListener('mousemove', onMouseMove)el.onmouseup = () => {document.removeEventListener('mousemove', onMouseMove)el.onmouseup = null}}}
}
</script>

与普通的 <script> 一起使用

<script setup> 可以和普通的 <script> 一起使用。普通的 <script> 在有这些需要的情况下或许会被使用到:

  • 声明无法在 <script setup> 中声明的选项,例如 inheritAttrs 或插件的自定义选项 (在 3.3+ 中可以通过 defineOptions 替代)。
  • 声明模块的具名导出 (named exports)。
  • 运行只需要在模块作用域执行一次的副作用,或是创建单例对象。
<script>
// 普通 <script>,在模块作用域下执行 (仅一次)
runSideEffectOnce()// 声明额外的选项
export default {inheritAttrs: false,customOptions: {}
}
</script><script setup>
// 在 setup() 作用域中执行 (对每个实例皆如此)
</script>

在同一组件中将 <script setup><script> 结合使用的支持仅限于上述情况。具体来说:

  • 不要为已经可以用 <script setup> 定义的选项使用单独的 <script> 部分,如 propsemits
  • <script setup> 中创建的变量不会作为属性添加到组件实例中,这使得它们无法从选项式 API 中访问。我们强烈反对以这种方式混合 API。

如果你发现自己处于以上任一不被支持的场景中,那么你应该考虑切换到一个显式的 setup() 函数,而不是使用 <script setup>

defineOptions()

这个宏可以用来直接在 <script setup> 中声明组件选项,而不必使用单独的 <script> 块:

<script setup>
defineOptions({inheritAttrs: false,customOptions: {/* ... */}
})
</script>

这是一个宏定义,选项将会被提升到模块作用域中,无法访问 <script setup> 中不是字面常数的局部变量。

在 Vue 3 的 <script setup> 语法里,默认组件名是由文件名决定的。不过你也能通过 defineOptions 宏来明确指定组件名。以下是一个示例:

<template><div><p>这是 {{ name }} 组件</p></div>
</template><script setup>
// 定义组件选项,指定组件名
defineOptions({name: 'MyCustomComponent'
})
</script>

顶层 await

<script setup> 中可以使用顶层 await。结果代码会被编译成 async setup()

<script setup>
const post = await fetch(`/api/post/1`).then((r) => r.json())
</script>

<script setup> 中使用 await 时,Vue 会自动将代码编译为保留当前组件实例上下文的格式。这意味着在 await 表达式之后,代码仍然能够正确访问组件实例的相关数据和方法,而不会因为异步操作导致上下文丢失。例如:

<script setup>
const data = await fetchData(); // 在这里使用 await
console.log(data); // 仍然可以访问组件实例的上下文
</script>

这种处理方式确保了在异步操作完成后,组件的逻辑能够继续正常运行,而无需手动绑定上下文。

async setup() 必须与 Suspense 组合使用才能保证页面正常渲染,该特性目前仍处于实验阶段。

父组件中用 Suspense 包裹 <Child> 组件以避免警告和渲染错误:

<template><Suspense><Child /></Suspense>
</template>
<script setup>
import Child from './Child.vue'
</script>

导入语句

在代码实例中,展示了 Vue 3 中 <script setup> 语法下的几种导入方式。

以下是对每种导入方式的详细讲解:

  1. 标准相对路径导入
import { componentA } from './Components'
  • 含义: 使用相对路径 ./Components 导入 componentA
  • 特点:
    • ./Components 表示当前文件所在目录。
    • 这种方式是标准的 ECMAScript 模块导入方式,适用于项目中明确的文件路径。
  • 适用场景: 当文件路径明确且不需要跨目录时,推荐使用这种方式。

  1. 使用 @ 别名导入
import { componentB } from '@/Components'
  • 含义: 使用 @ 作为别名导入 componentB
  • 特点:
    • @ 通常被配置为项目的 src 目录的别名。
    • 这种别名需要在构建工具(如 Vite 或 Webpack)的配置文件中定义。例如:
      // Vite 配置示例
      import { defineConfig } from 'vite'
      import vue from '@vitejs/plugin-vue'export default defineConfig({plugins: [vue()],resolve: {alias: {'@': '/src', // 定义 @ 为 src 目录},},
      })
      
  • 适用场景: 当项目结构复杂且需要频繁引用 src 目录下的文件时,使用别名可以提高代码可读性和维护性。

  1. 使用 ~ 别名导入
import { componentC } from '~/Components'
  • 含义: 使用 ~ 作为别名导入 componentC
  • 特点:
    • ~ 是另一种常见的别名,具体含义取决于构建工具的配置。
    • 在某些项目中,~ 可能被配置为项目根目录或其他特定目录。
    • 例如,在 Vite 中可以这样配置:
      resolve: {alias: {'~': '/project-root', // 定义 ~ 为项目根目录},
      }
      
  • 适用场景: 当需要引用项目根目录或其他特定目录的文件时,可以使用 ~ 别名。

总结:

  • 相对路径导入: 标准且无需额外配置,但路径可能较长。
  • @ 别名导入: 常用于引用 src 目录,简化路径。
  • ~ 别名导入: 灵活性更高,具体含义取决于项目配置。

在实际项目中,推荐根据团队约定和项目需求选择合适的导入方式,并确保在构建工具中正确配置别名。

http://www.xdnf.cn/news/1456741.html

相关文章:

  • 【深度学习】(9)--调整学习率
  • ACMESSL自动续签教程
  • 安徽某能源企业积极推进运维智能化转型,引入高压配电房机器人巡检系统
  • 笔记2 FreeRTOS任务
  • 如何在Spring Boot项目中使用MapStruct?
  • 旅游安全急救实训室助力应急处置技能实战化
  • Websocket的Key多少个字节
  • 【Big Data】云原生与AI时代的存储基石 Apache Ozone 的技术演进路径
  • 深度学习篇---SENet网络结构
  • 【C语言】第二课 基础语法
  • 【开题答辩全过程】以 基于微信小程序的宠物领养系统为例,包含答辩的问题和答案
  • 理解 C# `async` 的本质:从同步包装到状态机
  • 云手机与网络游戏相结合的优势?
  • AI大模型企业落地指南-笔记05
  • 【75】OpenCV C++实战篇——OpenCV 图像拼接、全景拼接(教程合集)
  • 【华为培训笔记】ASON原理
  • 关于嵌入式学习——嵌入式硬件3
  • 如何在MacOS上卸载并且重新安装Homebrew
  • 企业微信SCRM工具推荐:微盛AI·企微管家为什么是首选?
  • c#泛型公共类示例
  • Next.js App Router 中文件系统路由与页面跳转实践(以用户详情页面为例)
  • 1688拍立淘接口对接实战案例
  • Playwright-ui自动化工具
  • 如何设置PPTX的默认打开应用为PowerPoint
  • ​​AI生成PPT工具推荐,从此以后再也不用担心不会做PPT了​​
  • Effective Python 第10条 - 用赋值表达式减少重复代码
  • 股价暴跌后扔出 “王炸”,美团 LongCat 大模型到底是续命还是真有料?
  • Linux网络服务——基础设置
  • 【Kubernetes】知识点4
  • 吐槽一下福昕pdf阅读器高级专业版