本文是系列文章的一部分:框架实战指南 - 基础知识
我们之前在本书中讨论过如何将值作为属性传递给组件:
<!-- FileDate.vue -->
<script setup>// ...
const props = defineProps(["inputDate"]);const dateStr = ref(formatDate(props.inputDate));const labelText = ref(formatReadableDate(props.inputDate));// ...</script><template> <span :aria-label="labelText">{{ dateStr }}</span></template>
formatDate你可能会注意到,我们从同一个属性派生了两个值。起初这工作正常,但当我们意识到和formatReadableDate方法在初始渲染期间只运行一次时,问题就出现了。
因此,如果我们将更新传递inputDate给组件,和FileDate的值将与父级传递的不同步。formatDate``formatReadableDate``inputDate
<!-- File.vue --><script setup>import { ref, onMounted, onUnmounted } from "vue";import FileDate from "./FileDate.vue";// ...const inputDate = ref(new Date());const interval = ref(null);onMounted(() => { // Check if it's a new day every 10 minutes interval.value = setInterval( () => { const newDate = new Date(); if (inputDate.value.getDate() === newDate.getDate()) return; inputDate.value = newDate; }, 10 * 60 * 1000, );});onUnmounted(() => { clearInterval(interval.value);});</script><template> <!-- ... --> <!-- This may not show the most up-to-date `formatDate` or `formatReadableDate` --> <FileDate v-if="isFolder" :inputDate="inputDate" /> <!-- ... --></template>
虽然上述组件正确File更新,但我们的组件从未监听改变的输入值,因此从未重新计算或值以显示给用户。inputDate``FileDate``formatDate``formatReadableDate
我们怎样才能解决这个问题?
方法一:道具聆听
解决属性值和显示值之间差异的第一个方法(可以说是最容易在脑海中建模的)是简单地监听属性值何时更新并重新计算显示值。
幸运的是,我们可以利用现有的副作用知识来做到这一点:
<!-- FileDate.vue -->
<script setup>import { ref, watch } from "vue";// ...const props = defineProps(["inputDate"]);const dateStr = ref(formatDate(props.inputDate));const labelText = ref(formatReadableDate(props.inputDate));watch( () => props.inputDate, (newDate, oldDate) => { dateStr.value = formatDate(newDate); labelText.value = formatReadableDate(newDate); },);</script><template> <span :aria-label="labelText">{{ dateStr }}</span></template>
Vue 的watch逻辑允许您根据其键跟踪给定属性或状态值的变化。
在这里,我们正在观察inputDateprops 键,当它发生变化时,dateStr我们labelText会根据新的属性值进行更新。
虽然这种方法有效,但它往往会引入重复的开发逻辑。例如,请注意我们必须重复声明dateStr和labelText值两次:一次是在初始定义时,另一次是在属性监听器中。
幸运的是,这个问题有一个简单的解决方案,称为“计算值”。
方法 2:计算值
我们以前从属性获取值的方法遵循两个步骤:
- 设置初始值
- 当基数改变时更新并重新计算值
然而,如果我们可以将这个想法简化为一个步骤呢?
- 对某个值运行一个函数,并在其发生变化时进行实时更新。
这可能会让您想起我们已经用于实时更新文本和属性绑定的类似模式。
幸运的是,这三个框架都有办法做到这一点!
<!-- FileDate.vue --><script setup>import { computed } from "vue";// ...const props = defineProps(["inputDate"]);const dateStr = computed(() => formatDate(props.inputDate));const labelText = computed(() => formatReadableDate(props.inputDate));</script><template> <span :aria-label="labelText">{{ dateStr }}</span></template>
我们不需要使用ref来构造一组变量,然后在我们watcha之后重新初始化这些值,而是可以简单地告诉 Vue 使用propsprop为我们执行相同的过程。computed
Vue 能够 ✨ 神奇地 ✨ 检测computed函数内部哪些数据是动态的,就像 一样watchEffect。当这些动态数据发生变化时,它会自动使用内部函数返回的新值重新初始化赋值的变量。
computed然后可以通过与data属性相同的方式从模板和 Vue 中访问这些props <script>。
非 Prop 派生值
虽然我们今天主要使用组件输入来演示派生值,但到目前为止我们使用的两种方法都适用于内部组件状态和输入。
考虑支持
假设我们number在组件中有一个状态,并且想要显示此属性的双倍值,而不将此状态传递给新组件:
<!-- CountAndDouble.vue -->
<script setup>import { ref, computed } from "vue";const number = ref(0);function addOne() { number.value++;}const doubleNum = computed(() => number.value * 2);</script><template> <div> <p>{{ number }}</p> <p>{{ doubleNum }}</p> <button @click="addOne()">Add one</button> </div></template>
在这个组件中,我们可以看到两个数字——一个数字将另一个数字的值加倍。然后我们有一个按钮,可以让我们增加第一个数字,因此,使用派生值,第二个数字也会更新。
挑战
在构建我们的持续文件托管应用程序时,让我们思考一下如何Size计算以显示在 UI 中,如下所示:
文件大小通常以存储文件所需的字节数来衡量。然而,超过一定大小后,这些信息就没什么用了。让我们使用以下 JavaScript 代码,根据字节数计算文件大小:
const kilobyte = 1024;const megabyte = kilobyte * 1024;const gigabyte = megabyte * 1024;function formatBytes(bytes) { if (bytes < kilobyte) { return `${bytes} B`; } else if (bytes < megabyte) { return `${Math.floor(bytes / kilobyte)} KB`; } else if (bytes < gigabyte) { return `${Math.floor(bytes / megabyte)} MB`; } else { return `${Math.floor(bytes / gigabyte)} GB`; }}
在家挑战一下你的代码——你能用更少的代码写出上面的代码吗?🤔
通过这段 JavaScript,我们可以使用派生值来显示相应的显示尺寸。让我们使用专用组件来实现这一点DisplaySize:
<!-- DisplaySize.vue --><script setup>import { computed } from "vue";const props = defineProps(["bytes"]);const humanReadableSize = computed(() => formatBytes(props.bytes));const kilobyte = 1024;const megabyte = kilobyte * 1024;const gigabyte = megabyte * 1024;function formatBytes(bytes) { if (bytes < kilobyte) { return `${bytes} B`; } else if (bytes < megabyte) { return `${Math.floor(bytes / kilobyte)} KB`; } else if (bytes < gigabyte) { return `${Math.floor(bytes / megabyte)} MB`; } else { return `${Math.floor(bytes / gigabyte)} GB`; }}</script><template> <p>{{ humanReadableSize }}</p></template>