框架实战指南-派生值

86 阅读4分钟

本文是系列文章的一部分:框架实战指南 - 基础知识

我们之前在本书中讨论过如何将值作为属性传递给组件:

<!-- 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会根据新的属性值进行更新。

虽然这种方法有效,但它往往会引入重复的开发逻辑。例如,请注意我们必须重复声明dateStrlabelText值两次:一次是在初始定义时,另一次是在属性监听器中。

幸运的是,这个问题有一个简单的解决方案,称为“计算值”。

方法 2:计算值

我们以前从属性获取值的方法遵循两个步骤:

  1. 设置初始值
  2. 当基数改变时更新并重新计算值

然而,如果我们可以将这个想法简化为一个步骤呢?

  1. 对某个值运行一个函数,并在其发生变化时进行实时更新。

这可能会让您想起我们已经用于实时更新文本属性绑定的类似模式。

幸运的是,这三个框架都有办法做到这一点!

<!-- 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 中,如下所示:

image.png

文件大小通常以存储文件所需的字节数来衡量。然而,超过一定大小后,这些信息就没什么用了。让我们使用以下 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>