Vue 3.0 改变简介

1,731 阅读15分钟

V3.0 对 v2.0 兼容,原 2.0 的功能特性仍然可以继续使用,同时可以使用 V3.0 Composition API 新特性进行开发。

Vue Composition API

1 Vue (@vue/cli - 4.x)

1.1 Vue 初始化

  • 2.0版本
    通过 new 关键字 声明一个 Vue实例
// 2.0 Vue 初始化
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import './App.scss'
 
const app = new Vue({
    el: '#app',
    ...App,
    router,
    store
})
  • 3.0版本
    通过 Vue 提供的 createApp hook 初始化
// 3.0 Vue 初始化
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import './App.scss'
 
const app = createApp(App)
app.use(router).use(store).mount('#app')

1.2 setup

setup 函数是一个新的Vue组件, 是Composition API 入口

1.2.1 简介

组件实例化流程 先创建组件实例,然后初始化props,紧接着就调用setup函数。从生命周期钩子的视角来看,它会在beforeCreate钩子之前被调用,3.0 取消beforeCreate、Created 钩子函数,所以 setup函数 主要用来声明数据集,定义监听等 组件初始化 逻辑。

// 类型定义
interface Data {
  [key: string]: unknown
}
 
interface SetupContext {
  attrs: Data
  slots: Slots
  emit: (event: string, ...args: unknown[]) => void
}
 
function setup(props: Data, context: SetupContext): Data

1.2.2 参数

  • props
    作为 setup函数 的第一个参数,存放组件属性集,props对象是响应式,可以通过 watchwatchEffect 监听到 props 的更新,注意,不能对props 进行解构,会失去响应性,可以通过toRefs转换后结构。
export default {
  props: {
    name: String,
  },
  setup({ name }) {
    watchEffect(() => {
      console.log(`name is: ` + name) // Will not be reactive!
    })
  },
}
  • context
    作为 setup函数 的第二个参数, 类似 vm实例,作为上下文对象,暴露一些 vm 的 property,如attrsslotsemit,允许对context进行解构赋值。

1、attrs

对应 2.0 this.$attrs, 包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。

2、slots

对应 2.0 this.$slots,对应组件的插槽内容。

3、 emit

对应 2.0 this.$emit,即用于触发事件。

const MyComponent = {
  setup(props, context) {
    context.attrs
    context.slots
    context.emit
  },
}
 
// 允许对 context 解构赋值
const MyComponent = {
  setup(props, { attrs }) {
    // 一个可能之后回调用的签名
    function onClick() {
      console.log(attrs.foo) // 一定是最新的引用,没有丢失响应性
    }
  },
}

1.2.3 返回值(重点)

  • 返回一个对象
    通常 setup函数 返回 一个对象 ,对象包含组件模版 template 所使用的 响应式数据data(ref、reactive) 和一些 事件函数或其他函数,对象的属性将会被合并到组件模板的渲染上下文(this.xx)。注意 setup 返回的 ref 在模板中会自动解开,不需要写 .value。
<template>
  <div>{{ count }} {{ object.foo }}</div>
</template>
 
<script>
  import { ref, reactive } from 'vue'
 
  export default {
    setup() {
      const count = ref(0)
      const object = reactive({ foo: 'bar' })
 
      // 暴露给模板
      return {
        count,
        object,
      }
    },
  }
</script>
  • 返回一个函数(render函数 / JSX)
    函数对应的就是 Vue2.0的render 函数,需要定义 h 函数(从 vue 中获取函数hook),可以在这个函数里面使用 JSX(类似React函数组件 return 返回JSX)
// 返回 render 函数
import { h, ref, reactive } from 'vue'
 
export default {
  setup() {
    const count = ref(0)
    const object = reactive({ foo: 'bar' })
 
    return () => h('div', [count.value, object.foo]) // render 函数
  },
}
 
// 返回JSX
import { ref, reactive } from 'vue'
 
export default {
  setup() {
    const count = ref(0)
    const object = reactive({ foo: 'bar' })
 
    return <div value={count.value} foo={object.foo}></div>
  },
}

1.2.4 注意

this 在 setup() 中不可用。由于 setup() 在解析 2.x 选项前被调用,setup() 中的 this 将与 2.x 选项中的 this 完全不同(setup 中的 this 并不是你所想要的 组件实例)。

1.3 reactive hook

接收一个 普通对象参数 然后返回该普通对象的 响应式代理(返回的对象 不等于 对象参数)。等同于 2.x 的 Vue.observable()。差别在于 vue2.0 需要使用 Vue.set 来更新对象字段,才能触发响应更新,这个问题是因为Vue2.0使用的Object.defineProperty 无法监听到某些场景比如新增属性,但是到了Vue3.0中通过 Proxy 将这个问题解决了,所以我们可以直接在 reactive 声明的对象上面添加新的属性。

reactive
// 类型定义 //
function reactive<T extends object>(raw: T): T
 
// 声明一个obj //
const obj = { count: 0 }
const objReactive = reactive(obj)
console.log(objReactive == obj) // false
 
// 实例 //
<template>
  <!--在模板中通过state.name使用setup中返回的数据-->
  <div>{{ state.name }}</div>
</template>
 
<script>
import { reactive } from "vue";
 
export default {
  setup() {
    // 通过reactive声明一个可响应式的对象
    const state = reactive({
      name: "子君"
    });
 
    // 5秒后将子君修改为 前端有的玩
    setTimeout(() => {
      state.name = "前端有的玩";
    }, 1000 * 5);
 
    // 将state添加到一个对象中然后返回
    return {
      state
    };
  }
};
</script>
可以通过 isReactive 判断一个对象是否为 reactive 响应式对象

