vue系列-vue3入门

Vue3.x 官网:https://v3.cn.vuejs.org/

理论

与Vue 2.x版本的比较

Vue2.x 的组织代码形式,叫 Options API,而 Vue3 最大的特点是 Composition API 中文名是合成函数:以函数为载体,将业务相关的逻辑代码抽取到一起,整体打包对外提供相应能力。可以理解它是我们组织代码,解决逻辑复用的一种方案。

其中 setupComposition API 的入口函数,是在 beforeCreate 声明周期函数之前执行的。还提供了 ref 函数定义一个响应式的数据,reactive 函数定义多个数据的响应式等等。

  • 组合式 API 方式组织代码进行业务和逻辑的解耦。
  • 生命周期调用
  • 响应式原理
  • vue 2使用的是 Flow.js ,vue 3使用的是 typescript

组合式 API

为什么

组件变大时,逻辑关注点列表也会增长,导致组件难以维护。

如果能够将同一个逻辑关注点相关代码收集在一起会更好。

业务代码和逻辑代码解耦。

合成函数:以函数为载体,将业务相关的逻辑代码抽取到一起,整体打包对外提供相应能力

setup()

新的 setup 选项在组件创建之前执行,一旦 props 被解析,就将作为组合式 API 的入口。

setup 中你应该避免使用 this,因为它不会找到组件实例。setup 的调用发生在 data property、computed property 或 methods 被解析之前,所以它们无法在 setup 中被获取。

setup 选项是一个接收 propscontext 的函数。此外,我们将 setup 返回的所有内容都暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板。

如果在setup函数中定义并返回的数据需要添加响应式,则需要使用ref函数进行包裹。

ref()

可以通过一个新的 ref 函数使任何响应式变量在任何地方起作用。

ref 接收参数并将其包裹在一个带有 value property 的对象中返回,然后可以使用该 property 访问或更改响应式变量的值。

ref 为我们的值创建了一个响应式引用。在整个组合式 API 中会经常使用引用的概念。

import { ref } from 'vue'

const counter = ref(0)

console.log(counter) // { value: 0 }
console.log(counter.value) // 0

counter.value++
console.log(counter.value) // 1

在任何值周围都有一个封装对象,这样我们就可以在整个应用中安全地传递它,而不必担心在某个地方失去它的响应性。

watch 和 computed

watch 和 computed 函数可以从 vue 中导出,在 setup 函数中使用。

效果与 watch 和 computed API一样。

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs, computed } from 'vue'

// 在我们的组件中
setup (props) {
  // 使用 `toRefs` 创建对 props 中的 `user` property 的响应式引用
  const { user } = toRefs(props)

  const repositories = ref([])
  const getUserRepositories = async () => {
    // 更新 `props.user ` 到 `user.value` 访问引用值
    repositories.value = await fetchUserRepositories(user.value)
  }

  onMounted(getUserRepositories)

  // 在 user prop 的响应式引用上设置一个侦听器
  watch(user, getUserRepositories)

  const searchQuery = ref('')
  const repositoriesMatchingSearchQuery = computed(() => {
    return repositories.value.filter(
      repository => repository.name.includes(searchQuery.value)
    )
  })

  return {
    repositories,
    getUserRepositories,
    searchQuery,
    repositoriesMatchingSearchQuery
  }
}

setup函数中可以进一步拆分,将相关代码提出模块导入使用。

生命周期

lifecycle

你可以通过在生命周期钩子前面加上 “on” 来访问组件的生命周期钩子。

下表包含如何在 setup () 内部调用生命周期钩子:

选项式 API Hook inside setup
beforeCreate Not needed*
created Not needed*
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured
renderTracked onRenderTracked
renderTriggered onRenderTriggered
activated onActivated
deactivated onDeactivated
与vue2.x 对比,新增的生命周期
  • renderTracked:跟踪虚拟 DOM 重新渲染时调用。钩子接收 debugger event 作为参数。此事件告诉你哪个操作跟踪了组件以及该操作的目标对象和键。
  • renderTriggered:当虚拟 DOM 重新渲染被触发时调用。和 renderTracked 类似,接收 debugger event 作为参数。此事件告诉你是什么操作触发了重新渲染,以及该操作的目标对象和键。

响应式原理

推荐阅读:林三心画了8张图,最通俗易懂的Vue3响应式核心原理解析

什么是响应式
  • 当一个值被读取时进行追踪
  • 当某个值改变时进行检测
  • 重新运行代码来读取原始值
vue 如何知道哪些代码在执行

