Learning vue

From Logic Wiki
Jump to: navigation, search


Creating a project

npm create vite@latest <APPLICATION_NAME> -- --template vue

Template Syntax

Attribute Bindings

<span>Message: {{ msg }}</span>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
<div :id="dynamicId"></div>


<!-- same as :id="id" -->
<div :id></div>

<button :disabled="isButtonDisabled">Button</button>

// JS Expressions 
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div :id="`list-${id}`"></div>

v-show="[alertText.length > 0]"

<a v-bind:href="url"> ... </a>
<a :[attributeName]="url"> ... </a>

<span v-text="msg"></span>
<div v-html="html"></div>

<div v-cloak>
  {{ message }}
</div>

Dynamically Binding Multiple Attributes​

If you have a JavaScript object representing multiple attributes that looks like this:

const objectOfAttrs = {
  id: 'container',
  class: 'wrapper',
  style: 'background-color:green'
}

You can bind them to a single element by using v-bind without an argument:

<div v-bind="objectOfAttrs"></div>

Directives

<p v-if="seen">Now you see me</p>
<a @click="doSomething"> ... </a> // <a v-on:click="doSomething"> ... </a>

<form @submit.prevent="onSubmit">...</form>

The .prevent modifier tells the v-on directive to call event.preventDefault() on the triggered event

Reactivity Fundamentals

ref() takes the argument and returns it wrapped within a ref object with a .value property:

import { ref } from 'vue'

const count = ref(0)

console.log(count) // { value: 0 }
console.log(count.value) // 0

<div>{{ count }}</div>

Notice that we did not need to append .value when using the ref in the template. For convenience, refs are automatically unwrapped when used inside templates

For more complex logic, we can declare functions that mutate refs in the same scope and expose them as methods alongside the state:

import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)

    function increment() {
      // .value is needed in JavaScript
      count.value++
    }

    // don't forget to expose the function as well.
    return {
      count,
      increment
    }
  }
}

Exposed methods can then be used as event handlers:

<button @click="increment">
  {{ count }}
</button>

<script setup>

Manually exposing state and methods via setup() can be verbose. Luckily, it can be avoided when using Single-File Components (SFCs). We can simplify the usage with <script setup>:

<script setup>
import { ref } from 'vue'

const count = ref(0)

function increment() {
  count.value++
}
</script>

<template>
  <button @click="increment">
    {{ count }}
  </button>
</template>

Deep Reactivity

A ref will make its value deeply reactive. This means you can expect changes to be detected even when you mutate nested objects or arrays:

import { ref } from 'vue'

const obj = ref({
  nested: { count: 0 },
  arr: ['foo', 'bar']
})

function mutateDeeply() {
  // these will work as expected.
  obj.value.nested.count++
  obj.value.arr.push('baz')
}

DOM Update Timing​

When you mutate reactive state, the DOM is updated automatically. However, it should be noted that the DOM updates are not applied synchronously. Instead, Vue buffers them until the "next tick" in the update cycle to ensure that each component updates only once no matter how many state changes you have made.

To wait for the DOM update to complete after a state change, you can use the nextTick() global API:

import { nextTick } from 'vue'

async function increment() {
  count.value++
  await nextTick()
  // Now the DOM is updated
}

reactive()

There is another way to declare reactive state, with the reactive() API. Unlike a ref which wraps the inner value in a special object, reactive() makes an object itself reactive:

import { reactive } from 'vue'

const state = reactive({ count: 0 })

Usage in template:

template
<button @click="state.count++">
  {{ state.count }}
</button>
Limitations
  • Limited value types: it only works for object types (objects, arrays, and collection types such as Map and Set). It cannot hold primitive types such as string, number or boolean.
  • Cannot replace entire object
  • Not destructure-friendly

Due to these limitations, we recommend using ref() as the primary API for declaring reactive state.

Computed Properties

<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 ref
const publishedBooksMessage = computed(() => {
  return author.books.length > 0 ? 'Yes' : 'No'
})
</script>