isReactive

判断是否为reactive对象

1.4 ref hook

ref

接受一个 参数值(初始值)并返回一个 响应式且可改变 的 ref 对象。ref 对象拥有一个指向内部值的单一属性 .value。如果传入 ref 的是一个对象,将调用 reactive 方法进行深层响应转换。

ref
// 类型定义
interface Ref<T> {
  value: T
}
 
function ref<T>(value: T): Ref<T>
 
// ref声明
setup(props, context) {
    const count = ref(0)
    console.log(count.value) // 0
 
    count.value++
    console.log(count.value) // 1
 
    return {
        count
    }
}

在 setup 函数中,访问 ref 对象,需要通过 .value 读取对应字段值,但在 template 不需要 .value ,setup 函数 return ref对象 会自动解开。当 ref 作为 reactive 普通对象参数property 被访问或修改时,也将自动解套 value 值,其行为类似普通属性(修改 reactive 对象的数值 会影响 对应 ref对象 的数值)。

// stage 1 //
// ref 对象作为 reactive对象 的 property 被访问,自动解套
const count = ref(0)
const state = reactive({
  count, // ref 解套
})
 
console.log(state.count) // 0
 
state.count = 1
console.log(count.value) // 1 受 reactive 修改影响
 
 
// stage 2 //
// 使用 新的 ref 分配给现有的 ref,将替换旧的 ref
const otherCount = ref(2)
 
state.count = otherCount // state 将被替换
console.log(state.count) // 2
console.log(count.value) // 1
 
 
// stage 3 //
// 使用Array、Map 等集合类(不是普通类)访问 ref 不会自动解套
const arr = reactive([ref(0)])
// 这里需要 .value
console.log(arr[0].value)
 
const map = reactive(new Map([['foo', ref(0)]]))
// 这里需要 .value
console.log(map.get('foo').value)

注意当嵌套在 reactive Object(一定是要普通对象 {}) 中时,ref 才会解套。从 Array 或者 Map 等原生集合类中 访问 ref 时,不会自动解套。

unref

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

function useFoo(x: number | Ref<number>) {
  const unwrapped = unref(x) // unwrapped 现在一定是数字类型
}

toRef (reactive的属性 转 ref对象)

可以通过 toRef 函数 用 reactive 对象的属性 创建一个 ref 对象,reactive 对象属性 与 ref对象 绑定,修改将会互相影响。

// toRef 函数
const state = reactive({
  foo: 1,
  bar: 2,
})
 
// 使用 toRef 函数
const fooRef = toRef(state, 'foo')
 
// reactive 对象属性 与 ref对象 绑定
fooRef.value++
console.log(state.foo) // 2
 
state.foo++
console.log(fooRef.value) // 3

toRefs (reactive 对象 转 普通对象(属性字段都是 ref对象))

可以通过 toRefs 将 reactive对象(响应式) 转换成 普通对象,但 对象里面每个属性 都是 ref对象,和 reactive对象(响应式) 的属性一一对应(返回对象的属性值(ref)与 reactive对象属性值相互影响)。

toRefs
const state = reactive({
  foo: 1,
  bar: 2,
})
 
const stateAsRefs = toRefs(state)
 
/*
stateAsRefs 的类型如下:
{
  foo: Ref<number>,
  bar: Ref<number>
}
*/
 
// ref 对象 与 原属性的引用是 "链接" 上的
state.foo++
console.log(stateAsRefs.foo.value) // 2
 
stateAsRefs.foo.value++
console.log(state.foo) // 3

isRef

可以通过 isRef 检查一个值是否为 ref对象;通过 unref 获取 ref对象 的value 是 val = isRef(val) ? val.value : val 的语法糖(参数不是 ref对象 则 返回参数本身)

// isRef、unref
function useFoo(x: number | Ref<number>) {
  const unwrapped = unref(x) // unwrapped 一定是 number 类型
}

customRef

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

// 自定义 composition API
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)
      }
    }
  })
}

shallowRef

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

//跟踪自身 {},foo 是ref对象,.value 指向自身,但不会转换为reactive对象
const foo = shallowRef({})
// foo 是ref对象,改变 ref 的值是响应
foo.value = {a: 1} 
// 但是这个值{a: 1}不会被转换。 foo.value 不会转换成reactive对象
isReactive(foo.value) // false

triggerRef

手动执行shallowRef

const shallow = shallowRef({
  greet: 'Hello, world'
})
// shallow.value = {greet: 'Hello, world'} (对象只是普通对象,不是reactive)

// 第一次运行时记录一次 "Hello, world"
watchEffect(() => {
  console.log(shallow.value.greet)
})

// 这不会触发副作用,因为 ref 是浅层的
shallow.value.greet = 'Hello, universe'

// 记录 "Hello, universe"
triggerRef(shallow)

1.5 readonly hook

传入一个对象(响应式或普通)或 ref,返回一个原始对象的 只读代理。一个只读的代理是“深层的”,对象内部任何嵌套的属性也都是只读的。

// reactive 响应对象
const original = reactive({ count: 0 })
 
// readonly 对象
const copy = readonly(original)
 
watchEffect(() => {
  // 依赖追踪
  console.log(copy.count)
})
 
// original 上的修改会触发 copy 上的侦听
original.count++
 
