Insights on Using Arrays with ref() and reactive()
As I delved into learning the composition API while developing a basic todo list application, I encountered challenges related to handling arrays using ref()
and reactive()
. Through this experience, I gained insights that could be beneficial for others exploring the composition API. Hence, I decided to jot down my thoughts here. Feel free to provide feedback if you spot any errors!
1. Challenges Encountered When Using reactive() with Arrays
Initially, everything seemed to work seamlessly until I started working on the delete function.
I attempted to create a button that would invoke the deleteHandler
function upon being clicked. This function was supposed to filter out elements from the todos
array:
This is an excerpt of my code:
<template>
<div>
<h1>Using reactive</h1>
<button @click="add">Click</button>
<div v-for="item in todos" :key="item">
<button @click="mark(item)">Mark</button>
<span>{{item}}</span>
<button @click="deleteHandler(item.id)">Delete</button>
</div>
</div>
</template>
<script>
import { reactive, ref } from "vue";
export default {
name: "ReactiveMethod",
setup(){
let todos = reactive([])
const id = ref(0);
function add(){
todos.push({id:id.value, name:"hallo", state:"undone"});
id.value += 1
}
function mark(item){
if(item.state === "undone"){
item.state = "done";
}else{
item.state = "undone";
}
}
function deleteHandler(id){
const temp = todos.filter((element) => {
return element.id !== id
});
todos = temp;
}
return {
todos,
id,
deleteHandler,
add,
mark
};
}
}
</script>
However, a crucial issue arose as the filter
function did not mutate the original value but instead returned a new value. Consequently, Vue failed to detect changes within the todos
array.
To address this challenge, I made a modification to my code. Instead of assigning todos
as reactive([])
, I encapsulated the array within an object like so -> reactive({ todos: [] })
. This adjustment resolved the problem!
<template>
<div>
<h1>Using reactive</h1>
<button @click="add">Click</button>
<div v-for="item in todos" :key="item">
<button @click="mark(item)">Mark</button>
<span>{{item}}</span>
<button @click="deleteHandler(item.id)">Delete</button>
</div>
</div>
</template>
<script>
import { reactive, ref, toRefs } from "vue";
export default {
name: "ReactiveMethod",
setup(){
const state = reactive({
todos: []
});
const id = ref(0);
function add(){
state.todos.push({ id: id.value, name: "hallo", state: "undone" });
id.value += 1;
}
function mark(item){
if(item.state === "undone"){
item.state = "done";
} else {
item.state = "undone";
}
}
function deleteHandler(id){
const temp = state.todos.filter((element) => {
return element.id !== id;
});
state.todos = temp;
}
return {
...toRefs(state),
id,
deleteHandler,
add,
mark
};
}
}
</script>
Conclusion
It appears that Vue can only observe changes with the same reference (objects in JavaScript are called by reference), and cannot detect changes when the reference itself is altered. Thus, I believe that "wrapping the array inside an object" presents a more effective approach to dealing with arrays in the composition API.
2. Usage of ref() for Primitive and Reactive Values
Based on prevalent information, the general consensus seems to advocate for:
ref() for primitive values and reactive() for object values
Nevertheless, even if we write code in the following manner, Vue is still capable of detecting changes within it:
const obj = ref({ name: "charles" });
return {
...toRefs(obj)
}
The rationale behind this lies in the fact that when data is passed into ref()
, it first verifies whether the data is primitive or an object. If it's an object, ref()
calls upon reactive()
to handle it. In essence, reactive()
is the one actually undertaking the task behind the scenes.
Final Thoughts
At present, it seems feasible to use ref()
across various scenarios. Nonetheless, I opine that it's preferable to utilize reactive()
for objects and ref()
for primitives to maintain clear distinctions! (If you have any insights regarding this matter, do share them with me!)