现在,当我写这篇文章时,我们离Vue 3的发布越来越近。我认为,最有趣的事情是观察其他开发人员如何看待和使用它。在过去的几个月中,我有机会试用Vue 3,但我知道有些人还没有。
新版本中最重要的创新是Composition API。它提供了一种创建组件的替代方法,与现有的Options API有很大不同。我不难承认,当我第一次见到他时,我并不理解他。但是,随着它的应用,含义开始出现。您可能不会使用Composition API重写整个应用程序,但是本文将为您提供一个思考组件创建和组合如何工作的机会。
我最近做了几个演示,一个常见的问题是何时使用Ref和何时使用反应性来声明反应性。我没有一个好的答案,花了几周时间才找到答案,这篇文章是我研究的结果。
此外,我想指出的是,以上是我的观点,请不要认为“仅通过这种方式,而不必这样做”是必要的。我计划以这种方式使用Ref和Reactive,直到有人提出其他建议,或者直到我自己找出更好的方法为止。我认为了解任何新技术都需要花费一些时间,然后可能会出现经过验证的技术。
在继续之前,我假设您至少已经简要地熟悉了成分API并了解其组成。本文着重于ref和react之间的差异,而不是API组成机制。
Vue 2反应性
为了让您快速上手,我将快速介绍如何在Vue 2应用程序中创建反应式属性。当您希望Vue 2跟踪属性的更改时,需要在data函数返回的对象中声明它们。
<template>
<h1>{{ title }}</h1>
</template>
<script>
export default {
data() {
return {
title: "Hello, Vue!"
};
}
};
</script>
在Vue 2的幕后,将为每个属性调用Object.defineProperty()来创建一个获取器和设置器以跟踪更改。这是对该过程的最简单的解释,我想传达一个想法:其中没有魔术。您无法在任何地方创建反应性属性,并且期望Vue跟踪它们的变化。您需要在数据函数中设置反应特性。
REF和ACTIVE
在使用Options API时,在声明反应性时我们需要遵循一些规则,在使用Composition API时也应遵循同样的规则。您不能只是创建一个属性并期望其反应性。在下面的示例中,我声明了title属性,setup()函数将其返回,从而使其可用于模板。
<template>
<h1>{{ title }}</h1>
</template>
<script>
export default {
setup() {
let title = "Hello, Vue 3!";
return { title };
}
};
</script>
这将起作用,但title属性将不会起作用。那些。如果有人更改标题,这些更改将不会反映在众议院中。例如,如果您在5秒钟后更改标题,则下面的代码将不会更改首页。
<template>
<h1>{{ title }}</h1>
</template>
<script>
export default {
setup() {
let title = "Hello, Vue 3!";
setTimeout(() => {
title = "THIS IS A NEW TITLE";
}, 5000);
return { title };
}
};
</script>
我们可以导入ref并将其用于使该属性具有反应性。在后台,Vue 3将创建一个Proxy。
<template>
<h1>{{ title }}</h1>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
const title = ref("Hello, Vue 3!");
setTimeout(() => {
// , .value ...
//
title.value = "New Title";
}, 5000);
return { title };
}
};
</script>
我想澄清一下,关于Ref和Reactive,我认为有两种不同的情况。首先是像上面的示例一样创建一个组件,您需要使用反应特性。第二个是创建允许在组件和功能中使用合成的功能时。我们将在本文中讨论这两种情况。
参考
创建简单类型的属性时,ref()是您的首选。这不是灵丹妙药,但值得一开始。让我提醒您JavaScript的七个原始类型:
- 串
- 数
- 大整数
- 布尔型
- 符号
- 空值
- 未定义
import { ref } from "vue";
export default {
setup() {
const title = ref("");
const one = ref(1);
const isValid = ref(true);
const foo = ref(null);
}
};
在前面的示例中,标题为String类型,因此为了使该属性具有反应性,我们选择ref()。如果下面的代码使您不担心某些问题,那么我也有同样的问题。
import { ref } from "vue";
export default {
setup() {
const title = ref("Hello, Vue 3!");
setTimeout(() => {
title.value = "New Title";
}, 5000);
return { title };
}
};
如果标题会更改,为什么还要使用const?为什么不使用let?如果将标题打印到控制台,则可能希望看到Hello,Vue 3!,但是它将显示一个对象:
{_isRef: true}
value: (...)
_isRef: true
get value: ƒ value()
set value: ƒ value(newVal)
__proto__: Object
ref()函数将接受一个参数,并返回一个反应性和可变的ref对象。Ref对象具有一个属性,值,它引用参数。这意味着,如果要获取或更改值,则必须使用title.value,并且由于这是一个不会更改的对象,因此我将其声明为const。
致电REF
下一个问题是为什么我们不在模板中调用title.value?
<template>
<h1>{{ title }}</h1>
</template>
当Ref作为呈现上下文中的属性(由setup()函数返回的对象)作为属性返回并在模板中被引用时,Ref将自动返回值。您无需在模板中添加.value。
在setup()函数内部,计算属性的工作方式相同,将它们称为.value。
反应性的
我们刚刚看到了如何使用ref()使简单类型的属性具有反应性。如果我们想创建一个反应性对象怎么办?我们仍然可以使用ref(),但是在Vue的幕后,我将使用react(),因此我将坚持使用react()。
另一方面,react()不适用于基本类型。react()函数接受一个对象并返回原始对象的反应代理。这等效于Vue 2中的.observable(),并且此函数名称已更改,以避免与RxJS中的observable混淆。
import { reactive } from "vue";
export default {
setup() {
const data = reactive({
title: "Hello, Vue 3"
});
return { data };
}
};
主要区别在于我们如何引用模板中的反应对象。在前面的示例中,数据是一个包含标题属性的对象。您需要像这样在模板中引用它-data.title:
<template>
<h1>{{ data.title }}</h1>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
const data = ref({
title: "Hello, Vue 3"
});
return { data };
}
};
</script>
组件中REF和REACTIVE之间的区别
根据我们所讨论的内容,答案似乎在暗示自己?我们将ref()用于简单类型,将react()用于对象。当我开始构建组件时,事实证明并非总是如此,文档中说:
在某种程度上,使用ref和react之间的区别可以与您用JavaScript编写标准程序逻辑的方式相媲美。
我仔细考虑了这个短语,得出以下结论。随着应用程序的增长,我获得了以下属性:
export default {
setup() {
const title = ref("Hello, World!");
const description = ref("");
const content = ref("Hello world");
const wordCount = computed(() => content.value.length);
return { title, description, content, wordCount };
}
};
在JavaScript中,我将理解这些都是页面的所有属性。在这种情况下,我会将它们分组为一个页面对象,为什么现在不这样做。
<template>
<div class="page">
<h1>{{ page.title }}</h1>
<p>{{ page.wordCount }}</p>
</div>
</template>
<script>
import { ref, computed, reactive } from "vue";
export default {
setup() {
const page = reactive({
title: "Hello, World!",
description: "",
content: "Hello world",
wordCount: computed(() => page.content.length)
});
return { page };
}
};
</script>
这是我对Ref或Reactive的处理方式,但是很高兴知道您的意见。你也一样吗 也许这不是正确的方法?请评论。
合成逻辑
在组件中使用ref()或react()时,您不会出错。这两个函数都将创建反应性数据,并且如果您了解如何在setup()函数和模板中访问它,则没有问题。
当您开始编写合成函数时,您需要了解它们之间的区别。我使用了RFC文档中的示例,其中一些示例很好地说明了细微差别。
您需要创建用于跟踪鼠标位置的逻辑。必要时,您还应该能够在任何组件中使用相同的逻辑。您创建一个合成函数,该函数跟踪x和y坐标,然后将其返回给客户端代码。
import { ref, onMounted, onUnmounted } from "vue";
export function useMousePosition() {
const x = ref(0);
const y = ref(0);
function update(e) {
x.value = e.pageX;
y.value = e.pageY;
}
onMounted(() => {
window.addEventListener("mousemove", update);
});
onUnmounted(() => {
window.removeEventListener("mousemove", update);
});
return { x, y };
}
如果要在组件中使用此逻辑并调用此函数,请对返回的对象进行解构,然后将x和y坐标返回到模板。
<template>
<h1>Use Mouse Demo</h1>
<p>x: {{ x }} | y: {{ y }}</p>
</template>
<script>
import { useMousePosition } from "./use/useMousePosition";
export default {
setup() {
const { x, y } = useMousePosition();
return { x, y };
}
};
</script>
这将起作用,但是如果在查看该函数之后,您决定重构并返回一个位置对象而不是x和y:
import { ref, onMounted, onUnmounted } from "vue";
export function useMousePosition() {
const pos = {
x: 0,
y: 0
};
function update(e) {
pos.x = e.pageX;
pos.y = e.pageY;
}
// ...
}
这种方法的问题在于,组合功能的客户端必须始终具有对返回对象的引用,以使反应性得以持久。这意味着我们不能破坏对象或应用散布运算符:
//
export default {
setup() {
// !
const { x, y } = useMousePosition();
return {
x,
y
};
// !
return {
...useMousePosition()
};
//
// `pos` , x y : `pos.x` `pos.y`
return {
pos: useMousePosition()
};
}
};
但这并不意味着在这种情况下我们不能使用反应式。有一个toRefs()函数,可将反应对象转换为简单对象,其每个属性都是原始对象对应属性的ref。
function useMousePosition() {
const pos = reactive({
x: 0,
y: 0
});
// ...
return toRefs(pos);
}
// x & y ref!
const { x, y } = useMousePosition();
因此,创建合成函数时需要考虑一些方面。如果您了解客户端代码如何使用您的函数,那么一切都会好起来的。
总
当我第一次开始使用Composition API时,我对何时应用ref()和何时应用react()的问题感到困惑。也许我还是做得不好,除非有人告诉我,否则我将坚持这种方法。希望有助于澄清一些问题,并希望听到您的反馈。
快乐编码