Vue3基础

创建vue3项目

  • vue-cli 4.5.0以上
    1
    2
    3
    vue create vue3-test
    选择vue3项目即可
    若需创建ts项目及自定义配置项可选Manually select features, 然后按提示操作创建,最后不要保存位默认配置。
  • 创建vite+ts项目
    1
    2
    3
    4
    5
    6
    7
    npm create vite@latest

    yarn create vite

    pnpm create vite

    注意:node版本要大于14.18,最好16以上

初始化工程变化

main.js变化

1
2
3
4
5
6
// 不在引入Vue构造函数,而引入createApp工程函数
import { createApp } from 'vue'

import App from './App.vue'
// 挂载方式改变
createApp(App).mount('#app')

.vue文件template不在必须要根标签包裹

1
2
3
4
5
6
<template>
<!-- <div> -->
<p></p>
<span></span>
<!-- </div> -->
</template>

组合式API

区别于vue2中options api(配置式)形式,如data,methods,computed…等.
组合式api统一写在setup函数中.
setup必须有返回值

  • 返回对象(常用)
  • 返回render函数
  • 返回promise;(suspense相关)
    • 只有suspense和动态引入组件时setup才能返回promise,也只有这种情况setup能加async

setup执行早于beforeCreate, this为undefine

setup的入参:

  • props: 即组件传参;需要正常定义props才能接收到
  • context: 上下文;组件传参,插槽相关
    • attrs 组件传参,没用props接收时,有值
    • slots 插槽vnode
      1
      2
      3
      4
      <!-- vue2中具名插槽 -->
      <template slot="a"></template>
      <!-- vue3中具名插槽 -->
      <template v-slot:a></template>
    • emit 自定义事件触发
      1
      2
      3
      // 子组件需要接收自定义事件,和props类似
      props: []
      emits: []
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { h } from 'vue' // setup返回render函数方式,需要引入
export default {
name: 'App',
props: ['msg'],
emits: ['hello'], // 父组件自定义事件也需要接收,否则报警告
setup(props) {
let name = 'victor'

function showSomething () {
alert(name)
}

return {
name,
showSomething
}

// return () => h('h1', 'render方式')
}
}

vue2配置式api的问题是模块比较分散,不同功能混合,(data中放的所以功能的数据,methods中放的所以功能的方法),分散,不集中.
vue3组合式api可通过书写顺序或hooks明显区分功能模块

数据响应式

setup中直接变量声明方式创建的数据并非响应式.需要

  • ref: 处理基本数据类型(引用类型也可)
  • reactive: 专门处理引用类型(深层响应)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import { ref, reactive } from 'vue'

    setup () {
    let name = ref('victor')
    let age = ref(18)
    let person = ref({
    name: 'gss',
    age: 18
    })

    let person_reactive = reactive({
    name: 'v',
    age: 20
    })

    function edit () {
    name.value = 'gss'
    person.value.name = 'victor'
    person_reactive.name = 'g'
    }
    }
    经过ref函数处理后,数据变为响应式,但需要注意处理后的数据不再是原本类型,而是引用对象(RefImpl);里面包裹原有数据的值在value上;所以需要.value读写.
    经过reactive函数处理后,数据变为proxy对象,响应式,能直接 . 读取

ref函数实现响应式原理同vue2,Object.defineProperty();
但对于对象的内层则是用Proxy实现;实际上是调用了reactive方法实现的.

打印上述person和person.value, person_reactive和person.value一样,如下图

  • 为什么在template模板中不用.value读取数据呢
    因为complie模板解析时能获取数据类型,RefImpl类型会自动.value

toRef & toRefs

将对象的某个属性单独做成响应式,并且保持和原对象的引用关系
简化模板插值写法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<p>{{person.name}}</p>

setup() {
let person = reactive({
name: '**',
...
})
return {
person
}
}
//=============================================
<p>{{name}}</p>
<p>{{job.type}}</p>
<p>{{type}}</p>

setup() {
let person = reactive({
name: '**',
job: {
type: 1
}
...
})
let type = toRef(person.job, 'type') // 单独响应某个属性,并且type引用保持在person上,即type变-person.job.type也变
return {
...toRefs(person), // 扩展person所有属性,深层响应
type
}
}

shallowReactive & shallowRef

shallow: 浅层的
shallowReactive: 只响应对象的一层,不深层响应
shallowRef: 基础类型同ref, 不响应对象

1
2
3
4
5
6
7
8
9
10
let obj = shallowReactive({
a:{
b:1 // 内层不响应
},
c:2
})
let num = shallowRef(18) // 同ref
let obj = shallowRef({
a:1 // 不响应
})

readonly & shallowReadonly

只读和浅层只读

1
2
3
4
import { readonly, shallowReadonly } from 'vue'
let person = {}
person = readonly(person)
person = shallowReadonly(person)

toRaw & markRow

还原和标记还原

  • toRaw: 将reactive响应式数据还原普通数据
  • markRow: 响应式数据添加新属性时,也是响应式,用此阻止新属性的响应
    1
    2
    3
    4
    5
    // import ...
    let p = reactive({})
    let p_raw = toRaw(p) // 还原成原始数据

    p.someObj = markRow({}) // 新属性someObj将不再响应

customRef

自定义ref,能够通过get,set实现相关逻辑控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { customRef } from 'vue'
function myCustomData (value) {
return customRef((track, trigger) => {
return {
get() {
track() // 跟踪数据变化
return value
},
set(newValue) {
value = newValue
trigger() // 触发数据更新
}
}
})
}

