# Vue3 的正确打开方式

Vue3 已经出道了 3.2 版本了,但是目前 Vue3 的普及程度还是远不如 Vue2;但是 Vue3 的学习却是已经刻不容缓了;Vue2 的项目能否直接升级 Vue3 呢?那么 Vue2 到 Vue3 消失了那些 API?Vue3 新增或增强的 API 呢?

# Vue2 的项目能否直接升级 Vue3 呢

Vue2 的项目不能直接升级 Vue3;因为 Vue3 对 Vue2 并不是全兼容的;但是如果你把 Vue3 当成 Vue2 来使用也是没有问题的(只是用来做一些简单数据渲染,状态绑定);

# 消失的 API

# 消失的生命周期

  1. beforeDestroy --> beforeUnmount
  2. destroyed --> unmounted

从官方文档上看,这两个钩子有啥区别,不知道为什么要换名字;

既然没有了destroyed,那么自然在实例中就不存在$destroy方法了;以后再也不能愉快的手动销毁组件了;

# 消失的$listeners

不得不说$attrs, $listener的出现是 Vue2 的以一大进步,在早期开发中还没有$attrs, $listener的时候,只要做组件的封装不得不吐槽 Vue2 的呆板,跟 react 相比,只能说 react 的全属性继承,全事件透传是真香;

Vue3 中$listener已经被遗弃;但是Vue3并没有放弃全事件透传的能力;反而更加向react靠拢,书写起来更方便,更简单;将$listeners 的与$attrs合并;以后再不用组件上使用v-on="$listeners"了;来看一下代码的区别;

Vue2 中:

<!-- 父组件 -->
<template>
  <child :placeholder"请输入" :max-length="10" @change="changeHandler"></child>
</template>

<!-- 子组件 -->
<template>
  <div >
    <el-input v-bind="$attrs" v-on="$listeners"></el-input>
  </div>
</template>

Vue3

<template>
  <child :placeholder"请输入" :max-length="10" @change="changeHandler"></child>
</template>

<!-- 子组件 -->
<template>
  <div >
    <el-input v-bind="$attrs"></el-input>
  </div>
</template>

Vue3 当需要透传的属性是动态的时候:

<template>
  <child :attrs="attrs"> </child>
</template>
<script>
export default {
  data() {
    return {
      attrs: {
        placeholde: '请输入',
        maxLength: 10,
        onChange: this.changeHandler
      }
    }
  },
  methods: {
    changeHandler() {}
  }
}
</script>
<!-- 子组件 -->
<template>
  <div>
    <el-input v-bind="attrs"></el-input>
  </div>
</template>
<script>
export default {
  props: {
    attrs: {
      type: Object,
      default: () => ({})
    }
  }
}
</script>

重点

当事件作为属性透传的时候一定,必须是以 on 开头,接事件名称

# 消失的 Vue.prototype

import { createApp } from 'vue'
const app = createApp()

Vue3 中,不再支持在 Vue.prototype 上绑定全局属性;

  1. Vue3 中的 Vue 不再是一个函数,而是一个对象
  2. createApp 函数返回的是一个 VueComponent 对象, 对象不存在 prototype 属性, 所以也不能在 createApp函数返回的对象的原型链上绑定全局属性
  3. Vue 组件中的this对象任然保留 Vue 自定义全局属性,例如($attrs, $nextTick, $set......)

Vue3 中提供了一个全局对象配置 app.config.globalProperties,需要挂载到全局的属性可以放在这个全局对象上;挂载到全局对象上的属性可以再组件内部通过this访问;

提示

setup函数中不存在this对象, 在 setup 中想要获取实例,请使用 getCurrentInstance 方法,该方法返回组件实例;

# 消失的实例属性

  1. $children
  2. $scopedSlots
  3. $isServer
  4. $listeners

# 消失的特殊指令

  1. slot
  2. slot-scope
  3. scope

总结一句话就是 Vue3 废除作用域插槽,具名插槽;仅支持 v-slot; v-slot 仅限用于 template 组件,且组件仅能只有 v-slot 这一个属性;

# 消失的 observable

observable最早出现在 2.6 的版本中;主要用于做全局响应式数据的存储,在普通的项目中,如果全局响应式的数据不是很多的情况是可以不必要使用 vuex, 用 observable 即可;用法跟 vuex 差不多,简单易用;

在 Vue3 中,Vue3 提供了更为强大的相应 API reactive, 用法跟 observable 一样;

