vue3学习笔记

272 阅读3分钟

1. 组合式API

1)setup组件选项

新的 setup 选项在组件创建之前执行,一旦 props 被解析,就将作为组合式 API 的入口。

使用 setup 函数时,它将接收两个参数:

  1. props
  2. context 因为 props 是响应式的,所以我们不能使用 ES6 解构,它会消除 props 的响应性。 如果一定需要使用结构,vue提供了toRefs方法,来完成此操作
import { toRefs } from 'vue'

setup(props) {
  const { title } = toRefs(props)

  console.log(title.value)
}

如果 title 是可选的 prop,则传入的 props 中可能没有 title 。在这种情况下,toRefs 将不会为 title 创建一个 ref 。你需要使用 toRef 替代它:

import { toRef } from 'vue'
setup(props) {
  const title = toRef(props, 'title')
  console.log(title.value)
}

context 是一个普通的 JavaScript 对象,也就是说,它不是响应式的,这意味着我们可以安全地对 context 使用 ES6 解构。

export default {
  setup(props, context) {
    // Attribute (非响应式对象,等同于 $attrs)
    console.log(context.attrs)

    // 插槽 (非响应式对象,等同于 $slots)
    console.log(context.slots)

    // 触发事件 (方法,等同于 $emit)
    console.log(context.emit)

    // 暴露公共 property (函数)
    console.log(context.expose)
  }
}

2)不能在setup中使用this

因为setup执行在除了props之外的其他组件选项之前的

在 setup 中应该避免使用 this,因为它不会找到组件实例。setup 的调用发生在 data property、computed property 或 methods 被解析之前,所以它们无法在 setup 中被获取。

setup 选项是一个接收 props 和 context 的函数

3)setup中可以使用任何生命周期钩子,需要在前面加 on

前提是需要引入这些钩子函数

image.png

4)Vue3 如何跟踪变化的

  1. 当一个值被读取时进行追踪:proxy 的 get 处理函数中 track 函数记录了该 property 和当前副作用。
  2. 当某个值改变时进行检测:在 proxy 上调用 set 处理函数。
  3. 重新运行代码来读取原始值trigger 函数查找哪些副作用依赖于该 property 并执行它们。

5)带 ref 的响应式变量

import { ref } from 'vue'
const counter = ref(0)
ref解包

Ref 解包仅发生在被响应式 Object 嵌套的时候。当从 Array 或原生集合类型如 Map访问 ref 时,不会进行解包

6)watch  响应式更改

就像我们在组件中使用 watch 选项并在 user property 上设置侦听器一样,我们也可以使用从 Vue 导入的 watch 函数执行相同的操作。它接受 3 个参数:

  • 一个想要侦听的响应式引用或 getter 函数
  • 一个回调
  • 可选的配置选项
import { ref, watch } from 'vue'

const counter = ref(0)
watch(counter, (newValue, oldValue) => {
  console.log('The new counter value is: ' + counter.value)
})

7)独立的 computed 属性

import { ref, computed } from 'vue'

const counter = ref(0)
const twiceTheCounter = computed(() => counter.value * 2)

counter.value++
console.log(counter.value) // 1
console.log(twiceTheCounter.value) // 2

2.teleport

Teleport 提供了一种干净的方法,允许我们控制在 DOM 中哪个父节点下渲染了 HTML,而不必求助于全局状态或将其拆分为两个组件。

这仅仅是移动实际的 DOM 节点,而不是被销毁和重新创建,并且它还将保持任何组件实例的活动状态。所有有状态的 HTML 元素 (即播放的视频) 都将保持其状态。

app.component('modal-button', {
  template: `
    <button @click="modalOpen = true">
        Open full screen modal! (With teleport!)
    </button>

    <teleport to="body">
      <div v-if="modalOpen" class="modal">
        <div>
          I'm a teleported modal! 
          (My parent is "body")
          <button @click="modalOpen = false">
            Close
          </button>
        </div>
      </div>
    </teleport>
  `,
  data() {
    return { 
      modalOpen: false
    }
  }
})

简而言之<teleport>中的to="body"告诉 Vue Teleport 这个 HTML 将会渲染为该body标签的子级。

如果 <teleport> 包含 Vue 组件,则它仍将是 <teleport> 父组件的逻辑子组件,即使这个子组件会在不同的地方渲染,但是它仍将是 父组件的子级,并从父组件中接受传参prop

props: + to: 必须是有效的查询选择器或 HTMLElement (如果在浏览器环境中使用)。指定将在其中移动 <teleport> 内容的目标元素 + disabled: 禁用<teleport>的功能

3.触发组件选项

可以在单个组件实例上创建多个v-model绑定

