Vue3(个人学习)

168 阅读19分钟

语法

基础

导入

以下均使用选项式api,组合式api使用方式请查看setup()。

1、通过 CDN 使用 Vue

通过 CDN 使用 Vue 时将无法使用单文件组件 (SFC) 语法(*.vue),并且所有顶层 API 都以属性的形式暴露在了全局的 Vue 对象上,效果如下:

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<div id="app">...</div>
<script>
  const { createApp } = Vue
  createApp({
    data() {
      ...
    }
  }).mount('#app')
</script>

2、使用 ES 模块构建版本

<div id="app">{{ message }}</div>
<script type="module">
  import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
  // 可拆分成组件模块,例如MyComponent组件
  // import MyComponent from './my-component.js'
  createApp(
  { data() { ... } } // 可替换为 MyComponent
  ).mount('#app')
</script>

3、启用 Import maps

我们可以使用导入映射表 (Import Maps) 来告诉浏览器如何定位到导入的 vue(存在浏览器兼容性问题):

<script type="importmap">
  {
    "imports": {
      "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
    }
  }
</script>
<div id="app">...</div>
<script type="module">
  import { createApp } from 'vue'
  createApp({
    data() {
      ...
    }
  }).mount('#app')
</script>

使用

使用方式为组合式api。

1、创建实例

Vue3采用createApp函数创建vue实例,实例创建后需要挂载至容器元素上,具体用法为:
createApp(rootComponent: Component, rootProps?: object).mount('#app')
使用实例具有许多api,具体参考

2、模板语法

动态参数:<a v-bind:[attributeName]="url"> ... </a>
attributeName为变量,变量名将自动转为小写,变量值限制为字符串与null,变量表达式不允许空格引号等特殊字符,如<a :['foo' + bar]="value">

3、响应式基础

组合式api只能够使用在setup(){...}函数或<script setup>...</script>中,后者需要配合单文件组件(SFC)使用。
Vue3通过使用Proxy能够跟踪监听响应式对象属性的访问与修改等操作。

reactive

创建一个深层响应式对象(代理对象),能够监听至最里层属性变动,与原始对象是不相等的(引用是不相同的,但是引用指针指向到的内存区都是同一个),只有代理对象是响应式的,更改原始对象(更改对象引用,而非更改对象属性值)不会触发更新。对同一个原始对象调用会返回相同代理对象,对代理对象调用会返回其本身,规则对其嵌套对象效果相同。

const raw = {}
const proxy = reactive(raw)
// 代理对象和原始对象不是全等的
console.log(proxy === raw) // false
// 在同一个对象上调用 reactive() 会返回相同的代理
console.log(reactive(raw) === proxy) // true
// 在一个代理上调用 reactive() 会返回它自己
console.log(reactive(proxy) === proxy) // true
const obj = {}
proxy.nested = obj
console.log(proxy.nested === raw) // false

let demo1 = {d: 1}
const demo2 = reactive(demo1)
console.log(demo2.d === demo1.d) // true,d被修改全都被修改
demo.1 = {d: 1}
console.log(demo2.d === demo1.d) // false,demo2代理的是之前对象

reactive具有两个局限性,一是仅对对象类型有效,原始类型无效。二是响应式是通过访问属性进行跟踪的,一旦响应式对象被更换后将失去其初始引用(响应性连接丢失)(模板中失去绑定),同时也意味属性赋值给其他变量或者解构赋值时,或者将属性传入函数时,将会失去其响应式。

let state = reactive({ count: 0 })
// 上面的引用 ({ count: 0 }) 将不再被追踪(响应性连接已丢失!,不在更新模板)
state = reactive({ count: 1 })
// n 是一个局部变量,同 state.count
// 失去响应性连接
let n = state.count
// 不影响原始的
state n++
ref

Vue提供可以使用任何类型的值创建响应式对象的方法,将传入参数的值包装为一个带 .value 属性的 ref 对象,和响应式对象的属性类似,ref 的 .value 属性也是响应式的。同时,当值为对象类型时,会用reactive()自动转换它的.value,ref弥补了reactive的缺点。

