Vue.js Composition API in UniApp: Ref, Reactive, Computed, and Watch – A Hilarious Deep Dive! 🚀
Alright, buckle up, buttercups! We’re diving headfirst into the wonderful world of Vue.js Composition API within the UniApp ecosystem. Forget the stuffy lectures of yesteryear. This is a party! 🎉 We’re going to explore the core concepts of ref, reactive, computed, and watch with enough humor and clarity to make even your grandma (who probably still uses Internet Explorer) understand.
Imagine the Options API (the old way of doing things) as a messy apartment. Your data is in the data section, your methods are in the methods section, your computed properties are in… well, you get the picture. Everything’s scattered and hard to find. The Composition API, on the other hand, is like a meticulously organized Marie Kondo’d apartment. Everything has its place, and related logic is neatly grouped together. Ah, bliss! 😌
So, grab your favorite beverage (mine’s a suspiciously bright green smoothie – don’t ask), and let’s get started!
I. The Setup: UniApp and the Composition API
First things first, let’s make sure we’re all on the same page. UniApp is a framework for building cross-platform apps using Vue.js. This means you can write one codebase and deploy it to iOS, Android, H5 (web), and even mini-programs (WeChat, Alipay, etc.). It’s like having a superpower! 🦸
The Composition API is a set of functions that allows you to organize your Vue component logic in a more logical and reusable way. It’s the new hotness, and for good reason!
How to use Composition API in UniApp?
Thankfully, UniApp supports the Composition API natively. You’ll primarily be working within the <script setup> tag. This is where the magic happens.
<template>
<view>
<h1>Hello, {{ name }}!</h1>
<button @click="increment">Increment Count</button>
<p>Count: {{ count }}</p>
<p>Double Count: {{ doubleCount }}</p>
</view>
</template>
<script setup>
import { ref, reactive, computed, watch } from 'vue'
// Our Composition API logic goes here!
</script>
See that <script setup> tag? That’s your playground. Let’s fill it with fun!
II. ref: Your Basic Reactive Variable (The Tin Can Phone)
Think of ref as a tin can connected to a string. You shout something into one end (update the value), and the other end hears it (the view updates). It’s a simple, yet effective, way to create reactive variables.
<script setup>
import { ref } from 'vue'
const name = ref('World')
const count = ref(0)
const increment = () => {
count.value++ // Access the value through the .value property!
}
</script>
Key takeaways about ref:
- Purpose: Creates a reactive reference to a primitive value (number, string, boolean) or an object.
- Accessing the value: You must use
.valueto get or set the value. This is the #1 gotcha for beginners, so remember it! 🧠 - Usage: Ideal for simple data points that need reactivity.
Why .value?
This might seem weird at first, but there’s a good reason for it. Vue needs to track when the value changes to trigger updates in the view. The .value property acts as a "hook" that allows Vue to do its reactivity magic. Think of it as Vue constantly eavesdropping on the tin can phone line! 🕵️
Example Table: ref in Action
| Scenario | Code Snippet | Explanation |
|---|---|---|
| Initializing a number | const age = ref(30) |
Creates a reactive number with an initial value of 30. |
| Updating a string | const message = ref('Hello'); message.value = 'Goodbye' |
Changes the reactive string from "Hello" to "Goodbye". |
Using ref in the template |
<h1>{{ age }}</h1> |
The template automatically unwraps the ref value, so you don’t need .value here. |
| Resetting a boolean | const isLoggedIn = ref(false); isLoggedIn.value = true |
Sets the reactive boolean to true. |
III. reactive: For Complex Objects (The Intercom System)
If ref is a tin can phone, reactive is a fancy intercom system. It allows you to make entire objects reactive. Any change to a property within the object will trigger updates in the view.
<script setup>
import { reactive } from 'vue'
const user = reactive({
firstName: 'John',
lastName: 'Doe',
age: 30
})
const updateFirstName = () => {
user.firstName = 'Jane' // No .value here!
}
</script>
<template>
<view>
<p>First Name: {{ user.firstName }}</p>
<p>Last Name: {{ user.lastName }}</p>
<p>Age: {{ user.age }}</p>
<button @click="updateFirstName">Update First Name</button>
</view>
</template>
Key takeaways about reactive:
- Purpose: Creates a reactive object.
- Accessing properties: You access properties directly, without
.value. This is a crucial difference fromref. - Usage: Ideal for complex data structures like objects and arrays.
Important Considerations for reactive:
- It’s deep:
reactivemakes all properties of the object reactive, even nested objects. - Replacement is a no-no: Don’t try to replace the entire reactive object with a new object. This will break reactivity. Instead, update the properties within the existing object.
- Use
toRefsif needed: If you need to destructure a reactive object and maintain reactivity, use thetoRefsutility function (more on that later!).
Example Table: reactive in Action
| Scenario | Code Snippet | Explanation |
|---|---|---|
| Initializing a user object | const profile = reactive({ name: 'Alice', city: 'Wonderland' }) |
Creates a reactive user profile object. |
| Updating a nested property | const product = reactive({ details: { price: 10 } }); product.details.price = 20 |
Updates the price property within the nested details object. Reactivity is maintained even with nested properties. |
Using reactive in the template |
<p>{{ profile.name }} lives in {{ profile.city }}</p> |
The template automatically accesses the properties of the reactive object. |
| Adding a new property | const myObject = reactive({}); myObject.newProperty = "Hello" |
You can add new properties to reactive objects after initialization, and they will also be reactive. |
IV. computed: The Smart Assistant (The Calculator)
computed properties are like having a smart assistant who automatically calculates values based on other reactive data. They only update when their dependencies change, making them incredibly efficient.
<script setup>
import { ref, computed } from 'vue'
const price = ref(10)
const quantity = ref(2)
const total = computed(() => {
return price.value * quantity.value
})
</script>
<template>
<view>
<p>Price: {{ price }}</p>
<p>Quantity: {{ quantity }}</p>
<p>Total: {{ total }}</p>
</view>
</template>
Key takeaways about computed:
- Purpose: Creates a read-only reactive value that depends on other reactive values.
- Automatic updates:
computedproperties are automatically updated whenever their dependencies change. - Caching:
computedproperties are cached, meaning they only re-evaluate when their dependencies change. This improves performance. - Read-only by default: You can define a setter function to make a
computedproperty writable, but it’s generally used for derived data.
Why use computed?
Instead of calculating the total directly in your template ({{ price * quantity }}), using a computed property offers several advantages:
- Readability: The logic is encapsulated in a dedicated property, making your template cleaner.
- Reusability: You can reuse the
totalproperty in multiple places in your template. - Performance: As mentioned before, caching prevents unnecessary calculations.
Example Table: computed in Action
| Scenario | Code Snippet | Explanation |
|---|---|---|
| Calculating full name from first and last | const firstName = ref('John'); const lastName = ref('Doe'); const fullName = computed(() => firstName.value + ' ' + lastName.value) |
Creates a fullName property that automatically updates whenever firstName or lastName changes. |
| Formatting a date | const rawDate = ref(new Date()); const formattedDate = computed(() => rawDate.value.toLocaleDateString()) |
Formats a raw date object into a user-friendly string. |
| Filtering an array | const numbers = ref([1, 2, 3, 4, 5]); const evenNumbers = computed(() => numbers.value.filter(n => n % 2 === 0)) |
Creates a new array containing only the even numbers from the original numbers array. This array will update automatically if numbers changes. |
| Writable Computed Property | const count = ref(0); const doubleCount = computed({ get: () => count.value * 2, set: (val) => { count.value = val / 2; } }) |
A computed property that can be read and written to. Setting a new value for doubleCount will update the underlying count ref. |
V. watch: The Overzealous Security Guard (The Alarm System)
watch is like a security guard who keeps a close eye on specific reactive values. Whenever those values change, the guard springs into action and executes a predefined function.
<script setup>
import { ref, watch } from 'vue'
const inputValue = ref('')
watch(inputValue, (newValue, oldValue) => {
console.log(`Input value changed from ${oldValue} to ${newValue}`)
// You can perform any action here, like making an API call
})
</script>
<template>
<view>
<input type="text" v-model="inputValue" />
</view>
</template>
Key takeaways about watch:
- Purpose: Allows you to react to changes in specific reactive values.
- Flexibility: You can perform any action within the
watchcallback function. - Explicit: Unlike
computed,watchis explicitly defined. You tell Vue exactly what you want to watch. - Side effects:
watchis typically used for performing side effects, like making API calls, updating local storage, or triggering animations.
Why use watch?
watch is ideal for scenarios where you need to:
- Make an API call when a specific value changes.
- Update local storage when a user saves their settings.
- Trigger an animation when a component becomes visible.
- Validate form input as the user types.
Example Table: watch in Action
| Scenario | Code Snippet | Explanation |
|---|---|---|
| Logging changes to a user’s name | const userName = ref('Default'); watch(userName, (newValue, oldValue) => console.log(Name changed from ${oldValue} to ${newValue})) |
Logs a message to the console whenever the userName ref changes. |
| Making an API call on search term change | const searchTerm = ref(''); watch(searchTerm, (newTerm) => { if (newTerm.length > 2) { fetch(/api/search?q=${newTerm}) } }) |
Makes an API call to search for results whenever the searchTerm ref changes and is longer than 2 characters. |
| Updating a component’s style based on a prop | const colorProp = ref('red'); watch(colorProp, (newColor) => { document.body.style.backgroundColor = newColor }) |
Changes the background color of the body element whenever the colorProp ref changes. This demonstrates watching a prop value. |
| Deep Watching an Object | const myObject = reactive({a: 1, b: 2}); watch(() => myObject, (newValue, oldValue) => console.log('object changed'), {deep: true}) |
Using {deep: true} allows you to watch for changes within the properties of a reactive object, not just a replacement of the entire object. |
VI. toRefs: The Destructuring Savior (The Decryption Key)
Remember how I said you shouldn’t destructure a reactive object directly? Well, that’s because destructuring breaks reactivity. But fear not! toRefs is here to save the day!
toRefs converts a reactive object into a plain object where each property is a ref pointing to the corresponding property in the original reactive object. This allows you to destructure the object while maintaining reactivity.
<script setup>
import { reactive, toRefs } from 'vue'
const user = reactive({
firstName: 'John',
lastName: 'Doe'
})
const { firstName, lastName } = toRefs(user)
const updateFirstName = () => {
firstName.value = 'Jane' // Need .value here!
}
</script>
<template>
<view>
<p>First Name: {{ firstName }}</p>
<p>Last Name: {{ lastName }}</p>
<button @click="updateFirstName">Update First Name</button>
</view>
</template>
Key takeaways about toRefs:
- Purpose: Allows you to destructure a reactive object while maintaining reactivity.
- Conversion to
ref: Each property in the returned object is aref. - Usage: Ideal for passing reactive data to child components as props.
Why use toRefs?
Imagine you have a complex reactive object with many properties, and you only need to pass a few of those properties to a child component. Using toRefs allows you to selectively expose those properties without sacrificing reactivity.
VII. Putting it All Together: A Grand Finale! 🎶
Let’s create a simple counter component that utilizes all four core concepts: ref, reactive, computed, and watch.
<template>
<view>
<h1>Counter Component</h1>
<p>Counter: {{ count }}</p>
<button @click="increment">Increment</button>
<button @click="decrement">Decrement</button>
<p>Is Even: {{ isEven }}</p>
<input type="text" v-model="message" />
<p>Message Length: {{ messageLength }}</p>
</view>
</template>
<script setup>
import { ref, reactive, computed, watch } from 'vue'
// Ref for the counter value
const count = ref(0)
// Reactive object for settings
const settings = reactive({
incrementAmount: 1,
decrementAmount: 1
})
// Computed property to check if the counter is even
const isEven = computed(() => count.value % 2 === 0)
// Method to increment the counter
const increment = () => {
count.value += settings.incrementAmount
}
// Method to decrement the counter
const decrement = () => {
count.value -= settings.decrementAmount
}
// Ref for the message
const message = ref('')
// Computed property for message length
const messageLength = computed(() => message.value.length)
// Watcher to log the counter value when it changes
watch(count, (newCount, oldCount) => {
console.log(`Counter changed from ${oldCount} to ${newCount}`)
})
</script>
Explanation:
- We use
reffor thecountandmessagevariables, as they are simple primitive values. - We use
reactivefor thesettingsobject, as it contains multiple properties that might change. - We use
computedforisEvenandmessageLength, as they are derived values based on other reactive data. - We use
watchto log the counter value whenever it changes.
VIII. Beyond the Basics: Advanced Techniques and Tips
shallowRefandshallowReactive: These variants create shallowly reactive objects. Only the top-level properties are reactive, not nested objects. This can improve performance if you have large, complex objects where you don’t need deep reactivity.readonlyandshallowReadonly: These functions create read-only versions of reactive objects. This prevents accidental modifications.triggerRef: Manually trigger arefupdate. Use with caution! (Usually not needed)customRef: Create your own reactive primitive with fine-grained control! For advanced use cases.
IX. Common Mistakes to Avoid
- Forgetting
.valuewithref: This is the most common mistake! Remember to use.valueto access and modify the value of aref. - Replacing reactive objects: Don’t replace a reactive object with a new object. Instead, update the properties within the existing object.
- Mutating reactive arrays directly: Use array methods like
push,pop,splice, etc., to modify reactive arrays. Avoid directly assigning a new array to the reactive array variable. - Overusing
watch:watchis powerful, but it can also lead to performance issues if used excessively. Consider usingcomputedfor derived values whenever possible.
X. Conclusion: Go Forth and Compose! 🚀
Congratulations! You’ve made it through this whirlwind tour of the Vue.js Composition API in UniApp. You’ve learned about ref, reactive, computed, and watch, and you’re now equipped to build more organized, reusable, and maintainable Vue components.
Remember, practice makes perfect! Experiment with these concepts in your own UniApp projects, and don’t be afraid to make mistakes. That’s how you learn!
Now go forth and compose! And remember, keep it fun! 😄
