title | description | created | updated |
---|---|---|---|
Vue.js |
The Progressive JavaScript Framework |
2022-10-25 |
2022-10-25 |
Notice
- This document is focused on Vue 3 composition API with concise
<script setup>
syntax
- Table of Contents
- I. Tools
- II. Templates
- III. Single-file Components
# init a project using an interactive scaffolding tool
npm init vue@latest
# run dev server
cd <project-name>
npm install
npm run dev
# project will be available at http://localhost:5173/
double curly braces {{Mustache}} syntax and outputs data as plain text
<!-- variable -->
<span>Message: {{ msg }}</span>
<!-- expression -->
<span>Nr: {{ nr + 1 }}</span>
<span>Accept: {{ ok ? 'YES' : 'NO' }}</span>
v-html
directive outputs real HTML
<span v-html="rawHtml"></span>
- using
v-bind
directive
<div v-bind:id="dynamicId"></div>
- using
:
shorthand
<div :id="dynamicId"></div>
- binding expression using
v-bind
shorthand
<div :id="`prefix-${dynamicId}`"></div>
- boolean attributes
<button :disabled="isButtonDisabled">Button</button>
- Dynamically Binding Multiple Attributes
<template>
<div v-bind="objectOfAttrs"></div>
</template>
<script setup>
const objectOfAttrs = {
id: 'container',
class: 'wrapper'
}
</script>
- Directives are special attributes prefixed with
v-
<p v-if="seen">Now you see me</p>
- Some directives with an "argument", denoted by a colon
<a v-bind:href="url"> ... </a>
<!-- shorthand -->
<a :href="url"> ... </a>
<a v-on:click="doSomething"> ... </a>
<!-- shorthand -->
<a @click="doSomething"> ... </a>
Built-in directives:
v-text
update the element's text contentv-html
update the element's innerHTMLv-show
toggle the element's visibilityv-if
conditionally render an element or a template fragmentv-else
denote the "else block" for v-if or a v-if / v-else-if chainv-else-if
denote the "else if block" for v-ifv-for
render the element or template block multiple times based on the source datav-on
attach an event listener to the elementv-bind
dynamically binds one or more attributes or a component prop to an expressionv-model
create a two-way binding on a form input element or a componentv-slot
denote named slots or scoped slots that expect to receive propsv-pre
skip compilation for this element and all its children.v-once
render the element and component once onlyv-memo
memoize a sub-tree of the templatev-cloak
hide un-compiled template until it is ready
- reactive() can be used to create a reactive object or array.
<script setup>
import { reactive } from 'vue'
const state = reactive({ count: 0 })
function increment() {
state.count++
}
</script>
<template>
<button @click="increment">
{{ state.count }}
</button>
</template>
- ref() can be used to create reactive variables.
import { ref } from 'vue'
const count = ref(0)
- To access ref variable use .value property
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
- Computed Properties are used to quickly calculate a value that depends on other values.
<script setup>
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Book 1'
]
})
// a computed ref
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
</script>
<template>
<p>Has published books: {{ publishedBooksMessage }}</p>
</template>
- Render element based on condition using
v-if
,v-else-if
,v-else
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else>
Not A/B
</div>
- Display element based on condition
v-show
v-show
is similar to v-if
, but element remains in DOM. It toggles the display
property under the hood.
<h1 v-show="ok">Hello!</h1>
- Iterate over
array
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
<li v-for="(item,index) in items">
{{ index }} - {{ item.message }}
</li>
- Iterate over
object
const myObject = reactive({
id: 1
name: 'name',
})
<li v-for="(value, key) in myObject">
{{ key }}: {{ value }}
</li>
- Iterate over
range
<span v-for="n in 10">{{ n }}</span>
- Iterate using
template
tag.- When using
v-if
andv-for
together, they should be used on a different element
- When using
<template v-for="todo in todos">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>
- Iterate with :key attribute
:key
is used by vue internally to track changes of items in a collection. It is always recommended to use:key
, unless the iterated DOM content is very simple.
<div v-for="item in items" :key="item.id">
<!-- content -->
</div>
<template v-for="todo in todos" :key="todo.name">
<li>{{ todo.name }}</li>
</template>
- inline class binding
<!-- :class can be used together with plain class attribute -->
<div class="item" :class="{ active: isActive }"></div>
- bind class to a reactive variable
<template>
<div :class="classObject"></div>
</template>
<script setup>
const classObject = reactive({
active: true,
'text-danger': false
})
</script>
- bind class to an array
<template>
<div :class="[activeClass, errorClass]"></div>
</template>
<script setup>
const activeClass = ref('active')
const errorClass = ref('text-danger')
</script>
- conditionally bind class using ternary operator
<div :class="[isActive ? activeClass : '', errorClass]"></div>
- conditionally bind class using the object and array syntax
<div :class="[{ active: isActive }, errorClass]"></div>
- inline styles binding
const activeColor = ref('red')
const fontSize = ref(30)
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
- bind styles to a reactive variable
<div :style="styleObject"></div>
const styleObject = reactive({
color: 'red',
fontSize: '13px'
})
- bind styles to an array
<div :style="[baseStyles, overridingStyles]"></div>
- Inline Handlers
const count = ref(0)
<button @click="count++">Add 1</button>
<p>Count is: {{ count }}</p>
- Method Handlers
const name = ref('Vue.js')
function greet(event) {
alert(`Hello ${name.value}!`)
// `event` is the native DOM event
if (event) {alert(event.target.tagName)}
}
<button @click="greet">Greet</button>
- Calling Methods with arguments using inline handlers
function say(message) {
alert(message)
}
<button @click="say('hello')">Say hello</button>
<button @click="say('bye')">Say bye</button>
-
Event Modifiers
- special postfixes that modify event behavior i.e.
.prevent
translates toevent.preventDefault()
- special postfixes that modify event behavior i.e.
-
Built-in Event Modifiers
.stop
,.prevent
,.self
,.capture
,.once
,.passive
<!-- the click event's propagation will be stopped -->
<a @click.stop="doThis"></a>
<!-- the submit event will no longer reload the page -->
<form @submit.prevent="onSubmit"></form>
<!-- modifiers can be chained -->
<a @click.stop.prevent="doThat"></a>
<!-- just the modifier -->
<form @submit.prevent></form>
<!-- only trigger handler if event.target is the element itself -->
<!-- i.e. not from a child element -->
<div @click.self="doThat">...</div>
<p>Message is: {{ message }}</p>
<input v-model="message" placeholder="edit me" />
<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<textarea v-model="message" placeholder="add multiple lines"></textarea>
<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{ checked }}</label>
const checkedNames = ref([])
<div>Checked names: {{ checkedNames }}</div>
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<div>Picked: {{ picked }}</div>
<input type="radio" id="one" value="One" v-model="picked" />
<label for="one">One</label>
<input type="radio" id="two" value="Two" v-model="picked" />
<label for="two">Two</label>
<div>Selected: {{ selected }}</div>
<select v-model="selected">
<option disabled value="">Please select one</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<div>Selected: {{ selected }}</div>
<select v-model="selected" multiple>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
.lazy
synced after "change" instead of "input"
<input v-model.lazy="msg" />
.number
automatically typecast as a number
<input v-model.number="age" />
.trim
trim whitespace
<input v-model.trim="msg" />
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
console.log(`the component is now mounted.`)
})
</script>
onMounted()
is called after the component has been mounted.onUpdated()
is called after the component has updated its DOM tree due to a reactive state change.onUnmounted()
is called after the component has been unmounted.onBeforeMount()
is called right before the component is to be mounted.onBeforeUpdate()
is called right before the component is about to update its DOM tree due to a reactive state change.onBeforeUnmount()
is called right before a component instance is to be unmountedonErrorCaptured()
is called when an error propagating from a descendant component has been captured.onActivated()
is called after the component instance is inserted into the DOM as part of a tree cached by .onDeactivated()
is called after the component instance is removed from the DOM as part of a tree cached by .
watch
triggers a callback whenever a piece of reactive state changes
<script setup>
import { watch, ref } from "vue";
const planet = ref("Earth");
watch(planet, (currentValue, oldValue) => {
console.log(currentValue);
console.log(oldValue);
});
</script>
<script setup>
import { watch, ref } from "vue";
export default {
setup() {
// array
const arr = ref([1, 2, 3]);
watch(() => [...arr.value], (currentValue, oldValue) => {
console.log(currentValue);
console.log(oldValue);
});
//object
const obj = ref({
name: "",
});
watch(obj.value, (currentValue, oldValue) => {
console.log(currentValue);
console.log(oldValue);
});
},
};
<script setup>
ref
is a special attribute that can be used to access to a DOM element directly.
<script setup>
import { ref, onMounted } from 'vue'
// declare a ref to hold the element reference
// the name must match template ref value
const input = ref(null)
onMounted(() => {
input.value.focus()
})
</script>
<template>
<input ref="input" />
</template>
<script setup>
import { ref, onMounted } from 'vue'
const items = ref(["item1","item2"])
const itemRefs = ref([])
onMounted(() => console.log(itemRefs.value.map(i => i.textContent))) // ["item1", "item2"]
</script>
<template>
<ul>
<li v-for="item in items" ref="itemRefs">
{{ item }}
</li>
</ul>
</template>
- A Single-file Component is a file with
.vue
extension containing template, logic and styling. - In
<script setup>
variables, function declarations, imports etc are directly usable in the template
<script setup>
// imports
import { capitalize } from './helpers'
import MyComponent from './MyComponent.vue'
// variable
const msg = 'Hello!'
// functions
function log() {
console.log(msg)
}
</script>
<template>
<button @click="log">{{ msg }}</button>
<div>{{ capitalize('hello') }}</div>
<MyComponent />
</template>
-
defineProps()
macro is used to declare props in<script setup>
: -
define props as an array of strings
<script setup>
const props = defineProps(['foo'])
console.log(props.foo)
</script>
- define props as an object with specified validation types
<script setup>
defineProps({
title: String,
likes: Number
})
</script>
- The built-in types include
String
,Number
,Boolean
,Array
,Object
,Date
,Function
,Symbol
- MyComponent.vue
<template>
<h1>{{titleText}}</h1>
</template>
<script setup>
defineProps({
titleText: String
})
</script>
- ParentComponent.vue
- By convention
<PascalCase>
is used for components andcamel-case
for props
<!-- Passing static string prop -->
<MyComponent title-text="hello" />
<!-- Dynamically assign the value -->
<MyComponent v-bind:title-text="post.title" />
<!-- Dynamically assign the value using shorthand -->
<MyComponent :title-text="post.title" />
<!-- Dynamically assign the value of a complex expression -->
<MyComponent :title="post.title + ' !'" />
<MyComponent :ids="[1, 2, 3]" />
<MyComponent :item="{id:1,lang:'js'}" />
<!-- Including the prop with no value will imply `true`. -->
<MyComponent has-results />
- In
<script setup>
,defineEmits
macro is used to define specific events to be emitted - By convention
camelCase
for event declaration andcamel-case
for event listener on parent component.
<template>
<button @click="buttonClick">click me</button>
</template>
<script setup>
const emit = defineEmits(['submit'])
function buttonClick() {
emit('submit')
}
</script>
<!-- parent component -->
<MyComponent @submit="callback" />
- Slots can be used to pass any template content to a component
<ContentWrapper>
<p>text</p>
<AwesomeIcon name="plus" />
</ContentWrapper>
<!-- ContentWrapper.vue -->
<div class="content-wrapper">
<slot></slot> <!-- slot outlet will be replaced the template content -->
</div>
- fallback content is used as a placeholder when no content is provided
<div class="content-wrapper">
<slot>
Lorem Ipsum <!-- fallback content -->
</slot>
</div>
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
<template v-slot:header>
or its shortcut<template #header>
can be used to pass content to a named slot
<BaseLayout>
<template #header>
<h1>header</h1>
</template>
<template #default>
<p>Lorem ipsum</p>
</template>
<template #footer>
<p>footer</p>
</template>
</BaseLayout>
<script setup>
import { provide } from 'vue'
provide(/* key */ 'message', /* value */ 'hello!')
</script>
import { createApp } from 'vue'
const app = createApp({})
app.provide(/* key */ 'message', /* value */ 'hello!')
- Inject provided by an ancestor component
<script setup>
import { inject } from 'vue'
const message = inject('message')
// if no data matching "message" was provided
// `value` will be "default value"
const value = inject('message', 'default value')
</script>
- Conditionally caches component
<!-- Inactive components will be cached! -->
<KeepAlive>
<component :is="activeComponent" />
</KeepAlive>
- Orchestrate async dependencies in a component tree.
It can render a loading state while waiting for multiple nested async dependencies to be resolved.The
<Suspense>
component has two slots:#default
and#fallback
.
<Suspense>
<!-- component with nested async dependencies -->
<Dashboard />
<!-- loading state via #fallback slot -->
<template #fallback>
Loading...
</template>
</Suspense>
- Teleport a part of a component's template into a DOM node that exists outside the DOM hierarchy of that component.
<button @click="open = true">Open Modal</button>
<Teleport to="body">
<div v-if="open" class="modal">
<p>Hello from the modal!</p>
<button @click="open = false">Close</button>
</div>
</Teleport>
- Apply animations when an element or component is entering and leaving the DOM.
<template>
<button @click="show = !show">Toggle</button>
<Transition>
<p v-if="show">hello</p>
</Transition>
</template>
<style>
/* we will explain what these classes do next! */
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
</style>
- Apply animations when an element or component is inserted into, removed from, or moved within a v-for list
<template>
<TransitionGroup name="list" tag="ul">
<li v-for="item in items" :key="item">
{{ item }}
</li>
</TransitionGroup>
</template>
<style>
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
</style>