provide & inject

提供/注入, 实现祖/后代组件传参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 祖
import { provide } from 'vue'
setup() {
let p = {}
provide('p', p)
provide('fn', (cb) => {cb()}) // 通过回调,获取后代组件中的函数执行
}
// 后代
import { inject } from 'vue'
setup() {
let = inject('p')
let fn = inject('fn')
fn(() => {
console.log('后代组件相关逻辑,在祖组件中触发执行')
})
}

判断是否响应式数据

  • isRef
  • isReactive
  • isReadonly
  • isProxy: 是否是reactive或readonly创建的数据
    1
    2
    3
    4
    5
    6
    // import { ... }
    let a = reactive({})
    isReactive(a) // true
    // 还原
    let b = toRaw(a)
    isReactive(b) // false

computed & watch & watchEffect

computed

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { reactive, computed } from 'vue'
setup () {
let num = reactive({
a: 0,
b: 0
})
num.c = computed(() => (num.a + num.b)) // 方法一

num.c = computed({ // 方法二
get() {
return num.a + num.b
},
set() {}
})


return {
num
}
}

watch & watchEffect

注意引用类型默认deep且不可取消,无法获取oldV

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import { ref, reactive, watch, watchEffect } from 'vue'
setup () {
let num = reactive({
a: 0,
b: 0,
obj: {
a: 1
}
})

let name = ref('victor')
let age = 18
// 能多次调用watch函数
watch(name, (newV, oldV) => {}, { immediate: true })
watch([name, age], (newV, oldV) => {
// 监听多个
// newV: ['victor!', 19]
// oldV: ['victor', 18]
}, { immediate: true })
watch(num, (newV, oldV) => {
// 引用类型 默认强制 开启deep, deep: false无效
// 无法获取oldV,oldV和newV一样
}, { immediate: true })
watch(() => num.obj.a, (newV, oldV) => {
// 单独监听某个属性,需函数返回值. newV和oldV正常
})

watchEffect(() => {
// 不是监听某个值,而是用到谁,监听谁,智能,自动deep
let n = name.value // 用到了name,此时会走此处监听,n为newV
let _num = num.obj.a // 能监听到深层
})
}

生命周期

vue2中的周期依然能在vue3中使用,除了beforeDestroy和destroyed改名为beforeUnmount和unmounted.
vue3中也能用组合式API形式调用.
setup中生命周期改动:

  • 没有明确的beforeCreate和created, setup即相当于这两个周期
    • 作为配置项还是能用beforeCreate和created的.
    • setup早于beforeCreate
  • 需引入对应周期函数,逻辑在回调函数中写
  • 周期名都加on
    1
    2
    3
    4
    5
    6
    import { onMounted } from 'vue'
    setup() {
    onMounted (() => {
    console.log('onMounted')
    })
    }

自定义hooks

hook: 一个函数, 是组合式api的封装. 类似vue2中mixin,因为vue3中配置项都是通过函数实现,所以hook非常方便.可以公共把数据操作,方法,包括用到的生命周期都能放到一个hook文件中.
步骤:

  • 创建hooks文件夹
  • 创建相关文件,命名一般以use开头(useSomething.js)
  • 编写hook文件,暴露带有返回值的函数.
  • 使用hook文件,引入-setup中调用即可
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // hooks/useSomething.js
    import { ref, onMounted } from 'vue'
    export default () => {
    let hookName = ref('victor') // 定义数据

    const getHookName = () => { // 定义方法
    hookName.value += '!'
    }
    onMounted(() => { // 生命周期可用
    hookName.value += '!' // 数据能响应式
    })
    return {hookName, getHookName}
    }

    // app.vue
    import useSomething from './hooks/useSomething'
    setup() {
    let { hookName, getHookName } = useSomething()
    return {
    hookName,
    getHookName
    }
    }

新组件

  • fragment: 内置组件,不用手写,自动渲染处理,组件内不用div包裹的原因.没有外层div时就自动用fragment了
  • teleport: 传送,将html结构传送到指定标签
    1
    2
    3
    4
    5
    <!-- to: css选择器 -->
    <teleport to="body">
    <dialog>...</dialog>
    </teleport>
    <!-- 将全局弹框传送到body,减少对结构的影响 -->
  • suspense: 试验阶段,结合动态引入组件使用;基于插槽实现;可用于加loading状态;
    • default插槽
    • fallback插槽
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      // 父组件
      <suspense>
      <template v-slot:default> // 默认插槽
      <helloWord></helloWord>
      </template>
      <template v-slot:fallback> // 备案插槽
      <div>
      loading
      </div>
      </template>
      </suspense>

      import { defineAsyncComponent } from 'vue' // 引入异步加载组件方法
      const helloWord = defineAsyncComponent(() => (import('./helloWord'))) // 引入组件

      components:{
      helloWord
      }

      // helloWord组件
      async setup() { // 确定组件被动态引入并且用suspense时,可用async
      // ...
      cosnt p = new Promise((resolve, reject) => {
      resolve({
      // ...
      })
      })
      return p // 确定组件被动态引入并且用suspense时,可返回promise
      }

其他改变

  • 全局api
    • const app = createApp()
  • 过度(transition)类名改变
  • 移除keyCode按键编码
  • 移除.native修饰符
    • 因为现在子组件组定义事件也需要接收,所以当子组件接收click时就算自定义事件,不接收时就算原生事件,不需要再用.native修饰
  • 移除filter: 建议用计算属性

参考:

官方文档