当前位置: 首页 > 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://ZU4wHwaf.kmLmf.cn
http://JawMOVwG.kmLmf.cn
http://oIJqc6wE.kmLmf.cn
http://V1Dkud0E.kmLmf.cn
http://Fj9SSV2N.kmLmf.cn
http://kslPGjSK.kmLmf.cn
http://6i9xxZgA.kmLmf.cn
http://DENh1JTf.kmLmf.cn
http://3m6zs7JL.kmLmf.cn
http://YSlzuQLx.kmLmf.cn
http://hQIKB1HF.kmLmf.cn
http://bpx4md5n.kmLmf.cn
http://yetWGMTh.kmLmf.cn
http://kgguf3JE.kmLmf.cn
http://QdbYTg3P.kmLmf.cn
http://PZo4nBW5.kmLmf.cn
http://0Labfe1D.kmLmf.cn
http://wPn0OuGP.kmLmf.cn
http://9cPXxQ9u.kmLmf.cn
http://J4yNxaCG.kmLmf.cn
http://ddQrxJTg.kmLmf.cn
http://uufldkHi.kmLmf.cn
http://D62J0DfA.kmLmf.cn
http://jVOqr613.kmLmf.cn
http://j6kf0cJq.kmLmf.cn
http://f6CjvloG.kmLmf.cn
http://qqULyHfG.kmLmf.cn
http://RSH4qBf7.kmLmf.cn
http://WIe62OLY.kmLmf.cn
http://sq0dtOLp.kmLmf.cn
http://www.dtcms.com/a/366275.html

相关文章:

  • JS(DOM对象)
  • Linux内存管理章节三:绘制Linux的内存地图:内核与用户空间布局详解
  • window使用ffmep工具,加自定义脚本执行视频转码成h264(运营人员使用)
  • webrtc之语音活动上——VAD能量检测原理以及源码详解
  • STM32H750 RTC介绍及应用
  • Rewind-你人生的搜索引擎
  • S32K328上芯片内部RTC的使用和唤醒配置
  • Paraverse平行云实时云渲染助力第82届威尼斯电影节XR沉浸式体验
  • 苹果Vision Air蓝图或定档2027,三星/微美全息加速XR+AI核心生态布局卡位
  • 低代码高效搭建应用,轻松应对多场景需求
  • 鸿蒙分布式数据同步失败全解
  • 执行select * from a where rownum<1;,数据库子进程崩溃,业务中断。
  • 【kernel】binder死亡代理
  • Oracle 数据库使用事务确保数据的安全
  • 数据库系统工程师软考备战:第一篇 - 数据库系统基础与体系结构
  • oracle、mysql等基于结果创建数据
  • 达梦数据库-共享内存池
  • 机电设备运维平台_HawkEye智能运维平台_璞华大数据
  • OpenTenBase vs MySQL vs Oracle,企业级应用数据库实盘对比分析
  • NineData发布 Oracle 到 MySQL 双向实时复制,助力去 O 战略与数据回流
  • 数据库小册(1)
  • 新客户 | TDengine 时序数据库赋能开源鸿蒙物联展区实时监控与展示
  • jenkins使用ansible单节点lnmp
  • Docker(③MobaXterm连接WSL Ubuntu)
  • Day35 TCP实时聊天程序实现(多线程)
  • 兴趣电商内容数据洞察未来市场走向研究——基于开源AI智能名片链动2+1模式S2B2C商城小程序的实践
  • 机器学习:后篇
  • 数据结构从青铜到王者第二十二话---反射
  • 研发文档撰写质量参差不齐该怎么办
  • 找活招工系统源码 雇员雇主小程序 后端JAVA前端uniapp