Vue3.x 基础使用(持续更新中...)

2,171 阅读8分钟

如果发现 有问题 或者觉得 有常用但是没写到 的地方,欢迎留言讨论,我会及时改正与添加

关于Composition Api(组合式API)

Vue3.x 带来了一个全新特性—— Composition API组合式API),理解是为了实现函数的聚合逻辑逻辑复用,而产生的。

回顾Option Api

Option Api的缺陷

  • 随着业务复杂度越来越高,代码量会不断的加大;
  • 由于相关业务的代码需要遵循option的配置写到特定的区域;
  • 如果没有非常好的约束,会导致后续维护非常的复杂,代码可复用性也不高。

Composition Api

Composition Api让相关功能的代码更加有序的组织在一起,聚合逻辑!

按官网分类包括:

  • setup
  • 生命周期钩子:除了beforeCreatecreated不在setup里之外,其他都是前面加个on,在setup里调用,例:onMounted
  • Provide / Inject
  • getCurrentInstance(详见官网): 支持访问内部组件实例,适合高阶使用场景,典型的比如在库中。。

Vue3.x 新增 composition api,但也向下兼容了 option api,可以混用, 但是从理念上来说,更加推荐setup的方式,来写我们的组件。

原因如下:Vue3的存在,本身是为了解决Vue2的问题的,Vue2的问题就是在于,聚合性不足,会导致代码越来越臃肿!setup的方式,能够让data、方法逻辑、依赖关系等聚合在一块,更方便维护。

Setup

  • 在执行 setup函数的时候,还没有执行 beforeCreated ,所以在 setup 函数中,无法使用 datamethods 的变量和方法, setupthis 指向 undefined

image.png

  • setup参数:
    • { Data } props,
    • { attrs, emit, slots, expose } context
  1. emit: 就是 vue2.x 的 this.$emit.... 用来触发父组件的方法
  2. attrs: 就是 vue2.x 的 this.$attrs.. 就是组件本身挂载的属性(除 class 和 style 除外的非 props 属性集合)
  3. slots: 就是 vue2.x 的 this.$slots.. 记录插槽的信息(带有dom的属性)
  4. expose(3.2+) 类似于 setup 中的 return{}. 在使用模板渲染函数后,return 被占用 就需要使用 expose 将属性暴露出去(是否好用?)

若要对传递给 setup() 的参数进行 类型推导,你需要使用 defineComponent详见官网 或者 Vue 中的 defineComponent

export default defineComponent ({
  // 需要先声明 props,才能在setup中取值
  props: {
    title: String
  },
  setup( props, { attrs, emit, slots, expose }) {
    // Attribute (非响应式对象,等同于 $attrs)
    console.log(attrs)

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

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

    // 暴露公共 property (函数)
    console.log(expose)
    
    const reset = () => {
      // 某些逻辑
    }
    expose({
      reset
    })

    console.log(props.title)
    
    return { }
  }
})

逻辑聚合,关注点分离(共识)

应该分两层意思:

第一层意思,Vue3的setup,本身就把相关的数据,处理逻辑放到一起,这就是一种关注点的聚合,更方便我们看业务代码。

第二层意思,就是当setup变得更大的时候,我们可以在setup内部,提取相关的一块业务,做到第二层的关注点分离。


import { defineComponent, ref, computed } from 'vue'
import useUserInfo from './useUserInfo.ts'
export default defineComponent({
    name: 'Gift',
    setup() {
        const counter = ref(0)
        const onClick = () => {
            router.push({ name: "AddGift" })
        }
        // 在此示例中,把 userInfo 的相关业务分离出去。也就是下面的 useUserInfo.ts
        const { userInfo } = useUserInfo()
        return {
            counter,
            onClick,
            userInfo
        }
    }
})

useUserInfo.ts( hooks )

import { getUserInfo } from "@/api/gift"
import { ref, onMounted } from "vue"

export default function useUserInfo() {
  const userInfo = ref([])
  const fetchUserInfo = async () => {
    let res = await getUserInfo()
    userInfo.value = res.data
  }

  onMounted(fetchUserInfo)

  return {
    userInfo
  }
}

这种方式避免了将功能逻辑都堆叠在setup的问题,我们可以将独立的功能写成单独的函数

<script setup>

script setup已在vue3.2的版本上发布

image.png