组件上的 v-model 使用 modelValue 作为 prop 和 update:modelValue 作为事件。我们可以通过向 v-model 传递参数来修改这些名称

<user-name
 v-model:first-name="firstName"
 v-model:last-name="lastName"
></user-name>
app.component('user-name', {
  props: {
    firstName: String,
    lastName: String
  },
  emits: ['update:firstName', 'update:lastName'],
  template: `
    <input 
      type="text"
      :value="firstName"
      @input="$emit('update:firstName', $event.target.value)">

    <input
      type="text"
      :value="lastName"
      @input="$emit('update:lastName', $event.target.value)">
  `
})

v-model自定义修饰符

<my-component v-model.capitalize="myText"></my-component> // 自定义修饰符 capitalize

组件内部通过modelModifiers prop 来承接修饰符

app.component('my-component', {
  props: {
    modelValue: String,
    modelModifiers: {    // 组件内部通过modelModifiers prop 来承接修饰符
      default: () => ({})
    }
  },
  emits: ['update:modelValue'],
  template: `
    <input type="text"
      :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)">
  `,
  created() {
      
    this.modelModifiers.capitalize   // 默认值为 true
      
    console.log(this.modelModifiers) // { capitalize: true }
  }
})

对于带参数的 v-model 绑定,生成的 prop 名称将为 arg + "Modifiers"

<my-component v-model:description.capitalize="myText"></my-component>
app.component('my-component', {
  props: ['description', 'descriptionModifiers'],
  emits: ['update:description'],
  template: `
    <input type="text"
      :value="description"
      @input="$emit('update:description', $event.target.value)">
  `,
  created() {
    console.log(this.descriptionModifiers) // { capitalize: true }
  }
})

4.单文件组件<script setup>

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

  • 样板内容更少,代码更简洁
  • 能够使用纯TS声明props和抛出事件
  • 更好的运行时性能(其模板会被编译成与其同一作用域的渲染函数,没有任何的中间代理)
  • 更好的IDE类型推断性能

5.关于provide和inject

vue3 中在setup中使用这两个 也是要 import { provide } from 'vue' import { inject } from 'vue' 来引入的。

provide 函数允许你通过两个参数定义 property:

  1. name (<String> 类型)
  2. value
provide('location', 'North Pole')

如果想让location变为响应式的话,那么在provide这里就要使用ref和reactive使值变为响应式的
修改响应式的话需要在provide组件中修改
一定要在inject中修改的话,那么就从provide传一个修改函数过去,inject使用这个修改函数修改

inject 函数有两个参数:

  1. 要 inject 的 property 的 name
  2. 默认值 (可选)
const userLocation = inject('location', 'The Universe')

6. filter过滤器被移除了

  1. 建议使用计算属性和方法来处理
  2. 全局的过滤器 可以声明一个$filter挂载到全局可以使用

7.一些小改变

  • destroyed 生命周期选项被重命名为 unmounted

  • beforeDestroy 生命周期选项被重命名为 beforeUnmount

  • data只接受函数的返回

    • mixin和组件本身的合并只会是浅拷贝
  • 当侦听一个数组时,只有当数组被替换时,回调才会触发,如果需要在变更时触发,则必须指定 deep 选项

  • <template> 如果没有特殊的指令在上面(v-if/else-if/elsev-for 或 v-slot),现在被视为普通元素,并将渲染为原生的 <template> 元素,而不是渲染其内部内容。

    • 渲染成document-fragment
  • 已挂载的应用不会取代他所挂载的元素

    • 在 Vue 2.x 中,当挂载一个具有 template 的应用时,被渲染的内容会替换我们要挂载的目标元素。在 Vue 3.x 中,被渲染的应用会作为子元素插入,从而替换目标元素的 innerHTML
  • 生命周期的 hook: 事件前缀改为 vnode-

    vue2中的写法

    <template>
      <child-component @hook:updated="onUpdated">
    </template>
    

    vue3中可以有下面的两种写法

    <template>
      <child-component @vnode-updated="onUpdated">
    </template>
    
    <template>
      <child-component @vnodeUpdated="onUpdated">
    </template>
    

8.自定义元素检测现在在模板编译时执行

在 Vue 2.x 中,通过 Vue.config.ignoredElements 将标签配置为自定义元素: ``` // 这将使 Vue 忽略在其外部定义的自定义元素 // (例如:使用 Web Components API)

Vue.config.ignoredElements = ['plastic-button']
```

在vue3中是通过vue-loader中的配置项来在模板编译时执行检测

```
    // webpack 中的配置
    rules: [
      {
        test: /.vue$/,
        use: 'vue-loader',
        options: {
          compilerOptions: {
            isCustomElement: tag => tag === 'plastic-button'
          }
        }
      }
      // ...
    ]
```

