最近发现,Vue、Preact、Solid、Svelte 官网都有非常好的交互式入门教程,这对快速了解一个框架非常有帮助,正好自己也想了解学习一下,就从 Vue 开始。
Vue 是目前最流行的 3 大框架之一。我在之前的工作中有专门写过,不过后面由于新公司技术栈的原因,改写 React 了。不过依然对 Vue 保持兴趣和好感。
先从 Vue 的单文件组件开始讲起。
单文件组件(Single-File Component,缩写为 SFC)
Vue.js 推荐采用单文件组件方式编写组件。所谓的“单文件组件”,就是将一个组件所需要的模板、脚本和样式都写在一个文件中,达到隔离封装的效果。
当然,单文件组件中甚至只写模板也行,脚本和样式都是可以忽略。
以下面的内容为例(App.vue
):
<template>
<h1>Hello World!</h1>
</template>
效果:
声明式渲染
声明式渲染是 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 但文件组件中:
-
通过 <script setup>
中的的内容来定义 Vue 组件实例,通过<template>
标签声明组件的模板 -
通过 ref
API 声明响应式状态 message,通过返回数据的.value
属性来访问内部值。另外,在组件的<script setup>
块中声明的响应式状态,可以直接在模板中使用。而在模板中访问状态数据时,直接使用 message 即可,Vue 会自动解包 -
有了组件状态,我们就可以通过插入语法 {{}}
(即即双大括号,又称“mustache 语法 ”),向<template>
中组件模板插入状态数据了
因此,上述组件的渲染结果如下:
❝
注意:Vue 还提供了
reactive()
API,不过只适用于对象 (包括数组和内置类型,如Map
和Set
)。❞
当我们修改 message
数据的时候,渲染会同步更新:
import { ref } from 'vue'
const message = ref('Hello World!')
对应结果:
当然,插入语法 {{}}
中的内容并不只限于标识符,我们可以使用任何有效的 JavaScript 表达式。
<h1>{{ message.split('').reverse().join('') }}</h1>
对应结果:
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>
中设定的样式,这个标题最终显示为红色。
实际开发时,由于会大量使用 v-bind
,因此有一个专门的简写语法——:attribute
,效果等同于 v-bind:attribute
。
<template>
<h1 :class="titleClass">Make me red</h1>
</template>
效果与未使用简写语法一样:
事件监听
当然,我们还要处理 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>
效果如下:
我们来分析下代码:
-
首先我们通过 v-on:click
给<button>
绑定了一个点击事件,处理函数是increment
-
处理函数 increment
在<script setup>
块中直接写就行,能在模板中直接使用 -
函数内部我们通过 count.value
的方式访问计数数据,自增加 1 -
模板内使用 {{ count }}
的地方随即同步更新
类似 v-bind
指令,实际开发时,由于 v-on
使用非常频繁,因此有一个专门的简写语法——只用 @event
的形式即可,效果等同于 v-on:event
。
表单绑定
学完 v-bind
和 v-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>
分析下代码:
-
我们通过 @input
监听输入框输入,并将最新输入内容同步给text
状态,在模板的<p>
元素中同步展示text
内容 -
同时,我们通过 :value
的方式,将更新后的值同步给输入框元素
实现效果如下:
在文本框里输入时,会看到 <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>
效果如下:
分析代码:
-
我们通过点击按钮在 true
/false
之间切换awesome
的值 -
使用 v-if
/v-else
指令根据awesome
的取值来决定渲染哪一个<h1>
标签 -
为 true
时,渲染<h1>Vue is awesome!</h1>
-
否则(为 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>
-
我们在 <li>
上使用v-for
指令遍历状态对象 todos。v-for
指令的语法类似for...in
循环 -
同时,我们在遍历过程中,还为 <li>
绑定了一个名为key
的 attribute,值为todo.i
——这是必要的,能确保 Vue 精确的移动并重用每个<li>
,以匹配对应的成员对象在数组中的位置 -
另外,我们还增加了 addTodo
、removeTodo
函数来实现 todos 项目的添加和删除。 -
这里的“ .prevent
”称为事件修饰符,效果等同于event.preventDefault()
,表示禁止<form>
元素的默认提交行为。 -
类似的修饰符还有“ .stop
”,效果等同于event.stopPropagation()
-
addTodo
给了<form>
元素,我们使用@submit.prevent
绑定事件,方法内部通过todos.value.push()
方法添加新的 todo 项 -
removeTodo
给了<button>
,点击按钮时,方法内部通过todos.value.filter()
方法过滤被删除的todo
项
实现效果如下:
计算属性
我们可以使用 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>
效果如下:
功能实现也比较简单,两步就能搞定:
-
首先,根据 hideCompleted.value
返回过滤后的 todo 项目,即这里的计算属性filteredTodos
-
然后,将 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>
效果如下:
除了 mounted
外,还有 created
、updated
等生命周期函数可以使用。
侦听器(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
文件):
// 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>
最终效果:
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>
效果如下:
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>
效果如下:
插槽
除了通过 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 基础的快速学习。为义 Vue 组件实例,通过了简明,整个过程忽略了大量的细节,还需要我们阅读快速上手[1]和官方文档[2]详细地学习学过的所有话题以及其他更多深入的内容。
除此之外,还有很多 demo 案例[3]用来参照学习。
参考资料
快速上手: 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