Skip to content

Vue 进阶

一: 响应式原理

在 Vue 2.x 和 Vue 3.x 中,响应式原理的核心概念是相似的,都是基于数据劫持和发布订阅模式。然而,在实现细节上,Vue 3.x 引入了 Proxy 对象,相比 Vue 2.x 的 Object.defineProperty,提供了更强大和灵活的响应式系统。

Vue 2.x 的响应式原理:

  1. 数据劫持: Vue 2.x 使用 Object.defineProperty 来劫持对象的属性,使得这些属性成为响应式的。

  2. Watcher 和 Dep: 当访问响应式对象的属性时,会触发 get 操作,此时会建立 Watcher 和 Dep 的关系。Dep 用于存储依赖的实例,而 Watcher 用于表示一个响应式的副作用。

  3. 派发更新: 当响应式对象的属性发生变化时,会触发 set 操作,此时会通知相关的依赖进行更新。

Vue 3.x 的响应式原理:

  1. Proxy: Vue 3.x 引入了 JavaScript 的 Proxy 对象,代替了 Object.definePropertyProxy 提供了更灵活且强大的拦截器,使得可以监听对象的各种操作,而不仅限于属性的 getset

  2. Reactive 函数和 Ref 类型: 在 Vue 3.x 中,通过 reactive 函数可以将对象变成响应式,通过 ref 类型可以将基本类型变成响应式。这些函数和类型都基于 Proxy 实现。

  3. Ref 类型: Vue 3.x 引入了 Ref 类型,用于表示一个包装过的响应式数据,而不仅仅是对象属性。Ref 类型在处理基本类型上更加方便。

  4. Effect 函数: Vue 3.x 中引入了 effect 函数,用于创建一个响应式的副作用,替代了 Vue 2.x 中的 Watcher。effect 函数基于 Proxy 实现,提供了更灵活的副作用管理。

javascript
// Vue 3.x 中的示例
import { reactive, effect } from 'vue';

const state = reactive({
  name: 'Alice',
});

effect(() => {
  console.log(state.name);
});

state.name = 'Bob'; // 触发更新,输出 'Bob'

在 Vue 3.x 中,响应式的实现更加灵活和高效,通过引入 Proxy 和新的 API,提供了更好的开发体验和性能。Composition API 的引入也使得组织组件逻辑更加方便。

Vue 2.x 的响应式原理示例:

javascript
function observe(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return;
  }

  Object.keys(obj).forEach(key => {
    defineReactive(obj, key, obj[key]);
  });
}

function defineReactive(obj, key, val) {
  observe(val); // 递归处理嵌套对象

  Object.defineProperty(obj, key, {
    get() {
      console.log(`访问属性 ${key}`);
      return val;
    },
    set(newVal) {
      if (newVal !== val) {
        console.log(`设置属性 ${key},新值为 ${newVal}`);
        val = newVal;
      }
    },
  });
}

const data = { name: 'Alice', age: 25 };
observe(data);

console.log(data.name); // 访问属性 name
data.name = 'Bob'; // 设置属性 name,新值为 Bob

Vue 3.x 的响应式原理示例:

javascript
const reactiveHandler = {
  get(target, key, receiver) {
    const result = Reflect.get(target, key, receiver);
    console.log(`访问属性 ${key}`);
    return result;
  },
  set(target, key, value, receiver) {
    const oldValue = target[key];
    const result = Reflect.set(target, key, value, receiver);
    if (value !== oldValue) {
      console.log(`设置属性 ${key},新值为 ${value}`);
    }
    return result;
  },
};

function reactive(obj) {
  return new Proxy(obj, reactiveHandler);
}

const data = reactive({ name: 'Alice', age: 25 });

console.log(data.name); // 访问属性 name
data.name = 'Bob'; // 设置属性 name,新值为 Bob

在 Vue 3.x 中,使用了 Proxy 对象,通过定义一个 reactiveHandler 对象,实现了类似 Vue 2.x 的 Object.defineProperty 的效果。Proxy 提供了更灵活的拦截器,使得开发者可以更方便地处理属性的访问和修改。