let state = ref({ count: 0 })
// 响应式替换,不影响state对value的访问
state.value = { count: 1 }
const obj = {
  foo: ref(1),
  bar: ref(2)
}
// 该函数接收一个 ref
// 需要通过 .value 取值
// 但它会保持响应性
callSomeFunction(obj.foo)
// 仍然是响应式的
const { foo, bar } = obj

需要注意的是,仅当ref在模板中作为顶层属性被访问时,它们才会被自动“解包”(模板访问ref对象,自动获取.value属性),如果硬要加上.value,会去寻找ref.value中的value属性,如果没有会返回underfined。当ref被嵌套在响应式对象(reactive)中,也会自动解包,如果将一个新的 ref 赋值给一个关联了已有 ref 的属性,那么它会替换掉旧的 ref:

const count = ref(0)
const state = reactive({
  count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1

const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
// 原始 ref 现在已经和 state.count 失去联系
console.log(count.value) // 1

但是如果ref作为根对象并且使用另外一个ref替换,那么将会失去响应性:

<h2>{{title}}</h2> // aaaa
<h2>{{xxx.title}}</h2>

let title = ref('aaaa')
let xxx = ref({
  title: ref('666')
})
title = ref('bbbb') // 修改无效,正确方式为title.value = 'bbbb'
xxx.value.title = ref('777') // 有效,因为模板只指向第一个变量

unref用于获取.value的值,对于非ref返回本身

4、计算属性

使用const message = computed(() => { return ... })创建一个计算属性,计算属性为ref。
计算属性与方法的区别,计算属性值会基于其响应式依赖被缓存,一个计算属性仅会在其响应式依赖更新时才重新计算,而方法调用总是会在重渲染发生时再次执行函数。

5、类与样式绑定

样式多值:你可以对一个样式属性提供多个 (不同前缀的) 值,数组仅会渲染浏览器支持的最后一个值。在这个示例中,在支持不需要特别前缀的浏览器中都会渲染为 display: flex

<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>

6、列表渲染

  • <div v-for="item in items" /> 之中可用of代替in。
  • 遍历对象时,第一个参数表示value,第二个参数表示key,第三个参数表示index。
  • 当遍历一个整数时,item将会从1开始。
  • v-if优先级高于v-for。

7、事件处理

  • 内联事件处理器:<button @click="count++ / fun()">
  • 方法事件处理器:<button @click="fun / fun.bar / fun['bar']">
  • 访问原生DOM事件,在内联非箭头函数是$event,在内联箭头函数是event,方法事件中也是event

8、侦听器

watch 的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组(注意,你不能直接侦听响应式对象的属性值):

const x = ref(0)
const y = ref(0)

// 单个 ref
watch(x, (newX) => {
  console.log(`x is ${newX}`)
})

// getter 函数
watch(
  () => x.value + y.value,
  (sum) => {
    console.log(`sum of x + y is: ${sum}`)
  }
)

// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
  console.log(`x is ${newX} and y is ${newY}`)
})

值得注意的是

  • 传入一个ref时,默认侦听ref.value的引用地址或者值是否发生改变,浅层监听。
  • 传入响应式对象时,会创建深层侦听器,侦听深层属性是否发生改变。
  • 一个返回响应式对象的 getter 函数,只有在返回不同的对象时,才会触发回调,浅层监听。 浅层转换成深层需要在第三个参数对象内加入deep: true
  • 回调函数中访问的DOM是在Vue更新之前,如果需要访问更新之后的DOM,需要在config中加入flush:'post',后置刷新的watchEffect别名为watchPostEffect

watchEffect
watch() 是懒执行的:仅当数据源变化时,才会执行回调。而watchEffect会立即执行一遍回调函数,并且自动追踪依赖数据,有点像计算属性computed。

watchEffect(async () => {
  const response = await fetch(url.value)
  data.value = await response.json()
})

停止侦听器
侦听器必须用同步语句创建:如果用异步回调创建一个侦听器,那么它不会绑定到当前组件上,你必须手动停止它,以防内存泄漏。要手动停止一个侦听器,请调用 watch 或 watchEffect 返回的函数:

const unwatch = watchEffect(() => {})