// 隐性setup的script
<script setup>
  import {reactive} from "vue";
  const states = reactive({
    name: 'xiaoming',
    age: 18,
    fun(){
      console.log('xxxxx~')
    }
  })
  // 也可以单独直接定义
  const { name,age,fun } = states
</script>
// 优点: 更少的模板代码,简洁
// 缺点: 所有定义的变量都会暴露出去(是否必要?);  没有这两个`name` `inheritAttrs`

关于 <script setup> 介绍,可以看这篇 知乎:现在Vue3的script setup体验如何?

视频介绍的不错, 但是更新一点,Vue3.2 版本中<script setup>结束实验特性,已经是 稳定特性(传送门)

image.png

不过我们应当避免两种混用在一个文件里:

// bad 
<script setup>

</script>

// 显性setup的script
<script>
export default {
  setup(){
    return{
        //此时,这里暴露出的对象,无法被temp读取
    }
  }
}
</script>

生命周期函数

setup中使用生命周期钩子,除了BeforeDestroy变成了onBeforeUnmountdestroyed变成了onUnmounted 之外,其他都是前面加个on,例:onMounted

生命周期钩子中使用 async/await

setup() {
    const users = ref([])
    onBeforeMount(async () => {
      const res = await loadData()
      users.value = res.data
      console.log(res)
    })

    return {
      users,
    }
  },

引申出使用 async/await

setup() {
  async function handleSubmit() {
    try {
      // this parse may fail
      const values = JSON.parse(await validate())
      setModalProps({ confirmLoading: true })
      // TODO custom api
      console.log(values)
    } catch (err) {
      console.log(err)
    }
  }
  return {
      handleSubmit
  }
}

使用 async/await,也可以利用 Suspense 新增 包裹你的组件, 是否谨慎使用/避免使用

<template>
     <suspense>
        <router-view></router-view>
    </suspense>
</template>

export default {
  async setup() {
    // 在 `setup` 内部使用 `await` 需要小心
    // 因为大多数组合式 API 函数只会在,第一个 `await` 之前执行
    const data = await loadData()
    // 它隐性地包裹在一个 Promise 内
    // 因为函数是 `async` 的
    return {
      // ...
    }
  }
}

关于Reactivity API(响应式API)

Reactive && Ref

ref我们用来将基本数据类型定义为响应式数据,其本质是基于Object.defineProperty()重新定义属性的方式来实现

reactive用来将引用类型定义为响应式数据,其本质是基于Proxy实现对象代理

<template>
  <p> {{ name }} </p>
</template>
<script >
import { reactive, toRefs } from "vue";
export default {
    setup(){
      const obj = reactive({
        name: 'xiaoming',
        gender: '男',
        age: 18
      })

      return{
        // 直接解构相当于 ...obj ==> name:obj.name
        ...toRefs(obj)
      }
    }
}
</script >

如果直接解构,namestring,失去了响应性

image.png

使用 toRefs 详见官网 可以看到name是一个响应式字符串。可以在不失去响应性的情况下解构

image.png

toRefs会将我们一个响应式的对象转变为一个普通对象,然后将这个普通对象里的每一个属性变为一个响应式的数据

引申

既然如此,我们就可以将处理 同一块业务逻辑 的变量定义在一起,甚至是方法

<script >
import { reactive, toRefs } from "vue";
export default {
  setup(props){
    // 处理相同业务逻辑
    const state = reactive({
      isMoving: false,
      distance: '',
      startTime: 0,
      recordList: [],
      wrapper: {},
      // 甚至可以
      xxCallBack(){
        // do ...
      },
    })
    
    // 可以这么做,取决于你是否愿意
    const login = reactive({
      param: {
        username: '123',
        password: '123456',
      },
      rules: {
        username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
        password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
      },
      login(){
        this.param.username = 'inline'
        this.param.password = '123456'
        console.log('成功登录!')
      }
    })
    return{
      ...toRefs(state),
      // 避免这里一堆变量
      ...toRefs(login)
    }
  }
}  
</script>

Readonly

readonly 接受一个对象(响应式或纯对象) 或 ref 并返回原始对象的 只读代理。只读代理是 深层的:任何被访问的嵌套 property 也是只读的。