或者可以设置 app.config.compilerOptions.isCustomElement

```
    const app = Vue.createApp({})
    app.config.compilerOptions.isCustomElement = tag => tag === 'plastic-button'
```

9.定制内置元素

在 3.0 中,我们将 Vue 对 is attribute 的特殊处理限制在了 <component> 标签中。

  • 在保留的 <component> 标签上使用时,它的行为将与 2.x 中完全相同;

  • 在普通组件上使用时,它的行为将类似于普通 attribute:

    <foo is="bar" />
    
    • 2.x 的行为:渲染 bar 组件。
    • 3.x 的行为:渲染 foo 组件,并将 is attribute 传递给它。
  • 在普通元素上使用时,它将作为 is attribute 传递给 createElement 调用,并作为原生 attribute 渲染。这支持了自定义内置元素的用法。

    <button is="plastic-button">点击我!</button>
    
    • 2.x 的行为:渲染 plastic-button 组件。

    • 3.x 的行为:通过调用以下函数渲染原生的 button

      document.createElement('button', { is: 'plastic-button' })
      

**总结: **
动态组件中

  • 3.x 只关注<component> 标签,只有<component> 标签才会把is传入的值当做组件渲染出来,其余的 只当成is 属性来渲染
  • 2.x 中,无论是 <component> <div> 亦或者是<foo />自定义组件,只要有is属性,都会把传入的当成组件渲染出来

10.emits选项

子组件的emits需要预先定义,子组件的emits接收一个数组或者对象

11.v-model的用法更改

  • 非兼容:用于自定义组件时,v-model prop 和事件默认名称已更改:

    • prop:value -> modelValue
    • 事件:input -> update:modelValue
  • 新增:现在可以在同一个组件上使用多个 v-model 绑定;
  • 新增:现在可以自定义 v-model 修饰符。

2.x的语法

在 2.x 中,在组件上使用 v-model 相当于绑定 value prop 并触发 input 事件:

    <ChildComponent v-model="pageTitle" />

    <!-- 是以下的简写: -->

    <ChildComponent :value="pageTitle" @input="pageTitle = $event" />

如果想要更改 prop 或事件名称,则需要在 ChildComponent 组件中添加 model 选项:

    <!-- ParentComponent.vue -->

    <ChildComponent v-model="pageTitle" />
    
    // ChildComponent.vue

    export default {
      model: {
        prop: 'title',
        event: 'change'
      },
      props: {
        // 这将允许 `value` 属性用于其他用途
        value: String,
        // 使用 `title` 代替 `value` 作为 model 的 prop
        title: {
          type: String,
          default: 'Default title'
        }
      }
    }

3.x的语法

在 3.x 中,自定义组件上的 v-model 相当于传递了 modelValue prop 并接收抛出的 update:modelValue 事件:

<ChildComponent v-model="pageTitle" />

<!-- 是以下的简写: -->

<ChildComponent
  :modelValue="pageTitle"
  @update:modelValue="pageTitle = $event"
/>

若需要更改 model 的名称,现在我们可以为 v-model 传递一个参数,以作为组件内 model 选项的替代:

<ChildComponent v-model:title="pageTitle" />

<!-- 是以下的简写: -->

<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />

这里子组件里面原本的modelValue就被替换成了title, 同理,在自定义组件中使用多个v-model

<ChildComponent v-model:title="pageTitle" v-model:content="pageContent" />

<!-- 是以下的简写: -->

<ChildComponent
  :title="pageTitle"
  @update:title="pageTitle = $event"
  :content="pageContent"
  @update:content="pageContent = $event"
/>

当我们设置自定义修饰符的时候,

<ChildComponent v-model.capitalize="pageTitle" />

在子组件中通过 props承接一个modelModifiers对象,而这个capitalize自定义修饰符就是存在于modelModifiers对象中的属性,我们需要在子组件模板中的@input或者重新定义的事件名称的函数中,做一些这个自定义修饰符需要做的事情

12.<template v-for>语法

<!-- Vue 2.x -->
<template v-for="item in list">
  <div :key="'heading-' + item.id">...</div>
  <span :key="'content-' + item.id">...</span>
</template>


<!-- Vue 3.x key应被设置在template标签上 -->
<template v-for="item in list" :key="item.id">
  <div>...</div>
  <span>...</span>
</template>

13.v-for 和v-if 使用在同一个元素上时,优先级修改

首先是不推荐这么使用,但是有时候我们不得不这么用

  • 2.x中 v-for的优先级更高
  • 3.x中 v-if的优先级更高