前置
围绕组合式API进行学习。
环境搭建
创建第一个Vue应用(基于vite)
1.安装并执行create-due(官方脚手架)
npm init vue@latest
不确定是否要开启哪个功能,之间一路no到底。
2.根据以下步骤安装依赖
> cd <your-project-name>
> npm install
> npm run dev
3.**(非必选)**需要将应用发布到生产环境运行下面的命令
npm run build
初步的vue环境就搭建好了。
基础
创建一个应用
这部分是写在main.js中的。
import { createApp } from 'vue'
const app = createApp({
/* 根组件选项 */
})
import { createApp } from 'vue'
// 从一个单文件组件中导入根组件
import App from './App.vue'
const app = createApp(App)
这两种而言,第二种个人用的多一点,创建完之后需要挂载应用。
<div id="app"></div>
app.mount('#app')
模版语法
前置工作:先创建一个vue组件(TemplateSyntax.vue)并加入到App组件中
<!-- App.vue -->
<script setup>
import TemplateSynax from './components/TemplateSyntax.vue'
</script>
<template>
<TemplateSynax></TemplateSynax>
</template>
<!-- TemplateSyntax.vue -->
<script setup></script>
<template></template>
文本插值
语法就是两个大括号:{{ msg }}
<script setup>
const msg = "你好vue3"
</script>
<template>
<span>Message:{{msg}}</span>
</template>
这里的页面就会渲染出:Message:你好vue3
后面就不写script和template这两个标签了。
两个大括号这样没办法直接插html语句,它会将html语句解释成文本,可以用下面这个方法,采用v-html指令。
const rawHtml = '<span style="color: red">你好</span>'
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
属性绑定v-bind指令
v-bind指令可以简写成:
<div v-bind:id="dynamicId"></div>
<div :id="dynamicId"></div>
v-bind
指令指示 Vue 将元素的 id
attribute 与组件的 dynamicId
属性保持一致。如果绑定的值是 null
或者 undefined
,那么该 attribute 将会从渲染的元素上移除。
动态绑定多个值:
const objectOfAttrs = {
id: 'container',
class: 'wrapper'
}
<!--通过不带参数的 v-bind,可以将它们绑定到单个元素上-->
<div v-bind="objectOfAttrs"></div>
使用js表达式
Vue 实际上在所有的数据绑定中都支持完整的 JavaScript 表达式(能够被求值的 JavaScript 代码)
{{count++}}
{{ ok ? 'YES' : 'NO' }}
指令参数和动态参数
**指定参数:**某些指令会需要一个“参数”,在指令名后通过一个冒号隔开做标识。
**动态参数:**在指令参数上也可以使用一个 JavaScript 表达式,需要包含在一对方括号内:
<a v-bind:[attributeName]="url"> ... </a>
举例来说,如果你的组件实例有一个数据属性 attributeName
,其值为 "href"
,那么这个绑定就等价于 v-bind:href
。动态参数中表达式的值应当是一个字符串,或者是 null
。特殊值 null
意为显式移除该绑定。其他非字符串的值会触发警告。动态参数表达式因为某些字符的缘故有一些语法限制,比如空格和引号,在 HTML attribute 名称中都是不合法的。
修饰符
修饰符是以点开头的特殊后缀,表明指令需要以一些特殊的方式被绑定。例如 .prevent
修饰符会告知 v-on
指令对触发的事件调用 event.preventDefault()
响应式基础
声明响应式状态
使用 reactive()
函数创建一个响应式对象或数组,当然还有一个是ref。
reactive 和 ref 都是用来定义响应式数据的 reactive更推荐去定义复杂的数据类型**(对
string
、number
和boolean
无效)** ref 更推荐定义基本类型ref 和 reactive 本质我们可以简单的理解为ref是对reactive的二次包装, ref定义的数据访问的时候要多一个.value
使用ref定义基本数据类型,ref也可以定义数组和对象。
<script setup>
import { reactive } from 'vue'
const state = reactive({ count: 0 })
function increment() {
state.count++
}
</script>
<template>
<button @click="increment">
{{ state.count }}
</button>
</template>
点击按钮count+1,如果没有reactive,count的响应更新不会那么及时(很难受的)。
DOM更新时机
若要等待一个状态改变后的 DOM 更新完成,你可以使用 nextTick() 这个全局 API
import { nextTick } from 'vue'
function increment() {
state.count++
nextTick(() => {
// 访问更新后的 DOM
})
}
计算属性
<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>
在这里定义了一个计算属性 publishedBooksMessage
。computed()
方法期望接收一个 getter 函数,返回值为一个计算属性 ref。和其他一般的 ref 类似,你可以通过 publishedBooksMessage.value
访问计算结果。计算属性 ref 也会在模板中自动解包,因此在模板表达式中引用时无需添加 .value
。页面:Has published books: Yes.
计算属性相对方法的好处
计算属性和方法在结果上是相同的,不同之处在于计算属性值会基于其响应式依赖被缓存。被缓存的意思是我计算过了,下次再访问就不用在计算了(直接访问缓存就可以拿到),但是方法不行,需要重新计算。
类与样式的绑定
条件渲染(v-if/v-show)
v-if
有更高的切换开销,而v-show
有更高的初始渲染开销。因此,如果需要频繁切换,则使用v-show
较好;如果在运行时绑定条件很少改变,则v-if
会更合适。
v-if
v-if
指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回真值时才被渲染。(当awesome为真值,内容才会出现在页面上。)
<h1 v-if="awesome">Vue is awesome!</h1>
v-else
是配合v-if
来用的,另外还有v-else-if
。
补充:当 v-if
和 v-for
同时存在于一个元素上的时候,v-if
会首先被执行。
v-show
v-show
用法和v-if
基本一样。不同之处在于 v-show
会在 DOM 渲染中保留该元素;v-show
仅切换了该元素上名为 display
的 CSS 属性。v-show
不支持在 <template>
元素上使用,也不能和 v-else
搭配使用。
<h1 v-show="ok">Hello!</h1>
列表渲染v-for
基于数组渲染一个列表:
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
<li v-for="item in items">{{ item.message }}</li>
v-for与对象
使用 v-for
来遍历一个对象的所有属性。遍历的顺序会基于对该对象调用 Object.keys()
的返回值来决定。
const myObject = reactive({
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
})
<ul><li v-for="value in myObject">{{ value }}</li></ul>
<li v-for="(value, key, index) in myObject">
{{ index }}. {{ key }}: {{ value }}
</li>
v-for中使用范围值
从1开始的,而不是从0
<span v-for="n in 10">{{ n }}</span>
当它们同时存在于一个节点上时,v-if
比 v-for
的优先级更高。
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo.name }}
</li>
监听事件
v-on
(简写@
)指令来监听DOM事件,并在事件触发时执行对应的 JavaScript。用法如下:
v-on:click="methodName"
事件处理器有:内联事件处理器和方法事件处理器。
内联事件处理器
事件触发时执行js语句。
const count = ref(0)
<button @click="count++">Add 1</button>
<p>Count is: {{ count }}</p>
在内联事件中调用方法
function say(message) {
alert(message)
}
<button @click="say('hello')">Say hello</button>
<button @click="say('bye')">Say bye</button>
在内联事件中访问事件参数(向事件处理器中传入$event
变量,或使用内联箭头函数)
<!-- 使用特殊的 $event 变量 -->
<button @click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
<!-- 使用内联箭头函数 -->
<button @click="(event) => warn('Form cannot be submitted yet.', event)">
Submit
</button>
function warn(message, event) {
// 这里可以访问原生事件
if (event) {
event.preventDefault()
}
alert(message)
}
方法事件处理器
事件触发执行某个方法。
const name = ref('Vue.js')
function greet(event) {
alert(`Hello ${name.value}!`)
// `event` 是 DOM 原生事件
if (event) {
alert(event.target.tagName)
}
}
<!-- `greet` 是上面定义过的方法名 -->
<button @click="greet">Greet</button>
事件修饰符
饰符是用 .
表示的指令后缀。
<!-- 单击事件将停止传递 -->
<a @click.stop="doThis"></a>
<!-- 提交事件将不再重新加载页面 -->
<form @submit.prevent="onSubmit"></form>
<!-- 修饰语可以使用链式书写 -->
<a @click.stop.prevent="doThat"></a>
<!-- 也可以只有修饰符 -->
<form @submit.prevent></form>
<!-- 仅当 event.target 是元素本身时才会触发事件处理器 -->
<!-- 例如:事件处理器不来自子元素 -->
<div @click.self="doThat">...</div>
<!-- 添加事件监听器时,使用 `capture` 捕获模式 -->
<!-- 例如:指向内部元素的事件,在被内部元素处理前,先被外部处理 -->
<div @click.capture="doThis">...</div>
<!-- 点击事件最多被触发一次 -->
<a @click.once="doThis"></a>
<!-- 滚动事件的默认行为 (scrolling) 将立即发生而非等待 `onScroll` 完成 -->
<!-- 以防其中包含 `event.preventDefault()` -->
<div @scroll.passive="onScroll">...</div>
按键修饰符
<!-- 仅在 `key` 为 `Enter` 时调用 `submit` -->
<input @keyup.enter="submit" />
表单输入绑定
v-model
指令实现表单输入绑定。
手动连接值绑定和更改事件监听器可能会很麻烦,如下:
<input :value="text" @input="event => text = event.target.value">
使用v-model指令:
<input v-model="text">
其他基本用法
其他基本用法:https://cn.vuejs.org/guide/essentials/forms.html#radio
生命周期钩子
vue 组件实例在创建时都需要经历一系列的初始化步骤,比如设置好数据侦听,编译模板等。在此过程中,它也会运行被称为生命周期钩子的函数,让开发者有机会在特定阶段运行自己的代码。
注册周期钩子
onMounted
钩子可以用来在组件完成初始渲染并创建 DOM 节点后运行代码:
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
console.log(`the component is now mounted.`)
})
</script>
当调用 onMounted
时,Vue 会自动将回调函数注册到当前正被初始化的组件实例上。这意味着这些钩子应当在组件初始化时被同步注册。例如,请不要这样做:
setTimeout(() => {
onMounted(() => {
// 异步注册时当前组件实例已丢失
// 这将不会正常工作
})
}, 100)
注意这并不意味着对 onMounted
的调用必须放在 setup()
或 <script setup>
内的词法上下文中。onMounted()
也可以在一个外部函数中调用,只要调用栈是同步的,且最终起源自 setup()
就可以。
还有一些其他的钩子函数,具体看文档吧。
生命周期图
图片来自官文。
侦听器
模版引用
组件基础
定义/使用一个组件
xxx.vue
定义了一个组件ButtonCounter并在父组件中使用它:
<script setup>
import ButtonCounter from './ButtonCounter.vue' //导入
</script>
<template>
<h1>Here is a child component!</h1>
<ButtonCounter /> //使用
</template>
props
跨组件属性传递就需要用到props了,比如说我们想要在子组件中访问父属性。举例:
父组件定义变量message,子组件使用v-bind绑定message这个属性,再传到子组件中props做接受。
父组件:
<script setup>
import son from "./son.vue"
const message = "你好props"
</script>
<template>
<div>
<p>子组件</p>
<span>父组件:{{ message }}</span>
<son :msg = "message"></son>
</div>
</template>
子组件:
<script setup>
const props = defineProps(['msg'])
</script>
<template>
<p>msg的值是父组件中的message:{{ msg }}</p>
</template>
单向数据流
所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。
另外,每次父组件更新后,所有的子组件中的 props 都会被更新到最新值,这意味着你不应该在子组件中去更改一个 prop。
props数据校验
defineProps({
// 基础类型检查
// (给出 `null` 和 `undefined` 值则会跳过任何类型检查)
propA: Number,
// 多种可能的类型
propB: [String, Number],
// 必传,且为 String 类型
propC: {
type: String,
required: true
},
// Number 类型的默认值
propD: {
type: Number,
default: 100
},
// 对象类型的默认值
propE: {
type: Object,
// 对象或数组的默认值
// 必须从一个工厂函数返回。
// 该函数接收组件所接收到的原始 prop 作为参数。
default(rawProps) {
return { message: 'hello' }
}
},
// 自定义类型校验函数
propF: {
validator(value) {
// The value must match one of these strings
return ['success', 'warning', 'danger'].includes(value)
}
},
// 函数类型的默认值
propG: {
type: Function,
// 不像对象或数组的默认,这不是一个工厂函数。这会是一个用来作为默认值的函数
default() {
return 'Default function'
}
}
})
事件
$emit
方法触发自定义事件,举例:
<!-- MyComponent -->
<!-- 点击按钮触发加1事件 -->
<button @click="$emit('someEvent')">click me</button>
<!--父组件可以通过 v-on (缩写为 @) 来监听事件:-->
<MyComponent @some-event="callback" />
声明触发的事件
通过 defineEmits()
来声明:
<script setup>
defineEmits(['inFocus', 'submit'])
</script>
<script setup>
const emit = defineEmits(['inFocus', 'submit'])
function buttonClick() {
emit('submit')
}
</script>
声明了两个事件,当触发了这个buttonClick方法后会触发submit事件。
举例:
<!-- 子组件 child.vue -->
<script setup>
const emit = defineEmits(['custom-event'])
const callEvent = () => {
const now = new Date()
emit('custom-event', now)
}
</script>
<template>
<button @click="callEvent">点击</button>
</template>
<!-- 父组件 parent.vue -->
<script setup>
import child from './child.vue'
const handleCustomEvent = (val) => {
console.log(val)
}
</script>
<template>
<child @custom-event="handleCustomEvent"></child>
</template>
页面会渲染出一个点击
按钮,当点击按钮后会触发子组件中callEvent方法进而触发custom-event事件,执行handleCustomEvent方法。callEvent方法抛出了custom-event事件,并传入了一个参数,handleCustomEvent方法接受了val参数,并打印输出。