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

2023-10-08 19:06:43 浏览数 (1)

示例

代码语言: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

0 人点赞