Vue2到Vue3之进阶篇

179 阅读5分钟

Vue2到Vue3之进阶篇

巩固一些概念

自定义v-model

我们都知道v-model一般用于表单组件中,比如一个input标签中,要和data中的响应式属性text绑定input中的value,第一步是需要把text=value,然后在input输入是value改变时进行第二次绑定,即value=text,看完我们不难想到v-model实质结合了v-bindv-on的语法糖。下面我们来看看怎么实现:

<input type="text" :value="text" @input="$emit('change', $event.target.value)" />

不难看出,其实就是把text绑定到value中,在input修改时去触发了一个change事件,由change事件分发value值赋给text。

nextTick

为什么会有nextTick?首先我们来看看这个例子:

<template>
  <div>
    <ul ref="ul">
      <li v-for="(item, index) in list" :key="index">{{item}}</li>
    </ul>
    <button @click="addItem">添加</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      list: ['a', 'b', 'c'],
    }
  },
  methods: {
    addItem() {
      this.list.push(`${new Date()}`)
      this.list.push(`${new Date()}`)
      this.list.push(`${new Date()}`)

      const ulElem = this.$refs.ul
      console.log(ulElem.childNodes.length)
    },
  },
}
</script>

我们想要在button点击后更新list,接着去打印了ul的子节点个数,一开始已经有了3个,加完3个应该是6个,但是我们看到的打印结果却是3,再次点击才是6。

1644744007680.png

由此我们可以知道:

  • data改变不会立刻渲染更新,是异步渲染的,并且页面渲染会将data修改做整合在一起修改

那么如果我们需要在修改完后获取页面信息,这时我们就需要用到$nextTick,它会等待DOM渲染完后在回调。上面的例子,我们可以修改看看结果

this.$nextTick(() => {
  const ulElem = this.$refs.ul
  console.log(ulElem.childNodes.length)
})

1644744409978.png

这时的结果就是正常的了。

动态组件和异步组件

<!-- 用于组件名还未确定的情况 -->
<component :is="componentId"></component>

<!-- 可以异步加载, 用到才加载 -->
() => import('component.vue')

mixin(Vue3解决)

  • 抽离公共逻辑
  • 但是容易造成来源不明确,不利于阅读
  • 多个mixin会有覆盖,造成命名冲突
  • 容易出现多对多关系,复杂度高

Vue2生命周期

放一张官网的图

cly

Vue3更新功能

Vue3升级了哪些重要功能

  • createApp
  • emits属性
  • 生命周期
  • 多事件
    • 比如click执行多个函数
  • Fragment
  • 移除.sync
  • 异步组件的写法
    • defineAsyncComponent
  • 移除filter管道
  • Teleport
  • Suspense异步组件
    • Suspence两个插槽 #fallback可以写loading...
  • Composition API
    • reactive
    • ref
    • readonly
    • watch和watchEffect
    • setup

Vue3比Vue2有什么优势

  • 性能更好
  • 体积更小
  • 更好的ts支持
  • 更好的代码组织
  • 更好的逻辑抽离
  • 更多新功能

描述Vue3生命周期

这里放一下官网列出的Options APIComposition API的生命周期函数比较

选项式 APIHook inside setup
beforeCreateNot needed*
createdNot needed*
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
errorCapturedonErrorCaptured
renderTrackedonRenderTracked
renderTriggeredonRenderTriggered
activatedonActivated
deactivatedonDeactivated

如何理解ref、toRef和toRefs

ref

  • 生成值类型的响应式数据
  • 可用于模板和reactive
  • 通过.value修改数据

toRef

  • 针对一个响应式对象(reactive封装)的prop
  • 具有响应式
  • 两者保持响应关系

toRefs

  • 将响应式对象(reactive对象)转换为普通对象
  • 对象的每个prop都是对应的ref
  • 两者保持引用关系
  • 不丢失响应式,可以解构
为何需要ref?

返回值类型,会丢失响应式

如果vue不定义ref,用户会自定义ref,造成混乱

为何需要.value?

ref是一个对象(不丢失响应性),通过.value存储值

通过.value属性的get和set实现响应性

当用于模板、reactive时,不需要.value取值,其他情况都需要

为何需要toRef、toRefs?