// 无法修改 copy 并会被警告
copy.count++ // warning!
可以通过 isReadonly 判断一个对象是否为 readonly 只读代理对象

1.6 computed hook

computed 函数 与 vue2中computed功能一致,它接收一个 getter函数 并返回一个 默认不可改变的响应式 ref对象

// computed hook declare with getter function
const count = ref(1)
// computed
const plusOne = computed(() => count.value + 1)
 
console.log(plusOne.value) // 2
 
plusOne.value++ // 错误! 默认不可修改
// 可传入一个拥有 get 和 set 函数的对象,创建一个可手动修改的计算状态。

// computed hook declare with object
// 3.0
const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: (val) => {
    // ref 使用 .value 赋值
    count.value = val - 1
  },
})
 
plusOne.value = 1
console.log(count.value) // 0
 
// 2.0
var vm = new Vue({
  data: { a: 1 },
  computed: {
    // 仅读取
    aDouble: function () {
      return this.a * 2
    },
    // 读取和设置
    aPlus: {
      get: function () {
        return this.a + 1
      },
      set: function (v) {
        this.a = v - 1
      }
    }
  }
})
vm.aPlus   // => 2
vm.aPlus = 3
vm.a       // => 2
vm.aDouble // => 4

isReadonly

判断是否为 readonly 对象

isProxy

判断是否为 reactive 或 readonly 返回的 proxy对象

1.7 watch hook (监听特定数据源)

watch hook 完全等效于 2.x this.$watch (以及 watch 中相应的选项)。watch 需要侦听特定的数据源,并在回调函数中执行副作用。默认情况是懒执行的,也就是说仅在侦听的源变更时才执行回调。

  • 参数:

1、source Ref|Array|Function(getter) 监听数据源

2、callback Function 副作用函数,用于在watch回调中执行

3、options Object watch监听参数 immediate(default: false)、deep

// watch hook typescript defined
// 侦听单数据源
function watch<T>(
  source: WatcherSource<T>,
  callback: (
    value: T,
    oldValue: T,
    onInvalidate: InvalidateCbRegistrator
  ) => void,
  options?: WatchOptions
): StopHandle
 
// 侦听多数据源
function watch<T extends WatcherSource<unknown>[]>(
  sources: T
  callback: (
    values: MapSources<T>,
    oldValues: MapSources<T>,
    onInvalidate: InvalidateCbRegistrator
  ) => void,
  options? : WatchOptions
): StopHandle
 
type WatcherSource<T> = Ref<T> | (() => T)
 
type MapSources<T> = {
  [K in keyof T]: T[K] extends WatcherSource<infer V> ? V : never
}
 
// 共有的属性 请查看 `watchEffect` 的类型定义
interface WatchOptions extends WatchEffectOptions {
  immediate?: boolean // default: false
  deep?: boolean
}
// 监听单个数据源
// watch hook watch sigle source
// 侦听一个 getter
const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
    /* ... */
  }
)
 
// 直接侦听一个 ref
const count = ref(0)
watch(count, (count, prevCount) => {
  /* ... */
})
  
// 监听多个数据源
//watch hook watch multiply source
const fooRef = ref(0)
const barRef = ref(1)
 
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  /* ... */
})
取消watch监听:watch hook 会返回一个 unwatch 函数,调用该函数可以停止watch监听。

watch hook watch stop
const unwatch = this.$watch('name',() => {})
// 两秒后停止监听
setTimeout(()=> {
  unwatch()
}, 2000)

1.8 watchEffect hook

传入一个函数,然后立即执行这个函数,对于函数里面的响应式依赖会进行监听,当依赖发生变化时,会重新调用传入的函数。

  • 参数:

1、effect Function 副作用函数

2、options Object 监听配置项 onTrack、onTrigger、flush<'post'|'sync'|'pre'>

// watchEffect hook typescript defined
function watchEffect(
  effect: (onInvalidate: InvalidateCbRegistrator) => void,
  options?: WatchEffectOptions
): StopHandle
 
interface WatchEffectOptions {
  flush?: 'pre' | 'post' | 'sync'
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}
 
interface DebuggerEvent {
  effect: ReactiveEffect
  target: any
  type: OperationTypes
  key: string | symbol | undefined
}
 
type InvalidateCbRegistrator = (invalidate: () => void) => void
 
type StopHandle = () => void
// 声明watchEffect
// watchEffect hook
const count = ref(0)
 
// 收集函数里的依赖
watchEffect(() => console.log(count.value))
// -> 打印出 0
 
setTimeout(() => {
  count.value++
  // -> 打印出 1
}, 100)

停止副作用监听:

watchEffect 会返回一个unwatch函数,用于停止监听。

watchEffect hook watch stop
const unwatch = watchEffect(() => {
  /* ... */
})
 
// 之后调用watchEffect返回的函数 停止监听
unwatch()

清除副作用:

当副作用函数需要执行一些异步的副作用, 这些响应需要在其失效时清除(即完成之前状态已改变了)。所以侦听副作用传入的函数可以接收一个 onInvalidate 函数作入参, 用来注册清理失效时的回调。当以下情况发生时,这个失效回调会被触发:

1、副作用即将重新执行时

2、侦听器被停止 (如果在 setup() 或 生命周期钩子函数中使用了 watchEffect, 则在卸载组件时)

watchEffect hook effect clear
watchEffect((onInvalidate) => {
  const token = performAsyncOperation(id.value)
  onInvalidate(() => {
    // 异步执行未完成
    // id 改变时 或 停止侦听时
    // 取消之前的异步操作
    token.cancel()
  })
})

