Vue
Concepts
A .vue files may have HTML, CSS, JS template syntax inside.
<template></template>is for HTML structure<style></style>is for CSS style.scopedattribute is used to limit the style to the current component<script></script>is for JavaScript script.setupattribute is used to limit the script to the current component
Vue have two code modes, Options and Composition Mode. But in this documentation. We use composition
| Options | Composition |
|---|---|
| Older and more structural | Newer and more flexible |
More codes, with export in script for setup | Less code, using <script setup> |
The Flow
Server to Client Rendering Pipeline
- The server pre-render the HTML in a Node.js, convert the HTML to a string, and send it to the client
- The client receive the HTML string, convert it back to HTML, and render it
- The client's browser download the Vue bundle
- Hydration: Vue walks through the existing HTML and attach event listeners and reactivity logic to the DOM. Also checking with the server that both client and server have the same HTML.
onMountedis a hook only on the client after the component has been mounted to the DOM. THis is the safe place to touchwindow,documentor start animations.isMountedis usually a hard-coded variable to sync between server and client that the component has been mounted. Which is whenonMountedis called.useMountedis a composable function that returns a boolean value that the component has been mounted. This is not a built-in function into Vue, it helps to prevent "Hydration Mismatch" error.
Getting started
Run the code below to get started
npm install -g @vue/cli
vue create my-vue-app
cd my-vue-app
npm run dev
# or
npm run serve
# For deployment
npm run build
Then, select manually select feature to suits your need. Router, Pinia (global state management) is recommended. Starter template will be at port 8080
vue-app/
├── public/
├── src/
│ ├── assets/
│ ├── components/ # For reusable components
│ │ └── ButtonComponent.vue
│ ├── router/
│ │ └── index.js
│ ├── store/
│ │ └── index.js
│ ├── views/ # For pages
│ │ ├── HomeView.vue
│ │ └── AboutView.vue
│ ├── App.vue
│ └── main.js
├── .gitignore
├── package.json
└── vue.config.js
Reactivity Syntax Tutorial
| Concept | How |
|---|---|
| Declarative rendering | let vueVariable = reactive({ count: 0 }) or ref for singular variable |
| Attribute bindings | :class="vueVariable" or v-bind:class="vueVariable" both are the same |
| Event listeners | @click="vueVariable++" or v-on:click="vueVariable++" both are the same |
| Form bindings | v-model="vueVariable" |
| Conditional rendering | v-if="vueVariable" or v-else |
| List rendering | v-for="vueVariable in array" |
| Computed property | computed(() => vueVariable) outputs a function with reactive variable changes |
| Lifecycle | onMounted(() => { ... }) run a function when component is mounted |
| Template refs | ref="vueVariable" manually access DOM after elements being mounted |
| Watchers | watch(vueVariable, () => { ... }) run a function when reactive variable changes |
| Components | import ChildComp from './ChildComp.vue' |
| Props | props="vueVariable" pass data to child component |
| Emits | emits("vueVariable") emit data to parent component |
| Slots | slot="vueVariable" pass down template fragments to child component |
The code below shows the implementation combination of the fundamentals concepts.
<!-- Only one setup can be made on each file -->
<script setup>
import { reactive, ref } from 'vue' // Import state library
// Declarative rendering: renders dynamically
const counter = reactive({ count: 0 }) // Access by counter.count
const message = ref('Hello World!') // Access by message.value
const styleRef = ref('red')
function increment() {
counter.count += 1
}
// Access DOM manually after elements being mounted. During script setup, the DOM is not created yet.
import { onMounted } from 'vue'
const pElementRef = ref(null)
onMounted(() => {
pElementRef.value.textContent = 'Mounted! (DOM operation)'
})
// Making(triggers) 'side effects' reactively using watch
import { 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)
// Import other components
import ChildComp from './ChildComp.vue'
// Define ref to receive child component data
const childMsg = ref('No child msg yet')
</script>
<template>
<!-- Attribute Binding: ':class' is 'v-bind:class', binding class values to styleRef ref, message under mustache '{{ message }}' render dynamically to message ref -->
<h1 :class="styleRef">{{ message }}</h1>
<!-- Event Listeners: '@click' is 'v-on:click', calls the function 'increment() when clicked' -->
<button @click="increment"> Count is: {{ counter.count }}</button>
<!-- Form Binding: 'v-model:"message"'create two-way binding -->
<input v-model="message" placeholder="Type here">
<!-- Conditional Rendering: 'v-if:"counter.count % 2 !== 0"' -->
<p v-if="counter.count % 2 !== 0">The counter is odd</p>
<p v-else>The counter is even</p>
<!-- Lifecycle and Template Refs: manually work with DOM -->
<p ref="pElementRef">Hello</p>
<!-- Watchers: trigger when reactivity data changes -->
<p>Todo id: {{ todoId }}
<button @click="todoId++" :disabled="!todoData">Fetch next todo</button>
</p>
<p v-if="!todoData">Loading...</p>
<pre v-else>{{ todoData }}</pre>
<!-- Import ChildComp component and prop to child component -->
<ChildComp :propToChild="'This is a prop to child component'" @response="(msg) => childMsg = msg">
<!-- Slots: pass down template fragments to child -->
Message to child: {{ message }}
</ChildComp>
<p>{{ childMsg }}</p>
</template>
<style>
.red {
color: red;
}
</style>
<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 }
])
// Computed property: computes its value based on other reactive data sources
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) {
// t is the item iterated in todos, filter returns list after removing item with false statement
todos.value = todos.value.filter((t) => t !== todo)
}
// Define props to receive data from parent
const props = defineProps({
propToChild: String
})
// Setup emit to send data to parent
const emitFromChild = defineEmits(['response'])
emitFromChild('response', 'This is an emit from child')
</script>
<template>
<h2>{{ propToChild || 'No props passed yet' }}</h2>
<form @submit.prevent="addTodo">
<input v-model="newTodo" required placeholder="new todo">
<button>Add Todo</button>
</form>
<ul>
<!-- List Rendering -->
<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>
<slot>Fallback content from parent</slot>
</template>
<style>
.done {
text-decoration: line-through;
}
</style>
Router
To control and remember URL routes on the site
The structure are as below. Configure index.js, add views and make sure main.js imports router to be use
import { createRouter, createWebHashHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import HomeView from '../views/LoginView.vue'
// You have two ways to configure router
const routes = [
{
path: '/', redirect: '/about/home', // Default route
},
{
path: '/about', name: 'about',
// Nested routes
children: [
{ path: 'home', component: HomeView },
{ path: 'login', component: LogInView },
],
// Route level code-splitting: this generates a separate chunk about.[hash].js)
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
},
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
<script>
import { useRouter } from 'vue-router';
const router = useRouter();
router.push('/path'); // Move to a route
</script>
<template>
<router-link to="/path"><a> Link </a></router-link>
<router-view> Router views will be rendered here </router-view>
</template>
<script setup>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'
</script>
<template>
<div class="home">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<template>
<div class="about">
<h1>This is an about page. Without importing any other components</h1>
</div>
</template>
Pinia
Official global state management tool for vue
Install pinia through npm
import { defineStore } from 'pinia';
export const useGlobalStore = defineStore('global', {
state: () => ({
// The global value
value: null,
uname: null
}),
getters: {
getValue: (state) => state.value,
getUname: (state) => state.uname
},
actions: {
setValue(newValue) {
this.value = newValue;
},
setUname(uname) {
this.uname = uname;
},
clear() {
this.value = null;
this.uname = null;
}
},
});
Component library
PrimeVue
Primevue is a framework to make modern UI by adding components into vue files. Visit PrimeVue to get started.