// sotre.js
import { reactive } from 'vue'
export const state = reactive({
  username: 'town',
  token: 'TK23423423'
})

export function setName(payload) {
  state.username = payload
}
// 组建中使用store

import { state, setName } from './store.js'

export default {
  computed: {
    username: () => state.username
  },
  methods: {
    setUserInfo() {
      setName('town2021')
    }
  }
}

# 消失的过渡 class

  1. .v-enter --> .v-enter-from
  2. .v-leave --> .v-leave-from

    重点

    transitontransition-group可以作为根节点; transition-group不再默认渲染根元素;

# 消失的事件 API

  1. $on
  2. $once
  3. $off

提示

原生事件修饰符.native被移除

# 消失的@hook

@hook是一个组件生命周期监听器,该属性的出现主要是为了方便在父组件,更优雅的监听子组件的生命周期钩子的触发,当然也可以用于在组件中监听自身生命周期钩子的触发;

Vue3 中不再支持@hook, 而是提供了能力更为强大的@vnode-; 该属性不仅可以作用于组件上,也可作用于 HTML 元素上;但是遗憾的是因为没有了$on$once@vnode-估计只能作用于 template 中,无法在组件生命周期钩子以及 methods 中使用;

# 消失的 filter

filter在 Vue3 中已经被移除,无论是局部 filter 还是全局 filter 都不支持;局部建议使用计算属性或者方法来替换,全局兼用将 filter 方法挂载到app.config.globalProperties上;

# 消失的 productionTip

对于 ES 模块构建,由于它们是与 bundler 一起使用的,而且在大多数情况下,CLI 或样板已经正确地配置了生产环境,所以本技巧将不再出现

# 消失的 extend

组件继承在 Vue3 中不被建议,Vue3 建议使用组合式 API 来替代继承和 mixin; 如果一定要使用基础,那么官方提供了extends属性;

如果使用 extend 来创建组件,Vue3 中将使用creatApp来替代;

# 消失的 config.keyCodes

Vue2 中可以通过config.keyCodes来配置键盘的按键事件;从KeyboardEvent.keyCode has been deprecated (opens new window) 开始,Vue 3 继续支持这一点就不再有意义了。因此,现在建议对任何要用作修饰符的键使用 kebab-cased (短横线) 大小写名称;

提示

Vue3 不再支持使用数字作为v-on修饰符

# 消失的 propsData

propsData 选项在 Vue2 中用于创建 vue 实例的过程中传入 prop,在 Vue3 中被移除;如果想在创建实例的时候传入 prop,需在createApp函数中出入第二参数;

# 消失的内联模板 inline-template

什么是 inline-template:以便将自定义组件内部内容用作模板,而不是将其作为分发内容;

基本用不上,可以忽略,使用该属性的人基本上是闲得无聊的;

# 函数式组件的变化

  1. 函数式组件不再必须functional属性,可以是一个纯函数
  2. 函数式组件接收两个参数props context;(context = {slots, attrs, emit}),跟setup函数一样;
  3. 需要显式的引入 h 函数(import { h } from 'vue');
  4. 函数式组件依然支持 Vue 的写法,只不过 render 函数发生了一点点的变化

Vue3 中使用函数式组件的两种方法

// 纯函数
const createDynamicCom = (props, { slots, attrs, emit }) => {
  return h('div', attrs, slots)
}
// functional声明
import { h } from 'vue'
export default {
  functional: true,
  // render不再接收 h函数
  render() {
    return h('div', this.attrs, this.slots)
  }
}

render 函数 接收 6 个参数,但是仅第一个参数有使用价值,且第一个参数的值安全等于 this 对象;所以一般我们直接使用 this 就行;一起来看看 this 对象有哪些属性;

{
  $: Object // app实例
  $attrs: Proxy // 组件的attribute
  $data: Object // 当前组件的data数据
  $el: HTMLELement // 当前组件的dom实例
  $emit: function // 触发emit时间的函数
  $forceUpdate: function // 强制更新组件函数
  $nextTick:function
  $options: Object // 自身组件的对象数据,
  $parent: Proxy // 父组件的引用对象,
  $props: Proxy // 显式在组件的props属性中生命的props对象
  $refs: Proxy // 当前组件的ref
  $root: Proxy // vue实例根节点
  $slots: Proxy // 当前组件的插槽
  $watch: function // 监听函数
}

重点

$parent 不再是当前函数式组件的父级组件,而是包裹当前函数式组件的 dom 或者自定义组件,这是跟 Vue2 不同的地方, 使用的时候请谨慎;