副作用刷新时机:

Vue 的响应式系统会缓存副作用函数,并异步地刷新它们,这样可以避免同一个 tick 中多个状态改变导致的不必要的重复调用。在核心的具体实现中, 组件的更新函数也是一个被侦听的副作用。当一个用户定义的副作用函数进入队列时, 会在所有的组件更新后执行(flush: 'post')。

如果副作用需要同步或在组件更新之前重新运行,我们可以传递一个拥有 flush 属性的对象作为选项(默认为 'post'),设置'pre' 在组件更新前、设置'sync' 同步更新。

// watchEffect hook flush
// 组件更新后执行
watchEffect(
  () => {
    /* ... */
  },
  {
    flush: 'post',
  }
)
 
// 同步运行
watchEffect(
  () => {
    /* ... */
  },
  {
    flush: 'sync',
  }
)
 
// 组件更新前执行
watchEffect(
  () => {
    /* ... */
  },
  {
    flush: 'pre',
  }
)

注意如果你想在 watchEffect 副作用函数 访问DOM(或模版ref),需要把 watchEffect 放在 onMounted 钩子函数里面。

侦听器调试

onTrack 和 onTrigger 选项可用于调试侦听器的行为。

  • onTrack 将在响应式 property 或 ref 作为依赖项被追踪时被调用。
  • onTrigger 将在依赖项变更导致副作用被触发时被调用。
watchEffect(
  () => {
    /* 副作用 */
  },
  {
    onTrigger(e) {
      debugger
    }
  }
)

1.9 生命周期

生命周期钩子注册函数只能在 setup() 期间同步使用, 因为它们依赖于内部的全局状态来定位当前组件实例(正在调用 setup() 的组件实例), 不在当前组件下调用这些函数会抛出一个错误。

组件实例上下文也是在生命周期钩子同步执行期间设置的,因此,在卸载组件时,在生命周期钩子内部同步创建的侦听器和计算状态也将自动删除。

两个钩子函数都接收一个 【DebuggerEvent】,与 【watchEffect 】参数选项中的 onTrack 和 onTrigger 类似。

在 setup函数 去掉【beforeCreate】、【created】生命周期钩子,新增 【renderTracked】、【rendertirggered】生命周期钩子。

注意原 2.0 生命周期仍然可以使用。

// lifetime hook
import { onMounted, onUpdated, onUnmounted } from 'vue'
 
const MyComponent = {
  setup() {
    onMounted(() => {
      console.log('mounted!')
    })
    onUpdated(() => {
      console.log('updated!')
    })
    onUnmounted(() => {
      console.log('unmounted!')
    })
  },
}
 
export default {
  onRenderTriggered(e) {
    debugger
    // 检查哪个依赖性导致组件重新渲染
  },
}

1.10 v-model

在Vue2.0中如何实现双向数据绑定呢?常用的方式又两种,一种是v-model,另一种是.sync, 为什么会有两种呢?这是因为一个组件只能用于一个v-model,但是有的组件需要有多个可以双向响应的数据,所以就出现了.sync。在Vue3.0中为了实现统一,实现了让一个组件可以拥有多个v-model,同时删除掉了.sync

2.0 版本 使用 v-model、2.3+ 使用.sync

// 2.0 v-model
// v-model 一个组件只有一个双向绑定
<template>
  <a-input v-model="value" placeholder="Basic usage" />
</template>
<script>
export default {
  data() {
    return {
      value: '',
    };
  },
};
</script>
 
// 利用sync 实现自定义双向绑定
// text-document 组件
<text-document v-bind:title.sync="doc.title"></text-document>
 
// 等同于
<text-document
  v-bind:title="doc.title"
  v-on:update:title="doc.title = $event"
></text-document>
 
// 组件内实现触发事件
this.$emit('update:title', newTitle)

3.0 版本 使用 v-model,需要指明绑定的属性名(新增),因此,绑定通过指明不同绑定属性名实现一个组件拥有多个双向绑定。

// 3.0 v-model
<template>
  <!--在vue3.0中,v-model后面需要跟一个modelValue,即要双向绑定的属性名-->
  <a-input v-model:value="value" placeholder="Basic usage" />
</template>
<script>
export default {
  // 在Vue3.0中也可以继续使用`Vue2.0`的写法
  data() {
    return {
      value: '',
    };
  },
};
</script>

1.11 provide、inject 依赖注入

provideinject 提供依赖注入,功能类似 2.x 的 provide/inject。两者都只能在当前活动组件实例的 setup() 中调用。可以使用 symbol 值作为注入值键名。

// Provide/Inject hook typescript defined
interface InjectionKey<T> extends Symbol {}
 
function provide<T>(key: InjectionKey<T> | string, value: T): void
 
// 未传,使用缺省值
function inject<T>(key: InjectionKey<T> | string): T | undefined
// 传入了默认值
function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T
project hook,inject hook 使用
// Provide/Inject hook
import { provide, inject } from 'vue'
// symbol 值
const ThemeSymbol = Symbol()
 
const Ancestor = {
  setup() {
    provide(ThemeSymbol, 'dark')
  },
}
 
const Descendent = {
  setup() {
    const theme = inject(ThemeSymbol, 'light' /* optional default value */)
    return {
      theme,
    }
  },
}

注入响应式,使用 ref 保证 provide 和 inject 之间的值是响应的

Provide/Inject hook
// 提供者:
const themeRef = ref('dark')
// symbol 值
const ThemeSymbol = Symbol()
 