需要注意的是,Vue 3.x 的响应式系统还引入了 RefReactive 这样的 API,用于处理特殊情况下的响应式数据,例如使用 ref 函数创建的响应式变量。这些 API 增强了 Vue 3.x 的响应式系统的功能。

二: Vue 3.0 相比于 2.0 有一系列的重大改进,其中一些主要的变化包括:

  1. Composition API:

    • Vue 2.x: 使用选项对象的方式(data、methods、computed 等)组织组件逻辑,可能导致复杂组件难以维护。
    • Vue 3.x: 引入 Composition API,允许开发者通过函数的方式组织和复用组件逻辑,使得代码更加灵活、清晰,尤其适用于大型组件和复杂逻辑的场景。
  2. 更好的 TypeScript 支持:

    • Vue 2.x: TypeScript 支持较弱,需要使用特殊的语法或工具才能实现良好的类型推导。
    • Vue 3.x: 提供了更好的 TypeScript 支持,通过 Composition API,能够更容易地进行类型推导。
  3. Proxy 替代 Object.defineProperty:

    • Vue 2.x: 使用 Object.defineProperty 实现响应式,存在一些限制,如无法监听数组的变化。
    • Vue 3.x: 使用 Proxy 对象替代 Object.defineProperty,提供了更灵活、强大的响应式系统,能够监听数组变化等。
  4. 优化的 Virtual DOM:

    • Vue 2.x: 基于 Snabbdom 的 Virtual DOM 实现,性能较好,但仍有一些潜在的优化空间。
    • Vue 3.x: 重新设计的 Virtual DOM,使用了静态树提升、Fragment 的引入等优化,提高了渲染性能。
  5. Tree-Shakable:

    • Vue 2.x: 无法进行有效的 Tree-Shaking,导致整个 Vue 库较大。
    • Vue 3.x: 设计为 Tree-Shakable,可以更方便地进行模块的按需加载,减小应用体积。
  6. 全局状态管理改进:

    • Vue 2.x: 全局状态使用 Vue 实例的 data 属性,较为繁琐,需要在每个组件中进行挂载。
    • Vue 3.x: 引入 createApp 函数和全局 provide/inject API,简化了全局状态的管理,更易于使用。
  7. 更好的 JSX 支持:

    • Vue 2.x: 支持 JSX,但相对较弱。
    • Vue 3.x: 对 JSX 的支持更加完善,提供了更多的选项和 API,使得使用 JSX 更为方便。
  8. Faster Mounting and Patching:

    • Vue 3.x: 通过优化 Virtual DOM 和编译器,提高了组件的挂载和更新性能。

这些改进使得 Vue 3.0 在开发体验、性能和可维护性等方面都有较大的提升,使得它更适合现代化的前端开发。但在升级时,也需要注意 API 的变化和一些迁移策略。

三: Vue 3.0 的 Composition API 引入了一种新的组织组件逻辑的方式,相较于 Vue 2.0 的选项对象形式,它具有一些明显的优势和适用场景:

优势:

  1. 更灵活的组织代码:

    • Vue 2.x: 使用选项对象,当组件逻辑较为复杂时,不同的选项(data、methods、computed 等)可能分散在不同地方,使得代码难以维护。
    • Vue 3.x: Composition API 允许将相关逻辑集中在一个函数中,使得代码更加清晰和直观,尤其适用于大型组件。
  2. 逻辑复用更灵活:

    • Vue 2.x: 使用 Mixin 或者 Higher-Order Component 等方式进行逻辑复用,但存在一些问题,如命名冲突和难以理解的执行顺序。
    • Vue 3.x: Composition API 提供了更灵活的逻辑复用方式,可以更自由地引用逻辑函数,使得代码更易于组织和理解。
  3. 更好的 TypeScript 支持:

    • Vue 2.x: TypeScript 支持较弱,需要使用特殊的语法或工具才能实现良好的类型推导。
    • Vue 3.x: Composition API 提供了更好的 TypeScript 支持,通过函数的方式进行组件逻辑的定义,使得类型推导更为容易。
  4. 逻辑封装更直观:

    • Vue 2.x: 逻辑封装在不同的选项中,可能导致代码难以理解。
    • Vue 3.x: Composition API 允许将相关逻辑封装在一个函数中,提高了代码的可读性和维护性。
  5. 按需加载和 Tree-Shakable:

    • Vue 2.x: 无法进行有效的 Tree-Shaking,导致整个 Vue 库较大。
    • Vue 3.x: Composition API 设计为 Tree-Shakable,可以更方便地进行模块的按需加载,减小应用体积。