<script >
import { readonly, reactive } from "vue";
export default {
    setup(){
      const original = reactive({ count: 0 })
      const copy = readonly(original)
      // 变更副本将失败并导致警告
      copy.count++ // 警告!

      return{ }
    }
}
</script >

Computed & watch

Computed

Vue 3.x中的 computed 也支持getter和setter

import { reactive, ref, toRefs, computed } from 'vue'

export default {
  setup () {
    const state = reactive({
      count: 0,
      double: computed(() => {
        return state.count * 2
      })
    })
    const num = ref(0)

    const addCount = function () {
      state.count++
    }
    const addNum = function () {
      num.value++
    }

    // only getter
    const totalCount = computed(() => state.count + num.value)
    // getter & setter
    const doubleCount = computed({
      get () {
        return state.count * 2
      },
      set (newVal) {
        state.count = newVal / 2
      }
    })

    return {
      ...toRefs(state),
      num,
      totalCount,
      doubleCount,
      addCount,
      addNum
    }
  }
}

Watch

watch(source, callback, {Object}[options])

3.x和2.x的watch一样,也支持immediatedeep选项,但3.x不再支持'obj.key1.key2'的"点分隔"写法;

3.x中watch支持监听单个属性,也支持监听多个属性

默认情况下,watch 是惰性的, 那什么情况下不是惰性的, 可以立即执行回调函数呢? 给第三个参数中设置immediate: true即可注册后会立即调用

import { reactive, ref, toRefs, computed, watch } from 'vue'

export default {
  setup () {
    const state = reactive({
      count: 0,
      double: computed(() => {
        return state.count * 2
      }),
      midObj: {
        innerObj: {
          size: 0
        }
      }
    })
    const num = ref(0)
    const totalCount = computed(() => state.count + num.value)
                
    const addCount = function () {
      state.count++
    }
    const addNum = function () {
      num.value++
    }

    // 监听单个属性
    // 侦听器数据源可以是一个具有返回值的 getter 函数,也可以直接是一个ref
    watch(() => totalCount.value, (newVal, oldVal) => {
      console.log(`count + num = ${newVal}`)
    })
    
    // 监听单个属性, immediate
    watch(() => totalCount.value, (newVal, oldVal) => {
      console.log(`count + num = ${newVal}, immediate=true`)
    }, {
      immediate: true
    })
    
    // 监听单个属性, deep
    watch(() => state.midObj, (newVal, oldVal) => {
      console.log(`state.midObj = ${JSON.stringify(newVal)}, deep=true`)
    }, {
      deep: true
    })
    setTimeout(() => {
      state.midObj.innerObj.size = 1
    }, 2000)
    
    // 监听多个属性
    watch([num, () => totalCount.value], ([numVal, totalVal], [oldNumVal, OldTotalVal]) => {
      console.log(`num is ${numVal}, count + num = ${totalVal}`)
    })
    

    return {
      ...toRefs(state),
      num,
      totalCount,
      addCount,
      addNum
    }
  }
}

watch demo 传送门:https://sfc.vuejs.org/

SFC Tip

Stop 停止监听

在组件中创建的watch监听,会在组件被销毁时自动停止。如果在组件销毁之前我们想要停止掉某个监听, 可以调用watch()函数的返回值,

const stopTotalCount = watch(() => totalCount.value, (newVal, oldVal) => { 
    console.log(`count + num = ${newVal}`) 
})

setTimeout(()=>{ 
    // 停止监听
    stopTotalCount() 
}, 5000)

WatchEffect

立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。

import { defineComponent, ref, reactive, toRefs, watchEffect } from "vue"
export default defineComponent({
  setup() {
    const state = reactive({ nickname: "xiaoming", age: 20 })
    let year = ref(0)

    const numInterval = setInterval(() =>{
        state.age++
        year.value++
    },1000)

    watchEffect(() => {
        console.log(state.age)
        console.log(year.value)
    })

    
    return {
        ...toRefs(state)
    }
  },
})

demo 传送门:https://sfc.vuejs.org/ : 执行结果首先打印一次stateyear值;然后每隔一秒,打印stateyear值。

从上面的代码可以看出, 并没有像watch一样需要先传入依赖,watchEffect会自动收集依赖, 只要指定一个回调函数。在组件初始化时, 会先执行一次来收集依赖, 然后当收集到的依赖中数据发生变化时, 就会再次执行回调函数。

