vue 系列 -- vue 3.0 新特性

1,198 阅读2分钟

本文主体内容转载自 还不会Vue3?一篇笔记带你快速入门

前言

Vue3.0的设计目标可以概括为体积更小、速度更快、加强TypeScript支持、加强API设计一致性、提高自身可维护性、开放更多底层功能。

1. script 差异

<script lang="ts">
import { defineComponent} from 'vue'

export default defineComponent({
  name: 'App',
  setup() {
    return {
        // 这里的属性 和 方法 会合并到 data 函数 和 methods 对象里
    }
  },
})
</script>
  • script 使用 ts 语言,只需 设置 lang 即可
  • defineComponent 方法创建一个组件
  • export default 直接导出一个组件

2. template 中不再需要一个根元素包裹

<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
</template>

实际上内部会将多个标签包含在一个Fragment虚拟元素中

好处:减少标签层级, 减小内存占用

3. Composition API

Vue2 中,我们实现一个功能得分到不同的地方,把数据放在 data ,computed 方法放在 methods 里面,分开的太散乱了,几个功能还好,几十个上百个,那就有点...

所以 Vue3 提出了 Composition API ,它可以把 一个逻辑的代码都收集在一起 单独写个hook,然后再引入,这样就不到处分布,显得很乱了

4. setup

setup 是 Composition API 的入口

(1)setup 执行顺序

它在beforeCreate之前执行一次beforeCreate这个钩子 的任务就是初始化,在它之前执行,那么 this 就没有被初始化 this = undefined 这样就不能通过 this 来调用方法 和 获取属性

image-20210811132514039

(2)setup 返回值

setup 返回的是一个对象,这个对象的属性会与组件中 data 函数返回的对象进行合并,返回的方法和 methods 合并,合并之后直接可以在模板中使用,如果有重名的情况,会使用 setup 返回的属性方法methodsdata 能够拿到 setup 中的方法应该进行了合并,反之 setup 不能拿到它们的属性和方法,因为这个时候 this = undefined

image-20210811134028917

(3)Suspense 组件

setup 使用 async/await

我们需要 setup 返回数据那么它肯定就不能使用 async 修饰,这样返回 promise 是我们不想看见情况,如果我们硬要用 async 修饰,我们就得用的在它的父组件外层需要嵌套一个suspense(不确定)内置组件,里面放置一些不确定的操作,比如我们就可以把异步组件放入进去

子组件

<template>
  {{ res }}
</template>

<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
  name: 'Son',
  async setup() {
    const res = await axios.get('地址')
    return {
      res,
    }
  },
})
</script>

父组件

<template>
    <Suspense>
        <!-- 子组件-->
        <Son></Son>
    </Suspense>
</template>

(4)setup 参数

setup(props, context)

setup 函数中的第一个参数是 props。它接收父组件传递的值,是的就是父子组件信息传递的 props

第二个参数是 context 里面包含3个属性 { attrs, slots, emit },这三个属性大家看名字就应该知道是什么吧 分别对应 this.$attrsthis.$slotsthis.$emit

  • attrs: 除了 props 中的其他属性
  • slots: 父组件传入插槽内容的对象
  • emit: 和用于父子组件通信

在上面 setup 写的数据都不是响应式的(修改了数据,视图并不会更新)。在 Vue3 中提供了两种方式定义响应式数据:

  • ref (双向绑定基本类型变量,借助 reactive 方法去双向绑定引用类型变量)
  • reactive (双向绑定引用类型变量)

5. 响应式

(1)ref 方法

ref 方法导入

import { defineComponent, ref } from 'vue'

ref 方法使用

setup() {
    // 方式一
    let number1 = ref(10)
    // 方式二
    let num = 0
    let number2 = ref(num)
    return {}
},

来查看一下 number1 是什么吧

image-20210811183344632

可以看见的是 number1 是一个 Ref 对象,我们设置的 10 这个值在这个对象的 value 属性上,也就是说我们修改的时候必须要修改的是 number1.value 但是在模板上使用的时候 不用写 number1.value 直接写 number1 即可,在模板编译的时候回自动加上 value

vue 3.0 通过给value属性添加 getter/setter 来实现对数据的劫持

ref 方法使用实例

<template>
  {{ number1 }}
  <button @click="updateNum">+</button>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
  name: 'Son',
  setup() {
    let number1 = ref(10)

    // 修改 number1
    function updateNum() {
      number1.value++
    }
    return {
      number1,
      updateNum,
    }
  },
})
</script>

ref 方法使用效果

35

刚才强调了说 ref 接收 基本类型的数据,那么它可以接收复杂类型吗,如object 类型等,当然可以

如果给 ref 传入复杂类型,其实它是调用 reactive 来实现的

