【Vue】Vue 快速教程

Vue tutorial

参考:教程 | Vue.js (vuejs.org)
该教程需要前置知识:HTML, CSS, JavaScript

学习前置知识,你可以去 MDN

Vue framework 是一个 JavaScript framework,以下简称 Vue,下面是它的特点

  • 声明式渲染(Declarative Rendering):即声明 JavaScript 对象,改变对象状态来更改 HTML,这个过程由 Vue 完成
  • 响应式(Reactivity):JavaScript 的对象状态改变会马上反映到 DOM(不知道 DOM 的去查 MDN 文档)

Declarative Rendering and Reactivity

Vue 实现了:

text 复制代码
JavaScript Obejct <-> Vue <-> DOM

但是 Vue 显然是利用 JavaScript 机制,那就是 Proxy,Proxy 可以实现

text 复制代码
JavaScript Object <-> Proxy <-> DOM

所以 Vue 把 Proxy 改造后封装成了 reactive(),调用这个 API 会返回一个特殊的对象,称之为响应式对象(reactive object)

  • reactive()
javascript 复制代码
import { reactive } from 'vue'

const counter = reactive({
  count: 0
})

console.log(counter.count) // 0
counter.count++

但是 reactive() 参数只能是对象(还有数组和内置类型),Vue 又把 reactive() 改造封装成了 ref(),它也会返回一个响应式对象,并且带一个 .value property,只不过它的参数可以填写值。

  • ref()
javascript 复制代码
import { ref } from 'vue'

const message = ref('Hello World!')

console.log(message.value) // "Hello World!"
message.value = 'Changed'

以上给 ref()reactive() 填写参数得到响应式对象的过程,就被成为数据绑定(data binding)。

Template syntax

在这里复习一下 HTML element 和 attribute 概念

Anatomy of an HTML element

这是 HTML element

这是 HTML attribute

在此之中,Class 为 attribute name,editor-note 为 attribute value

Vue 自己创造了一套 template language。最基本的数据绑定是文本插值(Text Interpolation),它可以改变 element 的 content,像这样

vue 复制代码
<span>{{ message }}</span>

这种语法被成为 "Mustache"语法 (即双大括号)。再结合上节讲到的 reactive object,我们可以这样写

javascript 复制代码
import { ref } from 'vue'

const message = ref('Hello World!')

效果是这样的

可能你会有些疑问,为什么不是写 {``{message.value}},因为它是 top-level property,会自动解包(unwrapping)

javascript 复制代码
const object = { id: ref(1) }

比如这个里面,id 就不是 top-level property,如果你这么写

html 复制代码
{{ object.id + 1 }}

渲染的结果将是 [object Object]1

你需要手动解包,才会渲染出 2

复制代码
{{ object.id.value + 1 }}

Directive

v-xxx 就是一种 attribute,在它的 template language 中被称为 directive

v-bind

Attribute bind

在 Vue 中,Mustache 语法只能用于文本插值来改变 element content,没法儿操作 element attribute。而且 element attribute 是静态的,为了给 element attribute 绑定一个动态值,需要使用 Vue 的 v-bind directive

html 复制代码
<div v-bind:id="dynamicId"></div>

冒号后面的 id 被称为 directive 的参数(argument),dynamicId 则为参数值,它会和响应式对象的 property 同步。它可以简写为

vue 复制代码
<div :id="dynamicId"></div>

例子:

vue 复制代码
<script setup>
import { ref } from 'vue'

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

<template>
  <h1 :class="titleClass">Make me red</h1>
</template>

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

v-on

Event Listen

可以通过 v-on 来监听 DOM event

javascript 复制代码
<button v-on:click="increment">{{ count }}</button>

简写

javascript 复制代码
<button @click="increment">{{ count }}</button>

点击 button 会触发 increment() 这个函数。

例子:

vue 复制代码
<script setup>
import { ref } from 'vue'

const count = ref(0)

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

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

v-model

Form bind

同时使用 v-bindv-on 对表单(form)进行绑定和监听

vue 复制代码
<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>

啊,这么写实在太麻烦,所以 vue 提供了 v-model。当然,最好看一下它支持哪些 element。

vue 复制代码
<script setup>
import { ref } from 'vue'

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

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

v-if and v-else

Conditional Rendering

v-ifv-else 可以根据条件来决定 element 是否在 DOM 中。

例子:

vue 复制代码
<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>

改变 awesome 的值来显示 "Vue is awesome!" 和 "Oh no 😢"。

v-for

当你想写一个列表时,一个个写列表的 element 实在太累了,如果有 1000 个那不就完蛋了,所以 v-for 可以通过循环,直接渲染出列表(当然,你得给相应的数据)

vue 复制代码
<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>

computed()

template 里面可以写这种计算表达式

vue 复制代码
<p>Has published books:</p>
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
javascript 复制代码
const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

但是这种计算写在 template 里是真的不好理解,个人感觉在结构上 View 里不能有逻辑,所以尽量不要这么写。

所以我们可以用 computed(),这样

vue 复制代码
<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'
  ]
})

// 一个计算属性 ref
const publishedBooksMessage = computed(() => {
  return author.books.length > 0 ? 'Yes' : 'No'
})
</script>

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

它和 method() 最大区别在于,它是只有在响应式对象更新时才会重新被调用和计算,否则就会直接返回缓存值 ,即 books 不变,重渲染(比如页面刷新,更新)时 computed() 不会被调用,而 method() 则会。插嘴一句 method(),重渲染时总会被调用。

Lifecycle and Template Refs

手动用 JavaScript 操作 DOM 是一件苦差事,所以我们用 vue 来帮忙,但是有时我们不得不操作 DOM,这个时候我们就得使用模板引用(template ref)