所以总结对比如下:

  • watchEffect 不需要手动传入依赖
  • watchEffect 会先执行一次用来自动收集依赖
  • watchEffect 无法获取到变化前的值,只能获取变化后的值(无oldVal)

响应式丢失

有一些操作,会导致我们丢失对象的响应式,我们应当避免

  • setup()的根范围 props 中获取一个值将导致该值失去响应性。
export default {
    props: {
        rowData: Object,
    },
    setup: (props) => {
       //bad
       const formData = props.rowData
       return { formData }
    }
}
  • setup中解构props
import {toRefs} from "vue"
export default {
    props:{
        a: string,
        b: string,
        c: string,
    },
    setup: (props) => {
        // bad 这里 a b c 都会失去响应性
        const { a, b, c } = props
        // good 需要将响应式对象转换为一组 ref
        const { a, b, c } = toRefs(props)

    }
}
  • 响应式状态解构
import { reactive,toRefs } from 'vue'

const book = reactive({
  title: 'Vue 3 Guide',
  price: 'free'
})
// bad 使用解构的 price,title 的响应性都会丢失
let { price, title } = book
// good
let { price, title } = toRefs(book)
title.value = 'Vue 3 Detailed Guide'
console.log(book.title) // 'Vue 3 Detailed Guide'
  • 使用let定义后不能直接重新赋值reactive对象,会导致响应式的代理被覆盖。
export default {
  name: 'App',
  setup() {
    let obj = reactive({a:1})
    // 这样重新赋值后会,obj会变成普通对象,失去响应式;
    setTimeout(() => {
      obj = {a:2,b:3}
    }, 1000)
    return {
      obj
    }
  }
}
// 应当,单独赋值
const obj = reactive({a:1})
setTimeout(() => {
  obj.a = 2;
  obj.b = 3;
}, 1000)
// 或,使用`Object.assign`
export default {
  name: 'App',
  setup() {
    const obj = reactive({a:1})
    setTimeout(() => {
      Object.assign(obj,{a:2, b:3})
    }, 1000)
    return {
      obj
    }
  }
}

组件间数据传递

Emits选项

vue3新增emits选项,使用 emits 记录每个组件所触发的所有事件,移除了 .native 修饰符

任何未在 emits 中声明的事件监听器都会被算入组件的 $attrs 并将默认绑定到组件的根节点上。

对于向其父组件透传原生事件的组件来说,这会导致有两个事件被触发:

<template>
  <button v-on:click="$emit('click', $event)">OK</button>
</template>
<script>
export default {
  emits: [] // 不声明事件
}
</script>

当一个父级组件拥有 click 事件的监听器时:

// vue 2.x 我们通常使用 .native 来处理这种情况
<my-button v-on:click="handleClick"></my-button>

该事件现在会被触发两次:

  • 一次来自 $emit()
  • 另一次来自应用在根元素上的原生事件监听器。

需要将组件发送的自定义事件定义在emits选项中:

<template>
  <button v-on:click="$emit('click', $event)">OK</button>
</template>
<script>
export default {
  emits: ['click'] // 声明事件
}
</script>

但是,我们定义 propsemit 对比,应该避免下面这样定义props: 官方风格指南

// 只有在原型开发时,这么做才能被接受
props: ['status']

子组件接收props

<script>
import { defineComponent } from "vue";
export default defineComponent({
    props: {
        name: String,
    },
    setup(props){
        console.log(props);
    }
})
</script>


/***或者***/

<script setup>
    import { defineProps } from "vue";
    let props=defineProps({
        name:String
    })
</script>


/***或者***/

<script setup>
    //这个api在下面的几种方法中都适用,但不建议用
    import { getCurrentInstance } from "vue"; 
    let instance=getCurrentInstance();
    console.log(instance.props);
</script>

子组件调用emit

<script>
import { defineComponent } from "vue";
export default defineComponent({
    setup(props,ctx){
        ctx.emit('myClick','这是传给父组件的值');
    }
})
</script>

父组件调用子组件方法

//子组件
setup(props, { expose }){
    const myChild = (val) => {
        console.log(val)
    }

    expose({
        myChild,
    })
}
// 父组件
<test ref="childs"></test>

const childs = ref()
onMounted(() => {
    childs.value.myChild(111)
})

Script setup里