跟踪局部变量分配

Vue 通过一个副作用 (effect) 来跟踪当前正在运行的函数。副作用是一个函数的包裹器,在函数被调用之前就启动跟踪。Vue 知道哪个副作用在何时运行,并能在需要时再次执行它。

// 维持一个执行副作用的栈
const runningEffects = []

const createEffect = fn => {
  // 将传来的 fn 包裹在一个副作用函数中
  const effect = () => {
    runningEffects.push(effect)
    fn()
    runningEffects.pop()
  }

  // 立即自动执行副作用
  effect()
}

当我们的副作用被调用时,在调用 fn 之前,它会把自己推到 runningEffects 数组中。这个数组可以用来检查当前正在运行的副作用。

副作用是许多关键功能的起点。例如,组件的渲染和计算属性都在内部使用副作用。任何时候,只要有东西对数据变化做出奇妙的回应,你就可以肯定它已经被包裹在一个副作用中了。

vue 如何跟踪变化

当我们从一个组件的 data 函数中返回一个普通的 JavaScript 对象时,Vue 会将该对象包裹在一个带有 getset 处理程序的 Proxy 中。Proxy 是在 ES6 中引入的,它使 Vue 3 避免了 Vue 早期版本中存在的一些响应性问题。

Proxy 是一个对象,它包装了另一个对象,并允许你拦截对该对象的任何交互。

const dinner = {
  meal: 'tacos'
}

const handler = {
  get(target, property) {
    console.log('intercepted!')
    return target[property]
  }
}

const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)

// intercepted!
// tacos

这里我们截获了读取目标对象 property 的举动。像这样的处理函数也称为一个*捕捉器 (trap)*。有许多可用的不同类型的捕捉器,每个都处理不同类型的交互。

除了控制台日志,我们可以在这里做任何我们想做的事情。如果我们愿意,我们甚至可以不返回实际值。这就是为什么 Proxy 对于创建 API 如此强大。

使用 Reflect 帮助 Proxy 进行 this 绑定。

const dinner = {
  meal: 'tacos'
}

const handler = {
  get(target, property, receiver) {
    // 第1步 收集依赖
    // track函数中处理 跟踪一个 property 何时被读取
    // 检查当前运行的是哪个 effect,并将其与 target 和 property 记录在一起 
    // 即Vue如何知道这个 property 是该副作用的依赖项(收集依赖)
    track(target, property)
    return Reflect.get(...arguments)
  },
  // 第2步 调用 set 处理函数
  // 在property值更改时重新运行这个副作用
  set(target, property, value, receiver) {
    // 第3步 触发依赖
    // trigger 函数查找那些副作用依赖于该 property 并执行他们
    trigger(target, property)
    return Reflect.set(...arguments)
  }
}

const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)

// tacos

从 Vue 3 开始,我们的响应性现在可以在一个独立包中使用。将 $data 包裹在一个代理中的函数被称为 reactive。我们可以自己直接调用这个函数,允许我们在不需要使用组件的情况下将一个对象包裹在一个响应式代理中。

注意
  • Vue 在内部跟踪所有已经被转成响应式的对象,所以它总是为同一个对象返回相同的代理。当从一个响应式代理中访问一个嵌套对象时,该对象在被返回之前被转换为一个代理。
  • proxy 代理的对象与原对象不等。不能使用 includes 或 indexOf 进行严等(===)比较操作。
Vue 3.x 数据劫持的优势

Proxy 的优势如下:

  • Proxy 可以直接监听对象(const proxy = new Proxy(target, handler));defineProperty 需要遍历对象属性进行监听。
  • Proxy 可以直接监听对象新增的属性;defineProperty 只能劫持一开始就存在的属性,新增属性需要手动 Observer。
  • Proxy 可以直接监听数组的变化;defineProperty 无法监听数组的变化。
  • Proxy 有多达 13 种拦截方法:不限于 getsethasdeletePropertyapplyownKeysconstruct 等等;除开 getset 其他都是 defineProperty 不具备的。
  • Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的;defineProperty 只能遍历对象属性直接修改;

Proxy 的劣势如下:

  • ES6 的 Proxy 的存在浏览器兼容性问题。

Proxy 和 Reflect 结合实现 Vue3 底层数据劫持原理。Reflect 设计的目的是为了优化 Object 的一些操作方法以及合理的返回 Object 操作返回的结果,对于一些命令式的 Object 行为,Reflect 对象可以将其变为函数式的行为。比如 ('name' in obj) = Reflect.has(obj, 'name')