这个时候就要使用 ref attribute

vue 复制代码
<p ref="pElementRef">hello</p>

如果要访问这个 ref,我们需要声明(declare)ref 并初始化

javascript 复制代码
const pElementRef = ref(null)

注意我们使用给 ref 的 argument 为 null,这是因为<script setup> 执行时,DOM 还没有初始化,template ref 只能在挂在(mount)后访问,所以我们可以使用生命周期钩子(lifecycle hook)比如 onMounted(),关于 lifecycle 请看生命周期图示

例子:

vue 复制代码
<script setup>
import { ref, onMounted } from 'vue'

const pElementRef = ref(null)

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

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

watch()

watch() 可以监察一个 ref,并触发一个 callback function,比如下面的例子就是监察 todoId,触发 fetchData

vue 复制代码
<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>

Component

Vue application 常常是多个 component 嵌套创建,所以就有 parent component 包含 child component

如果要使用 child component,就需要导入它

vue 复制代码
import ChildComp from './ChildComp.vue'

使用 child component

vue 复制代码
<ChildComp />

例子:

vue 复制代码
<!--App.vue-->

<script setup>
import ChildComp from './ChildComp.vue'
</script>

<template>
  <ChildComp />
</template>

Props

child component 可以通过 props 从 parent component 获取动态数据

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

注意,defineProps() 是一个 runtime marcro,不需要导入。

这样,msg 就可以在 child component 的 <template> 中使用

vue 复制代码
<template>
  <h2>{{ msg || 'No props passed yet' }}</h2>
</template>

而 parent component 则可以用 v-bind 传递数据

vue 复制代码
<!--App.vue-->
<ChildComp :msg="greeting" />

例子:

vue 复制代码
<!--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>
vue 复制代码
<!--ChildComp.vue-->
<script setup>
const props = defineProps({
  msg: String
})
</script>

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

child component 中采用的 "runtime declaration",还有一种是如果你用 typescript,需要采用 "type-based declaration",具体看官方文档。

Emits

child component 可以向 parent component 传 event,emit() 中,第一个 argument 是 event name,其他的会传给 event listener。

parent component 可以通过 v-on 监听 child-emitted event,并且可以将额外的 argument 赋值给 local state

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

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

<template>
  <ChildComp @response="(msg) => childMsg = msg" />
  <p>{{ childMsg }}</p>
</template>
vue 复制代码
<!--ChildComp.vue-->
<script setup>
const emit = defineEmits(['response'])

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

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

Solots

除了 props,parent component 可以将 template 片段传给 child component,而在 child component,则可以使用 <slot> 来显示片段的内容。

vue 复制代码
<!--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>
vue 复制代码
<template>
  <slot>Fallback content</slot>
</template>

以上 parent component 中,<ChildComp> 的内容会传给 child component 中的 <slot>,最后渲染在 parent component 上。

Essential

Create application

application instance and root component

Vue application 通常是通过 createApp 来创建一个 application instance

createApp 的参数则被称为 root component,而且其他 component 则作为 root 的 child component,所以 vue application 是由 root component 和 child component 组成的。

一般这种创建代码都在 <project-name>/src/main.js

javascript 复制代码
<!--main.js-->
import { createApp } from 'vue'
// 导入一个单组件
import App from './App.vue'
// 将这个单组件作为根组件
const app = createApp(App)

一个 Todo application 的例子

复制代码
App (root component)
├─ TodoList
│  └─ TodoItem
│     ├─ TodoDeleteButton
│     └─ TodoEditButton
└─ TodoFooter
   ├─ TodoClearButton
   └─ TodoStatistics

mount application

application instance 必须调用 mount() 才能渲染,而必须的 argument 则为 DOM 的 element 或者 CSS selector

在 vue project 中一般在 <project-name>/index.html

html 复制代码
<div id="app"></div>

注意 mount() 应该始终在应用配置完成后调用,简单点儿说就是最后调用。

Applicaiton configuration

application instance 会提供一个 config object,这样就可以配置 vue app,比如 app-level option,capture error

javascript 复制代码
app.config.errorHandler = (err) => {
  /* 处理错误 */
}

或者 component registration

比如 global registration

javascript 复制代码
app.component('TodoDeleteButton', TodoDeleteButton)

其他细节清参考 vue 文档。

Component registration

如果要使用 component,则必须是 registered

Global registration

使用 .component() method:

javascript 复制代码
import { createApp } from 'vue'

const app = createApp({})

app.component(
  // the registered name
  'MyComponent',
  // the implementation
  {
    /* ... */
  }
)

如果使用 SFC,则

javascript 复制代码
import MyComponent from './App.vue'

app.component('MyComponent', MyComponent)

Local registration

使用 component option

javascript 复制代码
import ComponentA from './ComponentA.js'

export default {
  components: {
    ComponentA
  },
  setup() {
    // ...
  }
}

使用 SFC

vue 复制代码
<script setup>
import ComponentA from './ComponentA.vue'
</script>

<template>
  <ComponentA />
</template>

Toolchain

当你创建一个 vue project 时,手动新建目录和文件是很麻烦的事情,所有我们有项目脚手架(Project Scaffolding)来自动创建 project 基本的目录和文件。

Vite

尤雨溪开发的 build tool,支持 SFC,通过 Vite 创建项目:

bash 复制代码
npm create vue@latest

Vue CLI

基于 webpack 的 build tool,但现在是维护状态,建议使用 Vite。

相关推荐
崔庆才丨静觅2 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅3 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅3 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅4 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊4 小时前
jwt介绍
前端
爱敲代码的小鱼4 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax