Vue.js 3.0:入门教程(组合式 API)

最近发现,Vue、Preact、Solid、Svelte 官网都有非常好的交互式入门教程,这对快速了解一个框架非常有帮助,正好自己也想了解学习一下,就从 Vue 开始。

Vue.js 3.0:入门教程(组合式 API)

Vue 是目前最流行的 3 大框架之一。我在之前的工作中有专门写过,不过后面由于新公司技术栈的原因,改写 React 了。不过依然对 Vue 保持兴趣和好感。

先从 Vue 的单文件组件开始讲起。

单文件组件(Single-File Component,缩写为 SFC)

Vue.js 推荐采用单文件组件方式编写组件。所谓的“单文件组件”,就是将一个组件所需要的模板、脚本和样式都写在一个文件中,达到隔离封装的效果。

当然,单文件组件中甚至只写模板也行,脚本和样式都是可以忽略。

以下面的内容为例(App.vue):

<template>
<h1>Hello World!</h1>
</template>

效果:

Vue.js 3.0:入门教程(组合式 API)

声明式渲染

声明式渲染是 Vue 最核心的功能。所谓“声明式渲染”就是将 JavaScript 变量映射成渲染页面时用到的数据,页面会跟随 JavaScript 变量的修改而产生相应地更新。

我们把上述 JavaScript 变量被称为“响应式状态”,把“页面跟随 JavaScript 变量的修改而更新”称为“响应式更新”。

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

// 组件逻辑
// 此处声明一些响应式状态
const message = ref('Hello World')
console.log(message.value)
</script>

<template>
<h1>{{message}}</h1>
</template>

在 Vue 但文件组件中:

  1. 通过 <script setup> 中的的内容来定义 Vue 组件实例,通过 <template> 标签声明组件的模板
  2. 通过 ref API 声明响应式状态 message,通过返回数据的 .value 属性来访问内部值。另外,在组件的 <script setup> 块中声明的响应式状态,可以直接在模板中使用。而在模板中访问状态数据时,直接使用 message 即可,Vue 会自动解包
  3. 有了组件状态,我们就可以通过插入语法 {{}}(即即双大括号,又称“mustache 语法 ”),向 <template> 中组件模板插入状态数据了

因此,上述组件的渲染结果如下:

Vue.js 3.0:入门教程(组合式 API)

注意:Vue 还提供了 reactive() API,不过只适用于对象 (包括数组和内置类型,如 MapSet)。

当我们修改 message 数据的时候,渲染会同步更新:

import { ref } from 'vue'

const message = ref('Hello World!')

对应结果:

Vue.js 3.0:入门教程(组合式 API)

当然,插入语法 {{}} 中的内容并不只限于标识符,我们可以使用任何有效的 JavaScript 表达式。

<h1>{{ message.split('').reverse().join('') }}</h1>

对应结果:

Vue.js 3.0:入门教程(组合式 API)

Attribute 绑定

Vue 中,我们还能通过 v-bind 指令给 HTML 标签绑定动态值。指令是由 v- 开头的一种特殊 attribute,是 Vue 模板语法的一部分。

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

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

<template>
<h1 v-bind:class="titleClass">Make me red</h1> <!-- 此处添加一个动态 class 绑定 -->
</template>

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

我们给 <h1> 使用 v-bind 指令,冒号后面的部分 (:class) 是指令的“参数”。本例中,我们将 class attribute  的值绑定到 titleClass 这个状态变量,如此,元素的 class attribute 将与组件状态里的 titleClass 属性保持同步。

模板最终的渲染结果:

<h1 class="title">Make me red</h1>

根据在 <style> 中设定的样式,这个标题最终显示为红色。

Vue.js 3.0:入门教程(组合式 API)

实际开发时,由于会大量使用 v-bind,因此有一个专门的简写语法——:attribute,效果等同于 v-bind:attribute

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

效果与未使用简写语法一样:

Vue.js 3.0:入门教程(组合式 API)

事件监听

当然,我们还要处理 DOM 事件。Vue 中使用 v-on 指令监听 DOM 事件:

以下是一个计数器的例子:

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

const count = ref(0)

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

<template>
<!-- 使此按钮生效 -->
<button v-on:click="increment">count is: {{ count }}</button>
</template>

效果如下:

Vue.js 3.0:入门教程(组合式 API)

我们来分析下代码:

  1. 首先我们通过 v-on:click<button> 绑定了一个点击事件,处理函数是 increment
  2. 处理函数 increment<script setup> 块中直接写就行,能在模板中直接使用
    1. 函数内部我们通过 count.value 的方式访问计数数据,自增加 1
    2. 模板内使用 {{ count }} 的地方随即同步更新

