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
.value
to 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:
reactive
makes 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
toRefs
if needed: If you need to destructure a reactive object and maintain reactivity, use thetoRefs
utility 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:
computed
properties are automatically updated whenever their dependencies change. - Caching:
computed
properties 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
computed
property 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
total
property 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
watch
callback function. - Explicit: Unlike
computed
,watch
is explicitly defined. You tell Vue exactly what you want to watch. - Side effects:
watch
is 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
ref
for thecount
andmessage
variables, as they are simple primitive values. - We use
reactive
for thesettings
object, as it contains multiple properties that might change. - We use
computed
forisEven
andmessageLength
, as they are derived values based on other reactive data. - We use
watch
to log the counter value whenever it changes.
VIII. Beyond the Basics: Advanced Techniques and Tips
shallowRef
andshallowReactive
: 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.readonly
andshallowReadonly
: These functions create read-only versions of reactive objects. This prevents accidental modifications.triggerRef
: Manually trigger aref
update. 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
.value
withref
: This is the most common mistake! Remember to use.value
to 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
:watch
is powerful, but it can also lead to performance issues if used excessively. Consider usingcomputed
for 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! 😄