// ...当该侦听器不再需要时
unwatch()

9、模板引用

在模板上使用ref字符串,还可以绑定函数,函数第一个入参为dom节点:

<template>
  <input ref="input" />
  <input :ref="(el) => { input.value = el }">
</template>
<script setup>
import { ref, onMounted } from 'vue'
// 声明一个 ref 来存放该元素的引用
// 必须和模板里的 ref 同名
const input = ref(null)
onMounted(() => {
  input.value.focus()
})
</script>

注意,你只可以在组件挂载后才能访问模板引用。在v-for中使用需要ref([])
关于组件上ref,子组件如果使用选项式api,那么父组件使用ref可以访问子组件的每一个属性和方法,组件式api默认所有属性与方法都是私有的,除非使用defineExpose进行显示暴露。

<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
  a,
  b
})
</script>

在子组件上使用ref获取到的是子组件实例的proxy对象,如果未对子组件属性方法进行暴露,使用JSON.stringify将为空对象

10、组件

在传统HTML中使用组件时:

  • /> 方式无效
  • 无论是 PascalCase(HeartDom) 形式的组件名称、camelCase(heartDom) 形式的 prop 名称还是 v-on 的事件名称,都需要转换为相应等价的 kebab-case (短横线连字符) 形式:
    <blog-post post-title="hello!" @update-post="onUpdatePost"></blog-post>
  • 在特殊元素中放入组件,浏览器将会忽略,可用is属性解决,is 的值必须加上前缀 vue: 才可以被解析为一个 Vue 组件:
<table><blog-post-row></blog-post-row></table> // 错误
<table> <tr is="vue:blog-post-row"></tr> </table> // 正确

动态组件:
使用component元素和特殊的is属性实现组件之间的来回切换,传给:is的值一般是组件名或导入的组件对象,通过配合keep-alive实现组件缓存
<component :is="tabs[currentTab]"></component>

props

一个组件可以有任意多的 props,默认情况下,所有 prop 都接受任意类型的值。
在<script setup>中,使用defineProps方法声明prop,返回一个包含所有props的对象,在<script>中如同vue,但是setup钩子第一个参数就是props,如下:

<script setup>
const props = defineProps(['title']) 
// 或者
const props = defineProps({
    title: String,
    money: Number
})
console.log(props.title)
</script>

// < script>使用方式
export default {
  props: ['title'],
  setup(props) {
    console.log(props.title)
  }
}

将一个对象所有属性都当做props传入

const post = {
  id: 1,
  title: 'My Journey with Vue'
}
<BlogPost v-bind="post" />  // 等于
<BlogPost :id="post.id" :title="post.title" />

组件传值需要遵循单向绑定原则,但是如果传入一个ref对象,在子组件内修改ref内部属性是能够修改的,但是最好不要这么做。
props的校验功能也很强大,Boolean外的默认值为underfined,Boolean为false。
type中能验证String、Number、Boolean、Object、Array、Function、Symbol、Date,此外还可以自定义类或者构造函数。

defineProps({
  // (给出 `null` 和 `undefined` 值则会跳过任何类型检查)
  propA: Number,
  propB: [String, Number], // 多种可能的类型
  propC: {
    type: String,
    required: true,
    default: '321'
  },
  propE: {
    type: Object,
    default(rawProps) {
      return { message: 'hello' }
    }
  },
  // 自定义类型校验函数
  propF: {
    validator(value) {
      return ['success', 'warning', 'danger'].includes(value)
    }
  },
  // 函数类型的默认值
  propG: {
    type: Function,
    // 不像对象或数组的默认,这不是一个工厂函数。这会是一个用来作为默认值的函数
    default() {
      return 'Default function'
    }
  }
})

Boolean类型具有类型转换特性,

<!-- 等同于传入 :disabled="true" --> 
<MyComponent disabled />
<!-- 等同于传入 :disabled="false" -->
<MyComponent />
<!-- 当一个 prop 被声明为允许多种类型时,无论声明类型的顺序如何,Boolean 类型的特殊转换规则都会被应用。 -->
defineProps({
  disabled: [Boolean, Number]
})
emits

