Single File Component (SFC)
An SFC is a reusable self-contained block of code that encapsulates HTML, CSS and JavaScript that belong together, written inside a .vue
file.
declarative rendering
The core feature of Vue is declarative rendering: using a template syntax that extends HTML, we can describe how the HTML should look like based on JavaScript state.
Reactive
State that can trigger updates when changed are considered reactive. We can declare reactive state using Vue’s reactive()
API. Objects created from reactive()
are JavaScript Proxies that work just like normal objects.
reactive()
only works on objects (including arrays and built-in types like Map
and Set
). ref()
, on the other hand, can take any value type and create an object that exposes the inner value under a .value
property.
Reactive state declared in the component’s <script setup>
block can be used directly in the template.
we did not need to use .value
when accessing the message
ref in templates: it is automatically unwrapped for more succinct usage.
<script setup>
const message = ref('Hello World!')
console.log(message.value)
</script>
<template>
<h1>
{{message}}
</h1>
</template>
Directive
<script setup>
import { ref } from 'vue'
const titleClass = ref('title')
</script>
<template>
<h1 v-bind:class='titleClass'>Make me red</h1>
<h1 :class='titleClass'>
for short
</h1>
</template>
A directive is a special attribute that starts with the v-
prefix. They are part of Vue’s template syntax. Similar to text interpolations, directive values are JavaScript expressions that have access to the component’s state.
Event Listener
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
// update component state
count.value++
}
</script>
<template>
<button v-on:click="increment">{{ count }}</button>
<button @click="increment">for short</button>
</template>
Two-Way bindings
<script setup>
const text = ref('')
function onInput(e) {
// a v-on handler receives the native DOM event
// as the argument.
text.value = e.target.value
}
</script>
<template>
<input :value="text" @input="onInput">
<input v-model="text"><!--for short-->
</template>
To simplify two-way bindings, Vue provides a directive, v-model
, which is essentially a syntax sugar.
v-model
automatically syncs the <input>
’s value with the bound state, so we no longer need to use a event handler for that.
v-model
works not only on text inputs, but also other input types such as checkboxes, radio buttons, and select dropdowns.
Conditional Rendering
We can use the v-if
directive to conditionally render an element, we can also use v-else
and v-else-if
to denote other branches of the condition.
<script setup>
import { ref } from 'vue'
const awesome = ref(true)
function toggle() {
awesome.value=!awesome.value
}
</script>
<template>
<button @click="toggle">toggle</button>
<h1 v-if='awesome'>Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
</template>
List Rendering
We can use the v-for
directive to render a list of elements based on a source array
we are also giving each todo object a unique id
, and binding it as the special key
attribute for each <li>
. The key
allows Vue to accurately move each <li>
to match the position of its corresponding object in the array.
There are two ways to update the list:
- Call mutating methods on the source array
- Replace the array with a new one
<script setup>
import { ref } from 'vue'
// give each todo a unique id
let id = 0
const newTodo = ref('')
const todos = ref([
{ id: id++, text: 'Learn HTML' },
{ id: id++, text: 'Learn JavaScript' },
{ id: id++, text: 'Learn Vue' }
])
function addTodo() {
todos.value.push({ id: id++, text: newTodo.value })
newTodo.value = ''
}
function removeTodo(todo) {
todos.value = todos.value.filter((t) => t !== todo)
}
</script>
<template>
<input v-model="newTodo" @keyup.enter="addTodo">
<button @click="addTodo">Add Todo</button>
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.text }}
<button @click="removeTodo(todo)">X</button>
</li>
</ul>
</template>
Computed property
We can create a computed ref that computes its .value
based on other reactive data sources by using computed()
A computed property tracks other reactive state used in its computation as dependencies. It caches the result and automatically updates it when its dependencies change.
<script setup>
import { ref, computed } from 'vue'
let id = 0
const newTodo = ref('')
const hideCompleted = ref(false)
const todos = ref([
{ id: id++, text: 'Learn HTML', done: true },
{ id: id++, text: 'Learn JavaScript', done: true },
{ id: id++, text: 'Learn Vue', done: false }
])
const filteredTodos = computed(() => {
return hideCompleted.value
? todos.value.filter((t) => !t.done)
: todos.value
})
function addTodo() {
todos.value.push({ id: id++, text: newTodo.value, done: false })
newTodo.value = ''
}
function removeTodo(todo) {
todos.value = todos.value.filter((t) => t !== todo)
}
</script>
<template>
<input v-model="newTodo" @keyup.enter="addTodo" />
<button @click="addTodo">Add Todo</button>
<ul>
<li v-for="todo in filteredTodos" :key="todo.id">
<input type="checkbox" v-model="todo.done" />
<span :class="{ done: todo.done }">{{ todo.text }}</span>
<button @click="removeTodo(todo)">X</button>
</li>
</ul>
<button @click="hideCompleted = !hideCompleted">
{{ hideCompleted ? 'Show all' : 'Hide completed' }}
</button>
</template>
<style>
.done {
text-decoration: line-through;
}
</style>
Lifecycle and Template Refs
We can request a template ref - i.e. a reference to an element in the template - using the special ref
attribute.
To access the ref, we need to declare a ref with matching name in <script setup>
. Notice the ref is initialized with null
value. This is because the element doesn’t exist yet when <script setup>
is executed. The template ref is only accessible after the component is mounted.
<script setup>
import { ref, onMounted } from 'vue'
const p = ref(null)
onMounted(() => {
p.value.textContent = 'mounted!'
})
</script>
<template>
<p ref="p">hello</p>
</template>
hooks such as onUpdated
and onUnmounted
are lifecycle hooks.
Watchers
Sometimes we may need to perform “side effects” reactively
watch()
can directly watch a ref, and the callback gets called whenever count
’s value changes.
<script setup>
import { ref, watch } from 'vue'
const todoId = ref(1)
const todoData = ref(null)
async function fetchData() {
todoData.value = null
const res = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
todoData.value = await res.json()
}
fetchData()
watch(todoId, fetchData)
</script>
<template>
<p>Todo id: {{ todoId }}</p>
<button @click="todoId++">Fetch next todo</button>
<p v-if="!todoData">Loading...</p>
<pre v-else>{{ todoData }}</pre>
</template>
Props
A child component can accept input from the parent via props. First, it needs to declare the props it accepts:
<!-- ChildComp.vue -->
<script setup>
const props = defineProps({
msg: String
})
</script>
Note defineProps()
is a compile-time macro and doesn’t need to be imported. Once declared, the msg
prop can be used in the child component’s template. It can also be accessed in JavaScript via the returned object of defineProps()
.
Emits
In addition to receiving props, a child component can also emit events to the parent:
<script setup>
// declare emitted events
const emitter = defineEmits(['response','test'])
// emit with argument
emitter('response', 'hello from child')
emitter('test', 'testtest')
</script>
The first argument to emit()
is the event name. Any additional arguments are passed on to the event listener.
The parent can listen to child-emitted events using v-on
- here the handler receives the extra argument from the child emit call and assigns it to local state:
<ChildComp @response="(msg) => childMsg = msg" />
Slots
In addition to passing data via props, the parent component can also pass down template fragments to the child via slots.
In the child component, it can render the slot content from the parent using the <slot>
element as outlet.
//parent
<template>
<ChildComp>
message to pass
</ChildComp>
</template>
//child
<template>
<p>
some word here
</p>
<slot>
message passed form parent will be present here, and this is the default words.
</slot>
</template>