前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >前端系列13集-内置内容,单文件组件,进阶 API

前端系列13集-内置内容,单文件组件,进阶 API

作者头像
达达前端
发布2023-10-08 19:06:43
2490
发布2023-10-08 19:06:43
举报
文章被收录于专栏:达达前端达达前端

示例

代码语言:javascript
复制
<span v-text="msg"></span>
<!-- 等同于 -->
<span>{{msg}}</span>

在你的站点上动态渲染任意的 HTML 是非常危险的,因为它很容易导致 [XSS 攻击]。请只对可信内容使用 HTML 插值,绝不要将用户提供的内容作为插值

在[单文件组件],scoped 样式将不会作用于 v-html 里的内容,因为 HTML 内容不会被 Vue 的模板编译器解析。如果你想让 v-html 的内容也支持 scoped CSS,你可以使用 [CSS modules]或使用一个额外的全局 <style> 元素,手动设置类似 BEM 的作用域策略。

当同时使用时,v-if 比 v-for 优先级更高。我们并不推荐在一元素上同时使用这两个指令

表示 v-if 或 v-if / v-else-if 链式调用的“else 块”。

基于原始数据多次渲染元素或模板块。

  • 期望的绑定值类型: Array | Object | number | string | Iterable

详细信息

指令值必须使用特殊语法 alias in expression 为正在迭代的元素提供一个别名:

代码语言:javascript
复制
<div v-for="item in items">
  {{ item.text }}
</div>

或者,你也可以为索引指定别名 (如果用在对象,则是键值):

代码语言:javascript
复制
<div v-for="(item, index) in items"></div>
<div v-for="(value, key) in object"></div>
<div v-for="(value, name, index) in object"></div>

v-for 的默认方式是尝试就地更新元素而不移动它们。要强制其重新排序元素,你需要用特殊 attribute(归于) (归于) key 来提供一个排序提示:

代码语言:javascript
复制
<div v-for="item in items" :key="item.id">
  {{ item.text }}
</div>

给元素绑定事件监听器。

  • 缩写: @
  • 期望的绑定值类型: Function | Inline Statement | Object (不带参数)
  • 参数: event (使用对象语法则为可选项)
  • 修饰符:
    • .stop - 调用 event.stopPropagation()
    • .prevent - 调用 event.preventDefault()
    • .capture - 在捕获模式添加事件监听器。
    • .self - 只有事件从元素本身发出才触发处理函数。
    • .{keyAlias} - 只在某些按键下触发处理函数。
    • .once - 最多触发一次处理函数。
    • .left - 只在鼠标左键事件触发处理函数。
    • .right - 只在鼠标右键事件触发处理函数。
    • .middle - 只在鼠标中键事件触发处理函数。
    • .passive - 通过 { passive: true } 附加一个 DOM 事件。

当监听原生 DOM 事件时,方法接收原生事件作为唯一参数。如果使用内联声明,声明可以访问一个特殊的 event 变量:v-on:click="handle('ok',

示例:

代码语言:javascript
复制
<!-- 方法处理函数 -->
<button v-on:click="doThis"></button>

<!-- 动态事件 -->
<button v-on:[event]="doThis"></button>

<!-- 内联声明 -->
<button v-on:click="doThat('hello', $event)"></button>

<!-- 缩写 -->
<button @click="doThis"></button>

<!-- 使用缩写的动态事件 -->
<button @[event]="doThis"></button>

<!-- 停止传播 -->
<button @click.stop="doThis"></button>

<!-- 阻止默认事件 -->
<button @click.prevent="doThis"></button>

<!-- 不带表达式地阻止默认事件 -->
<form @submit.prevent></form>

<!-- 链式调用修饰符 -->
<button @click.stop.prevent="doThis"></button>

<!-- 按键用于 keyAlias 修饰符-->
<input @keyup.enter="onEnter" />

<!-- 点击事件将最多触发一次 -->
<button v-on:click.once="doThis"></button>

<!-- 对象语法 -->
<button v-on="{ mousedown: doThis, mouseup: doThat }"></button>

监听子组件的自定义事件 (当子组件的“my- event(事件) (事件) ”事件被触发,处理函数将被调用):

代码语言:javascript
复制
<MyComponent @my-event="handleThis" />

<!-- 内联声明 -->
<MyComponent @my-event="handleThis(123, $event)" />