# 渲染函数 h 的变化

h函数不再能够根据组件名来渲染全局注册的组件;仅支持渲染 dom 标签,以及被加载的组件;

Vue2 中使用 h 函数渲染全局组件

export default {
  render() {
    return h('el-input')
  }
}

Vue3 中使用 h 函数渲染全局组件

import { resolveComponent } from 'vue'
const ELInput = resolveComponent('el-input')
export default {
  render() {
    return h(ELInput)
  }
}

Vue3 中使用 h 函数渲染非全局组件

import { ELinput } from 'element-ui'

export default {
  components: {
    ELinput
  },
  render() {
    return h(ELInput)
  }
}

# 新增或增强的 API

# 增强的 ref

ref 是一个组件或者 dom 的引用,Vue2 中,ref 一般是一个字符串;当多个组件使用同一个 ref 的时候,this.$refs[ref] 将返回一个引用数组; Vue3 中ref 可以是一个函数,函数接一个参数,改参数为绑定改 ref 的组件的实例;

<div v-for="item in list" :ref="setItemRef"></div>
export default {
  data() {
    return {
      itemRefs: []
    }
  },
  methods: {
    setItemRef(el) {
      if (el) {
        this.itemRefs.push(el)
      }
    }
  },
  beforeUpdate() {
    this.itemRefs = []
  },
  updated() {
    console.log(this.itemRefs)
  }
}

# 新增 defineAsyncComponent

创建一个只有在需要时才会加载的异步组件; 可以接受一个返回 Promise 的工厂函数。Promise 的 resolve 回调应该在服务端返回组件定义后被调用。你也可以调用 reject(reason) 来表示加载失败; 如果仅仅是这样好像跟() => import()没什么区别;

来看一下比较高阶的玩法

import { defineAsyncComponent } from 'vue'

const asyncModalWithOptions = defineAsyncComponent({
  loader: () => import('./Modal.vue'),
  delay: 200,
  timeout: 3000,
  errorComponent: ErrorComponent,
  loadingComponent: LoadingComponent
})

# 自定义指令的变化

指令生命周期的变化

Vue2 Vue3 备注
created 新增
bind beforeMount
inserted mounted
beforeUpdate 新的!这是在元素本身更新之前调用的,很像组件生命周期钩子
update 移除!请改用 updated
componentUpdated updated 此处判断组件更新调用十分频繁,如需使用此钩子,一定要写明确的触发的判断条件
beforeUnmount 新增!与组件生命周期钩子类似,它将在卸载元素之前调用。
unbind unmounted

访问组件实例

// vue2中
bind(el, binding, vnode) {
  const vm = vnode.context
}

// vue3中
mounted(el, binding, vnode) {
  const vm = binding.instance
}

# data 选项的变化

如果你不是用mixin或者extend,那么 data 的变化你基本是不能感知的;因为 data的变化主要在他的多个 data 合并时的合并策略;当合并来自 mixinextend 的多个 data 返回值时,现在是浅层次合并的而不是深层次合并的(只合并根级属性);

这简单的一句话暴露了一个非常重大的问题;不了解的话很有可能就踩坑了;

// index.mixin.js
export default {
  data () {
    return {
      userInfo: {
        age: 18
      }
    }
  }
}

// 组件中
import IndexMixin from './index.mixin.js'
export default {
  mixins: [IndexMixin],
  data () {
    return {
      userInfo: {
        name: 'jack',
        age: 20
      }
    }
  }
}

// Vue2合并后的结果
data () {
  return {
    userInfo: {
      name: 'jack',
      age: 18
    }
  }
}

// vue3合并后的结果
data () {
  return {
    userInfo: {
      age: 18
    }
  }
}

# 新增 emits 选项

emits 属性用法类似 props 属性;意在可以让开发者显式的定义该组件可以向父组件触发的事件;Vue2 中在触发 emits 的时候是不知道当前组件可以向父组件触发什么事件的;

emits 可以是简单的数组,也可以是对象,后者允许配置事件验证;个人感觉在实际开发中并没有太大的用武之地;因为在向父组件触发事件的时候一般是手动,且目的,数据十分明确的状态下的;但是还是来一起看看如何配置验证函数;

export default {
  emits: {
    click: payload => {
      if (payload.email && payload.password) {
        return true
      } else {
        console.warn(`Invalid submit event payload!`)
        return false
      }
    }
  }
}