provide(ThemeSymbol, themeRef)
 
// 使用者:
const theme = inject(ThemeSymbol, ref('light'))
watchEffect(() => {
  console.log(`theme set to: ${theme.value}`) // ref 对象 .value 取值
})

类似 React 的 环境上下文 createContext() → Context.Provider、Context.Consumer(提供者、消费者)

1.12 template refs

可以在 setup() 中声明一个 ref 并返回它,用来获得对 模板内元素 或 组件实例 的引用。在 Virtual DOM patch 算法中,如果一个 VNode 的 ref 对应一个渲染上下文中的 ref,则该 VNode 对应的元素或组件实例将被分配给该 ref。 这是在 Virtual DOM 的 mount / patch 过程中执行的,因此模板 ref 仅在渲染初始化后才能访问。

// template refs
<template>
  <div ref="root"></div>
</template>
 
<script>
  import { ref, onMounted } from 'vue'
 
  export default {
    setup() {
      const root = ref(null) // 初始null 值
 
      onMounted(() => {
        // 在渲染完成后, 这个 div DOM 会被赋值给 root ref 对象
        console.log(root.value) // <div/>
      })
 
      return {
        root, // 渲染上下文
      }
    },
  }
</script>

配合 render 函数 或 JSX 使用

template refs in JSX
export default {
  setup() {
    const root = ref(null)
 
    // render 函数
    return () =>
      h('div', {
        ref: root,
      })
 
    // 使用 JSX
    return () => <div ref={root} />
  },
}

在 v-for 中使用:需要使用 函数型的 ref(3.0 提供的新功能)来自定义处理方式

// template refs in v-for
<template>
  <div v-for="(item, i) in list" :ref="el => { divs[i] = el }">
    {{ item }}
  </div>
</template>
 
<script>
  import { ref, reactive, onBeforeUpdate } from 'vue'
 
  export default {
    setup() {
      const list = reactive([1, 2, 3])
      const divs = ref([])
 
      // 注意 //
      // 确保在每次变更之前重置引用
      onBeforeUpdate(() => {
        divs.value = []
      })
 
      return {
        list,
        divs,
      }
    },
  }
</script>

getCurrentInstance

支持访问内部组件实例,用于高阶用法或库的开发。只能在 setup 或生命周期钩子中调用。

const MyComponent = {
  setup() {
    const internalInstance = getCurrentInstance() // works

    const id = useComponentId() // works

    const handleClick = () => {
      getCurrentInstance() // doesn't work
      useComponentId() // doesn't work

      internalInstance // works
    }

    onMounted(() => {
      getCurrentInstance() // works
    })

    return () =>
      h(
        'button',
        {
          onClick: handleClick
        },
        `uid: ${id}`
      )
  }
}

// 在组合式函数中调用也可以正常执行
function useComponentId() {
  return getCurrentInstance().uid
}

teleport(新)

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

简单使用

<div>绑到 body 标签

<teleport to="body">
    <div>teleport</div>
</teleport>

多个目标上被绑定多个teleport

顺序将是一个简单的追加——稍后挂载将位于目标元素中较早的挂载之后。

<teleport to="#modals">
  <div>A</div>
</teleport>
<teleport to="#modals">
  <div>B</div>
</teleport>

<!-- result-->
<div id="modals">
  <div>A</div>
  <div>B</div>
</div>

props

  • to :string 指定将在其中移动 内容的目标元素,必须是有效的查询选择器或 HTMLElement (如果在浏览器环境中使用)。
<!-- 正确 -->
<teleport to="body" />
<teleport to="#some-id" />
<teleport to=".some-class" />
<teleport to="[data-teleport]" />

<!-- 错误 -->
<teleport to="h1" />  <!-- 不唯一 -->
<teleport to="some-string" />
  • disabled :boolean 此可选属性可用于禁用 的功能,这意味着其插槽内容将不会移动到任何位置,而是在您在周围父组件中指定了 的位置渲染。
<teleport to="#popup" :disabled="displayVideoInline">
  <video src="./my-movie.mp4">
</teleport>

应用API - createApp实例API

const app = createApp({})

config

配置应用

app.config(object)

应用配置

  • errorHandler

    指定一个处理函数,来处理组件渲染方法执行期间以及侦听器抛出的未捕获错误。这个处理函数被调用时,可获取错误信息和应用实例。

    app.config.errorHandler = (err, vm, info) => {
      // 处理错误
      // `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
    }
    
  • warnHandler

    为 Vue 的运行时警告指定一个自定义处理函数。注意这只会在开发环境下生效,在生产环境下它会被忽略。

    app.config.warnHandler = function(msg, vm, trace) {
      // `trace` 是组件的继承关系追踪
    }
    
  • globalProperties

    添加可以在应用程序内的任何组件实例中访问的全局 property。属性名冲突时,组件的 property 将具有优先权。

    // 之前(Vue 2.x)
    Vue.prototype.$http = () => {}
    
    // 之后(Vue 3.x)
    const app = Vue.createApp({})
    app.config.globalProperties.$http = () => {}
    
  • isCustomElement

    指定一个方法,用来识别在 Vue 之外定义的自定义元素(例如,使用 Web Components API)。如果组件符合此条件,则不需要本地或全局注册,并且 Vue 不会抛出关于 Unknown custom element 的警告。

    isCustomElement: (tag: string) => boolean | undefined

    app.config.isCustomElement = tag => tag.startsWith('ion-')
    
  • optionMergeStrategies

    自定义选项定义合并策略。

    合并策略选项分别接收在父实例和子实例上定义的该选项的值作为第一个和第二个参数,引用上下文实例被作为第三个参数传入。

    app.config.optionMergeStrategies.hello = (parent, child, vm) => {
      return `Hello, ${child}`
    }
    
  • performance :boolean

    设置为 true 以在浏览器开发工具的 performance/timeline 面板中启用对组件初始化、编译、渲染和更新的性能追踪。只适用于开发模式和支持 performance.mark API 的浏览器。