初衷:不丢失响应性的情况下,把对象数据分解/扩散

前提:针对的是响应式对象(reactive封装的)

注意不创造响应式,而是延续响应式

  1. 用reactive做对象响应式,用refs值响应式
  2. setup中返回toRefs
  3. 合成函数返回响应式对象时,使用toRefs
  4. 变量命名尽量使用xxxRef

用法:

<script setup>
import { ref, toRef, toRefs, reactive, onMounted } from 'vue'

// ref用法
const nameRef = ref('AirHua')
const obj = reactive({
  name: 'air',
  age: 20,
})

// toRef用法
const name1 = toRef(obj, 'name')
// toRefs
const { name, age } = toRefs(obj)
</script>

<template>
  <div>
    <h1>{{ nameRef }}</h1>
    <h1>{{ name1 }}</h1>
    <h1>{{ name }}</h1>
    <h1>{{ age }}</h1>
  </div>
</template>

Composition API代码复用

这里的代码复用可以理解为当一个功能多个页面可能都会用到,我们可以抽离逻辑代码到一个函数

如果了解多React的同学可以把这个和React hooks结合理解一下。

举个例子,我们写了一个监听鼠标移动的函数,可以把它抽离出来,命名为useMousePosition.js,代码如下:

import {
  reactive,
  toRefs,
  onMounted,
  onUnmounted
} from 'vue'

function useMousePosition() {
  const mousePosition = reactive({
    x: 0,
    y: 0
  })
  const {
    x,
    y
  } = toRefs(mousePosition)

  function update(e) {
    mousePosition.x = e.pageX
    mousePosition.y = e.pageY

  }
  onMounted(() => {
    window.addEventListener('mousemove', update)
  })

  onUnmounted(() => {
    window.removeEventListener('mousemove', update)
  })

  return {
    x,
    y
  }
}

export default useMousePosition

接下来我们在组件中使用它就可以了。

<script setup>
import useMousePosition from '../hook/useMousePosition'

const { x, y } = useMousePosition()
</script>

<template>
  <h1>坐标: {{ x }}, {{ y }}</h1>
</template>

<style scoped>
</style>

watch和watchEffect的区别是什么

  • watch需要明确监听哪个属性(immediate初始化之前就监听,deep深度监听)
  • watchEffect会根据其中的属性,自动监听其变化
// watch source can only be a getter/effect function
watch(
  () => state.age,
  (newVal, oldVal) => {
    console.log(newVal, oldVal)
  },
  {
    // deep: true,
  }
)
// 自动监听state.age和numberRef
watchEffect(() => {
  console.log(state.age)
  console.log(numberRef.value)
})

setup中如何获取组件实例

getCurrentInstance支持访问内部组件实例。

官方建议:强烈反对在应用的代码中使用 getCurrentInstance。请不要把它当作在组合式 API 中获取 this 的替代方案来使用。

Vue3为何比Vue2快

  • Proxy响应式
    • 在原理篇会详细讲到
  • PatchFlag
    • 编译模板时,AST中会给动态节点做标记
    • 标记分为不同的类型,如TEXT、PROPS CLASS
    • 用于在diff算法中,区分静态节点,以及不同类型动态节点的对应处理(diff算法优化)

可以来看一下,一个dom树,传入render函数被标记PatchFlag:

1644805879909.png

  • hoistStatic

    • 将静态节点的定义,提升到父级作用域,缓存起来
    • 多个相邻的静态节点,会被合并

1644806170382.png

  • chcheHandler
    • 缓存事件

1644806535780.png

  • SSR优化
    • 静态节点直接输出,绕过vdom
    • 动态节点,还是需要动态渲染

1644806714494.png

  • tree-shaking
    • 按需引入不同的API

Vite打包

  • 开发环境使用ES6 Module无需打包

Composition API和React Hooks对比

  • 前者setup在生命周期中只会调用一次,后者函数会被多次调用
  • 前者无需useMemo、useCallback,因为setup只会调用一次
  • 前者无需顾虑调用顺序,而后者需要保证hoks顺序一致

script setup(vue3.2.0+)

  • 顶级变量可直接用于template

  • defineProps定义属性返回props

  • defineEmits定义事件返回emits

  • defineExpose(父组件获取子组件信息)