验证函数接收当前组件传递给父组件的参数作为参数;验证函数应返回布尔值,以表示事件参数是否有效;

# 新增 fragments

组件支持多根节点,Vue2 中一个组件只能拥有一个根节点;

# key 属性的非必需性

对于 v-if/v-else/v-else-if 的各分支项 key 将不再是必须的,因为现在 Vue 会自动生成唯一的 key template v-for key 应该设置在 template 标签上 (而不是设置在它的子节点上),Vue2 中template不允许使用 key

# 优化版的$mount

挂载的内容将作为挂载容器的innerHTML; 而非整个替换挂载容器;

# 强化版的 v-model

v-model 不再特指 value,也不再特定使用 update 来触发;

  1. 现在可以在同一个组件上使用多个 v-model 进行双向绑定
  2. 现在可以自定义 v-model 修饰符 (不建议使用,使用起来会让你的代码变得更引用,可读性更低;)

# v-if 和 v-for 的优先级调整

Vue3 中 v-if 的优先级将高于 v-for; 真算是 Vue3 的一点点性能优化;但是还是不建议使用模板来做渲染逻辑处理,推荐使用计算属性来处理那些数据数据是否该被渲染;

# 增强的 v-bind

Vue2 中直接声明的 attribute 属性优先级高于 v-bind 中定义的属性;Vue3 中将会将直接生命的 attributev-bind 中的属性合并;优先级取决于直接声明的 attribute 是书写在 v-bind 的前面还是后面;也就是优先级由顺序决定,越往右优先级越高;

# 新增响应式 API

  1. reactive
  2. readonly
  3. isProxy --> 检查对象是否是由 reactive 或 readonly 创建的 proxy
  4. isReactive --> 检查对象是否是由 reactive 创建的响应式代理
  5. isReadonly --> 检查对象是否是由 readonly 创建的只读代理
  6. toRow
  7. markRow
  8. shallowReactive
  9. shallowReadonly

# reactive

reactive 简单来说就是用来定义响应式对象的;大部分情况我们在 setup 中把他充当 Vue2 中的 data 函数来使用的;只是 reactive 返回的响应式数据必须通过 setup 函数 return 出来; 对对象中的值为 ref 的属性自动解包;通过属性访问到的直接是值,而并非 ref 包;

这里我们必须明确的是经过 reactive 函数处理之后返回的并不是传入的对象本身,而是返回了原始对象的响应式副本, 且是深层次的代理,无论对象如何嵌套,所有属性都将被代理;所以后期你操作原始对象是不会触发视图更新的,但是数据会发生变化;

Tips

setup 函数中, return 之前,无论你操作原始对象还是响应式副本,数据会更新,并将最新的数据作用于视图;此处不要产生误解,好像操作原始数据能更新页面视图;但是其实在页面视图渲染之前数据已经发生更新;

问:更新原始数据之后,响应式副本的数据也已经发生了更新,那什么时候可以将更新的数据反应到视图上呢?

答:下一次响应式副本数据更新的时候

看到这是是不是觉得有点像 Vue2 中未产生响应式的状态

了解什么是响应式副本,我们先了解一个叫 Reflect (opens new window) 的特性;

# readonly

readonly 顾名思义就是只读;被 readonly 处理过的无论是响应式对象还是响应式基础类型数据返回的都只是原始对象的只读代理,这种代理是深层次的;也就是不要妄想改变 readonly 处理过的数据; 所有对 readonly 代理过的数据进行操作的都被将收到警告;

赋值警告: Set operation on key "${String(key)}" failed: target is readonly.

删除属性警告: Delete operation on key "${String(key)}" failed: target is readonly.

# toRow

toRow 这个可以说是比较鸡肋的一个 API;主要作用就是返回 reactive, readonly 代理的原始对象;主要作用就是为了实现上面讲 reactive 提到的数据更新,视图不更新的场景;但是大部分时候呢它是作为一个优化的项来使用的;

  1. 在临时读取数据的时候,从经过 toRow 处理过的原始对象读取数据无需承担代理访问/跟踪的开销;
  2. 在视图更新给紧急的时候,先做数据更新,等待所有数据处理完毕之后,将所有更新的数据进行一次性的更新,这样避免多次触发视图更新;

# markRow

在了解了 toRow 之后,看 markRow 的名称大概就能猜到了它的作用 --> “标记一个对象,使其永远不会被转换成 proxy,返回对象本身”

# shallowReactive