使用场景:

  1. 大型组件: 当一个组件逻辑较为复杂,包含多个生命周期钩子和数据属性时,使用 Composition API 可以更清晰地组织代码,避免逻辑分散在不同选项中。

  2. 逻辑复用: 当有多个组件需要共享一些逻辑时,Composition API 提供了更灵活的逻辑复用方式,避免了 Mixin 带来的一些问题。

  3. TypeScript 项目: 如果项目使用 TypeScript 进行开发,Composition API 提供了更好的类型推导支持,使得代码更为健壮。

  4. 按需加载: Composition API 的设计使得组件的逻辑可以更为灵活地进行按需加载,有助于提高应用性能。

总体而言,Composition API 是 Vue 3.0 中一个强大的特性,特别适用于开发大型、复杂的组件和逻辑复用的场景。在小型组件或者简单场景中,开发者仍可以选择使用选项对象形式。

四: keep-alive

<keep-alive> 是 Vue 中的一个内置组件,主要用来缓存不活动的组件实例,以便在组件切换时保留其状态。它的作用是优化性能,避免因组件的频繁创建和销毁而导致的性能损耗。

具体而言,<keep-alive> 主要用于以下两个方面:

  1. 组件缓存: 在包裹需要缓存的组件时,<keep-alive> 会将这些组件的实例保存在内存中,而不是每次切换组件时重新创建实例。这对于那些在多次切换时保持不变的状态(例如输入框中的文本、滚动位置等)非常有用。

  2. 生命周期钩子: 当组件被缓存时,它的生命周期钩子不会被销毁,而是被触发。这意味着组件的 activateddeactivated 钩子可以用来执行一些特定的逻辑,例如在组件被激活时发送请求、在组件被停用时清理资源。

使用示例:

html
<template>
  <div>
    <keep-alive>
      <component :is="currentComponent" />
    </keep-alive>
    <button @click="toggleComponent">Toggle Component</button>
  </div>
</template>

<script>
import FirstComponent from './FirstComponent.vue';
import SecondComponent from './SecondComponent.vue';

export default {
  data() {
    return {
      currentComponent: 'FirstComponent',
    };
  },
  methods: {
    toggleComponent() {
      this.currentComponent = this.currentComponent === 'FirstComponent' ? 'SecondComponent' : 'FirstComponent';
    },
  },
};
</script>

在上述示例中,<keep-alive> 包裹了一个动态组件,每次点击按钮切换组件时,被切换出去的组件实例并没有被销毁,而是被缓存起来,下次切换回来时直接使用缓存的实例。这有助于提高性能,特别是在组件的创建和销毁代价较高的情况下。

五: Vue 3 中怎样实现异步组件?

在 Vue 3 中,实现异步组件的方式与 Vue 2.x 有一些不同。Vue 3 使用 defineAsyncComponent 函数来定义异步组件,同时支持使用动态 import 语法或工厂函数返回一个 Promise。

以下是两种常见的实现异步组件的方式:

1. 使用动态 import 语法:

vue
<template>
  <div>
    <AsyncComponent />
  </div>
</template>

<script>
import { defineAsyncComponent } from 'vue';

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

export default {
  components: {
    AsyncComponent,
  },
};
</script>

在上述示例中,defineAsyncComponent 函数接受一个返回 Promise 的函数,这里使用动态 import 语法来异步加载组件。当组件被渲染时,它会自动按需加载。

2. 使用工厂函数返回 Promise:

vue
<template>
  <div>
    <AsyncComponent />
  </div>
</template>

<script>
import { defineAsyncComponent } from 'vue';

