英文:
Vue.js 3 vs Vue.js 2 reactivity differences on simple example
问题
我正在将一个项目从Vue2迁移到Vue3,遇到了一个关于动态更改对象不在模板中反映的问题。
例如,我有这个类TestClass,其中有一个名为duration的数字属性。在构造函数中,我每秒递增这个持续时间。这是这个类:
export default class TestClass {
constructor(data) {
this.name = data.name;
this.duration = data.duration;
setInterval(() => {
++this.duration;
}, 1000);
}
}
这应该在创建类时启动计时器。然而,当我在组件中使用该类时,除了初始设置值之外,我看不到任何更改。
这是我使用TestClass的TestComponent:
export default {
name: "TestComponent",
data() {
return {
testObject: new TestClass({ name: "Test Class", duration: 5 }),
};
},
computed: {
time() {
return this.testObject.duration;
},
},
};
与Vue2不同,Vue3中的更改总是保持在初始值(在本例中为5)。我的问题是,如何在这种情况下实现与Vue2相同的行为?我知道在2和3之间有响应性的变化。
英文:
I have a project which i'm migrating from Vue2 to Vue3. I came upon this issue with dynamic changes to objects not reflecting in the template.
For example, i have this class TestClass which has a number property duration. In the constructor I'm incrementing this duration every second. This is the class:
export default class TestClass {
constructor(data) {
this.name = data.name;
this.duration = data.duration;
setInterval(() => {
++this.duration;
}, 1000);
}
}
This is supposed to start the timer once the class is created with constructor call. However I don't see any changes when I use the class in a component, except from the initial setting of value.
This is my TestComponent in which I use the TestClass:
export default {
name: "TestComponent",
data() {
return {
testObject: new TestClass({ name: "Test Class", duration: 5 }),
};
},
computed: {
time() {
return this.testObject.duration;
},
},
};
<template>
<span> Class time: {{ time }}</span>
</template>
In contrast to Vue2 where changes would be displayed in the template, in Vue3 it always stays on inital value (this case 5). My question is, how to achieve same behavior as in Vue2 for this case? I'm aware that there are changes to reactivity between 2 and 3.
答案1
得分: 3
你需要使用 ref()
或 reactive()
函数来创建响应性,并且在 Vue 3 中不再使用面向对象编程(OOP)。尽管选项 API 仍在工作,但你必须记住 data()
中的值类似于 ref()
,为了实现响应性,你需要每次为 data()
函数中的每个变量分配新值。在你的代码中,你只做了一次,然后改变了不具有响应性的内部对象值。
以下是如何在组合式 API 中实现的示例。
可组合的闭包函数 composable/timer.js
export function useTimer(data) {
const name = ref(data.name)
const duration = ref(data.duration)
setInterval(() => {
++duration.value;
}, 1000);
return {name, duration}
}
组件
<script setup>
const { name, duration } = useTimer(data)
</script>
<template>
<span> 闭包函数 {{ name }} 时间: {{ duration }}</span>
</template>
英文:
You need to use ref()
or reactive()
functions to create reactivity. And forget about OOP in Vue 3. Well, option API is still working, but you have to remember that data()
values works like ref()
to achieve reactivity you need to every time assign new value to each variable in data()
function. In your code, you do it once, and then change inner object values that are not reactive.
Here is an example how to do it in Composition API.
Composable closure function composable/timer.js
export function useTimer(data) {
const name = ref(data.name)
const duration = ref(data.duration)
setInterval(() => {
++duration.value;
}, 1000);
return {name, duration}
}
Component
<script setup>
const { name, duration } = useTimer(data)
</script>
<template>
<span> Closure function {{ name }} time: {{ duration }}</span>
</template>
答案2
得分: 2
在Vue 3中,响应式变量被包装在代理对象中。 testObject
不包含TestClass的实例,它是该实例的代理。
增加 duration
发生在对象内部,而不是通过代理,因此不会检测到任何响应性变化。我个人建议使用Composition API中的可组合式路线,正如其他答案中所提到的,但如果您想继续使用Options API,一个解决方法是将异步操作移到构造函数之外(这本来就是反模式),然后从组件的created钩子中调用setInterval函数。
export default class TestClass {
constructor(data) {
this.name = data.name;
this.duration = data.duration;
}
init() {
setInterval(() => {
++this.duration;
}, 1000);
}
}
export default {
name: 'Test',
data() {
return {
testObject: new TestClass({ name: 'Test Class', duration: 5 })
};
},
computed: {
time() {
return this.testObject.duration;
}
},
created() {
this.testObject.init();
}
};
英文:
In Vue 3, reactive variables are wrapped in a Proxy object. testObject
then does not contain the TestClass instance, it is a proxy of that instance.
Incrementing duration
happens inside the object, not through the proxy so no reactivity change is detected. I would personally go the composable route with Composition API as noted in the other answer, but if you want to stay with Options API a workaround would be to move the asychronous action outside of the constructor (which is an antipattern anyways) and call the setInterval function from the component's created hook.
export default class TestClass {
constructor(data) {
this.name = data.name;
this.duration = data.duration;
}
init() {
setInterval(() => {
++this.duration;
}, 1000);
}
}
export default {
name: 'Test',
data() {
return {
testObject: new TestClass({ name: 'Test Class', duration: 5 })
};
},
computed: {
time() {
return this.testObject.duration;
}
},
created() {
this.testObject.init();
}
};
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论