类似 v-bind 指令,实际开发时,由于 v-on 使用非常频繁,因此有一个专门的简写语法——只用 @event 的形式即可,效果等同于 v-on:event

表单绑定

学完 v-bindv-on,我们就能表单元素上创建双向绑定了:

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

分析下代码:

  1. 我们通过 @input 监听输入框输入,并将最新输入内容同步给 text 状态,在模板的 <p> 元素中同步展示 text 内容
  2. 同时,我们通过 :value 的方式,将更新后的值同步给输入框元素

实现效果如下:

Vue.js 3.0:入门教程(组合式 API)

在文本框里输入时,会看到 <p> 里的文本也随着你的输入更新了。

为了简化双向绑定,Vue 提供了一个 v-model 指令来作为上述操作的语法糖:

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

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

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

效果一样,但是我们无需再声明事件处理函数 onInput 了!

另外,v-model 不仅支持文本输入框,也支持诸如多选框、单选框、下拉框之类的输入类型。

条件渲染

我们还可以使用 v-if/v-else 指令来有条件地渲染元素,判断方式与 JS  中 if/else 语句一样——真值 (Truthy) 就走 if,假值 (Falsy)就走 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>

效果如下:

Vue.js 3.0:入门教程(组合式 API)

分析代码:

  1. 我们通过点击按钮在 true/false 之间切换 awesome 的值
  2. 使用 v-if/v-else 指令根据 awesome 的取值来决定渲染哪一个 <h1> 标签
    1. true 时,渲染 <h1>Vue is awesome!</h1>
    2. 否则(为 false),渲染 <h1>Oh no 😢</h1>

当然,如果判断分支多余 2 条,还可以使用 v-if-else 指令。


列表渲染

我们可以使用 v-for 指令来渲染一个数组列表。来看下面一个 TODO demo——实现了增加和删除 todo 的简单功能。

<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">
<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>
  1. 我们在 <li> 上使用 v-for 指令遍历状态对象 todos。v-for 指令的语法类似 for...in 循环
  2. 同时,我们在遍历过程中,还为 <li> 绑定了一个名为 key 的 attribute,值为 todo.i——这是必要的,能确保 Vue 精确的移动并重用每个 <li>,以匹配对应的成员对象在数组中的位置
  3. 另外,我们还增加了 addTodoremoveTodo 函数来实现 todos 项目的添加和删除。
    1. 这里的“.prevent”称为事件修饰符,效果等同于 event.preventDefault() ,表示禁止 <form> 元素的默认提交行为。
    2. 类似的修饰符还有“.stop”,效果等同于 event.stopPropagation()
    3. addTodo 给了 <form> 元素,我们使用 @submit.prevent 绑定事件,方法内部通过 todos.value.push() 方法添加新的 todo 项
    4. removeTodo 给了 <button>,点击按钮时,方法内部通过 todos.value.filter() 方法过滤被删除的 todo

实现效果如下:

Vue.js 3.0:入门教程(组合式 API)

计算属性

我们可以使用 computed() API 声明一个响应式的属性,它的值由其他属性计算而来,这类属性称为“计算属性”。

计算属性的使用方式跟普通响应属性(通过 ref API 返回的状态数据)一样。

我们拿上面的 TODOs 应用为例,创建一个计算属性 filteredTodos,当我们点击“隐藏已完成”按钮时,不展示已完成的 todo 项目。

<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 filterdTodos = computed(() => {
return todos.value.filter(t => {
return hideCompleted.value
? t.done === false
: true
})
})

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">
<button>Add Todo</button>
</form>
<ul>
<li v-for="todo in filterdTodos" :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>

效果如下:

Vue.js 3.0:入门教程(组合式 API)

功能实现也比较简单,两步就能搞定:

  1. 首先,根据 hideCompleted.value 返回过滤后的 todo 项目,即这里的计算属性 filteredTodos
  2. 然后,将 v-for 指令中的 todos 替换成 filteredTodos 即可

生命周期和模板引用

有时我们会有手动操作 DOM 的需求。这个时候就要用到模板引用了,这是通过一个特殊的 ref attribute 来实现的。

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

ref 是 Vue 控制的一个特殊 attribute,我们使用 ref API 绑定这个 attribute,就能获得对应的 DOM 实例。因此,这里就可以通过 pElementRef.value 访问 <p> 所对应的那个 DOM 实例了。

不过需要注意的是,我们需要在 onMounted 生命周期函数调用后,也就是组件挂载到文档之后,才能访问。

import { onMounted } from 'vue'

onMounted(() => {
// 此时组件已经挂载。
})

下面举一个例子:

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

const pElementRef = ref(null)

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

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

