Vue基礎知識

Ch1ldKing發表於2024-07-10

Attribute v-bind

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

const titleClass = ref('title')
</script>

<template>
  <h1 :class="titleClass">Make me red</h1> <!-- 此處新增一個動態 class 繫結 -->
</template>

<style>
.title {
  color: red;
}
</style>

titleClass是一個類,它指向物件title。把這個類繫結到h1標籤上,在最後為title的顏色屬性賦值。繫結用的是v-bind,簡寫為:
class是規定好的HTML標籤屬性

事件監聽 v-on

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

const count = ref(0)
function increment(){
  count.value--
}
</script>

<template>
  <!-- 使此按鈕生效 -->
  <button @click="increment">Count is: {{ count }}</button>
</template>

把button用v-on:click監聽,簡寫為@,監聽click事件.

表單繫結 v-model

把函式和展示繫結在一起

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

const text = ref('')

function onInput(e) {
  text.value = e.target.value
}
</script>

<template>
  <input :value="text" @input="onInput" placeholder="Type here">
  <p>{{ text }}</p>
</template>

onInput函式和text物件繫結在一起

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

const text = ref('')
</script>

<template>
  <input v-model="text" placeholder="Type here">
  <p>{{ text }}</p>
</template>

條件渲染v-if,v-else

給元件加上邏輯關係來渲染

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

這樣實現有條件的渲染元件

迴圈渲染v-for

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

// 給每個 todo 物件一個唯一的 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>
  <form @submit.prevent="addTodo">
    <input v-model="newTodo" required placeholder="new todo">
    <button>Add Todo</button>
  </form>
  <ul>
    <li v-for="todo in todos" :key="todo.id">
      {{ todo.text }}
      <button @click="removeTodo(todo)">X</button>
    </li>
  </ul>
</template>

對todos進行迴圈渲染,並且把標籤<li>的key繫結到id上

計算屬性computed()

<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>
  <form @submit.prevent="addTodo">
    <input v-model="newTodo" required placeholder="new todo">
    <button>Add Todo</button>
  </form>
  <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>

我個人的理解是,本來要隱藏這個done的任務,是需要一個函式來計算是不是完成了,比如

function filteredTodos() { return hideCompleted.value ? todos.value.filter((t) => !t.done) : todos.value }

這樣的話,在每次我點選隱藏按鈕的時候,都要計算一遍過濾的陣列
其實這個過濾器filter,並沒有刪掉陣列中的元素,只是返回了一個經過過濾的子陣列
如果我採用computed(),這實質上是一個屬性,也就是一種vue提供的資料格式。

const filteredTodos = computed(() => {
  return hideCompleted.value
    ? todos.value.filter((t) => !t.done)
    : todos.value
})

用computed()的話,來定義一個量,其括號裡面放的其實還是函式內的語句,但問題在於

  1. 這個不是函式,而是一個量,像const int b;中的b
  2. 當這其中的值不變化,不需要重新呼叫computed中的函式
    What that means? 意思就是如果我採用一個函式,每次我點選隱藏按鈕的時候,都要計算一遍過濾的陣列。而採用computed就不需要了,如果我沒有改變任務是否完成(即todos陣列的值),我點選按鈕得到的值是上一次快取下來的

模板引用

<p ref="pElementRef">hello</p>

宣告一個指向DOM元素的ref,這是一種特殊的ref,要想訪問它,我們需要宣告一個同名的ref

const pElementRef = ref(null)

使用null進行初始化,是因為<script setup>執行的時候,後面模板中的DOM還未渲染。因此要採用函式來使這部分程式碼在元件掛載之後再執行

生命週期

使用onMounted()來實現在元件掛載之後再執行其內部的程式碼

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

const pElementRef = ref(null)

onMounted(() => {
  pElementRef.value.textContent = 'mounted!'
})
</script>

<template>
  <p ref="pElementRef">Hello</p>
</template>

在這裡,textContent是一種DOM屬性,是規定好的,其他規定好的https://www.cnblogs.com/Ch1ldKing/p/18293034

偵聽器watch()

<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++" :disabled="!todoData">Fetch next todo</button>
  <p v-if="!todoData">Loading...</p>
  <pre v-else>{{ todoData }}</pre>
</template>

對於這段程式碼,watch()在todoId變化時呼叫了fetchData(),實現監聽。
此處,fetchData()的作用是每次清空todoData的值,並且抓取這段傳輸的json資料顯示出來,並賦值給todoData

子元件

vue的nb之處之一在於巢狀元件
比如我有一個檔案ChildComp.vue,是我寫好的一個元件
我想在App.vue中呼叫它作為一個小元件

<!-- ChildComp.vue -->
<template>
  <h2>A Child Component!</h2>
</template>
<!-- App.vue -->
<script setup>
import ChildComp from './ChildComp.vue'
</script>

<template>
  <ChildComp />
</template>

渲染App.vue的效果:image.png

子元件的引數Props

和其他元件(button,form)等一樣,vue的子元件也支援attributes,可以用v-bind進行繫結。不同的是,子元件有何attributes需要自己定義。
attributes是指,比如button有type,img有src,是規定好的HTML標籤屬性

<!-- ChildComp.vue -->
<script setup>
const props = defineProps({
  msg: String
})
</script>

<template>
  <h2>{{ msg || 'No props passed yet' }}</h2>
</template>

props中就是定義好的屬性,此處有一個msg
屬性是對於呼叫這個子元件的父元件而言的。在父元件眼裡,這個msg是一個屬性。而對於子元件內部,msg是一個引數(變數)

<!-- App.vue -->
<script setup>
import { ref } from 'vue'
import ChildComp from './ChildComp.vue'

const greeting = ref('Hello from parent')
</script>

<template>
  <ChildComp :msg="greeting"/>
</template>

此處透過:(即v-bind)把msg屬性賦值為greeting。如果子元件中沒有msg,就會報錯

子元件定義事件Emit

和其他元件(button,form)等一樣,vue的子元件也支援事件,可以用v-on進行繫結。不同的是,子元件有何事件需要自己定義。
事件是指,比如button有click,form有submit,是規定好的HTML響應事件

<!-- ChildComp.vue -->
<script setup>
const emit = defineEmits(['response'])

emit('response', 'hello from child')
</script>

<template>
  <h2>Child component</h2>
</template>

defineEmits是規定的函式,注意到其中的引數其實是一個陣列,意味著你可以定義多個事件,like this

<!-- ChildComp.vue -->
<script setup>
const emit = defineEmits(['response','update'])

emit('response', 'hello from child')
emit('update', 'hello')
</script>

<template>
  <h2>Child component</h2>
</template>

然後在父元件中監聽事件(不是watch,是v-on)

<!-- App.vue -->
<script setup>
import { ref } from 'vue'
import ChildComp from './ChildComp.vue'

const childMsg = ref('No child msg yet')
</script>

<template>
  <ChildComp @update="(msg) => childMsg = msg" />
  <p>{{ childMsg }}</p>
</template>

插槽slot

在父元件中,呼叫子元件的時候插入內容

<!-- App.vue -->
<script setup>
import { ref } from 'vue'
import ChildComp from './ChildComp.vue'

const msg = ref('from parent')
</script>

<template>
  <ChildComp>Message: {{ msg }}</ChildComp>
</template>

這時候,其實如同<p>text</p>這種,在中間顯示一些值。但我們自己寫的元件需要在子元件中進行定義。

<!-- ChildComp.vue -->
<template>
  <slot>Fallback content</slot>
</template>