(2)reactive

reactive 接收一个普通对象然后返回该普通对象的响应式代理对象

其底层就是使用 Proxy 进行代理

new Proxy(target, handler) 方法

  • target :要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
  • handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p
// 模拟 Vue data
let data = {
    msg: '',
    age: '',
}
// 模拟 Vue 的一个实例
// Proxy 第一个
let vm = new Proxy(data, {
    // get() 获取值
    // target 表示需要代理的对象这里指的就是 data
    // key 就是对象的 键
    get(target, key) {
        return target[key]
    },
    // 设置值
    // newValue 是设置的值
    set(target, key, newValue) {
        // 也先判断下是否和之前的值一样 节省性能
        if (target[key] === newValue) return
        // 进行设置值
        target[key] = newValue
        document.querySelector('#app').textContent = target[key]
    },
})

reactive 方法导入

import { defineComponent, reactive } from 'vue'

reactive 方法使用

setup() {
    let obj = reactive({
        name: '小浪',
        age: 21,
    })
    return {
        obj,
    }
}

控制台输出返回的 Proxy 对象吧

image-20210811191209064

数据都在 target 中,在模板使用直接 {{obj.name}} 即可修改直接修改 obj[name] = ‘xxx’

操作代理对象,obj中的数据也会随之变化,同时如果想要在操作数据的时候,界面也要跟着重新更新渲染,那么也是操作代理对象

reactive 方法使用实例

响应式的数据是深层次的(递归深度响应式)。对于多层嵌套的数据也是响应式的

setup() {
    let obj = reactive({
        name: '小浪',
        age: 21,
        phone: {
            p_name: '小米',
            p_apps: {
                app_name: '小米运动',
            },
        },
    })
    function upadateName() {
        obj.phone.p_apps.app_name = '掘金'
    }
    console.log(obj)

    return {
        obj,
        upadateName,
    }
},

reactive 方法使用效果

36

(3)使用 ref 传入对象

setup() {
    let obj = ref({
        name: '小浪',
        age: 21,
    })
    console.log(obj)

    return {
        obj,
    }
}

image-20210811191739873

由此可见,ref 方法传入对象是使用 reactive 来进行操作的

(4)toRefs 方法

这个方法可以把 reactive 响应式对象,转化为 普通对象,普通对象的每个属性都是 Ref 对象,这样的话保证了 reactive 的每个属性还是响应式的,我们还可以把每个属性进行分解使用,这样在组件就不用 obj[属性] ,代码量减轻了,yyds

setup() {
  const user = reactive({
    name: '小浪',
    age: 21,
  })

  let userObj = toRefs(user)
  console.log(userObj)

  return {}
}

image-20210811223510320

可以看见 nameage 已经变成了 Ref 对象

我们可以解构 出 nameage 单独使用

setup() {
  const user = reactive({
    name: '小浪',
    age: 21,
  })

  let userObj = toRefs(user)

  return {
    ...userObj,
  }
}

(5)toRef 方法

还有一个 toRef 方法,它的作用和 toRefs 差不多,但是它只能把响应式对象/普通对象的某一个属性变为 Ref 对象

可以用来为源响应式对象上的 property 性创建一个 ref。然后可以将 ref 传递出去,从而保持对其源 property 的响应式连接。

export default {
  setup(props) {
    useSomeFeature(toRef(props, 'foo'))
  }
}

function useSomeFeature(foo: Ref) {
  // ...
}

拷贝了一份新的数据值单独操作, 更新时相互不影响

当您要将 propref 传递给复合函数时,toRef 很有用

可以从官方文档看出,用于在于组件之前的传递数据 从 props 拿出 'foo' 属性给复合函数,复合函数 useSomeFeature,接收的参数 fooRef 类型,刚好可以使用toRef 来进行转化

6. computed 计算属性

Vue3中使用computedVue2.X 有些不同,这里 computed是一个方法

首先还是得导入 computed 方法

import { defineComponent, computed } from 'vue'
复制代码

参数为一个回调 默认为 get

<template>
  <div class="box">
    <input type="text" v-model="name" />
    <br />
    <input type="text" v-model="age" />
    <br />
    <input type="text" v-model="getInfo" />
  </div>
</template>
复制代码
setup() {
    let name = ref('小浪')
    let age = ref(21)
	
    //计算属性
    let getInfo = computed(() => {
        return `我的名字:${name.value},今年${age.value},请多多指教`
    })
    return {
        name,
        age,
        getInfo,
    }
}
复制代码

37

这里没有实现 set 方法,所以修改下面没有用

参数为一个对象 在这里写 get set