可在子组件中直接使用$emit方法进行使用,比如<a @click="$emit('xxx', '1')">
在<script setup>中,使用defineEmits方法声明emit,返回一个等同于$emit的方法,在<script>中如同vue,但是setup钩子第二个的执行上下文中能够访问到emit函数,如下:

<script setup>
const emit = defineEmits(['xxx'])
emit('xxx', '1')
// emits允许我们对触发事件的参数进行验证
const emit = defineEmits({
  submit(payload) {
    // 通过返回值为 `true` 还是为 `false` 来判断
    // 验证是否通过
  }
})
</script>

// < script>使用方式
export default {
  setup(props, context) {
    context.emit('xxx', '1')
  }
}
v-model

在原生input上v-model的语法糖是:

<input :value="xxx" @input="xxx = $event.target.value" />

而在组件上v-model会被默认展开成以下形式:

<CustomInput v-mode="xxx" />
// 等同于
<CustomInput :modelValue="xxx" @update:modelValue="xxx = $event.target.value" />

// 因此需要在子组件中
<template>
const props = defineProps(['modelValue'])
const emits = defineEmits(['update:modelValue'])
</template>
// 方式一
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
// 方式二
<input v-model="value" />
const value = computed({
    get() {
        return props.modelValue
    },
    set(value) {
        emits('update:modelValue', value)
    }
})

组件上可以使用具名v-model:

<CustomInput v-mode:title="xxx" />
<!-- MyComponent.vue -->
<script setup>
defineProps(['title'])
defineEmits(['update:title'])
</script>

<template>
  <input :value="title" @input="$emit('update:title', $event.target.value)" />
</template>

如果在组件上需要使用修饰符,则需要再defineProps中声明对应model的修饰符对象,比如:

// 修饰符为xxx
<MyComponent v-model.xxx="myText" /> 
const props = defineProps({
  modelValue: String,
  modelModifiers: { default: () => ({}) }
})
console.log(props.modelModifiers) // { xxx: true }
// 如果是具名model
<MyComponent v-model:xx.xxx="myText" /> 
const props = defineProps({
  xx: String,
  xxModifiers: { default: () => ({}) }
})
console.log(props.xxModifiers) // { xxx: true }
透传

“透传 attribute”指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on 事件监听器。当一个组件以单个元素为根作渲染时,透传的 attribute 会自动被添加到根元素上。否则不添加至根元素,注意只有未被声明的attribute或事件才能被继续往下继承,最常见的例子就是 class、style 、id和v-on。

<MyButton class="large" />
<!-- <MyButton> 的模板 -->
<button class="btn">click me</button>
<!-- 相当于 -->
<button class="btn large">click me</button>

如果想要禁止继承,可以使用inheritAttrs: false,如果使用组合式api,需要额外使用一个<script>来声明:

<script>
// 使用普通的< script> 来声明选项 
export default { 
   inheritAttrs: false 
} 
</script>
<script setup>
// ...setup 部分逻辑
</script>

所有透传进来的 attribute 可以在模板的表达式中直接用 $attrs 访问到,并且保留了它们原始的大小写,支持自定义属性,比如$attrs['foo-bar']。而像事件click的事件被包装成一个函数$attrs.onClick,自定义事件需要遵循on+第一个大写字母原则$attrs.onXxx可以利用不带参数的v-bind会将一个对象的所有属性都传入的特性来使用继承比如:

// 能够将所有透传属性全都绑定在button元素上
<button class="btn" v-bind="$attrs">click me</button>

在js的组合式中的使用方式是使用useAttrs()API来获取所有透传,选项式中则绑定在上下文对象中,需要注意的是两则都不是响应式的,不能通过watch侦听。如果需要响应性,可以使用prop(监听prop即可)或者onUpdated()结合最新的attrs。

slots

插槽默认内容

<p>
    <slot>默认内容</slot> // slot标签内就是默认内容,如果父组件有传入内容将被替换
</p>

具名插槽
slot元素具有一个name属性,用于区分各个插槽,没有name属性的slot元素默认具有一个隐式的name="default",而父组件在使用时需要用到含有v-slot指令的<template>(必须),template标签作为占位符使用不在 DOM 中渲染,v-slot可以简写成#,所有位于顶级的非 <template> 节点都被隐式地视为默认插槽的内容。