const AsyncComponent = defineAsyncComponent(() =>
  new Promise((resolve) => {
    // 模拟异步加载过程
    setTimeout(() => {
      resolve({
        template: '<div>Async Component</div>',
      });
    }, 1000);
  })
);

export default {
  components: {
    AsyncComponent,
  },
};
</script>

在这个示例中,defineAsyncComponent 函数接受一个返回 Promise 的工厂函数。在 Promise 完成时,解析的对象可以包含组件的选项,例如模板、方法等。

这两种方式都能实现异步加载组件,具体选择取决于项目的需要和开发者的偏好。使用动态 import 更为简洁,而使用工厂函数可以提供更多的灵活性,允许在 Promise 完成时返回自定义的组件选项。

六: Teleport

在 Vue 3 中,<teleport> 组件的作用是提供一种方式,将其内容移动到 DOM 树中的任何其他位置。它可以帮助开发者在组件中的模板中定义一些内容,并将其渲染到应用的其他位置,而不受组件层级的限制。

<teleport> 通常用于在应用中创建一些全局的元素,例如模态框、弹出提示等,可以将这些全局的元素的内容定义在组件的模板中,但实际渲染的位置却可以在应用的其他地方。

以下是一个简单的 <teleport> 的使用示例:

vue
<template>
  <div>
    <button @click="toggleModal">Toggle Modal</button>

    <teleport to="body">
      <div v-if="showModal" class="modal">
        <h2>Modal Content</h2>
        <button @click="toggleModal">Close</button>
      </div>
    </teleport>
  </div>
</template>

<script>
import { ref } from 'vue';

export default {
  data() {
    return {
      showModal: false,
    };
  },
  methods: {
    toggleModal() {
      this.showModal = !this.showModal;
    },
  },
};
</script>

<style>
/* 在全局样式中定义 modal 样式 */
.modal {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: white;
  padding: 20px;
  border: 1px solid #ccc;
}
</style>

在这个示例中,当点击按钮时,<teleport> 将包含模态框的内容移动到 body 元素下。这样可以确保模态框不受组件层级的影响,而是被渲染到 body 下,使其能够在整个应用中覆盖显示。这种用法非常适用于需要在全局范围内显示的元素。

七: Suspense

<Suspense> 是 Vue 3 中的一个新组件,主要用于处理异步组件加载时的等待状态(loading state)。它提供了一种优雅的方式,让开发者在异步组件加载过程中显示占位内容,而不是直接展示加载中的错误或空白。

作用:

  1. 处理异步组件加载过程: <Suspense> 可以包裹一个或多个异步组件,当这些组件处于加载过程中时,可以显示备用内容(fallback),以提高用户体验。

使用场景:

  1. 延迟加载组件: 当某个组件在初次渲染时不需要加载,而是在需要时才加载,可以使用 <Suspense> 包裹异步组件,并设置一个备用内容,以确保在加载过程中展示一些有意义的信息。

  2. 优雅处理加载错误: <Suspense> 还可以用于处理异步组件加载失败的情况,显示备用内容而不是直接展示错误信息。

以下是一个简单的 <Suspense> 的使用示例:

vue
<template>
  <div>
    <Suspense>
      <template #default>
        <AsyncComponent />
      </template>
      <template #fallback>
        <div>Loading...</div>
      </template>
    </Suspense>
  </div>
</template>

<script>
import { ref, defineAsyncComponent } from 'vue';

const AsyncComponent = defineAsyncComponent(() =>
  import('./AsyncComponent.vue').catch(() => {
    // 处理加载错误
    return { template: '<div>Error!</div>' };
  })
);

export default {
  components: {
    AsyncComponent,
  },
};
</script>

在这个示例中,<Suspense> 包裹了一个异步组件 AsyncComponent。在异步组件加载的过程中,<Suspense> 会显示备用内容,即 "Loading..."。如果加载失败,可以在异步组件的 catch 部分处理错误,显示错误信息。

这样使用 <Suspense> 可以提供更好的用户体验,避免在异步加载过程中直接展示空白或错误信息。