模板和上面一样

  setup() {
    let name = ref('小浪')
    let age = ref(21)

    let getInfo = computed({
        // get 方法
        get() {
            return `${name.value},${age.value}`
        },
        // set 方法
        set(val: string) {
            let arr = val.split(',')
            name.value = arr[0]
            age.value = parseInt(arr[1])
        },
    })
    return {
        name,
        age,
        getInfo,
    }

38

7. watch 侦听器

和 Vue2.X 的 Watch 使用方法差不多

介绍

watch(data,handler,object)

  1. data:可以是返回值的 getter 函数,也可以是 ref
  2. handler:回调函数
  3. object:可选配置项 { immediate: true }

引入

import { defineComponent, watch } from 'vue'

data 为一个 ref

回调函数的参数是 (新值,旧值)

setup() {
    let name = ref('小浪')
    let age = ref(21)

    let watchName = ref('')
    watch(name, (newName, oldName) => {
        watchName.value = `我是新姓名${newName}
		我是老姓名${oldName}`
    })
    return {
        name,
        age,
        watchName,
    }
},

image-20210811204502523

可以看见页面第三栏没有显示,因为 name 值没有变化,所以就不用改变,watch的第三个参数是 配置对象,我们在里面可以设置 立即执行 { immediate: true }

就会执行一次 当然这个时候 oldNameundefined

watch(
  name,
  (newName, oldName) => {
    watchName.value = `我是新姓名${newName}
  我是老姓名${oldName}`
  },
  { immediate: true }
)

39

data 为一个 getter

watch(()=>haha,(newName, oldName)=>{
  // 处理...
})

()=> haha 直接返回一个值,相当于 getter 简写,haha可以不是响应式数据

data 为多个 ref

模板还是之前那个

<template>
  <div class="box">
    <input type="text" v-model="name" />
    <br />
    <input type="text" v-model="age" />
    <br />
    <input type="text" v-model="getInfo" />
  </div>
</template>

我们可以把多个 ref 放进一个数组里面

newNameAndAgeoldNameAndAge为一个数组保存着 新 和 旧的 [name,age]

setup() {
    let name = ref('小浪')
    let age = ref(21)

    let watchName = ref('')
    watch(
      [name, age],
      (newNameAndAge, oldNameAndAge) => {
        watchName.value = `new: ${newNameAndAge}
        old: ${oldNameAndAge}`
      },
      { immediate: true }
    )
    return {
        name,
        age,
        watchName,
    }
},

40

datareactive

setup() {
  let user = reactive({
    name: '小浪',
    age: 21,
  })

  let watchInfo = ref('')
  watch(
    user,
    (newInfo, oldInfo) => {
      console.log(newInfo === oldInfo)  // true
    }
  )
}

这里是对象 会出现问题,立即执行后,

如果加上 立即执行 除了第一次 newInfo{name: '小浪',age: 21}

oldInfoundefined ,之后始终返回该对象的当前值

所以 newInfo = oldInfo

对于这个问题,我们得加上配置对象 {deep: true}进行深度检测

深度检测还可以判断多重嵌套

watch(
  user,
  (newInfo, oldInfo) => {
    console.log(newInfo === oldInfo) // false
  },
  { deep: true }
)

8. Vue3 生命周期

Vue2.X 对应 Vue3组合API

Vue2.XVue3
beforeCreate--->setup()
created--->setup()
beforeMount--->onBeforeMount
mounted--->onMounted
beforeUpdate--->onBeforeUpdate
updated--->onUpdated
beforeDestroy--->onBeforeUnmount
destroyed--->onUnmounted
activated--->onActivated
deactivated--->onDeactivated
errorCaptured--->onErrorCaptured
onRenderTriggered
onRenderTracked

可以看出

beforeCreatecreated 在Vu3还是能正常使用,在Vue3我们可以用更好更快的 setup 代替

on开头的 生命周期需要 通过 import 导入,在 setup 函数中使用

Vue3 的生命周期 比 Vue2.X 的生命周期快

举个例子: onBeforeMountbeforeMount 快 其他同理

还多个两个钩子:

  • onRenderTriggered 跟踪虚拟 DOM 重新渲染时调用
  • onRenderTracked 当虚拟 DOM 重新渲染被触发时调用

9. 全局 API 转移

Vue2.XVue 上面的全局API ,比如自定义指令 Vue.directive,全局组件 Vue.component 在Vue3都进行改变,不再提供 Vue ,而是提供 app

具体改变可以看下面

Vue2.XVue3
Vue.configapp.config
Vue.config.productionTip移除
Vue.config.ignoredElementsapp.config.isCustomElement
Vue.componentapp.component
Vue.directiveapp.directive
Vue.mixinapp.mixin
Vue.useapp.use
Vue.prototypeapp.config.globalProperties
new Vue().$mount('#app')createApp(App).mount('#app')