<header> <slot name="header"></slot> </header>
<main> <slot></slot> </main>
<footer> <slot name="footer"></slot> </footer>
// 使用
<BaseLayout>
  <template v-slot:header>...</template>
  <!-- 隐式的默认插槽 -->
  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <template #footer>...</template>
</BaseLayout>

动态插槽<template #[dynamicSlotName]>
作用域插槽
在某些场景下插槽的内容想要同时使用父组件域内和子组件域内的数据。向插槽元素中加入自定义变量(name被保留),在父组件的v-slot中设置接收对象,具名

<div>
  <slot name="header" :text="greetingMessage" :count="1" />
</div>
// 父组件slotProps是自定义的
<MyComponent v-slot="slotProps">
  {{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
依赖注入

用于解决深层组件之间多层prop传递问题,provide 和 inject 可以帮助我们解决这一问题。使用方式为:

// 应用层
app.provide('key', 'value')
// 根组件
import {provide} from 'vue'
// 使用script setup
provide('key', 'value') // key可以字符串或者symbol, value可以任意值,provide可多次使用
// 不适用script setup
setup() {
    provide('key', 'value')
}
// 子组件
import {inject} from 'vue'
var key = inject('key', 'default') // default为默认值,可选
'or'
setup(){ const key = inject('key', 'default') }

值得注意的是传入值应是响应式数据,选项式使用时应传入一个computed,如果想确保传入值不能被子组件更改,可使用readonly()处理,如果需要在子组件中修改,推荐传入对象,对象中包含value与valueChangeEvent():

provide('key', {
    value,
    updateValue // 方法
})

同时推荐使用Sybmol作为key

异步组件

Vue 提供了 defineAsyncComponent 方法来实现此功能,也可配合import使用

const AsyncComp = defineAsyncComponent({
  // 加载函数,应为promise
  loader: () => import('./Foo.vue'),
  // 加载异步组件时使用的组件
  loadingComponent: LoadingComponent,
  // 展示加载组件前的组件的延迟时间,默认为 200ms,指的是LoadingComponent
  delay: 200,
  // 加载失败后展示的组件
  errorComponent: ErrorComponent,
  // 如果提供了一个 timeout 时间限制,并超时了
  // 也会显示这里配置的报错组件,默认值是:Infinity
  timeout: 3000
})

值得注意的是超时还会加载组件,内置onError好像获取不到异常。

11、组合式函数

组合式函数约定用驼峰命名法命名,并以“use”作为开头。在接收 ref 为参数时会产生响应式 effect,请确保使用 watch() 显式地监听此 ref,或者在 watchEffect() 中调用 unref() 来进行正确的追踪。我们约定组合式函数始终返回一个包含多个 ref 的普通的非响应式对象,事例:

export function useMouse() {
  // 被组合式函数封装和管理的状态
  const x = ref(0)
  const y = ref(0)

  // 组合式函数可以随时更改其状态。
  function update(event) {
    x.value = event.pageX
    y.value = event.pageY
  }

  // 一个组合式函数也可以挂靠在所属组件的生命周期上
  // 来启动和卸载副作用
  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))

  // 通过返回值暴露所管理的状态
  return { x, y }
}

在选项式api中使用组合式函数必须放在setup()中,返回的值需要return出来供给this以及模板

12、自定义指令

自定义指令主要是为了重用涉及普通元素的底层 DOM 访问的逻辑。
在 <script setup> 中,任何以 v 开头的驼峰式命名的变量都可以被用作一个自定义指令。在没有使用<script setup>的情况下,自定义指令需要通过 directives 选项注册。当然也可以全局注册。

// 在script setup中
const vFocus = {
  mounted: (el) => el.focus()
}
<input v-focus />
// 不在...中
export default {
  setup() { /*...*/ },
  directives: {
    // 在模板中启用 v-focus
    focus: { /* ... */ }
  }
}

钩子
指令钩子除了beforCreated之外都有,参数为(el,binding,vnode,prevVnode)