shallowReactivereactive 一样,都是产生响应式副本,但是 shallowReactive 产生的响应式副本却是浅层次 ,而非像 reactive 那样深层次,它只对自身根属性产生响应式;不执行嵌套对象的深层响应式转化;对值为 ref 的属性并不会自动解包,通过属性访问到的依然是 ref 包;

# shallowReadonly

shallowReadonlyshallowReactive 理解一样,只对对象自身的根属性产生只读影响,而不会执行嵌套对象的深度只读转换;对对象中值为 ref 的属性也不会产生只读效果,无论是不是根属性;其实这里也很好理解,ref 包裹后的基础类型数据也是一个对象;shallowReadonly 并不会自动将 ref 解包;

# ref

ref reactive 的作用一样,都是定义响应式数据的;既然功能类似为什么 Vue3 要提供两个 API 呢?

虽然功能类似,但是还说区别的:

  1. ref 返回的响应式数据是一个 ref 对象;
  2. reactive 返回的是一个 Proxy 数据;
  3. ref 代理的数据必须通过 ref 对象的内部属性 value 访问,value 是一个 Proxy 数据;
  4. ref 产生响应式数据的过程是先接受一个数据作为 ref 对象的内部属性 value 的值;然后对内部属性 value 做响应式处理;
  5. reactive 是直接将接受到的对象做响应式处理,并返回一个响应式副本;
  6. ref 可以用来代理引用数据;
  7. reactive Vue3 不能用来代理基础类型数据, 直接返回原始值;

# 说到这里:

问:为什么官方推荐基础类型的数据推荐使用 ref 而 引用类型数据推荐使用 reactive 呢?

答: Vue3 的 Proxy 只能为引用类型的数据提供代理服务, 无法为基础类型数据提供代理服务;

问:为什么 ref 能将基础类型数据通过 Proxy 代理?

答:ref 函数接收一个值,作为内部对象的 value 属性的值,返回的是一个 ref 对象;本质上就是将接收的值放到 ref 对象上,然后使用 Proxyref 对象进行代理;

问:同样是引用类型数据 refreactive 如何取舍?

答:如果你想对数据的每次更新都是重新赋值,那么建议使用 ref, 如果只是对对象的属性进行更新,那么建议使用 reactive

重点

  1. ref 不仅只能对基础类型数据做响应时代理
  2. reactive 一直只能对引用类型数据做响应式代理
  3. ref 代理的数据在 js 中访问必须通过 .value 访问,在模板中使用会自动解包,直接使用即可;
  4. 基础类型数据:stringnumberbooleanbigintnullundefinedsymbol

# unRef

如果参数是一个 ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val 的语法糖函数;

# toRef

可以用来为源响应式对象上的某个 property 新创建一个 ref。然后,ref 可以被传递,它会保持对其源 property 的响应式连接。 白话将就是可以将响应式对象中的某个属性结构出来,并且这个属性还将与响应式对象保持响应式链接,不会因为被解构而失去响应式;

const state = reactive({
  a: 1
})
const a = toRef(state, 'a')
a++
console.log(state.a) // 2

# toRefs

批量解构响应式对象,功能同 toRef

# customRef

创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收 tracktrigger 函数作为参数,并且应该返回一个带有 getset 的对象;

<input v-model="text" />
function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger()
        }, delay)
      }
    }
  })
}

export default {
  setup() {
    return {
      text: useDebouncedRef('hello')
    }
  }
}

其实意义不大,类似情况应该在我们应该我们的业务层代码去处理,而并非实现一个自定的 ref

# shallowRef

创建一个跟踪自身 .value 变化的 ref,但不会使其值也变成响应式的。类似于 shallowReactive;

# triggerRef

手动执行与 shallowRef 关联的任何副作用。

# computed

computed 如果是像 Vue2 那样去使用的话跟 Vue2 中的 computed 没有任何区别;

下面来看一下在 setup 函数中如何使用:

import { computed, reactive } from 'vue'

const state = reactive({
  name: 'town'
})

const name = computed(() => state.name)

state.name = 'town2021'

console.log(name) // town2021

// 当然我们还可以创建一个可以修改的计算属性
const name = computed({
  get: () => state.name,
  set: val => state.name === val
})

name = 'town2020'

console.log(state.name) // town2020

总结一句话,computed 接收一个 getter 函数, 并从 getter 返回值中返回一个不变的响应式 ref 对象;

# watchEffect / watch

watchEffect 监听响应式副作用函数,响应式地跟踪其依赖项时立即运行一个函数,并在更改依赖项时重新运行它。watchEffect 的作用跟 watch 类似;