<script setup>
  import { defineProps, defineEmits, defineExpose } from "vue";
  const props = defineProps({
    foo: String
  })
  const emit = defineEmits(['update', 'delete'])
  const b = ref(2)
  defineExpose({ b })
</script>

Provide / Inject

默认情况下,provide/inject 绑定 并不是 响应式的。

我们可以通过传递一个 ref property 或 reactive 对象(也可以用Computed)给 provide 来改变这种行为。


<!-- src/components/MyMap.vue -->
<template>
  <MyMarker />
</template>

<script>
import { provide, reactive, ref } from 'vue'
import MyMarker from './MyMarker.vue'

export default {
  components: {
    MyMarker
  },
  setup() {
    // 添加 provide 值和 inject 值之间的响应性
    const location = ref('North Pole')
    const getlocation = reactive({
      longitude: 90,
      latitude: 135
    })

    provide('location', location)
    provide('getlocation', getlocation)
  }
}
</script>

<!-- 使用 inject -->
<script>
import { inject } from 'vue'

export default {
  setup() {
    //inject 第二个参数默认值,可选
    const userLocation = inject('location', 'The Universe')
    const userGetlocation = inject('getlocation')

    return {
      userLocation,
      userGetlocation
    }
  }
}
</script>

Provide / Inject 修改响应式

小插曲

Vuex && Router

Vuex 3.x 是支持 Vue 2 的 Vuex , Vuex 4.x 支持 Vue 3

Vuex 属于Vue全家桶一部分,非必需, Vue Core Team 团队也出了 Pinia 详见官网 可以代替Vuex。

就像 Rudex 也可以用更轻量的 mobx-react 代替。

  • 在Vue2中,其实可以直接通过this.$store进行获取,Vue3中,是这么使用的:
import { useStore } from 'vuex'
import { defineComponent, computed } from 'vue'
export default defineComponent({
  name: 'Gift',
  setup() {
    const store = useStore()
    const storeData = computed(() => store) // 配合computed,获取store的值。
    return {
      storeData,
    }
  },
})
  • 在Vue2中,是通过this.$router的方式,进行路由的函数式编程,Vue3中:
import { useRouter } from "vue-router"
import { defineComponent, computed } from 'vue'
export default defineComponent({
    name: 'Gift',
    setup() {
        const router = useRouter()
        const onClick = () => {
            router.push({ name: "AddGift" })
        }
        return {
            onClick
        }
    }
})

CSS变量注入

<template>
  <span> Tom </span>  
</template>

<script setup>
  import { reactive } from 'vue'

  const state = reactive({
    color: 'red'
  })
</script>
  
<style scoped>
  span {
    // 使用v-bind绑定state中的变量
    color: v-bind('state.color');
  }  
</style>

Other

NextTick

import { createApp, nextTick } from 'vue'

const app = createApp({
  setup() {
    const message = ref('Hello!')
    const changeMessage = async newMessage => {
      message.value = newMessage
      await nextTick()
      console.log('Now DOM is updated')
    }
  }
})

Fragments

在 Vue3.x 中,你可以直接写多个根节点

<template> 
    <div></div>
    <div></div> 
</template>

Slot 具名插槽

在 Vue2.x 中具名插槽和作用域插槽分别使用slotslot-scope来实现,

在 Vue3.x 中将slotslot-scope进行了合并统一使用。 Vue3.0 中v-slot

<!-- 父组件中使用 -->
 <template v-slot:content="scoped">
   <div v-for="item in scoped.data">{{item}}</div>
</template>

<!-- 也可以简写成: -->
<template #content="{data}">
    <div v-for="item in data">{{item}}</div>
</template>

v-model的变化

v-model 详见官网 在vue3中发生了较大的变化:

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

    • prop:value -> modelValue
    • 事件:input -> update:modelValue
  • 非兼容v-bind 的 .sync 修饰符和组件的 model 选项已移除,可在 v-model 上加一个参数代替;

  • 新增:现在可以在同一个组件上使用多个 v-model 绑定;

  • 新增:现在可以自定义 v-model 修饰符。

看一下vue2.x中v-model的使用:

<ChildComponent v-model = "title />

它实际上是下面这种写法的简写:

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

也就是说,它是传递一个属性value,然后接收一个input事件。

Vue3中v-model的基础使用

<ChildComponent v-model = "title">

它是下面这种写法的简写:

<ChildComponent :modelValue = "title" @update:modelValue = "title = $event">

也就是说vue3中,value改成了modelValueinput方法了改成update:modelValue

在子组件中写法是:

export default defineComponent({
    name:"ValidateInput",
    props:{
        modelValue:String,   // v-model绑定的属性值
    },
    setup(props, { emit }){
        const updateValue = (e: KeyboardEvent) => {
          emit("update:modelValue",targetValue);   // 传递的方法
        }
    }
}

v-model 参数

modelValue不太具备可读性,在子组件的props中看到这个都不知道是什么。 因此,我们希望能够更加见名知意。可以通过: xxx传递参数xxx,更改名称

若需要更改 model 的名称,现在我们可以为 v-model 传递一个参数

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

//是以下的简写: 

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

image.png 在子组件中,就可以使用 title 代替 modelValue

export default defineComponent({
    name:"ValidateInput",
    props:{
       //modelValue:String,
        title:String,   // title替代了modelValue
    },
    setup(props, { emit }){
        const updateValue = (e: KeyboardEvent) => {
        //emit("update:modelValue",targetValue);   // 传递的方法
          emit("update:title",targetValue);   // 传递的方法
        }
    }
}

允许我们在自定义组件上使用多个 v-model 

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

v-model 修饰符

现在 3.x 支持自定义修饰符:

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

在 Custom Events 中查看自定义 v-model 修饰符的详细信息

Volar

Volar 是个 VS Code 的插件,除了支持如高亮、语法提示等之外,其最大的作用就是解决了 TS 提示问题。

注意,使用它时,要先移除 Vetur,以避免造成冲突。

image.png

image.png

编辑器快捷分割:点击小图标

vue文件,按照功能,被拆分成了三个视窗,并且每个视窗都负责自己的功能,其他的两个根元素都被合并了。

image.png

还有一些对class的友好交互:class追溯style里面的class引用

Event Bus

是否需要 mitt详见Github

import mitt from 'mitt'

const emitter = mitt()

// listen to an event
emitter.on('foo', e => console.log('foo', e) )

// listen to all events
emitter.on('*', (type, e) => console.log(type, e) )

// fire an event
emitter.emit('foo', { a: 'b' })

// clearing all events
emitter.all.clear()

// working with handler references:
function onFoo() {}
emitter.on('foo', onFoo)   // listen
emitter.off('foo', onFoo)  // unlisten

tiny-emitter

TS

json to ts: 可利用在api数据格式转换,根据json生成对应的 interface

高阶

Teleport

Teleport 是 Vue3.x 新推出的功能。粗暴的理解:Teleport 就像是哆啦 A 梦中的「任意门」,任意门的作用就是可以将人瞬间传送到另一个地方。

在子组件Header中使用到Dialog组件,我们实际开发中经常会在类似的情形下使用到 Dialog ,此时Dialog就被渲染到一层层子组件内部,处理嵌套组件的定位、z-index和样式都变得困难。 Dialog从用户感知的层面,应该是一个独立的组件,从 dom 结构应该完全剥离 Vue 顶层组件挂载的 DOM;同时还可以使用到 Vue 组件内的状态(data或者props)的值。

简单来说就是,即希望继续在组件内部使用Dialog, 又希望渲染的 DOM 结构不嵌套在组件的 DOM 中。 此时就需要 Teleport 上场,我们可以用<Teleport>包裹Dialog, 此时就建立了一个传送门,可以将Dialog渲染的内容传送到任何指定的地方。

Teleport Demo

demo定义一个Dialog组件Dialog.vue,留意 to 属性,与要达到的元素id选择器一致

Directive自定义指令

在 Vue 3 中对自定义指令的 API 进行了更加语义化的修改, 就如组件生命周期变更一样, 都是为了更好的语义化, 变更如下:

image.png

  1. 在 Vue3 中, 可以这样来自定义指令:
const { createApp } from "vue"

const app = createApp({})
app.directive('focus', {
    mounted(el) {
        el.focus()
    }
})

然后可以在模板中任何元素上使用新的 v-focus指令, 如下:

<input v-focus />
  1. 或者:

image.png

image.png

有了fragments的支持,组件可能会有多个根节点。当被应用于多根组件时,自定义指令将被忽略,并将抛出警告。

V1_04高级语法.png

文中部分参考以下链接内容片段,感谢🙏