跳到主要内容

Vue3 问题总结

vue3 获取组件实例

在 Vue 3 的 setup 函数中,this 不再指向组件实例,这与 Vue 2 的选项 API 有所不同。在 setup 中获取组件实例的方法是使用 getCurrentInstance 函数,该函数返回当前组件的实例。

import { getCurrentInstance } from 'vue';

export default {
setup() {
const instance = getCurrentInstance();
console.log(instance); // 打印组件实例

return {};
}
};

getCurrentInstance 返回的是组件实例对象的内部表示形式,包含了许多有用的属性和方法:

  • proxy: 组件实例的代理对象(可以通过它访问 $props$emit$slots 等)。
  • appContext: 当前应用的上下文。
  • type: 组件的类型(即组件的定义对象)。
  • props: 当前组件的属性对象。
  • emit: 用于触发事件的方法。

setupthis 不再指向组件实例,但你可以通过 instance.proxy 来访问组件的属性和方法,例如:

import { getCurrentInstance } from 'vue';

export default {
setup() {
const instance = getCurrentInstance();
// 访问组件的 props
const props = instance.proxy.$props;
// 触发事件
instance.proxy.$emit('custom-event', 'event data');
return {};
}
};

nextTick

Composition API 中有了更直接的替代方法。可以使用 nextTick 函数,它可以从 vue 包中导入并在 setup 函数或其他 Composition API 钩子中使用。

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

// 定义响应式状态
const count = ref(0);

// 增加计数的函数
function increment() {
count.value++;
nextTick(() => {
console.log('DOM updated');
});
}
</script>

<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
</template>

setup 函数

<script>
import { ref, computed, watch, onMounted, nextTick, getCurrentInstance } from 'vue';

export default {
name: 'AppName',
setup() {
const instance = getCurrentInstance();
console.log('instance', instance); // 打印组件实例

// 定义响应式状态
const count = ref(0);
// 定义计算属性
const doubleCount = computed(() => count.value * 2);

// 定义方法
function increment() {
count.value++;
nextTick(() => {
console.log('DOM 更新');
});
}

// 侦听器
watch(count, (newValue, oldValue) => {
console.log(`Count changed from ${oldValue} to ${newValue}`);
});

// 生命周期钩子
onMounted(() => {
console.log('Mounted instance:', instance);
console.log('Component props:', instance.proxy.$props);
});

return {
count,
doubleCount,
increment
};
}
};
</script>

<template>
<div>
<p>Count: {{ count }}</p>
<p>Double Count: {{ doubleCount }}</p>
<button @click="increment">Increment</button>
</div>
</template>

在 App 全局挂载插件

  1. 声明插件
import type { App, Plugin } from 'vue';

interface MyPluginOptions {
message: string;
}

const MyPlugin: Plugin = {
install(app: App, options: MyPluginOptions) {
app.config.globalProperties.$myPluginMessage = options.message;
}
};

export default MyPlugin;
  1. 挂载到全局并传参
import { createApp } from 'vue';
import MyPlugin from './MyPlugin';

import App from './App.vue';

createApp(App).use(MyPlugin, { message: 'Hello, World!' }).mount('#app');
  1. 使用插件
<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
name: 'MyComponent',
computed: {
message(): string {
return this.$myPluginMessage;
}
}
});
</script>

<template>
<div>{{ message }}</div>
</template>

<!--
setup 语法糖:
<script setup lang="ts">
import { getCurrentInstance, computed } from 'vue'

const instance = getCurrentInstance()

// 获取插件提供的全局属性
const message = computed(() => {
return instance.appContext.config.globalProperties.$myPluginMessage
})
</script>
-->

vue3 响应式数据

1. ref

ref 用于声明基本类型(如字符串、数字、布尔值)或对象类型的数据,使其成为响应式的。使用 ref 时,返回的是一个包含响应式数据的对象,该对象有一个 .value 属性来访问或修改其值。

import { ref } from 'vue';

const count = ref(0);

function increment() {
count.value++;
}