效果如下:

Vue.js 3.0:入门教程(组合式 API)

除了 mounted 外,还有 createdupdated 等生命周期函数可以使用。

侦听器(watcher)

有时候,某些状态数据改变后,需要响应性地执行一些“副作用”。比如:当一个数字改变时将其输出到控制台。这个就可以通过通过侦听器来实现。

import { ref, watch } from 'vue'

const count = ref(0)

watch(count, (newCount) => {
// 没错,console.log() 是一个副作用
console.log(`new count is: ${newCount}`)
})

侦听器是通过在 watch API 添加的,watch API 的第一个参数对应要侦听的属性(也就是 count),第二个参数就是处理函数了。这里,我们在处理函数内部使用 console.log 打印出了新的计数。

再举一个例子,先看下代码:

<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()
}

watch(() => {
fetchData()
})

fetchData()
</script>

<template>
<p>Todo id: {{ todoId }}</p>
<button @click="todoId++">Fetch next todo</button>
<p v-if="!todoData">Loading...</p>
<pre v-else>{{ todoData }}</pre>
</template>

我们希望在每一次 todoId 发生变化的时候,触发一次 fetchData 方法调用,获取信的数据并展示。该如何实现呢?

不卖关子了:我们只需要使用 watch API 增加对 todoId 状态的监听,在处理函数内部调用 fetchData() 就行了 。

export default {
// ...
watch: {
todoId(newTodoId) {
this.fetchData()
}
},
// ...
}

演示效果:

Vue.js 3.0:入门教程(组合式 API)

组件

目前为止,我们只使用了单个组件。而真实项目中,一张页面往往是由很多嵌套组件构成的。接下来介绍,如何在当前组件中,引入子组件(当前组件就自然成为“父组件”了)。

首先引入子组件(单独的 .vue 文件):

// ChildComp.vue
<template>
  <h2>A Child Component!</h2>
</template>

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

<template>
  <!-- render child component -->
  <ChildComp />
</template>

在当前组件的直接 import 子组件 ChildComp,然后就能在模板内通过 <ChildComp /> 方式引用了。

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

<template>
<!-- render child component -->
<ChildComp />
</template>

最终效果:

Vue.js 3.0:入门教程(组合式 API)

Props

除了存在父子组件嵌套关系,有时候也有从父组件向子组件传递数据的需求,这个时候就要用到 Props 了。那如何使用 Props 呢?

首先,需要在子组件中通过 defineProps API 声明它所能接受的 props 定义:

注意:defineProps() 是一个编译时宏,并不需要导入。

<script setup>
const props = defineProps({
msg: String
})
</script>

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

当前组件能接收一个字符串 prop 属性 msg

而在父组件中,可以像声明 HTML attributes 一样传递 props。若要传递动态值,也可以使用 v-bind 语法:

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

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

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

效果如下:

Vue.js 3.0:入门教程(组合式 API)

Emits

除了接收 props,子组件还可以向父组件触发事件,这里就要用 Emits 了。

首先,要在子组件中通过 defineEmits API 声明能触发的事件类型,然后通过再通过返回值 emit,使用 emit('eventname') 方式就能触发事件了。

<script setup>
const emit = defineEmits(['response'])

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

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


以上,我们为子组件注册了一个 response 事件,并在组件刚创建完成时,触发该事件。

父组件可以使用 v-on:response 来监听子组件触发的事件:

<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.js 3.0:入门教程(组合式 API)

插槽

除了通过 props 传递数据外,父组件还可以通过插槽 (slots) 将模板片段传递给子组件:

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

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

<template>
<ChildComp></ChildComp>
</template>

在子组件中,可以使用 <slot> 元素作为插槽出口 (slot outlet) ,渲染父组件中的插槽内容。

<template>
<slot>Fallback content</slot>
</template>

<slot> 插口中的内容将被当作“默认”内容:它会在父组件没有传递任何插槽内容时显示。

Vue.js 3.0:入门教程(组合式 API)

至此,我们完成了 Vue 基础的快速学习。为义 Vue 组件实例,通过了简明,整个过程忽略了大量的细节,还需要我们阅读快速上手[1]官方文档[2]详细地学习学过的所有话题以及其他更多深入的内容。

除此之外,还有很多 demo 案例[3]用来参照学习。

参考资料

[1]

快速上手: https://cn.vuejs.org/guide/quick-start.html

[2]

官方文档: https://cn.vuejs.org/guide/essentials/application.html

[3]

很多 demo 案例: https://cn.vuejs.org/examples/


原文始发于微信公众号(写代码的宝哥):Vue.js 3.0:入门教程(组合式 API)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/243725.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!