动态的绑定一个或多个 attribute(归于) (归于) ,也可以是组件的 prop。

  • 缩写: : 或者 . (当使用 .prop 修饰符)
  • 期望: any (带参数) | Object (不带参数)
  • 参数: attrOrProp (可选的)
  • 修饰符:
    • .camel - 将短横线命名的 attribute(归于) (归于) 转变为驼峰式命名。
    • .prop - 强制绑定为 DOM property(属性) (属性) 。3.2+
    • .attr - 强制绑定为 DOM attribute(归于) (归于) 。3.2+

示例:

代码语言:javascript
复制
<!-- 绑定 attribute -->
<img v-bind:src="imageSrc" />

<!-- 动态 attribute 名 -->
<button v-bind:[key]="value"></button>

<!-- 缩写 -->
<img :src="imageSrc" />

<!-- 缩写形式的动态 attribute 名 -->
<button :[key]="value"></button>

<!-- 内联字符串拼接 -->
<img :src="'/path/to/images/' + fileName" />

<!-- class 绑定 -->
<div :class="{ red: isRed }"></div>
<div :class="[classA, classB]"></div>
<div :class="[classA, { classB: isB, classC: isC }]"></div>

<!-- style 绑定 -->
<div :style="{ fontSize: size + 'px' }"></div>
<div :style="[styleObjectA, styleObjectB]"></div>

<!-- 绑定对象形式的 attribute -->
<div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>

<!-- prop 绑定。“prop” 必须在子组件中已声明。 -->
<MyComponent :prop="someThing" />

<!-- 传递子父组件共有的 prop -->
<MyComponent v-bind="$props" />

<!-- XLink -->
<svg><a :xlink:special="foo"></a></svg>

当在 DOM 内模板使用 .camel 修饰符,可以驼峰化 v-bind attribute(归于) (归于) 的名称,例如 SVG viewBox attribute(归于) (归于) :

代码语言:javascript
复制
<svg :view-box.camel="viewBox"></svg>

如果使用字符串模板或使用构建步骤预编译模板,则不需要 .camel

在表单输入元素或组件上创建双向绑定。

  • 期望的绑定值类型:根据表单输入元素或组件输出的值而变化
  • 仅限:
    • <input>
    • <select>
    • <textarea>
    • components(组件) (组件)
  • 修饰符:
    • [.lazy] - 监听 change 事件而不是 input
    • [.number] - 将输入的合法字符串转为数字
    • [.trim] - 移除输入内容两端空格

用于声明具名插槽或是期望接收 props 的作用域插槽。

示例:

代码语言:javascript
复制
<!-- 具名插槽 -->
<BaseLayout>
  <template v-slot:header>
    Header content
  </template>

  <template v-slot:default>
    Default slot content
  </template>

  <template v-slot:footer>
    Footer content
  </template>
</BaseLayout>

<!-- 接收 prop 的具名插槽 -->
<InfiniteScroll>
  <template v-slot:item="slotProps">
    <div class="item">
      {{ slotProps.item.text }}
    </div>
  </template>
</InfiniteScroll>

<!-- 接收 prop 的默认插槽,并解构 -->
<Mouse v-slot="{ x, y }">
  Mouse position: {{ x }}, {{ y }}
</Mouse>

跳过该元素及其所有子元素的编译。

  • 无需传入
  • 详细信息 元素内具有 v-pre,所有 Vue 模板语法都会被保留并按原样渲染。最常见的用例就是显示原始双大括号标签及内容。

在随后的重新渲染,元素/组件及其所有子项将被当作静态内容并跳过渲染。这可以用来优化更新时的性能。

仅渲染元素和组件一次,并跳过之后的更新。

代码语言:javascript
复制
<!-- 单个元素 -->
<span v-once>This will never change: {{msg}}</span>
<!-- 带有子元素的元素 -->
<div v-once>
  <h1>comment</h1>
  <p>{{msg}}</p>
</div>
<!-- 组件 -->
<MyComponent v-once :comment="msg" />
<!-- `v-for` 指令 -->
<ul>
  <li v-for="i in list" v-once>{{i}}</li>
</ul>

缓存一个模板的子树。在元素和组件上都可以使用。为了实现缓存,该指令需要传入一个固定长度的依赖值数组进行比较。如果数组里的每个值都与最后一次的渲染相同,那么整个子树的更新将被跳过。