use

安装 Vue.js 插件。

app.use(plugin, [options])

  • plugin :object|function Vue插件,如果是对象,必须包含一个 install 方法。 如果是函数,默认作为install方式。
  • options :object 插件传参,作为install第二参数,第一参数为组件实例。

mount

将应用实例的根组件挂载在提供的 DOM 元素上。

返回根组件实例。

app.mount('#app')

unmount

卸载应用实例的根组件。

app.unmount()

component

注册或检索全局组件。注册还会使用给定的 name 参数自动设置组件的 name。

如果传入 definition 参数,返回应用实例(注册)。如果不传入 definition 参数,返回组件定义(根据组件名称)(检索)。

app.component(name, definition)

  • name :string 组件名
  • definition :object|function 组件对象或者函数组件

mixin

将一个 mixin 应用在整个应用范围内。插件作者可以使用此方法将自定义行为注入组件。不建议在应用代码中使用。

应用实例

app.mixin(object)

directive

注册或检索全局指令。

如果传入 definition 参数,返回应用实例(注册)。如果不传入 definition 参数,返回指令定义(检索)。

app.directive(name, definition)

  • name :string 指令名
  • directive :object|function 指令定义,如果传入函数,作为 mounted 、update 钩子函数执行。

provide

设置一个可以被注入到范围内所有组件中的值。组件应该使用 inject 来接收 provide 的值。应用实例。

app.provide(key, value)

  • key :string|Symbol
  • value :any

重点:该方法不应该与 provide 组件选项或组合式 API 中的 provide 方法混淆。虽然它们也是相同的 provide/inject 机制的一部分,但是是用来置组件 provide 的值而不是应用 provide 的值。

import { createApp } from 'vue'

// 向根组件中注入一个 property,值由应用提供。
const app = createApp({
  inject: ['user'],
  template: `
    <div>
      {{ user }}
    </div>
  `
})

app.provide('user', 'administrator')

全局API

h 函数

返回一个VNode对象,用于手动编写的渲染函数。

render() {
  return Vue.h('h1', {}, 'Some title')
}

defineComponent

定义一个组件对象

defineComponent(object|function)

  1. 具有组件选项的对象
import { defineComponent } from 'vue'

const MyComponent = defineComponent({
  data() {
    return { count: 1 }
  },
  methods: {
    increment() {
      this.count++
    }
  }
})
  1. setup 函数-函数名作为组件名
import { defineComponent, ref } from 'vue'

const HelloWorld = defineComponent(function HelloWorld() {
  const count = ref(0)
  return { count }
})

defineAsyncComponent

创建一个只有在需要时才会加载的异步组件。

defineAsyncComponent(promise|object)

  1. 基本用法 接受一个返回 Promise 的工厂函数,可以调用 reject(reason) 来表示加载失败。
// 全局注册
import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() =>
  import('./components/AsyncComponent.vue')
)

app.component('async-component', AsyncComp)

// 局部注册
import { createApp, defineAsyncComponent } from 'vue'

createApp({
  // ...
  components: {
    AsyncComponent: defineAsyncComponent(() =>
      import('./components/AsyncComponent.vue')
    )
  }
})
  1. 高级用法 接受一个对象。
import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent({
  // 工厂函数
  loader: () => import('./Foo.vue')
  // 加载异步组件时要使用的组件
  loadingComponent: LoadingComponent,
  // 加载失败时要使用的组件
  errorComponent: ErrorComponent,
  // 在显示 loadingComponent 之前的延迟 | 默认值:200(单位 ms)
  delay: 200,
  // 如果提供了 timeout ,并且加载组件的时间超过了设定值,将显示错误组件
  // 默认值:Infinity(即永不超时,单位 ms)
  timeout: 3000,
  // 定义组件是否可挂起 | 默认值:true
  suspensible: false,
  /**
   *
   * @param {*} error 错误信息对象
   * @param {*} retry 一个函数,用于指示当 promise 加载器 reject 时,加载器是否应该重试
   * @param {*} fail  一个函数,指示加载程序结束退出
   * @param {*} attempts 允许的最大重试次数
   */
  onError(error, retry, fail, attempts) {
    if (error.message.match(/fetch/) && attempts <= 3) {
      // 请求发生错误时重试,最多可尝试 3 次
      retry()
    } else {
      // 注意,retry/fail 就像 promise 的 resolve/reject 一样
      // 必须调用其中一个才能继续错误处理。
      fail()
    }
  }
})

resolveComponent

resolveComponent 只能在 render 或 setup 函数中使用。 resolveComponent(name:string)

允许按名称解析 component。如果找到返回一个 Component。如果没有找到,则返回接收的参数 name。

// 声明组件
const app = Vue.createApp({})
app.component('MyComponent', {
  /* ... */
})

// 解析组件
import { resolveComponent } from 'vue'
render() {
  const MyComponent = resolveComponent('MyComponent')
}

resolveDynamicComponent

resolveDynamicComponent 只能在 render 或 setup 函数中使用。 允许使用与 <component :is=""> 相同的机制来解析一个 component。

