=computed() =ref()
在 Vue 中,computed()
和 ref()
是 Vue 3 组合式 API 的核心工具,它们分别用于 计算属性 和 响应式数据。以下是它们的区别和用法:
1. ref()
作用
- 用于创建响应式的单一数据。
- 可以是基本类型(如字符串、数字、布尔值)或对象类型。
- 当
ref
的值发生变化时,依赖它的组件会自动重新渲染。
用法
javascript">import { ref } from 'vue';const count = ref(0); // 创建一个响应式变量// 修改值
count.value++; // 必须通过 .value 修改或访问 ref 的值console.log(count.value); // 输出: 1
特点
- 基本类型:
ref
会将值包装为一个响应式对象,访问或修改时需要通过.value
。 - 对象类型:如果
ref
包裹的是对象,Vue 会自动将对象的属性变为响应式。
示例
javascript">import { ref } from 'vue';const user = ref({ name: 'Alice', age: 25 }); // 包裹一个对象// 修改对象属性
user.value.name = 'Bob';
console.log(user.value.name); // 输出: Bob
2. computed()
作用
- 用于定义基于其他响应式数据的 派生状态。
computed
的值会根据依赖的响应式数据自动更新。- 适合用来处理需要动态计算的值。
用法
javascript">import { ref, computed } from 'vue';const count = ref(0);// 定义一个计算属性
const doubleCount = computed(() => count.value * 2);console.log(doubleCount.value); // 输出: 0count.value++; // 修改 count 的值
console.log(doubleCount.value); // 输出: 2
特点
computed
的值是只读的,不能直接修改。- 如果需要可写的计算属性,可以传入
get
和set
方法。
可写计算属性
javascript">import { ref, computed } from 'vue';const count = ref(0);// 可写计算属性
const doubleCount = computed({get: () => count.value * 2,set: (newValue) => {count.value = newValue / 2; // 反向修改 count}
});doubleCount.value = 10; // 修改 doubleCount
console.log(count.value); // 输出: 5
3. ref()
和 computed()
的区别
特性 | ref() | computed() |
---|---|---|
用途 | 定义响应式的单一数据 | 定义基于其他响应式数据的派生状态 |
是否需要 .value | 是 | 是 |
是否可写 | 是 | 默认不可写(可通过 get 和 set 实现) |
依赖性 | 独立存在,不依赖其他响应式数据 | 依赖其他响应式数据 |
性能 | 直接存储值,简单高效 | 有缓存机制,只有依赖数据变化时才重新计算 |
4. 综合示例
以下是一个同时使用 ref()
和 computed()
的示例:
javascript"><script setup>
import { ref, computed } from 'vue';// 定义响应式数据
const price = ref(100);
const quantity = ref(2);// 定义计算属性
const total = computed(() => price.value * quantity.value);// 修改响应式数据
price.value = 150;console.log(total.value); // 输出: 300
</script><template><div><p>单价: {{ price }}</p><p>数量: {{ quantity }}</p><p>总价: {{ total }}</p></div>
</template>
5. 在 Options API 中的等价用法
在 Vue 3 的 Options API 中,ref()
和 computed()
的功能可以通过 data
和 computed
选项实现:
等价代码
javascript"><script>
export default {data() {return {price: 100, // 等价于 ref(100)quantity: 2 // 等价于 ref(2)};},computed: {total() {return this.price * this.quantity; // 等价于 computed(() => price.value * quantity.value)}}
};
</script><template><div><p>单价: {{ price }}</p><p>数量: {{ quantity }}</p><p>总价: {{ total }}</p></div>
</template>
6. 总结
ref()
:用于定义响应式的单一数据,适合存储基本类型或对象。computed()
:用于定义基于其他响应式数据的派生状态,具有缓存机制。- 组合使用:
ref()
定义基础数据,computed()
定义基于基础数据的动态值。
case 2
javascript">
<script setup>
import { ref, computed } from 'vue'; // 原始数据
const data = ref([ { position: { x: 1, y: 2 } }, { position: { x: 3, y: 4 } }, { position: { x: 5, y: 6 } }
]); // 数据转换函数
const convertData = (sourceData) => { try { const paths = sourceData .filter(item => item?.position && typeof item.position === 'object') .map(item => item.position); return [{ paths: paths }]; } catch (error) { console.error('数据转换错误:', error); return [{ paths: [] }]; }
}; // 计算属性
const convertTodata = computed(() => convertData(data.value)); // 更新数据的方法
const updateData = (newData) => { data.value = newData;
};
</script> <template> <div class="p-4"> <h2 class="text-xl mb-4">转换后的数据:</h2> <pre class="bg-gray-100 p-4 rounded"> {{ JSON.stringify(convertTodata, null, 2) }} // 使用 JSON.stringify 将 convertTodata 的结果格式化为 JSON 字符串并显示在页面</pre> </div>
</template>
javascript">
<script>
export default { name: 'DataConverter', // ref() -> data() data() { return { // const data = ref([...]) 变成: data: [ { position: { x: 1, y: 2 } }, { position: { x: 3, y: 4 } }, { position: { x: 5, y: 6 } } ] } }, // computed() -> computed: {} computed: { // const convertTodata = computed(() => ...) 变成: convertTodata() { return this.convertData(this.data); } }, methods: { convertData(sourceData) { try { const paths = sourceData .filter(item => item?.position && typeof item.position === 'object') .map(item => item.position); return [{ paths: paths }]; } catch (error) { console.error('数据转换错误:', error); return [{ paths: [] }]; } }, // const updateData = (newData) => { data.value = newData } 变成: updateData(newData) { this.data = newData; } }
};
</script> <template> <div class="p-4"> <h2 class="text-xl mb-4">转换后的数据:</h2> <pre class="bg-gray-100 p-4 rounded"> {{ JSON.stringify(convertTodata, null, 2) }} </pre> </div>
</template> <style scoped>
.p-4 { padding: 1rem;
} .text-xl { font-size: 1.25rem; line-height: 1.75rem;
} .mb-4 { margin-bottom: 1rem;
} .bg-gray-100 { background-color: #f3f4f6;
} .rounded { border-radius: 0.25rem;
}
</style>
主要转换规则
-
ref() 转换:
javascript">// Composition API const data = ref([...]) // Options API data() { return { data: [...] } }
-
computed() 转换:
javascript">// Composition API const result = computed(() => {...}) // Options API computed: { result() { return {...} } }
-
方法转换:
javascript">// Composition API const updateData = (newData) => { data.value = newData } // Options API methods: { updateData(newData) { this.data = newData } }
-
数据访问:
javascript">// Composition API data.value // Options API this.data
=computed() 能改为 =ref() 吗
在 Vue 3 中,computed()
和 ref()
是不同的工具,虽然它们都可以创建响应式数据,但它们的用途和行为不同。computed()
不建议直接改为 ref()
,因为它们的核心区别在于:
ref()
是用于存储和管理响应式的单一数据。computed()
是用于基于其他响应式数据派生出新的值,并且具有 缓存机制。
如果你想用 ref()
替代 computed()
,需要手动实现类似 computed()
的功能,但这通常不推荐,因为 computed()
已经为你处理了依赖追踪和缓存。=ref()
会丢失 computed()
的依赖追踪和缓存特性。
什么时候可以用 ref()
替代 computed()
?
如果你的计算逻辑非常简单,且不需要依赖缓存机制,可以用 ref()
和 watchEffect()
或 watch()
来实现类似的功能。
如何用 ref()
替代 computed()
?
以下是一个示例,展示如何用 ref()
和 watchEffect()
替代 computed()
:
原始代码:使用 computed()
javascript">import { ref, computed } from 'vue';const count = ref(0);
const doubleCount = computed(() => count.value * 2);console.log(doubleCount.value); // 输出: 0count.value++;
console.log(doubleCount.value); // 输出: 2
改为使用 ref()
和 watchEffect()
javascript">import { ref, watchEffect } from 'vue';const count = ref(0);
const doubleCount = ref(0);// 使用 watchEffect 手动更新 doubleCount
watchEffect(() => {doubleCount.value = count.value * 2;
});console.log(doubleCount.value); // 输出: 0count.value++;
console.log(doubleCount.value); // 输出: 2
对比分析
特性 | computed() | ref() + watchEffect() | watch |
---|---|---|---|
依赖追踪 | 自动追踪依赖 | 否, 使用 watchEffect 手动更新值 | 否, 使用 watch 手动更新值 |
缓存机制 | 有缓存,每次使用时使用的预先计算存储的的 cache,依赖变化时重新计算, 使用时使用的重新计算存储的的 new cache | 没有缓存,每次使用时重新计算,依赖变化时重新计算 | 否, 每次 data(){} 变化都会重新执行 |
代码复杂度 | 简洁,直接定义计算逻辑 | 需要手动更新值,代码稍显冗长 | |
适用场景 | 适合派生状态,依赖多个响应式数据 | 适合简单逻辑或不需要缓存的场景 |
完整示例:从 computed()
改为 ref()
假设你有一个组件,使用 computed()
来计算数据:
原始代码:使用 computed()
javascript"><script setup>
import { ref, computed } from 'vue';const price = ref(100);
const quantity = ref(2);// 计算总价
const total = computed(() => price.value * quantity.value);
</script><template><div><p>单价: {{ price }}</p><p>数量: {{ quantity }}</p><p>总价: {{ total }}</p></div>
</template>
改为使用 ref()
和 watchEffect()
javascript"><script setup>
import { ref, watchEffect } from 'vue';const price = ref(100);
const quantity = ref(2);// 使用 ref 存储总价
const total = ref(0);// 手动更新 total 的值
watchEffect(() => {total.value = price.value * quantity.value;
});
</script><template><div><p>单价: {{ price }}</p><p>数量: {{ quantity }}</p><p>总价: {{ total }}</p></div>
</template>
原始代码:使用 computed()
javascript"><script>
export default { name: 'DataConverter', // ref() -> data() data() { return { // const data = ref([...]) 变成: data: [ { position: { x: 1, y: 2 } }, { position: { x: 3, y: 4 } }, { position: { x: 5, y: 6 } } ] } }, // computed() -> computed: {} computed: { // const convertTodata = computed(() => ...) 变成: convertTodata() { return this.convertData(this.data); } }, methods: { convertData(sourceData) { try { const paths = sourceData .filter(item => item?.position && typeof item.position === 'object') .map(item => item.position); return [{ paths: paths }]; } catch (error) { console.error('数据转换错误:', error); return [{ paths: [] }]; } }, // const updateData = (newData) => { data.value = newData } 变成: updateData(newData) { this.data = newData; } }
};
</script> <template> <div class="p-4"> <h2 class="text-xl mb-4">转换后的数据:</h2> <pre class="bg-gray-100 p-4 rounded"> {{ JSON.stringify(convertTodata, null, 2) }} </pre> </div>
</template>
改为使用 ref()
和 watchEffect()
javascript"><template> <div class="p-4"> <h2 class="text-xl mb-4">转换后的数据:</h2> <pre class="bg-gray-100 p-4 rounded"> {{ JSON.stringify(convertTodata, null, 2) }} </pre> </div>
</template> <script>
export default { data() { return { // 原始数据 data1: [ { position: { x: 1, y: 2 } }, { position: { x: 3, y: 4 } }, { position: { x: 5, y: 6 } } ],irrelevantData: 'something', // 无关数据 // 将 computed 改为 data 中的属性 // 移除了 computed 属性:原来的计算属性被转换为 data 中的普通响应式数据// 无自动追踪依赖,需watch 无缓存机制 不是只有依赖变化时( data1.value 变化时)才重新计算,每次 data ( 指总的 data(){},例如 irrelevantData change ) 变化都会执行convertTodata: [{ paths: [] }],} }, // 监听 data ( 指总的 data(){},例如 irrelevantData change )的变化,更新 convertTodata watch: { data: { // 指总的 data(){}immediate: true, // 确保初始化时执行一次,立即执行一次 handler(newData) { // 当 data 变化时更新 convertTodata this.convertTodata = this.convertData(newData); } } }, methods: { convertData(sourceData) { try { const paths = sourceData .filter(item => item?.position && typeof item.position === 'object') .map(item => item.position); return [{ paths: paths }]; } catch (error) { console.error('数据转换错误:', error); return [{ paths: [] }]; } }, updateData(newData) { this.data = newData; } }
}
</script>
注意事项
-
性能问题:
computed()
有缓存机制,只有依赖的数据发生变化时才会重新计算。ref()
+watchEffect()
每次依赖变化都会重新执行逻辑,可能会带来性能开销。
-
代码简洁性:
computed()
更加简洁,适合大多数场景。ref()
+watchEffect()
需要手动更新值,代码稍显冗长。
-
推荐使用场景:
- 如果你的逻辑依赖多个响应式数据,并且需要缓存,优先使用
computed()
。 - 如果你的逻辑非常简单,或者不需要缓存,可以使用
ref()
+watchEffect()
。
- 如果你的逻辑依赖多个响应式数据,并且需要缓存,优先使用
总结
computed()
是首选:它更简洁、性能更好,适合大多数场景。ref()
替代computed()
:可以通过watchEffect()
手动实现,但代码复杂度会增加,且没有缓存机制。
参考:
=ref() =computed()