代码语言:javascript
复制
<div v-memo="[valueA, valueB]">
  ...
</div>

当组件重新渲染,如果 valueA 和 valueB 都保持不变,这个 <div> 及其子项的所有更新都将被跳过。实际上,甚至虚拟 DOM 的 vnode 创建也将被跳过,因为缓存的子树副本可以被重新使用。

正确指定缓存数组很重要,否则应该生效的更新可能被跳过。v-memo(备忘录)传入空依赖数组 (v-memo="[]") 将与 v-once 效果相同。

与 v-for 一起使用

v-memo 仅用于性能至上场景中的微小优化,应该很少需要。最常见的情况可能是有助于渲染海量 v-for 列表 (长度超过 1000 的情况):

代码语言:javascript
复制
<div v-for="item in list" :key="item.id" v-memo="[item.id === selected]">
  <p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p>
  <p>...more child nodes</p>
</div>

当组件的 selected 状态改变,默认会重新创建大量的 vnode,尽管绝大部分都跟之前是一模一样的。v-memo 用在这里本质上是在说“只有当该项的被选中状态改变时才需要更新”。这使得每个选中状态没有变的项能完全重用之前的 vnode 并跳过差异比较。注意这里 memo(备忘录) (备忘录) 依赖数组中并不需要包含 item.id,因为 Vue 也会根据 item(项目) (项目) 的 :key 进行判断。

当搭配 v-for 使用 v-memo,确保两者都绑定在同一个元素上。**v-memo 不能用在 v-for 内部。**

用于隐藏尚未完成编译的 DOM 模板。

  • 无需传入
  • 详细信息 该指令只在没有构建步骤的环境下需要使用。

当使用直接在 DOM 中书写的模板时,可能会出现一种叫做“未编译模板闪现”的情况:用户可能先看到的是还没编译完成的双大括号标签,直到挂载的组件将它们替换为实际渲染的内容。

v-cloak 会保留在所绑定的元素上,直到相关组件实例被挂载后才移除。配合像 [v-cloak] { display: none } 这样的 CSS 规则,它可以在组件编译完毕前隐藏原始模板。

示例:

代码语言:javascript
复制
[v-cloak] {
  display: none;
}
代码语言:javascript
复制
<div v-cloak>
  {{ message }}
</div>

组件注册和使用

内置组件无需注册便可以直接在模板中使用。它们也支持 tree- shake(动摇) (动摇) :仅在使用时才会包含在构建中。

在[渲染函数]中使用它们时,需要显式导入。例如:

代码语言:javascript
复制
import { h, Transition } from 'vue'

h(Transition, {
  /* props */
})

不是组件

<component><slot> 和 <template> 具有类似组件的特性,也是模板语法的一部分。但它们并非真正的组件,同时在模板编译期间会被编译掉。因此,它们通常在模板中用小写字母书写。

按注册名渲染组件 (选项式 API):

代码语言:javascript
复制
<script>
import Foo from './Foo.vue'
import Bar from './Bar.vue'

export default {
  components: { Foo, Bar },
  data() {
    return {
      view: 'Foo'
    }
  }
}
</script>

<template>
  <component :is="view" />
</template>

按定义渲染组件 (<script setup> 组合式 API):

代码语言:javascript
复制
<script setup>
import Foo from './Foo.vue'
import Bar from './Bar.vue'
</script>

<template>
  <component :is="Math.random() > 0.5 ? Foo : Bar" />
</template>

[内置组件]都可以传递给 is,但是如果想通过名称传递则必须先对其进行注册。举例来说:

代码语言:javascript
复制
<script>
import { Transition, TransitionGroup } from 'vue'

export default {
  components: {
    Transition,
    TransitionGroup
  }
}
</script>

<template>
  <component :is="isGroup ? 'TransitionGroup' : 'Transition'">
    ...
  </component>
</template>

如果在 <component> 标签上使用 v-model,模板编译器会将其扩展为 modelValue prop 和 update:modelValue 事件监听器,就像对任何其他组件一样。但是,这与原生 HTML 元素不兼容,例如 <input> 或 <select>。因此,在动态创建的原生元素上使用 v-model 将不起作用:

代码语言:javascript
复制
<script setup>
import { ref } from 'vue'