返回已解析的 Component 或新创建的 VNode,其中组件名称作为节点标签。如果找不到 Component,将发出警告。

import { resolveDynamicComponent } from 'vue'
render () {
  const MyComponent = resolveDynamicComponent('MyComponent')
}

resolveDirective

resolveDirective 只能在 render 或 setup 函数中使用。 如果在当前应用实例中可用,则允许通过其名称解析一个 directive。

返回一个 Directive。如果没有找到,则返回 undefined。

// 声明指令
const app = Vue.createApp({})
app.directive('highlight', {})

// 解析指令
import { resolveDirective } from 'vue'
render () {
  const highlightDirective = resolveDirective('highlight')
}

mergeProps

将包含 VNode prop 的多个对象合并为一个单独的对象。其返回的是一个新创建的对象,而作为参数传递的对象则不会被修改。

可以传递不限数量的对象,后面参数的 property 优先。事件监听器被特殊处理,class 和 style 也是如此,这些 property 的值是被合并的而不是覆盖的

import { h, mergeProps } from 'vue'
export default {
  inheritAttrs: false,
  render() {
    const props = mergeProps({
      // 该 class 将与 $attrs 中的其他 class 合并。
      class: 'active'
    }, this.$attrs)
    return h('div', props)
  }
}

2 Vue-Router (vue-router@4)

2.1 Vue-Router 初始化

  • 2.0 版本
    2.0 先调用 Vue.use 使用 VueRouter 插件,再通过 new 关键字声明一个 VueRouter实例,并将实例 router 传入 Vue 构造函数进行绑定。
// 2.0 VueRouter 初始化代码
// router.js
import Vue from 'vue'
import VueRouter from 'vue-router'
 
Vue.use(VueRouter)
 
const router = new VueRouter({
  mode: 'history', // hashHistory
  routes: []
})
 
// 全局路由守卫
router.beforeEach(() => {
   
})
 
router.afterEach(() => {
   
})
 
export default router
 
// main.js
const app = new Vue({
  el: '#app',
  router,
})
  • 3.0 版本
    3.0 通过 Vuex 提供的 createRouter hook 实例化,vue实例使用 use 方法进行绑定。路由模式 通过 createWebHashHistory hook, createWebHistory hook 创建对应模式。
3.0 VueRouter 初始化代码
// router.js
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
const router = createRouter({
  // vue-router有 hash 和 history 两种路由模式,可以通过 createWebHashHistory 和 createWebHistory 来指定
  history: createWebHashHistory(),
  routes: []
})
 
// 全局路由守卫
router.beforeEach((to,from,[next]) => 
    // return false/undefined/true/path
})
 
router.afterEach(() => {
   
})
 
export default router
 
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
 
const app = createApp(App)
app.use(router)

2.2 在setup hook 中 使用 Route、Router

  • 2.0 版本
    2.0 版本 可以通过 vm.route获取当前路由信息、通过vm.route 获取当前路由信息、通过 vm.router 获取 VueRouter 实例(全局唯一)
// 2.0 使用 Route、Router
// 通过 this.$route 获取路由元信息、路由参数
export default {
  methods: {
    fetchData () {
      this.error = this.post = null
      this.loading = true
      // replace getPost with your data fetching util / API wrapper
      // 获取路由对象携带的参数
      getPost(this.$route.params.id, (err, post) => {
        // ...
      })
    }
  }
}
 
// 通过 this.$router 进行路由跳转、获取路由匹配matches
this.$router.push(location, onComplete?, onAbort?)
this.$router.push(location).then(onComplete).catch(onAbort)
this.$router.replace(location, onComplete?, onAbort?)
this.$router.replace(location).then(onComplete).catch(onAbort)
this.$router.go(n)
this.$router.back()
this.$router.forward()
  • 3.0 版本
    3.0 版本 通过 VueRouter提供的 useRoute hook 获取当前路由信息,通过 useRouter hook 获取 VueRouter 实例。
// 3.0 使用 Route、Router
import { useRoute, useRouter } from 'vue-router'
 
export default {
  setup() {
    // 获取当前路由
    const route = useRoute()
    // 获取路由实例
    const router = useRouter()
    // 当当前路由发生变化时,调用回调函数
    watch(() => route, () => {
      // 回调函数
    }, {
      immediate: true,
      deep: true
    })
     
    // 路由跳转
    function getHome() {
      router.push({
        path: '/home'
      })
    }
     
    return {
      getHome()
    }
  }
}

2.3 VueRouter 组件守卫

  • 2.0 版本
    2.0 可直接在组件内定义 beforeRouteUpdate、beforeRouteLeave 路由守卫。
// 2.0 组件路由守卫
// Foo 组件
const Foo = {
  template: `...`
  beforeRouteUpdate (to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave (to, from, [next]) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
  }
}
  • Vue 3.0
    通过 VueRouter 提供的 onBeforeRouteUpdate hook、onBeforeRouteEnter hook、onBeforeRouteLeave hook 进行路由守卫。
// 3.0 组件路由守卫
import { onBeforeRouteEnter, onBeforeRouteUpdate, onBeforeRouteLeave, useRoute } from 'vue-router'
export default {
  setup() {
    onBeforeRouteEnter(() => {
      console.log('当进入当前页面路由时,调用回调函数')
    })
 
    onBeforeRouteUpdate(() => {
      console.log('当当前路由发生变化时,调用回调函数')
    })
 
    onBeforeRouteLeave(() => {
        console.log('当当前页面路由离开的时候调用')
    })
  }
}