特点:

  • 适用于基本类型和对象:可以用于基本类型(如字符串、数字)和对象。
  • 通过 .value 访问值:需要通过 .value 属性访问和修改其值。
  • 自动解包:在模板中,ref 会自动解包,不需要显式调用 .value

2. reactive

reactive 用于声明复杂类型(如对象、数组)的响应式数据。它接收一个对象,并返回该对象的响应式代理。通过代理,可以直接访问和修改对象的属性,而无需使用 .value

import { reactive } from 'vue';

const state = reactive({
count: 0,
user: {
name: 'John Doe',
age: 30
}
});

function increment() {
state.count++;
}

function updateUserName(newName) {
state.user.name = newName;
}

特点:

  • 适用于对象和数组:更适合用于复杂的嵌套对象和数组。
  • 直接访问属性:可以直接访问和修改对象的属性,无需使用 .value
  • 深度响应reactive 创建的是一个深度响应式对象,内部所有嵌套的属性都会被转换为响应式。

3. toRef 和 toRefs

在 Vue 3 中,toReftoRefs 是两个用于处理响应式数据的工具函数。它们主要用于将响应式对象的属性转换为 ref,以便在解构或传递属性时保持响应式特性。这对于处理复杂状态和组合多个状态非常有用。

  • toRef 用于将响应式对象的单个属性转换为 ref。这在你希望单独引用和操作响应式对象的某个属性时特别有用。
import { reactive, toRef } from 'vue';

const state = reactive({
count: 0,
user: {
name: 'John Doe',
age: 30
}
});

// 将 state 的 count 属性转换为 ref
const countRef = toRef(state, 'count');

function increment() {
countRef.value++;
}

console.log(countRef.value); // 输出 0
  • toRefs 用于将响应式对象的所有属性转换为 ref。这在你希望解构响应式对象时保持响应式特性特别有用。
import { reactive, toRefs } from 'vue';

const state = reactive({
count: 0,
user: {
name: 'John Doe',
age: 30
}
});

// 将 state 的所有属性转换为 ref
const { count, user } = toRefs(state);

function increment() {
count.value++;
}

function updateUserName(newName: string) {
user.value.name = newName;
}

console.log(count.value); // 输出 0
console.log(user.value.name); // 输出 'John Doe'

4. 示例

<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
<p>User: {{ user.name }} - {{ user.age }}</p>
<button @click="updateUserName">Update User Name</button>
</div>
</template>

<script setup lang="ts">
import { reactive, toRefs } from 'vue'

const state = reactive({
count: 0,
user: {
name: 'John Doe',
age: 30
}
})

const { count, user } = toRefs(state)

function increment() {
count.value++
}

function updateUserName() {
user.value.name = 'Jane Doe'
}
</script>

Teleport

<Teleport> 是一个内置组件,它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。

<button @click="open = true">Open Modal</button>

<Teleport to="body">
<div v-if="open" class="modal">
<p>Hello from the modal!</p>
<button @click="open = false">Close</button>
</div>
</Teleport>

ref 和 shallowRef 的区别

在 Vue 3 中,refshallowRef都是用于创建响应式引用的函数,但它们之间有一些区别。

1 ref

  • ref是用于创建一个包装器对象,将普通 JavaScript 值转换为响应式对象。它返回一个带有.value属性的对象,通过.value属性来访问和修改包装的值。
  • 当响应式对象中包含嵌套的对象或数组时,ref会将整个对象或数组转换为响应式对象。
  • 例如:
javascriptCopy codeimport { ref } from 'vue';

const obj = ref({ count: 0 });
console.log(obj.value.count); // 访问嵌套的属性
obj.value.count++; // 修改嵌套的属性

2. shallowRef

  • shallowRef也是用于创建一个包装器对象,将普通 JavaScript 值转换为响应式对象。但与ref不同的是,shallowRef会对嵌套的对象或数组进行浅响应式转换。
  • 当使用shallowRef创建响应式对象时,只有对象的第一层属性会被转换为响应式对象,而不会递归地转换整个对象。
  • 例如:
javascriptCopy codeimport { shallowRef } from 'vue';

const obj = shallowRef({ count: 0 });
console.log(obj.value.count); // 访问嵌套的属性
obj.value.count++; // 修改嵌套的属性

// 在这种情况下,只有第一层属性`count`会被转换为响应式对象,而不会递归转换整个对象。

3. 区别总结

  • ref会递归地将整个对象或数组转换为响应式对象,而shallowRef只会将对象的第一层属性转换为响应式对象,不会递归转换整个对象。
  • 当你需要对整个对象进行深层次的响应式转换时,应该使用ref;当你只需要对对象的第一层属性进行响应式转换时,可以使用shallowRef

ref 与 reactive 区别

1. ref

function ref(value) {
return createRef(value, false);
}

function createRef(rawValue, shallow) {
if (isRef(rawValue)) {
return rawValue;
}
return new RefImpl(rawValue, shallow);
}

class RefImpl {
constructor(value, __v_isShallow) {
this.__v_isShallow = __v_isShallow;
this.dep = void 0;
this.__v_isRef = true;
this._rawValue = __v_isShallow ? value : toRaw(value);
this._value = __v_isShallow ? value : toReactive(value);
}
get value() {
trackRefValue(this);
return this._value;
}
set value(newVal) {
const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal);
newVal = useDirectValue ? newVal : toRaw(newVal);
if (shared.hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal;
this._value = useDirectValue ? newVal : toReactive(newVal);
triggerRefValue(this, 4, newVal);
}
}
}
  • ref是一个函数,用于创建一个包装器对象,将普通 JavaScript 值转换为响应式对象。它返回一个带有.value属性的对象,这个属性用来访问和修改包装的值。
  • ref适用于简单的数据类型,如基本类型和对象。
  • 当你需要为基本类型数据或普通对象创建响应式引用时,应该使用ref
javascriptCopy codeimport { ref } from 'vue';

const count = ref(0);
console.log(count.value); // 访问包装后的值
count.value++; // 修改包装后的值

2. reactive

function reactive(target) {
if (isReadonly(target)) {
return target;
}
return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}
function createReactiveObject(target, isReadonly2, baseHandlers, collectionHandlers, proxyMap) {
if (!shared.isObject(target)) {
{
warn(`value cannot be made reactive: ${String(target)}`);
}
return target;
}
if (target['__v_raw'] && !(isReadonly2 && target['__v_isReactive'])) {
return target;
}
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy;
}
const targetType = getTargetType(target);
if (targetType === 0 /* INVALID */) {
return target;
}
const proxy = new Proxy(target, targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers);
proxyMap.set(target, proxy);
return proxy;
}
  • reactive是一个函数,用于创建一个响应式代理对象,使对象的属性变化能够触发视图更新。它返回一个代理对象,你可以直接访问和修改这个对象的属性。
  • reactive适用于复杂的数据类型,如对象和数组。
  • 当你需要为对象或数组创建响应式代理时,应该使用reactive
javascriptCopy codeimport { reactive } from 'vue';

const state = reactive({
count: 0,
message: 'Hello'
});

console.log(state.count); // 直接访问属性
state.count++; // 直接修改属性

3. 区别总结

  • ref适用于简单的数据类型,提供了一个包装器对象,需要通过.value访问和修改值。
  • reactive适用于复杂的数据类型,直接创建一个代理对象,可以直接访问和修改对象的属性。
  • 通常情况下,如果你只需要为一个简单的值创建响应式引用,可以使用ref;如果你需要创建一个响应式的对象或数组,应该使用reactive

首先 vue3 通过 reactive 的方法,利用 proxy 的代理对象解决大量在 vue2 中 Object.defineProperty 的痛点,极好的支持对象监听,但同时 proxy 并不能直接监听基础类型,需要每次都构建一个对象去用 Proxy 代理,这样就会造成极大的性能损耗。

因此 vue2 就提供了一个 ref 方法,通过返回一个简单的响应式对象,专门用于处理基础类型的数据响应。