const tag = ref('input')
const username = ref('')
</script>

<template>
  <!-- 由于 'input' 是原生 HTML 元素,因此这个 v-model 不起作用 -->
  <component :is="tag" v-model="username" />
</template>

当我们想要使用内置指令而不在 DOM 中渲染元素时,<template> 标签可以作为占位符使用。

单文件组件使用[顶层的 <template> 标签]来包裹整个模板。这种用法与上面描述的 <template> 使用方式是有区别的。该顶层标签不是模板本身的一部分,不支持指令等模板语法。

举例来说:

代码语言:javascript
复制
<transition>
  <span :key="text">{{ text }}</span>
</transition>

使用选项式 API,引用将被注册在组件的 this.$refs 对象里:

代码语言:javascript
复制
<!-- 存储为 this.$refs.p -->
<p ref="p">hello</p>

使用组合式 API,引用将存储在与名字匹配的 ref 里:

代码语言:javascript
复制
<script setup>
import { ref } from 'vue'

const p = ref()
</script>

<template>
  <p ref="p">hello</p>
</template>

关于 ref 注册时机的重要说明:因为 ref 本身是作为渲染函数的结果来创建的,必须等待组件挂载后才能对它进行访问。

this.$refs 也是非响应式的,因此你不应该尝试在模板中使用它来进行数据绑定。

在 is attribute(归于) (归于) 的值中加上 vue: 前缀,这样 Vue 就会把该元素渲染为 Vue 组件:

代码语言:javascript
复制
<table>
  <tr is="vue:my-row-component"></tr>
</table>

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

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

<script setup> 中的代码会在每次组件实例被创建的时候执行

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

代码语言:javascript
复制
<script setup>
// 变量
const msg = 'Hello!'

// 函数
function log() {
  console.log(msg)
}
</script>

<template>
  <button @click="log">{{ msg }}</button>
</template>

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

代码语言:javascript
复制
<script setup>
import { capitalize } from './helpers'
</script>

<template>
  <div>{{ capitalize('hello') }}</div>
</template>

响应式状态需要明确使用[响应式 API]来创建。和 setup() 函数的返回值一样,ref 在模板中使用的时候会自动解包:

vue

代码语言:javascript
复制
<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <button @click="count++">{{ count }}</button>
</template>

<script setup> 范围里的值也能被直接作为自定义组件的标签名使用:

代码语言:javascript
复制
<script setup>
import MyComponent from './MyComponent.vue'
</script>

<template>
  <MyComponent />
</template>

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

代码语言:javascript
复制
<script setup>
import Foo from './Foo.vue'
import Bar from './Bar.vue'
</script>

<template>
  <component :is="Foo" />
  <component :is="someCondition ? Foo : Bar" />
</template>

一个单文件组件可以通过它的文件名被其自己所引用。例如:名为 FooBar.vue 的组件可以在其模板中用 <FooBar/> 引用它自己。

请注意这种方式相比于导入的组件优先级更低。如果有具名的导入和组件自身推导的名字冲突了,可以为导入的组件添加别名:

代码语言:javascript
复制
import { FooBar as FooBarChild } from './components'

为了在声明 props 和 emits 选项时获得完整的类型推导支持,我们可以使用 defineProps 和 defineEmits API,它们将自动地在 <script setup> 中可用:

代码语言:javascript
复制
<script setup>
const props = defineProps({
  foo: String
})

const emit = defineEmits(['change', 'delete'])
// setup 代码
</script>
  • defineProps 和 defineEmits 都是只能在 <script setup> 中使用的编译器宏。他们不需要导入,且会随着 <script setup> 的处理过程一同被编译掉。
  • defineProps 接收与 props 选项相同的值,defineEmits 接收与 emits 选项相同的值。
  • defineProps 和 defineEmits 在选项传入后,会提供恰当的类型推导。
  • 传入到 defineProps 和 defineEmits 的选项会从 setup 中提升到模块的作用域。因此,传入的选项不能引用在 setup 作用域中声明的局部变量。这样做会引起编译错误。但是,它可以引用导入的绑定,因为它们也在模块作用域内。

可以通过 defineExpose 编译器宏来显式指定在 <script setup> 组件中要暴露出去的属性:

代码语言:javascript
复制
<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

defineExpose({
  a,
  b
})
</script>