但是也有几点不同:

  1. watch 惰性执行副作用,watchEffect 会立即执行;
  2. 更具体地说明应触发侦听器重新运行的状态
  3. 访问被侦听状态的先前值和当前值

第一点在上文已经提到了

第二点,大家来看一段代码;

const state = {
  name: 'town'age: 20
}

// 并未声明我要监听什么,但是该函数只会监听state.name的变化
const watchEffect(() => {
  if (state.name) {
    console.log(state.name)
  }
})

// 显式的声明了当前watch监听的是state.name
watch(() => state.name, (val, oval) => {
  console.log(val, oval)
})

// 其实这段代码也说明了第三点的不同,watch 的回调接收两个参数,一个是state.name的当前状态值,一个是更新之前的值

现在来看一下 watchEffectwatch 都是如何监听多个属性变化的;

const state = {
  name: 'town'age: 20
}

// watchEffect 监听多个属性变化,
// name 和 age 变化的时候都会触发 watchEffect 的执行
const watchEffect(() => {
  if (state.name) {
    console.log(state.name)
  }
  if (state.age === 22) {
    console.log(state.age)
  }
})

// watch 监听多个属性变化
// 回调函数接收两个数组,分别是监听元素的当前值数组和更新之前的值数组
watch([state.name, state.age], ([name, age], [oname, oage]) => {
  console.log(name, age)
})

问:为什么 watchEffect 不需要显式的声明监听的数据也能正确的监听数据的变化;

# 新增组合式 API

# setup

setup 函数接受两个参数:

  1. props
  2. context

# props

props: 显式声明在 props 选项中的属性, props 跟 Vue2 中的 props 一样,是响应式的,所以不建议使用 ES6 解构,会消除 props 的响应式;

如果一定要进行解构且希望保留响应式的话,Vue3 也为我们提供了安全的方法:

props: {
  name: String,
  age: Number
},
setup (props) {
  // 第一种解构方式
  const { name } = toRefs(props)
  // 第二种解构方式
  const newProps = { ...toRefs(props) }
  // 第三种解构方式
  const name = toRef(props, 'title')
}
// 这三种解构方式都可保持响应式, 只要是被使用toRefs之后的数据后续无论你再如何进行解构都将保持响应式连接;
// 第三种解构方式的主要是为了解决当传入的props中不存在name的情况下,toRefs将不会为name创建一个 ref 对象,
// 所以name也就不存在了,后续技术props传入了name,那么我们结构出来的数据也不会是响应式的;

推荐使用第一种解构方式,在 JS 中使用,按需解构;切记请勿将 props 中结构出来的数据使用在模板上

问:如何能让产生了响应式的数据失去响应式?

# context

context: 因为在 setup 函数执行的时候,组件实例还未被创建,所以在 context 中仅有 attrsslotsemit 这三个属性;context 只是一个普通的对象,所以允许解构;

  1. attrs:父组件传入的属性,非响应式
  2. slots:当前组件的插槽,非响应式
  3. emit:触发父组件定义的自定义事件的触发器

# 渲染函数

setup 支持返回一个渲染函数,渲染函数的内容将替换 template 的内容;该渲染函数可以返回一个 h 函数返回的结果;也可以返回一段 JSX;

h 函数的渲染

setup () {
  const state = reactive({
    title: '组件'
  })
  return () => h('div', {}. 'h函数的渲染' + state.title)
}

jsx 渲染

setup () {
  const state = reactive({
    title: '组件'
  })
  return () => (
    <div>jsx渲染{state.title}</div>
  )
}

推荐使用 JSX 写法,dom 解构,数据逻辑更清晰;

注意

setup 函数中 this 并非当前活跃实例的引用;如果需要引用当前活跃实例,请使用 getCurrentInstance 函数;

# 组合 API 生命周期钩子

# 选项 API 生命周期选项和组合式 API 之间的映射

  1. beforeCreate -> 使用 setup()
  2. ~~ created~~ -> 使用 setup()
  3. beforeMount -> onBeforeMount
  4. mounted -> onMounted
  5. beforeUpdate -> onBeforeUpdate
  6. updated -> onUpdated
  7. beforeUnmount -> onBeforeUnmount
  8. unmounted -> onUnmounted
  9. errorCaptured -> onErrorCaptured
  10. renderTracked -> onRenderTracked
  11. renderTriggered -> onRenderTriggered
  12. activated -> onActivated
  13. deactivated-> onDeactivated