vue js

Synopsis

Vue.js is an open-source JavaScript framework for building user interfaces and single-page applications. It is designed to improve code quality and maintainability. Vue.js is a progressive framework, which means that it can be used incrementally in existing projects. It is also fully featured and flexible, which means that it can be used to create a wide variety of applications.

I. Tools

1. Create-vue

# 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/

II. Templates

1. Templates

double curly braces {{Mustache}} syntax and outputs data as plain text

a) Text Interpolation

<!-- variable -->
<span>Message: {{ msg }}</span>
<!-- expression -->
<span>Nr: {{ nr + 1 }}</span>
<span>Accept: {{ ok ? 'YES' : 'NO' }}</span>

b) Raw HTML

v-html directive outputs real HTML

<span v-html="rawHtml"></span>

c) Attributes

Attributes Binding
<div v-bind:id="dynamicId"></div>
<div :id="dynamicId"></div>
<div :id="`prefix-${dynamicId}`"></div>
<button :disabled="isButtonDisabled">Button</button>
<template>
  <div v-bind="objectOfAttrs"></div>
</template>
<script setup>
const objectOfAttrs = {
  id: "container",
  class: "wrapper",
};
</script>

d) Directives

<p v-if="seen">Now you see me</p>
<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:

2. Reactivity

a) reactive()

<script setup>
import { reactive } from "vue";
const state = reactive({ count: 0 });
function increment() {
  state.count++;
}
</script>
<template>
  <button @click="increment">
    {{ state.count }}
  </button>
</template>

b) ref()

import { ref } from "vue";
const count = ref(0);
const count = ref(0);
console.log(count); // { value: 0 }
console.log(count.value); // 0
count.value++;
console.log(count.value); // 1

c) Computed Properties

<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>

3. Conditional Rendering

<div v-if="type === 'A'">
  A
</div>
<div v-else-if="type === 'B'">
  B
</div>
<div v-else>
  Not A/B
</div>

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>

4. List Rendering

v-for

const items = ref([{ message: "Foo" }, { message: "Bar" }]);
<li v-for="(item, index) in items">
 {{ index }} - {{ item.message }}
</li>
const myObject = reactive({
  id: 1
  name: 'name',
})
<li v-for="(value, key) in myObject">
  {{ key }}: {{ value }}
</li>
<span v-for="n in 10">{{ n }}</span>
<template v-for="todo in todos">
  <li v-if="!todo.isComplete">
    {{ todo.name }}
  </li>
</template>
<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>

5. Class & Style Binding

a) Class Binding

<!-- :class can be used together with plain class attribute  -->
<div class="item" :class="{ active: isActive }"></div>
<template>
  <div :class="classObject"></div>
</template>
<script setup>
const classObject = reactive({
  active: true,
  "text-danger": false,
});
</script>
<template>
  <div :class="[activeClass, errorClass]"></div>
</template>
<script setup>
const activeClass = ref("active");
const errorClass = ref("text-danger");
</script>
<div :class="[isActive ? activeClass : '', errorClass]"></div>
<div :class="[{ active: isActive }, errorClass]"></div>

b) Style Binding

const activeColor = ref("red");
const fontSize = ref(30);
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
<div :style="styleObject"></div>
const styleObject = reactive({
  color: "red",
  fontSize: "13px",
});
<div :style="[baseStyles, overridingStyles]"></div>

6. Event Handling

const count = ref(0);
<button @click="count++">Add 1</button>
<p>Count is: {{ count }}</p>
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>
function say(message) {
  alert(message);
}
<button @click="say('hello')">Say hello</button>
<button @click="say('bye')">Say bye</button>
<!-- 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>

7. Form Inputs

a) Text

<p>Message is: {{ message }}</p>
<input v-model="message" placeholder="edit me" />

b) Multiline text using Textarea

<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<textarea v-model="message" placeholder="add multiple lines"></textarea>

c) Checkbox

<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{ checked }}</label>

d) Multiple Checkboxes bind to one value

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>

e) Radio

<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>

f) Select

<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>

g) Multiple Select

<div>Selected: {{ selected }}</div>

<select v-model="selected" multiple>
  <option>A</option>
  <option>B</option>
  <option>C</option>
</select>

h) Form Input Modifiers

<input v-model.lazy="msg" />
<input v-model.number="age" />

.trim trim whitespace

<input v-model.trim="msg" />

8. Lifecycle Hooks

a) Registering Lifecycle Hooks

<script setup>
import { onMounted } from "vue";

onMounted(() => {
  console.log(`the component is now mounted.`);
});
</script>

a) Built-in Lifecycle Hooks

9. Watchers

watch triggers a callback whenever a piece of reactive state changes

a) Simple data types

<script setup>
import { watch, ref } from "vue";
const planet = ref("Earth");
watch(planet, (currentValue, oldValue) => {
  console.log(currentValue);
  console.log(oldValue);
});
</script>

b) Complex data types

<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>

10. Template Refs

a) Accessing refs

<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>

b) Refs inside v-for

<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>

III. Single-file Components

1. Single-file Components using <script setup>

<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>

2. Props

a) Declaring Props

<script setup>
const props = defineProps(["foo"]);
console.log(props.foo);
</script>
<script setup>
defineProps({
  title: String,
  likes: Number,
});
</script>

b) Passing Props

<template>
  <h1>{{ titleText }}</h1>
</template>

<script setup>
defineProps({
  titleText: String,
});
</script>
<!-- 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 />

3. Events

a) Emitting & Listening to Events

<template>
  <button @click="buttonClick">click me</button>
</template>

<script setup>
const emit = defineEmits(["submit"]);
function buttonClick() {
  emit("submit");
}
</script>
<!-- parent component -->
<MyComponent @submit="callback" />

4. Slots

a) Using a slot

<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>

b) Using a slot with fallback

<div class="content-wrapper">
  <slot>
    Lorem Ipsum <!-- fallback content -->
  </slot>
</div>

c) Named Slots

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>
<BaseLayout>
  <template #header>
    <h1>header</h1>
  </template>
  <template #default>
    <p>Lorem ipsum</p>
  </template>
  <template #footer>
    <p>footer</p>
  </template>
</BaseLayout>

5. Provide & Inject

a) Provide

<script setup>
import { provide } from "vue";
provide(/* key */ "message", /* value */ "hello!");
</script>

b) App-level Provide

import { createApp } from "vue";
const app = createApp({});
app.provide(/* key */ "message", /* value */ "hello!");

c) Inject

<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>

6. Built-in Components

a) KeepAlive

<!-- Inactive components will be cached! -->
<KeepAlive>
  <component :is="activeComponent" />
</KeepAlive>

b) Suspense

<Suspense>
  <!-- component with nested async dependencies -->
  <Dashboard />
  <!-- loading state via #fallback slot -->
  <template #fallback>
    Loading...
  </template>
</Suspense>

c) Teleport

<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>

d) Transition

<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>

e) TransitionGroup

<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>