<template>
  <p>Has published books:</p>
  <span>{{ publishedBooksMessage }}</span>
</template>

Here we have declared a computed property publishedBooksMessage. The computed() function expects to be passed a getter function, and the returned value is a computed ref. Similar to normal refs, you can access the computed result as publishedBooksMessage.value

Writable Computed

You can create one by providing both a getter and a setter:

<script setup>
import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed({
  // getter
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // setter
  set(newValue) {
    // Note: we are using destructuring assignment syntax here.
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})
</script>
== Class and Style Bindings ==
<pre>
<div :class="{ active: isActive }"></div>

The above syntax means the presence of the active class will be determined by the truthiness of the data property isActive.

<div
  class="static"
  :class="{ active: isActive, 'text-danger': hasError }"
></div>
 
<div :class="[activeClass, errorClass]"></div>

Conditional Rendering

Prefer v-show if you need to toggle something very often, and prefer v-if if the condition is unlikely to change at runtime.

<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>

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

<template v-if="ok">
  <h1>Title</h1>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</template>

<h1 v-show="ok">Hello!</h1>

List Rendering

<li v-for="item in items">
  {{ item.message }}
</li>


<li v-for="(item, index) in items">
    {{ index }} - {{ item.message }}
</li>

<li v-for="(value, key) in myObject">
  {{ key }}: {{ value }}
</li>

<span v-for="n in 10">{{ n }}</span>

<ul>
  <template v-for="item in items">
    <li>{{ item.msg }}</li>
    <li class="divider" role="presentation"></li>
  </template>
</ul>

<template v-for="todo in todos" :key="todo.name">
  <li>{{ todo.name }}</li>
</template>

Event Handling

function say(message) {
  alert(message)
}

<button @click="say('hello')">Say hello</button>
<button @click="say('bye')">Say bye</button>


<form @submit.prevent="onSubmit"></form>
<form @submit.prevent></form>
<a @click.stop="doThis"></a>
<input @keyup.enter="submit" />
<input @keyup.page-down="onPageDown" />

.exact modifier

<!-- this will fire even if Alt or Shift is also pressed -->
<button @click.ctrl="onClick">A</button>

<!-- this will only fire when Ctrl and no other keys are pressed -->
<button @click.ctrl.exact="onCtrlClick">A</button>

<!-- this will only fire when no system modifiers are pressed -->
<button @click.exact="onClick">A</button>

Form Bindings

<input
  :value="text"
  @input="event => text = event.target.value">
 
<input v-model="text">
  • <input> with text types and <textarea> elements use value property and input event;
  • <input type="checkbox"> and <input type="radio"> use checked property and change event;
  • <select> uses value as a prop and change as an event.
<input v-model="message" placeholder="edit me" />

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



const checkedNames = ref([])

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

<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</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>


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

Lifecycle Hooks

onMounted hook can be used to run code after the component has finished the initial rendering and created the DOM nodes:

<script setup>
import { onMounted } from 'vue'

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

There are also other hooks which will be called at different stages of the instance's lifecycle, with the most commonly used being onMounted, onUpdated, and onUnmounted.

Click Here to See Lifecycle Diagram

Options API

State

export default {
  data() {
    return { a: 1 }
  },
  created() {
    console.log(this.a) // 1
    console.log(this.$data) // { a: 1 }
  }
}

export default {
  props: ['size', 'myMessage']
}

export default {
  props: {
    // type check
    height: Number,
    // type check plus other validations
    age: {
      type: Number,
      default: 0,
      required: true,
      validator: (value) => {
        return value >= 0
      }
    }
  }
}

export default {
  data() {
    return { a: 1 }
  },
  computed: {
    // readonly
    aDouble() {
      return this.a * 2
    },
    // writable
    aPlus: {
      get() {
        return this.a + 1
      },
      set(v) {
        this.a = v - 1
      }
    }
  },
}

export default {
  data() {
    return { a: 1 }
  },
  methods: {
    plus() {
      this.a++
    }
  },
}