Common APIs of Vue3#
reactive# Vue Guide#
Data Properties#
The data
option of a component is a function. Vue calls this function during the creation of a new component instance. It should return an object, which Vue wraps with the reactivity system and stores in the component instance as $data
. For convenience, any top-level properties of this object are also directly exposed through the component instance:
- Essentially, the component's
data
option is just a function. - This function is called when Vue creates the component instance.
- This function returns an object.
- The returned object is wrapped by Vue as reactive data.
- The wrapped reactive data is stored in the component instance as
$data
. - For convenience, any top-level properties of the object returned by
data
can be accessed directly through the component instance, provided they are all included in the object returned by thedata
function. - If new properties are added directly to the component instance, they can be added successfully, but since they are not in the reactive object
$data
, they will not be reactive. - Properties of the object returned by
data
should avoid starting with$
and_
.
Principle Analysis#
Why is the
data
property a function instead of an object?
- Conclusion:
data
can be defined as either a function or an object, but the root component can be instantiated as either an object or a function, while component instances can only be instantiated as a function. - Principle: The root instance is a singleton, which prevents data pollution; component instances are not singletons. To prevent multiple component instances from sharing the same
data
, which could lead to data pollution, a function form is used. When initializingdata
, it acts as a factory function that returns a newdata
object.
Computed Properties#
Application scenarios:
- In templates, long expressions can make the template complex and difficult to maintain. To solve this problem,
computed
was born. - It has a caching effect, which improves performance; when the dependent data has not changed, cached data is used.
For any complex logic that includes reactive data, computed properties should be used.
Computed Properties vs. Methods#
Computed properties are cached based on their reactive dependencies. They will only re-evaluate when their related reactive dependencies change.
In contrast, methods will always execute the function again whenever a re-render is triggered.
Why do we need caching?
Suppose we have a performance-intensive computed property list
, which needs to traverse a huge array and perform a lot of calculations. We may have other computed properties that depend on list
. Without caching, we will inevitably execute the getter
of list
multiple times! If you do not want caching, please use method
instead.
Watchers#
Options API Implementation of Watch#
Regular Watch#
export default {
props:['dataList'],
data(){
return {
sourceData:[],
total:0
}
},
// When the props' dataList changes, the values in data will be recalculated and assigned
watch:{
dataList(newVal,oldVal){
this.total = newVal.length
this.sourceData = newVal
}
}
}
Object Watch#
export default {
data(){
return {
firstName:'Li',
lastName:'Huan'
}
},
// When the props' dataList changes, the values in data will be recalculated and assigned
watch:{
dataList:{
// getter
get:function(){
return this.firstName + this.lastName
},
set:function(val){
// this.dataList = 'Wang Feng'
const [ firstName,lastName ] = val.split(' ')
this.firstName = firstName
this.lastName = lastName
},
{
// Objects and arrays are reference types, and reference type variables store addresses. If the address does not change, it will not trigger watch. In this case, we need to perform a deep watch, adding the deep: true property.
deep:true,
// Watch has a characteristic: when the value is first bound, the listener function will not be executed. It will only execute when the value changes. If you want the function to execute when the initial bound value is also executed, you need to add the immediate property.
immediate:true
}
}
}
}
Reactive API Implementation of Watch#
The watch function is used to listen to specific data sources and execute side effects in the callback function. By default, it is lazy, meaning it only executes the callback when the watched source data changes.
watch(source, callback, [options]);
Parameter description:
source
: can support String, Object, Function, and Array to specify the reactive variable to be watched.callback
: the callback function to be executed.options
: supportsdeep
,immediate
, andflush
options.
Listening to Reactive#
import { defineComponent, reactive, watch } from 'vue'
export default defineComponent({
setup(){
const state = reactive({
firstName:'Li',
lastName:'Wang'
})
// When firstName is modified, the watch callback function will be automatically triggered
watch(
()=>state.firstName,
(newVal,oldVal)=>{
console.log(`New value: ${newVal}`,`Old value: ${oldVal}`)
},
{deep:true})
}
})
Listening to Multiple Data#
const stop = watch([() => state.firstName, () => state.lastName], ([curAge, newVal], [preAge, oldVal]) => {
console.log('New value:', curAge, 'Old value:', preAge);
console.log('New value:', newVal, 'Old value:', oldVal);
});
unmounted(()=>{
// Stop listening
stop()
})
watchEffect#
watchEffect
does not require dependencies to be passed in like watch
; watchEffect
will automatically collect dependencies as long as a callback function is specified. During component initialization, it will execute once to collect dependencies, and when the collected dependencies change, it will execute the callback function again.
watchEffect(()=>{
// Automatically collect dependencies, execute once during initialization
console.log(firstName);
console.log(lastName)
})
Style Settings#
Object Syntax#
<template>
<div class='static' :class="{active:isActive,danger:hasError}"></div>
</template>
<script>
export default {
data(){
return {
isActive:true,
hasError:true
}
}
}
</script>
<!-- After rendering -->
<div class="static active danger"></div>
Directly Passing an Object#
<template>
<div class='static' :class="classObj"></div>
</template>
<script>
export default {
data(){
return {
classObj:{
active:true,
hasError:true
}
}
}
}
</script>
Passing Computed#
<template>
<div class="static" :class="clsObject"></div>
</template>
<script>
export default {
data(){
return {
isActive:true,
hasError:true
}
},
computed:{
classObj(){
return {
active:this.isActive,
hasError:this.hasError
}
}
}
}
</script>
Conditional Rendering#
<template>
<div v-if='show'></div>
<div v-else></div>
<div v-show='show'></div>
</template>
<script>
export default {
data(){
return {
show:true
}
}
}
</script>
v-if
vs v-show
:
v-show
simply modifies the element'scss
property, setting the element'sdisplay
tonone
.v-if
removes the element from the component tree. When the element is a component, changing theshow
value will trigger the lifecycle functions of the component element.
Built-in Components#
component#
Dynamic components
// Dynamic component
<template>
<!-- The component also changes when currentTab changes -->
<component :is="tabs[currentTab]"></component>
</template>
In the above example, the value passed to :is
can be one of the following:
- Registered component name
- Imported component object
transition#
Animation component
<!-- Template -->
<template>
<Transition name="slide-fade">
</Transition>
</template>
/*
Enter and leave animations can use different
durations and speed curves.
*/
.slide-fade-enter-active {
transition: all 0.3s ease-out;
}
.slide-fade-leave-active {
transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-fade-enter-from,
.slide-fade-leave-to {
transform: translateX(20px);
opacity: 0;
}
- Transition hook functions
<template>
<Transition
@before-enter="onBeforeEnter"
@enter="onEnter"
@after-enter="onAfterEnter"
@enter-cancelled="onEnterCancelled"
@before-leave="onBeforeLeave"
@leave="onLeave"
@after-leave="onAfterLeave"
@leave-cancelled="onLeaveCancelled"
>
</Transition>
</template>
// Called before the element is inserted into the DOM
// Use this to set the element's "enter-from" state
function onBeforeEnter(el) {}
// Called in the next frame after the element is inserted into the DOM
// Use this to start the enter animation
function onEnter(el, done) {
// Call the callback function done to indicate the transition is complete
// If used with CSS, this callback is optional
done()
}
// Called when the enter transition is complete.
function onAfterEnter(el) {}
function onEnterCancelled(el) {}
// Called before the leave hook
// Most of the time, you will only use the leave hook
function onBeforeLeave(el) {}
// Called when the leave transition starts
// Use this to start the leave animation
function onLeave(el, done) {
// Call the callback function done to indicate the transition is complete
// If used with CSS, this callback is optional
done()
}
// Called when the leave transition is complete,
// and the element has been removed from the DOM
function onAfterLeave(el) {}
// Only available in v-show transitions
function onLeaveCancelled(el) {}
keep-alive#
Used to cache component state and avoid re-rendering components.
keep-alive
has the following three attributes:
include
: a string or regular expression; only components with matching names will be cached.exclude
: a string or regular expression; any components with matching names will not be cached.max
: a number; the maximum number of component instances that can be cached.
slot#
Default slot
<template>
<!-- Parent component -->
<Child>
<div>Default slot</div>
</Child>
<!-- Child component -->
<template>
<slot>
<p>Fallback content for the slot</p>
</slot>
</template>
</template>
Named Slots#
<!-- Inside the component -->
<div class="container">
<header>
<!-- We want to place the header here -->
</header>
<main>
<!-- We want to place the main content here -->
</main>
<footer>
<!-- We want to place the footer here -->
</footer>
</div>
<!-- Inside the component -->
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
Scoped Slots#
When the parent component customizes the slot content in the child component, it needs to access the component data from the child component.
<template>
<!-- Child -->
<ul>
<li v-for="(item, index) in items">
<slot :item="item"></slot>
</li>
</ul>
<!-- Parent -->
<todo-list>
<template #default="{slotProps}">
<span class="green">{{ slotProps.item }}</span>
</template>
</todo-list>
</template>
Teleport#
Teleport is a new feature introduced in Vue3.x that provides a clean way to control which parent node in the DOM the HTML is rendered under.
<template>
<Teleport to="body">
<div> teleport to body </div>
</Teleport>
</template>
import { reactive } from 'vue';
export default {
// `setup` is a special hook for the Composition API
setup() {
const state = reactive({ count: 0 }); // Expose state to the template
return { state };
},
};
ref#
import { ref } from 'vue';
const count = ref(0);
console.log(count); // { value: 0 }
console.log(count.value); // 0
count.value++;
console.log(count.value); // 1
computed#
<script setup>
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
// A computed property ref
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
</script>
<template>
<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>
</template>
watch#
<script setup>
import { ref, watch } from 'vue'
const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')
// Can directly watch a ref
watch(question, async (newQuestion, oldQuestion) => {
if (newQuestion.indexOf('?') > -1) {
answer.value = 'Thinking...'
try {
const res = await fetch('https://yesno.wtf/api')
answer.value = (await res.json()).answer
} catch (error) {
answer.value = 'Error! Could not reach the API. ' + error
}
}
})
</script>
<template>
<p>
Ask a yes/no question:
<input v-model="question" />
</p>
<p>{{ answer }}</p>
</template>
watchEffect#
watchEffect() will immediately execute the callback function once. If the function produces side effects, Vue will automatically track the dependencies of the side effects and analyze the reactive sources.
watchEffect(async () => {
const response = await fetch(url.value);
data.value = await response.json();
});
watch vs. watchEffect#
Both
watch
andwatchEffect
can reactively execute callbacks with side effects. The main difference between them is how they track reactive dependencies:watch
only tracks explicitly watched sources. It does not track anything accessed within the callback. Additionally, it only triggers the callback when the reactive source actually changes.watch
avoids tracking dependencies during side effects, allowing for more precise control over when the callback function is triggered.
watchEffect
, on the other hand, tracks dependencies during side effects. It automatically tracks all accessible reactive properties during synchronous execution. This is more convenient, and the code is often cleaner, but its reactive dependency relationships are less explicit.
State Management with Pinia#
Store#
// stores/counter.js
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => {
return { count: 0 };
},
// could also be defined as
// state: () => ({ count: 0 })
actions: {
increment() {
this.count++;
},
},
getters(){
finishedTodos(store){
return store.count++
}
}
});
Action#
import { useCounterStore } from '@/stores/counter';
export default {
setup() {
const counter = useCounterStore();
counter.count++;
// with autocompletion ✨
counter.$patch({ count: counter.count + 1 });
// or using an action instead
counter.increment();
},
};
vue-router#
router.js
const Home = { template: '<div>Home</div>' };
const About = { template: '<div>About</div>' };
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
];
const router = VueRouter.createRouter({
history: VueRouter.createWebHashHistory(),
routes,
});
const app = Vue.createApp({});
app.use(router);
app.mount('#app');
Rendering
<template>
<router-view />
</template>
setup Syntax#
defineProps defineEmits#
TypeScript syntax support
const props = defineProps<{
foo: string
bar?: number
}>()
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
defineExpose#
The setup component is closed by default.
<script setup>
import {ref} from 'vue' const a = 1 const b = ref(2) defineExpose({a, b})
</script>