el:被绑定的DOM节点
binding:{
    value: 传递的值
    oldValue: 之前的值,仅在beforeUpdate和update可用
    arg:传递给指令的参数
    modifiers:修饰符
    instance:使用该指令的组件实例
    dir:指令的定义对象
}
vnode:绑定元素的底层vnode节点
prevNode: 之前的绑定元素的底层vnode节点,仅在beforeUpdate和update可用

例子:
<div v-example:foo.bar="baz">
{
  arg: 'foo',
  modifiers: { bar: true },
  value: /* `baz` 的值 */,
  oldValue: /* 上一次更新时 `baz` 的值 */
}

如果默认情况下直接赋值一个函数,那么将只有mountedupdated会触发。
在组件上使用自定义指令,它会应用于子组件内的根节点,如果有多个根节点,将会抛出警告。

13、插件

插件 (Plugins) 是一种能为 Vue 添加全局功能的工具代码。
主要作用于添加全局组件、指令、依赖(provide)、app.config.globalProprerties全局属性,使用app.use(plugins, option),plugins为插件,可以是一个含有install(app, option)方法的对象,也可以是这个方法本身,app为使用插件的实例本身,option为使用插件时传入的option

app.use(myPlugin, { /* 可选的选项 */ })
const myPlugin = { install(app, options) { // 配置此应用 } }
// or
const myPlugin = (app, options)=>{}

内置组件

Transition

它可以将进入和离开动画应用到通过默认插槽传递给它的元素或组件上。可通过v-if,v-show,<component>切换动态组件触发。
在动画进入(显示组件)或离开(隐藏)过渡中将自动为组件添加以下css:

v-enter-from:进入过渡动画开始的起始状态,元素插入前添加,过渡效果在插入完成后下一帧移除,动画效果在触发animationend事件后移除
v-enter-active:进入过渡动画的生效状态,元素插入前添加,动画结束后移除。定义动画的持续时间、延迟、速度曲线等
v-enter-to:进入过渡动画的结束状态,在元素插入完成后的下一帧添加,动画完成后移除

v-leave-from:离开过渡动画的起始状态,离开过渡效果被触发时添加,下一帧移除
v-leave-active:离开过渡动画的生效状态,离开过渡效果被触发时添加,动画完成后移除
v-leave-to:离开过渡动画的结束状态,离开过渡效果被触发时的下一帧添加,

使用动画时一般只使用v-enter-active或者v-leave-active绑定动画。
class可命名,只需要在Transition 组件添加name="xxx"属性后,class就可命名为xxx-enter-from 等,或者直接利用class属性命名,name可以是动态的

// v-enter-from == xxx-enter-from
<Transition name="xxx" />
// v-enter-from == xxx
<Transition enter-from-class="xxx" />

对于有些元素内的元素也有具有过渡动画并且css采用父子选择器是情况下不希望主动画结束后删除父级css,对于leave可以使用transition-delay,但是enter下没有特别好的办法,面对这种情况提供了duration属性,可以显式指定过渡时长(经过多次时间移除css),可以分别指定leave和enter时间duration={enter:500, leave: 200}

钩子
在使用仅由 JavaScript 执行的动画时,最好是添加一个 :css="false" prop。这显式地向 Vue 表明可以跳过对 CSS 过渡的自动探测。除了性能稍好一些之外,还可以防止 CSS 规则意外地干扰过渡效果。

<Transition
  @before-enter="onBeforeEnter"
  @enter="onEnter"
  @after-enter="onAfterEnter"
  @enter-cancelled="onEnterCancelled"
  @before-leave="onBeforeLeave"
  @leave="onLeave"
  @after-leave="onAfterLeave"
  @leave-cancelled="onLeaveCancelled"
>
  ...
</Transition>

// 在元素被插入到 DOM 之前被调用
// 用这个来设置元素的 "enter-from" 状态
function onBeforeEnter(el) {}

// 在元素被插入到 DOM 之后的下一帧被调用
// 用这个来开始进入动画
function onEnter(el, done) {
  // 调用回调函数 done 表示过渡结束
  // 如果与 CSS 结合使用,则这个回调是可选参数
  done()
}

// 当进入过渡完成时调用。
function onAfterEnter(el) {}
function onEnterCancelled(el) {}

// 在 leave 钩子之前调用
// 大多数时候,你应该只会用到 leave 钩子
function onBeforeLeave(el) {}

// 在离开过渡开始时调用
// 用这个来开始离开动画
function onLeave(el, done) {
  // 调用回调函数 done 表示过渡结束
  // 如果与 CSS 结合使用,则这个回调是可选参数
  done()
}

// 在离开过渡完成、
// 且元素已从 DOM 中移除时调用
function onAfterLeave(el) {}

// 仅在 v-show 过渡中可用
function onLeaveCancelled(el) {}

动态组件的切换使用:is,同时还提供自带的mode,有out-in和in-out两种选择

<Transition name="fade" mode="out-in">
  <component :is="activeComponent" />
</Transition>

transition-group

<TransitionGroup> 是一个内置组件,用于对 v-for 列表中的元素或组件的插入、移除和顺序改变添加动画效果。
<TransitionGroup> 支持和 <Transition> 基本相同的 props、CSS 过渡 class 和 JavaScript 钩子监听器,但有以下几点区别:

  • 默认情况下,它不会渲染一个容器元素。但你可以通过传入 tag prop 来指定一个元素作为容器元素来渲染。
  • mode在这里不可用,因为我们不再是在互斥的元素之间进行切换。
  • 列表中的每个元素都必须有一个独一无二的 key attribute。
  • CSS 过渡 class 会被应用在列表内的元素上,而不是容器元素上。

keep-alive

能在多个组件间动态切换时缓存被移除的组件实例。
使用方法是用 <KeepAlive> 内置组件将这些动态组件包装起来:

<KeepAlive>
  <component :is="activeComponent" /> 
  // 所有组件都会被储存
</KeepAlive>

<KeepAlive> 默认会缓存内部的所有组件实例,我们可以通过 include(包含)和 exclude(排除)prop 来定制该行为。这两个 prop 的值都可以是一个以英文逗号分隔的字符串、一个正则表达式,或是包含这两种类型的一个数组,它会根据组件的 name 选项进行匹配,使用 <script setup> 的单文件组件会自动根据文件名生成对应的 name 选项。
最大缓存实例数max属性 。
同时被缓存组件会具有onActivated()onDeactivated()两个生命周期,onActivated首次加载已经从缓存插入会调用,onDeactivated卸载或进入缓存时调用。

teleport

为了解决一个组件模板的一部分在逻辑上从属于该组件,但从功能上想让其挂载在外部的DOM上这个问题,teleport可以帮助我们完成该功能,<Teleport to="xxx">xxx可以是css选择器字符串,也可以是DOM元素,<Teleport> 挂载时,传送的 to 目标必须已经存在于 DOM 中。
同时<Teleport>还提供一个禁用属性disabled。
多个 Teleport 共享同一个DOM目标将会按顺序追加至DOM元素内。

<Teleport to="#modals">
  <div>A</div>
</Teleport>
<Teleport to="#modals">
  <div>B</div>
</Teleport>
// 结果为
<div id="modals">
  <div>A</div>
  <div>B</div>
</div>

Suspense(实验中)

是用来在组件树中协调对异步依赖的处理。它让我们可以在组件树上层等待下层的多个嵌套异步依赖项解析完成,并可以在等待时渲染一个加载状态。<Suspense> 可以等待的异步依赖有两种:

  • 带有异步async setup() 钩子的组件。这也包含了使用 < script setup> 时有顶层 await 表达式的组件。
  • 异步组件。异步组件也可以通过在选项中指定 suspensible: false 表明不用 Suspense 控制,并让组件始终自己控制其加载状态。

<Suspense>组件有两个插槽:#default 和 #fallback。两个插槽都只允许一个直接子节点。在可能的时候都将显示默认槽中的节点。否则将显示后备槽中的节点。

<Suspense>
  <!-- 具有深层异步依赖的组件 -->
  <Dashboard />

  <!-- 在 #fallback 插槽中显示 “正在加载中” -->
  <template #fallback>
    Loading...
  </template>
</Suspense>

在初始渲染时,<Suspense> 将在内存中渲染其默认的插槽内容。如果在这个过程中遇到任何异步依赖,则会进入挂起状态。在挂起状态期间,展示的是后备内容。当所有遇到的异步依赖都完成后,<Suspense> 会进入完成状态,并将展示出默认插槽的内容。

如果在初次渲染时没有遇到异步依赖,<Suspense> 会直接进入完成状态。

进入完成状态后,只有当默认插槽的根节点被替换时,<Suspense> 才会回到挂起状态。组件树中新的更深层次的异步依赖不会造成 <Suspense> 回退到挂起状态。

发生回退时,后备内容不会立即展示出来。相反,<Suspense> 在等待新内容和异步依赖完成时,会展示之前 #default 插槽的内容。这个行为可以通过一个 timeout prop 进行配置:在等待渲染新内容耗时超过 timeout 之后,<Suspense> 将会切换为展示后备内容。若 timeout 值为 0 将导致在替换默认内容时立即显示后备内容。

<Suspense> 组件会触发三个事件:pendingresolve 和 fallbackpending 事件是在进入挂起状态时触发。resolve 事件是在 default 插槽完成获取新内容时触发。fallback 事件则是在 fallback 插槽的内容显示时触发。

<Suspense> 组件自身目前还不提供错误处理,不过你可以使用 errorCaptured选项或者 onErrorCaptured()钩子,在使用到 <Suspense> 的父组件中捕获和处理异步错误。

我们常常会将 <Suspense> 和 <Transition><KeepAlive><RouterView>等组件结合。要保证这些组件都能正常工作,嵌套的顺序非常重要。

<RouterView v-slot="{ Component }">
  <template v-if="Component">
    <Transition mode="out-in">
      <KeepAlive>
        <Suspense>
          <!-- 主要内容 -->
          <component :is="Component"></component>

          <!-- 加载中状态 -->
          <template #fallback>
            正在加载...
          </template>
        </Suspense>
      </KeepAlive>
    </Transition>
  </template>
</RouterView>

问题

  1. SFC(单文件组件)的优点,以及推荐在那些场景中使用?

    • 使用熟悉的HTML、CSS和JS语法编写模块化组件
    • 让本来就强相关的关注点自然内聚
    • 预编译模板,减少编译时的开销,优化编译性能
    • 组件作用域的css
    • 在使用组件式api时语法更简单
    • 更好的IDE支持,提供自动补全以及模板中表达式的类型检查
    • 开箱即用的模块热更新(HMR)支持

    推荐在单页面应用(SPA)、静态站点生成(SSG)

  2. SFC是如果生成的?
    Vue SFC 是一个框架指定的文件格式,因此必须交由 @vue/compiler-sfc 底层库编译为标准的 JavaScript 和 CSS,一个编译后的 SFC 是一个标准的 JavaScript(ES) 模块,这也意味着在构建配置正确的前提下,你可以像导入其他 ES 模块一样导入 SFC。
    @vitejs/plugin:为 Vite 提供 Vue SFC 支持的官方插件。
    vue-loader:为 webpack 提供 Vue SFC 支持的官方 loader。

  3. vue-cli、vite、webpack、create-vue、rollup分别是什么?以及之前的关系与区别?
    脚手架工具:能根据配置快速的生成一个具有完整开发环境的基础项目的工具。
    构建工具:建立项目的运行环境,需要手动安装插件。
    打包工具:代码写好之后,为了更好的使用,需要打包处理一下。
    vue-cli:是一款vue早期提供的基于webpack的脚手架工具
    create-vue:是现在vue提供的基于vite的脚手架工具
    vite:Vite 是一个轻量级的、速度极快的构建工具,包含一个开发服务器与一套构建指令,它使用Rollup打包代码
    webpack:是一个模块打包工具,极度灵活导致配置项极度复杂
    rollup:也是一个模块打包工具,更多使用在库的打包上 | 脚手架 | 构建工具 | 打包工具 | | --- | --- | --- | | vue-cli | | webpack | | create-vue | vite | rollup |