在 Vue 3 中,ref 和 reactive 都是用于声明响应式状态的工具,但它们的使用场景和内部工作机制有所不同。Vue 3 推荐使用 ref 而不是 reactive 的原因主要涉及到以下几个方面:
-
简单的原始值响应式处理:
ref更适合处理简单的原始值(如字符串、数字、布尔值等),而reactive更适合处理复杂的对象或数组。
-
一致性和解构:
- 使用
ref时,解构不会丢失响应性,因为ref会返回一个包含.value属性的对象。而reactive对象在解构时会丢失响应性。
- 使用
-
类型推导和代码提示:
ref更容易与 TypeScript 配合使用,提供更好的类型推导和代码提示。
示例代码
以下是一个详细的代码示例,演示为什么在某些情况下推荐使用 ref 而不是 reactive。
使用 ref 的示例
import { ref } from 'vue';
export default {
setup() {
// 使用 ref 声明响应式状态
const count = ref(0);
function increment() {
count.value++;
}
return {
count,
increment
};
}
};
使用 reactive 的示例
import { reactive } from 'vue';
export default {
setup() {
// 使用 reactive 声明响应式状态
const state = reactive({
count: 0
});
function increment() {
state.count++;
}
return {
state,
increment
};
}
};
解构问题
使用 ref 解构
import { ref } from 'vue';
export default {
setup() {
const count = ref(0);
function increment() {
count.value++;
}
// 解构时不会丢失响应性
const { value: countValue } = count;
return {
countValue,
increment
};
}
};
使用 reactive 解构
import { reactive } from 'vue';
export default {
setup() {
const state = reactive({
count: 0
});
function increment() {
state.count++;
}
// 解构时会丢失响应性
const { count } = state;
return {
count,
increment
};
}
};
代码解释
-
使用
ref:ref返回一个包含.value属性的对象,因此在模板中使用时需要通过.value访问实际值。- 解构时,可以直接解构
.value属性,不会丢失响应性。
-
使用
reactive:reactive适用于复杂的对象或数组,返回一个代理对象。- 直接解构
reactive对象的属性会丢失响应性,因为解构后得到的属性是原始值,不再是响应式的。
总结
- 简单值:对于简单的原始值(如字符串、数字、布尔值等),推荐使用
ref,因为它更简洁,并且在解构时不会丢失响应性。 - 复杂对象:对于复杂的对象或数组,推荐使用
reactive,因为它可以更方便地处理嵌套属性的响应性。 - 一致性:
ref在解构时不会丢失响应性,而reactive在解构时会丢失响应性,这使得ref在某些情况下更为可靠。
通过理解 ref 和 reactive 的不同使用场景和内部工作机制,可以更好地选择适合的工具来管理 Vue 3 应用中的响应式状态。
补充回复
tips:上面是基础讲解,我看有两个老哥评论了ref和reactive都是一样的,下面我会详细说一下,详细讲解在Vue 3中为何推荐使用ref而不是reactive,从原理层面分析,并通过详细的代码示例进行说明。
Vue 3中的响应式系统概述
Vue 3引入了全新的响应式系统,基于Proxy实现,极大地增强了其响应式能力。主要通过两个API来创建响应式数据:
ref:用于创建持有单个值的响应式引用,适用于基本类型以及希望保持引用透明性的场景。reactive:用于创建深层响应式对象,适用于复杂的数据结构,如对象和数组。
为什么推荐使用ref而不是reactive
1. 响应性跟踪的精确性
ref提供了更精确的响应性跟踪。每个ref都有其独立的响应式依赖,这意味着当ref的值变化时,只有依赖于该ref的组件会重新渲染。
<template>
<div>
<p>计数器:{{ count }}</p>
<button @click="increment">增加</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
export default defineComponent({
name: 'RefExample',
setup() {
const count = ref(0);
const increment = () => {
count.value++;
};
return {
count,
increment,
};
},
});
</script>
2. 更好的类型推断
使用ref时,TypeScript能够更好地推断出具体的类型,尤其是对于基本类型(如number、string等),这在使用reactive时可能需要额外的类型声明。
<template>
<div>
<p>姓名:{{ name }}</p>
<input v-model="name" placeholder="输入姓名" />
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
export default defineComponent({
name: 'RefTypeExample',
setup() {
const name = ref<string>('张三');
return {
name,
};
},
});
</script>
3. 避免响应式对象的泄漏
使用reactive创建的对象是深层响应式的,这在某些情况下可能导致不必要的响应式对象嵌套,增加内存消耗。而ref只包裹单一值,避免了这种情况。
<template>
<div>
<p>用户姓名:{{ user.name }}</p>
<input v-model="user.name" placeholder="输入姓名" />
</div>
</template>
<script lang="ts">
import { defineComponent, reactive } from 'vue';
export default defineComponent({
name: 'ReactiveLeakExample',
setup() {
const user = reactive({
name: '李四',
});
return {
user,
};
},
});
</script>
在上述示例中,user对象是一个深层响应式对象,可能会带来不必要的响应式追踪。而使用ref可以避免这种情况。
<template>
<div>
<p>用户姓名:{{ name }}</p>
<input v-model="name" placeholder="输入姓名" />
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
export default defineComponent({
name: 'RefNoLeakExample',
setup() {
const name = ref<string>('李四');
return {
name,
};
},
});
</script>
4. 更好的兼容性与组合
ref可以与其他组合式API更好地协作,如computed和watch,提供更灵活的响应式操作。
<template>
<div>
<p>原始计数:{{ count }}</p>
<p>计数的双倍:{{ doubleCount }}</p>
<button @click="increment">增加</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed } from 'vue';
export default defineComponent({
name: 'ComputedRefExample',
setup() {
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
const increment = () => {
count.value++;
};
return {
count,
doubleCount,
increment,
};
},
});
</script>
5. 更清晰的读写语义
ref通过.value进行访问和修改,能够明确区分响应式引用和值本身,增加代码的可读性。
<template>
<div>
<p>消息:{{ message }}</p>
<input v-model="message" placeholder="输入消息" />
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
export default defineComponent({
name: 'RefSemanticExample',
setup() {
const message = ref<string>('Hello Vue 3');
const updateMessage = (newMessage: string) => {
message.value = newMessage;
};
return {
message,
updateMessage,
};
},
});
</script>
ref与reactive的原理解析
ref的实现原理
ref本质上是对基本类型数据的包装,通过在内部使用Proxy来实现响应式。每当.value被访问或修改时,都会触发依赖追踪和更新。
import { track, trigger } from './effect';
export function ref<T>(initialValue: T) {
let _value = initialValue;
const r = {
get value() {
track(r, 'get', 'value');
return _value;
},
set value(newVal) {
if (newVal !== _value) {
_value = newVal;
trigger(r, 'set', 'value');
}
},
};
return r;
}
reactive的实现原理
reactive通过Proxy对整个对象进行代理,深度劫持其所有属性。当任意属性被访问或修改时,都会触发相应的依赖追踪和更新。
import { track, trigger } from './effect';
export function reactive<T extends object>(target: T): T {
return new Proxy(target, {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver);
track(target, 'get', key);
return typeof result === 'object' && result !== null ? reactive(result) : result;
},
set(target, key, value, receiver) {
const oldValue = (target as any)[key];
const result = Reflect.set(target, key, value, receiver);
if (oldValue !== value) {
trigger(target, 'set', key);
}
return result;
},
});
}
区别总结
- 作用范围:
ref适用于基本类型和需要明确引用的场景。reactive适用于复杂的对象和数组结构。
- 响应式追踪:
ref只追踪.value的变化。reactive追踪对象所有属性的变化。
- 类型推断:
ref更适合与TypeScript结合,提供更好的类型检查。reactive在深层对象中类型推断可能较为复杂。
详细代码示例
以下是一个综合示例项目,展示了在不同场景下使用ref和reactive的区别和优势。
项目结构
src/
├── components/
│ ├── RefCounter.vue
│ ├── ReactiveCounter.vue
│ ├── RefVsReactive.vue
├── services/
│ └── dataService.ts
├── store/
│ ├── useCounterStore.ts
├── App.vue
└── main.ts
1. 使用ref实现计数器
<template>
<div>
<h2>Ref计数器</h2>
<p>计数值:{{ count }}</p>
<button @click="increment">增加</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
export default defineComponent({
name: 'RefCounter',
setup() {
const count = ref<number>(0);
const increment = () => {
count.value++;
};
return {
count,
increment,
};
},
});
</script>
<style scoped>
h2 {
color: #42b983;
}
</style>
2. 使用reactive实现计数器
<template>
<div>
<h2>Reactive计数器</h2>
<p>计数值:{{ state.count }}</p>
<button @click="increment">增加</button>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive } from 'vue';
export default defineComponent({
name: 'ReactiveCounter',
setup() {
const state = reactive({
count: 0,
});
const increment = () => {
state.count++;
};
return {
state,
increment,
};
},
});
</script>
<style scoped>
h2 {
color: #35495e;
}
</style>
3. 对比ref与reactive
<template>
<div>
<RefCounter />
<ReactiveCounter />
<hr />
<h2>Ref与Reactive对比</h2>
<p>Ref count: {{ refCount }}</p>
<p>Reactive count: {{ reactiveState.count }}</p>
<button @click="incrementBoth">同时增加</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, reactive } from 'vue';
import RefCounter from './RefCounter.vue';
import ReactiveCounter from './ReactiveCounter.vue';
export default defineComponent({
name: 'RefVsReactive',
components: {
RefCounter,
ReactiveCounter,
},
setup() {
const refCount = ref<number>(0);
const reactiveState = reactive({
count: 0,
});
const incrementBoth = () => {
refCount.value++;
reactiveState.count++;
};
return {
refCount,
reactiveState,
incrementBoth,
};
},
});
</script>
<style scoped>
h2 {
color: #ff9800;
}
hr {
margin: 20px 0;
}
</style>
4. 数据服务
import { ref } from 'vue';
export function useDataService() {
const data = ref<string>('初始数据');
const updateData = (newData: string) => {
data.value = newData;
};
return {
data,
updateData,
};
}
5. 全局状态管理(使用ref)
import { ref } from 'vue';
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', () => {
const count = ref<number>(0);
const increment = () => {
count.value++;
};
return {
count,
increment,
};
});
6. 应用主组件
<template>
<div id="app">
<h1>Vue 3 `ref` vs `reactive` 示例</h1>
<RefVsReactive />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import RefVsReactive from './components/RefVsReactive.vue';
export default defineComponent({
name: 'App',
components: {
RefVsReactive,
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
margin-top: 60px;
}
</style>
7. 主入口文件
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
const app = createApp(App);
app.use(createPinia());
app.mount('#app');
扩展示例:表单处理与状态管理
表单组件(使用ref)
<template>
<div>
<h2>Ref表单</h2>
<form @submit.prevent="handleSubmit">
<label for="name">姓名:</label>
<input id="name" v-model="name.value" type="text" />
<br/>
<label for="age">年龄:</label>
<input id="age" v-model.number="age.value" type="number" />
<br/>
<button type="submit">提交</button>
</form>
<p>提交的数据:{{ submittedData }}</p>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
interface FormData {
name: string;
age: number;
}
export default defineComponent({
name: 'RefForm',
setup() {
const name = ref<string>('');
const age = ref<number>(0);
const submittedData = ref<FormData | null>(null);
const handleSubmit = () => {
submittedData.value = {
name: name.value,
age: age.value,
};
// 重置表单
name.value = '';
age.value = 0;
};
return {
name,
age,
submittedData,
handleSubmit,
};
},
});
</script>
<style scoped>
h2 {
color: #8e44ad;
}
label {
display: inline-block;
width: 50px;
}
input {
margin-bottom: 10px;
}
</style>
状态管理组件(使用ref与Pinia)
<template>
<div>
<h2>Pinia计数器</h2>
<p>全局计数值:{{ counterStore.count }}</p>
<button @click="counterStore.increment">增加</button>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { useCounterStore } from '../store/useCounterStore';
export default defineComponent({
name: 'CounterStore',
setup() {
const counterStore = useCounterStore();
return {
counterStore,
};
},
});
</script>
<style scoped>
h2 {
color: #2ecc71;
}
</style>
结论
通过以上详细的原理解析与代码示例,可以看到在Vue 3中使用ref相比于reactive具有以下优势:
- 更精确的响应性追踪:避免不必要的依赖更新,提高性能。
- 更好的类型推断:尤其在使用TypeScript时,
ref能够提供更准确的类型检查。 - 避免响应式对象的泄漏:减少内存消耗,提升应用性能。
- 更清晰的语义:
.value的使用使代码更具可读性和可维护性。 - 更好的组合性:与其他组合式API如
computed和watch更好地协同工作。
尽管reactive在处理复杂对象时非常有用,但在很多情况下,ref提供了更为简洁和高效的解决方案。因此,推荐在适当的场景下优先考虑使用ref。