在 <script setup> 使用 slots 和 attrs 的情况应该是相对来说较为罕见的,因为可以在模板中直接通过 

代码语言:javascript
复制
<script setup>
import { useSlots, useAttrs } from 'vue'

const slots = useSlots()
const attrs = useAttrs()
</script>

useSlots 和 useAttrs 是真实的运行时函数,它的返回与 setupContext.slots 和 setupContext.attrs 等价。它们同样也能在普通的组合式 API 中使用。

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

代码语言:javascript
复制
<script setup>
const post = await fetch(`/api/post/1`).then((r) => r.json())
</script>

创建原生元素:

代码语言:javascript
复制
import { h } from 'vue'

// 除了 type 外,其他参数都是可选的
h('div')
h('div', { id: 'foo' })

// attribute 和 property 都可以用于 prop
// Vue 会自动选择正确的方式来分配它
h('div', { class: 'bar', innerHTML: 'hello' })

// class 与 style 可以像在模板中一样
// 用数组或对象的形式书写
h('div', { class: [foo, { bar }], style: { color: 'red' } })

// 事件监听器应以 onXxx 的形式书写
h('div', { onClick: () => {} })

// children 可以是一个字符串
h('div', { id: 'foo' }, 'hello')

// 没有 prop 时可以省略不写
h('div', 'hello')
h('div', [h('span', 'hello')])

// children 数组可以同时包含 vnode 和字符串
h('div', ['hello', h('span', 'hello')])

创建组件:

代码语言:javascript
复制
import Foo from './Foo.vue'

// 传递 prop
h(Foo, {
  // 等价于 some-prop="hello"
  someProp: 'hello',
  // 等价于 @update="() => {}"
  onUpdate: () => {}
})

// 传递单个默认插槽
h(Foo, () => 'default slot')

// 传递具名插槽
// 注意,需要使用 `null` 来避免
// 插槽对象被当作是 prop
h(MyComponent, null, {
  default: () => 'default slot',
  foo: () => h('div', 'foo'),
  bar: () => [h('span', 'one'), h('span', 'two')]
})

如果你不需要合并行为而是简单覆盖,可以使用原生 object(目的) (目的) spread 语法来代替。

示例

代码语言:javascript
复制
import { h, cloneVNode } from 'vue'

const original = h('div')
const cloned = cloneVNode(original, { id: 'foo' })

示例

代码语言:javascript
复制
import { h, withModifiers } from 'vue'

const vnode = h('button', {
  // 等价于 v-on:click.stop.prevent
  onClick: withModifiers(() => {
    // ...
  }, ['stop', 'prevent'])
})

示例

代码语言:javascript
复制
import type { PropType } from 'vue'

interface Book {
  title: string
  author: string
  year: number
}

export default {
  props: {
    book: {
      // 提供一个比 `Object` 更具体的类型
      type: Object as PropType<Book>,
      required: true
    }
  }
}

示例

代码语言:javascript
复制
import axios from 'axios'

declare module 'vue' {
  interface ComponentCustomProperties {
    $http: typeof axios
    $translate: (key: string) => string
  }
}

用来扩展组件选项类型以支持自定义选项。

示例

代码语言:javascript
复制
import { Route } from 'vue-router'

declare module 'vue' {
  interface ComponentCustomOptions {
    beforeRouteEnter?(to: any, from: any, next: () => void): void
  }
}

用于扩展全局可用的 TSX props,以便在 TSX 元素上使用没有在组件选项上定义过的 props。

示例

代码语言:javascript
复制
declare module 'vue' {
  interface ComponentCustomProps {
    hello?: string
  }
}

export {}
代码语言:javascript
复制
// 现在即使没有在组件选项上定义过 hello 这个 prop 也依然能通过类型检查了
<MyComponent hello="world" />

用于扩展在样式属性绑定上允许的值的类型。

  • 示例

允许任意自定义 CSS 属性:

代码语言:javascript
复制
declare module 'vue' {
  interface CSSProperties {
    [key: `--${string}`]: string
  }
}
代码语言:javascript
复制
<div style={ { '--bg-color': 'blue' } }>
代码语言:javascript
复制
<div :style="{ '--bg-color': 'blue' }">

仓库地址:https://github.com/webVueBlog/WebGuideInterview

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-10-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档