3 Vuex (vuex@next)

3.1 Vuex 初始化

  • 2.0 版本
    2.0 先 调用 Vue.use 使用 Vuex 插件,再通过 new 关键字声明一个 Vuex实例,并将实例 store 传入 Vue 构造函数进行绑定。
// 2.0 Vuex 初始化代码
// store.js
import Vue from 'vue'
import Vuex from 'vuex'
 
Vue.use(Vuex)
 
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})
 
// main.js
const app = new Vue({
  el: '#app',
  store: store,
})
  • 3.0 版本
    3.0 通过 Vuex 提供的 createStore hook 实例化,vue实例使用 use 方法进行绑定
// 3.0 Vuex 初始化代码
// store.js
import { createStore } from 'vuex'
 
export default createStore({
  state: {},
  mutations: {},
  actions: {}
})
 
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
 
const app = createApp(App)
app.use(store)

3.2 在setup hook 中 使用 Vuex

  • 2.0 版本
    2.0 版本 可以通过 vm.$store 字段读取数据(全局唯一),或者通过 Vuex 提供的辅助函数 mapState、mapGetters、mapMutations、mapActions 读取数据、提交数据。
// 2.0 使用Vuex
// 创建一个 Counter 组件
// 通过绑定在 vue 实例上的 $store 字段读取数据
const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return this.$store.state.count
    }
  }
}
 
// 通过 Vuex 提供的辅助函数进行数据读取
import { mapState } from 'vuex'
 
export default {
  // ...
  computed: mapState({
    // 箭头函数可使代码更简练
    count: state => state.count,
 
    // 传字符串参数 'count' 等同于 `state => state.count`
    countAlias: 'count',
 
    // 为了能够使用 `this` 获取局部状态,必须使用常规函数
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}
  • 3.0 版本
    3.0 版本 通过 Vuex 提供的 useStore hook 获取实例化的 store,通过 store 实例获取数据,并将数据放到 setup 函数的 return 语句上,才能在 template 访问。可以通过 computed hook 进行取值。
// 3.0 使用Vuex
import { useStore } from 'vuex'
export default {
  setup() {
    const store = useStore()
    const roleMenus = store.getters['roleMenus']
        const func = () => {
            store.commit('SUBMIT')
            // store.dispatch('SUBMIT')
        }
    return {
      roleMenus
    }
  }
}

Ant Design Vue 2.0

// ConfigProvider
// ConfigProvider
import { reactive, provide } from 'vue';
import PropTypes from '../_util/vue-types';
import { getComponent, getSlot } from '../_util/props-util';
import defaultRenderEmpty from './renderEmpty';
import LocaleProvider, { ANT_MARK } from '../locale-provider';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
 
function getWatch(keys = []) {
  const watch = {};
  keys.forEach(k => {
    watch[k] = function(value) {
      this.configProvider[k] = value;
    };
  });
  return watch;
}
 
const ConfigProvider = {
  name: 'AConfigProvider',
  props: {
    getPopupContainer: PropTypes.func,
    prefixCls: PropTypes.string,
    renderEmpty: PropTypes.func,
    csp: PropTypes.object,
    autoInsertSpaceInButton: PropTypes.bool,
    locale: PropTypes.object,
    pageHeader: PropTypes.object,
    transformCellText: PropTypes.func,
  },
  created() {
    this.configProvider = reactive({
      ...this.$props,
      getPrefixCls: this.getPrefixCls,
      renderEmpty: this.renderEmptyComponent,
    });
    provide('configProvider', this.configProvider);
  },
  watch: {
    ...getWatch([
      'prefixCls',
      'csp',
      'autoInsertSpaceInButton',
      'locale',
      'pageHeader',
      'transformCellText',
    ]),
  },
  methods: {
    renderEmptyComponent(name) {
      const renderEmpty = getComponent(this, 'renderEmpty', {}, false) || defaultRenderEmpty;
      return renderEmpty(name);
    },
    getPrefixCls(suffixCls, customizePrefixCls) {
      const { prefixCls = 'ant' } = this.$props;
      if (customizePrefixCls) return customizePrefixCls;
      return suffixCls ? `${prefixCls}-${suffixCls}` : prefixCls;
    },
    renderProvider(legacyLocale) {
      return (
        <LocaleProvider locale={this.locale || legacyLocale} _ANT_MARK__={ANT_MARK}>
          {getSlot(this)}
        </LocaleProvider>
      );
    },
  },
 
  render() {
    return <LocaleReceiver children={(_, __, legacyLocale) => this.renderProvider(legacyLocale)} />;
  },
};
 
export const ConfigConsumerProps = {
  getPrefixCls: (suffixCls, customizePrefixCls) => {
    if (customizePrefixCls) return customizePrefixCls;
    return `ant-${suffixCls}`;
  },
  renderEmpty: defaultRenderEmpty,
};
 
/* istanbul ignore next */
ConfigProvider.install = function(app) {
  app.component(ConfigProvider.name, ConfigProvider);
};
 
export default ConfigProvider;
Input
export default {
  name: 'AInput',
  inheritAttrs: false,
  props: {
    ...inputProps,
  },
  setup() {
    return {
      configProvider: inject('configProvider', ConfigConsumerProps),
    };
  }
  ...
}

参考

Vue3.0来袭,你想学的都在这里(二)

Vue3.0来袭,你想学的都在这里(一)

Vue3.0 && Vue3.0初体验 一

Vue3.0变动简介 - 掘金

Vue Composition API