前言
本文介绍前端Vue
框架,先从npm
工具创建的Vue
项目开始,对项目结构的一些文件用途进行说明,随后对Vue
文件编写所用的两种风格(选项式API和组合式API风格)做了区分,同时对编写代码中常见的生命周期钩子函数做了一些概述,最后对Vue
常见的内置指令包括内容渲染、条件渲染、列表渲染、属性绑定、表单绑定以及DOM
的节点引用进行了详细介绍
Vue简介
Vue
(发音为 /vjuː/,类似 view) 一个用于构建用户界面的JavaScript
框架,基于标准的HTML
、CSS
、JavaScript
构建
Vue
的两个核心功能,一是声明式渲染,基于HTML提供拓展了一套模版语法,以及声明式地描述了最终输出的HTML与JavaScript状态之间的关系;二是响应式更新,Vue自动跟踪JavaScript状态并在其状态变化时响应式更新DOM,通过Vue提供的一套声明式组件化的编程模型,可以高效地开发用户界面
本文介绍的内容基于Vue 3.x 版本,需要注意的是Vue 2.0 发布于2016年,Vue 2最终版本为 2.7.16,该补丁版本包含一些对 2.7 功能的最终修复,并改进了与 Vue 3 的类型一致性,Vue 2 已于 2023 年 12 月 31 日达到终止支持时间,因此新项目推荐采用Vue 3.x 版本
Vue项目创建
前提条件
采用Vue
官方脚手架创建Vue项目之前,确保本地有Node.js
运行环境,对于最新的Vue3.x
需先安装 18.3 或更高版本的 Node.js
安装Node.js
,以下载得到的node-v20.15.1-x64.msi
为例,默认安装到C:\Program Files\nodejs\
目录
若此前安装过,用node -v
命令检查已安装版本以避免重复安装,为了方便使用Node.js
提供的npm
、npx
、node
工具命令,安装时注意勾选将其添加到环境变量,若Node.js
忘记勾选可在安装后手动将安装目录添加Path
环境变量
npm创建Vue项目
安装好Node.js
之后,确保当前工作目录是要创建项目的目录,然后使用以下命令创建一个Vue 3.x
项目
npm create vue@latest
命令执行过程中会提示输入项目名称,执行过程如下:
D:\BigFile\VSCode>npm create vue@latest
Need to install the following packages:
create-vue@3.10.4
Ok to proceed? (y)
> npx
> create-vue
Vue.js - The Progressive JavaScript Framework
√ 请输入项目名称: ... vue3-simple-app # vue项目的名称,一般为小写英文的连字符形式
√ 是否使用 TypeScript 语法? ... 否 / 是 # TypeScript 是 JavaScript 的超集,增加了类型检查和其他功能,可增强代码的可维护性和可读性
√ 是否启用 JSX 支持? ... 否 / 是 # JSX 是一种语法扩展,可以在 JavaScript 中编写类似 XML 的代码,通常用于 React,启用则可在 Vue 组件中使用 JSX 语法编写模板
√ 是否引入 Vue Router 进行单页面应用开发? ... 否 / 是 # Vue Router 是 Vue.js 的官方路由管理器,用于构建单页面应用(SPA)
√ 是否引入 Pinia 用于状态管理? ... 否 / 是 # Pinia 是 Vue.js 的状态管理库,类似于 Vuex,但更加现代和简洁
√ 是否引入 Vitest 用于单元测试? ... 否 / 是 # Vitest 是一个快速的单元测试框架,专为 Vite 项目设计
√ 是否要引入一款端到端(End to End)测试工具? » Cypress # 端到端测试工具用于模拟用户操作,测试整个应用的功能
不需要
Cypress
Nightwatch
Playwright
√ 是否引入 ESLint 用于代码质量检测? ... 否 / 是 # ESLint 是一个静态代码分析工具,用于识别和修复代码中的问题
√ 是否引入 Vue DevTools 7 扩展用于调试? (试验阶段) ... 否 / 是 # Vue DevTools 是一个浏览器扩展,用于调试 Vue.js 应用
正在初始化项目 D:\BigFile\VSCode\vue3-simple-app...
项目初始化完成,可执行以下命令:
cd vue3-simple-app # 切换到新创建的项目
npm install # 启动之前先用该命令安装依赖
npm run dev # 启动服务,该命令在package.json文件的scripts属性配置,指定了一个构建工具vite
# Vite 是一个轻量级的、速度极快的构建工具,作者为Vue的尤雨溪,在Vue2.7版本开始支持该工具,之前版本使用的 Vue CLI 是基于 Webpack 的 Vue 工具链,现已经处于维护模式
Vue项目文件结构
npm
创建一个Vue 3.x
项目,默认提供一个基本的目录结构、并给出了一些默认配置
D:\BigFile\VSCode\vue3-simple-app>tree /F /A
卷 DATA 的文件夹 PATH 列表
卷序列号为 3C2C-987F
D:.
| .eslintrc.cjs
| .gitignore # 指定哪些文件和目录不应被 Git 版本控制系统跟踪
| .prettierrc.json # 用于格式化工具Prettier 代码格式化工具配置,可以统一代码风格
| cypress.config.ts # 创建项目时选择了测试工具Cypress,则会有该文件,用于配置测试文件位置、环境、浏览器等
| env.d.ts # 用于定义TypeScript环境变量的类型声明,以获取到类型提示、检查特性
| index.html # 项目入口点,默认有个<div id="app"></div>根节点,Vite构建生成的脚本和样式注入到这个文件中
| package-lock.json # 由npm自动生成,记录了 node_modules 中安装的精确版本的包
| package.json # 项目的配置文件,包含项目的元数据、脚本命令、依赖包列表等
| README.md # 项目说明文件,通常包含项目简介、安装和运行说明等
| tsconfig.app.json # 应用程序源代码的 TypeScript 配置文件,扩展了 tsconfig.json 并可以覆盖或指定应用程序的其他 TypeScript 编译选项
| tsconfig.json # 基本的 TypeScript 配置文件,定义 TypeScript 编译器的根设置,如目标 JavaScript 版本、模块解析策略和其他编译选项
| tsconfig.node.json # 专门用于 Node.js 环境的 TypeScript 配置文件,主要用于 Node.js 脚本和工具的 TypeScript 配置
| tsconfig.vitest.json # 用于Vitest的一些设置,这是一个快速、轻量的单元测试的框架
| vite.config.ts # Vite构建工具的配置文件,使用 TypeScript 编写,用于定义 Vite 的配置选项,例如插件、别名、代理等
| vitest.config.ts # 用于配置 Vitest 的测试设置,包括测试环境、测试文件的位置、测试覆盖率等
|
+---.vscode # 存放vs code配置文件
| extensions.json # 用于推荐和管理项目所需的扩展,vs code 根据该配置推荐用户安装一些有用的插件
| settings.json # vs code 相关偏好设置,如编辑器格式化、主题等
+---node_modules # 项目依赖,包含用npm或yarn安装的所有依赖
+---public # 静态资源目录,这些文件会被直接复制到最终的构建输出中
| favicon.ico # 浏览器上显示的图标
|
\---src # vue项目源码目录
| App.vue # 根组件,其他的所有组件都是该组件的子组件
| main.ts # 应用入口文件,在这里创建Vue实例并挂载到根节点,还有引入路由、状态管理和全局组件等
|
+---assets # 组件中所需的应用资源,包括图片、样式等,该目录下的资源可通过相对路径或URL引入
| base.css
| logo.svg # 一个svg格式图标,可看到App.vue通过相对路径<img src="./assets/logo.svg"/>引入
| main.css # 一个全局的css样式文件,可看到main.ts中使用相对路径 import './assets/main.css'
|
\---components # 存放 Vue 组件,存放一些可复用的、可共享的组件
| | HelloWorld.vue # 一个Vue组件,可使用相对路径导入到页面中使用
| | TheWelcome.vue
| |
| +---icons # svg图标,只不过这里的图标是以组件的形式给出
| | IconCommunity.vue
| | IconTooling.vue
| |
| \---__tests__ # 若创建时选择引入 Vitest 用于单元测试则有该目录
| HelloWorld.spec.ts # 单元测试文件,使用 npm run test:unit 命令可以查看测试情况
|
+---router # 若创建时选择引入 Vue Router 进行单页面应用开发,则会添加依赖 vue-router并生成router目录
| index.ts # 该文件中用createRouter创建路由,并导入到main.ts中使用app.use(router)注册
|
+---stores # 若创建时选择引入 Pinia 用于状态管理,在main.ts中使用app.use(createPinia())注册了pinia
| counter.ts # 文件中defineStore定义了一个简单的示例
| # Pinia 核心功能之一就是在不同的组件或页面间共享和管理状态,当你在一个页面或组件中改变了状态,所有依赖该状态的组件都会自动更新,从而反映最新的状态
\---views # 页面组件,可将一些共享的组件组合成为为页面以供路由访问
AboutView.vue
HomeView.vue
Vue文件编写及生命周期
Vue
文件带有".vue"后缀,包含三个部分template
、script
、style
Vue 3.x
中,通过默认的Vite
构建工具将Vue
文件中的内容编译和打包,使其能在浏览器中正常工作
<template>
<!-- 模版部分: 每个 *.vue 文件最多可以包含一个顶层 template 块,它是语法层面合法的HTML,Vue会将其编译为高度优化的JavaScript代码 -->
</template>
<script>
// 脚本部分:每个 *.vue 文件最多可以包含一个 <script> 块
// Vue 3.x中,官方代码示例通常将script脚本部分放在template模版部分之前,官方文档虽然无无明确规定script的位置,但是在社区实践中比较普遍,也可根据个人偏好来
</script>
<style scoped>
/* 组件样式:每个 *.vue 文件可以包含多个 style 标签,带有scoped属性style,其样式只影响当前组件 */
</style>
Vue文件(选项式API风格)
以下给出的是Vue 3.x中一种选项式API风格的Vue文件,以及常见的Vue组件中包含的一些选项如props、emits、components、computed、watch、watch、methods
以及钩子函数beforeCreate,created,updated,mounted, unmounted
等
<script>
// 当前文件: ChildVue.vue (选项式API风格)
import { inject,defineComponent } from 'vue';
import HelloWorld from '@/components/HelloWorld.vue';
import SimpleDemo from '@/components/SimpleDemo.vue';
// 也可以使用 export default defineComponent({ props:{},data(){} }) 这样的形式,可支持正确地推导出组件选项内的类型
export default { // 定义和导出一个Vue组件,组件里面可以添加一个些会被自动调用的钩子函数
props: { // props 选项:定义父组件传给当前组件的一些属性,Vue中数量流是单向的,不要在当前组件直接修改
title: String, // 组件的属性
desc: { // 指定属性的类型、默认值、是否必传
type: String,
default: "一些描述...",
required: false // 指定为false,表示不是必传
}
},
components: { HelloWorld, SimpleDemo }, // 注册局部组件,使得当前组件的模版可以使用这些组件
emits: ['updateData'], // emits选项:确保组件只发出预定义的事件
data() { // data选项:定义组件的响应式数据,返回一个对象,Vue创建新组件实例时调用此函数
return {
content: "",
username: "",
checkResult: "",
themeStyle: ""
};
},
computed: { // computed选项: 用于定义计算属性,当前依赖数据改变时进行计算
reversedContent() { //this指示当前组件实例,当content值改变,会自动计算返回一个逆转的字符串
return this.content.split('').reverse().join('');
}
},
watch: { // watch 用于观察数据变化,并在数据变化时执行一些操作,比如更新其他属性或请求数据
username(newval, oldval) { // 观察属性username,改变时设置用户名检测结果
let regex = /^[a-zA-Z][a-zA-Z_0-9]*$/
this.checkResult = regex.test(this.username) ? "合法的用户名" : "不合法的用户名";
}
},
beforeCreate() {
console.log("beforeCreate 在组件实例初始化完成之后立即调用.")
},
created() {
console.log("created钩子函数:组件实例处理完所有与状态相关的选项后调用,此时挂载阶段还未开始.");
},
updated() {
console.log("updated钩子函数:数据发生变化并重新渲染后调用,可执行一些与更新后DOM相关的操作.");
},
mounted() {
console.log("mounted钩子函数:组件被挂在到DOM后调用,这里常进行与DOM相关的数据请求、初始化第三方库等.")
window.addEventListener('beforeunload', this.beforeUnloadHandler); // 监听刷新页面或关闭浏览器行为提供一些提示
// provide 和 inject:用于跨级组件传递数据,provide("theme",data) 在父组件中定义,inject("theme") 在子组件中使用
this.themeStyle = inject("theme");
},
methods: {
saveData() {
this.$emit("updateData", this.content); // 向父组件发送消息
window.alert('保存内容:' + this.content);
},
beforeUnloadHandler(event) {
console.log("beforeUnloadHandler...")
event.preventDefault(); // 阻止默认行为,提供一个页面刷新前的提示
event.returnValue = '你确定要离开此页面吗?';
return event.returnValue;
}
},
beforeUnmount() {
console.log('beforeUnmount函数:组件被卸载之前调用,可以进行一些清理工作,如取消定时器等.');
},
unmounted() {
window.removeEventListener('beforeunload', this.beforeUnloadHandler);
console.log("unmounted函数:组件被卸载后调用,进行一些清理工作,如移除监听器");
}
}
</script>
<template>
<div :style="themeStyle">
<h2>标题:{{ title }}</h2>
<p>描述:{{ desc }}</p>
<textarea style="display:block" rows="5" cols="20" v-model="content" placeholder="请输入内容"></textarea>
<button @click="saveData">保存</button>
<textarea style="display: block" rows="5" cols="20" v-model="reversedContent" placeholder="结果"></textarea>
<div>
<label>用户名</input></input>:</label> <input type="text" v-model="username"></input>
<div><label>用户名检测结果:{{ checkResult }}</label></div>
</div>
<HelloWorld />
<SimpleDemo />
</div>
</template>
<style scoped></style>
在一个父组件中导入上述组件并使用
<script>
import ChildVue from '@/views/ChildVue.vue'
import { provide } from 'vue'
export default {
components: { ChildVue },
data() {
return {
content: "",
theme: { 'font-size': '20px', 'background-color': '#CDCDCD' }
}
},
mounted() {
provide("theme", this.theme); // 提供一个共享的数据,子组件中使用inject获取该数据
},
methods: {
receiveData(message) { // 收到子组件的数据
this.content = message;
}
}
}
</script>
<template>
<div class="about">
<h1>This is an about page</h1>
<p>收到内容:{{ content }}</p>
<!-- @update-data指定回调函数名称,子组件中emit使用"updateData"给当前组件发送数据(驼峰中大写字符被转为小写并添加了连字符),title、desc为传递给子组件的属性 -->
<ChildVue @update-data="receiveData" title="Hello Child Vue" desc="关于Vue的一些用法" />
</div>
</template>
<style>
@media (min-width: 1024px) { /* 一些css样式,使用了媒体查询可动态判断浏览器可视窗口宽度以设置生效样式 */
.about {
min-height: 100vh;
display: flex;
align-items: center;
}
}
</style>
Vue文件(组合式API风格)
采用组合式API,也可以实现上述选项式API的功能
<script setup lang="ts">
// 当前文件: ChildVue.vue (组合式API风格)
// 当 lang="ts" 用于 script标签时,其内部的表达式将受到更严格的类型检查
import { inject, ref, reactive, defineProps, defineEmits, watch, computed, onMounted, onUnmounted } from 'vue';
import HelloWorld from '@/components/HelloWorld.vue';
import SimpleDemo from '@/components/SimpleDemo.vue';
const props = defineProps({ // defineProps 接收与 props 选项相同的值,并提供类型推导
title: String,
desc: {
type: String,
default: "描述...",
required: false
}
});
const emit = defineEmits(['updateData']); // defineEmits 接收与 emits 选项相同的值,并提供类型推导
const info = reactive({ // reactive 返回一个响应式代理,可处理数组和其他复杂数据结构,它会影响所有嵌套的属性,当其中一个属性值改变,页面会响应式地渲染出来
content: "",
username: "",
themeStyle: {}
});
info.themeStyle = inject("theme");
// console.log("info.themeStyle:", info.themeStyle); // 这里可以输出,但当注释掉console后页面themeStyle就不生效,这可能是组合式API使用inject存在的一种bug
const checkResult = ref("");
// ref 适合于创建一个响应式的原始数据类型,它也可以用于包装一个对象,使得该对象成为响应式的,但通常情况下reactive 更适合处理对象
// 可能会错误写为 watch(info.username, (newval, oldval)=>{/* */} 这样是监听不到的
watch(() => info.username, (newval, oldval) => {
console.log("watch:", newval);
let regex = /^[a-zA-Z][a-zA-Z_0-9]*$/
checkResult.value = regex.test(info.username) ? "合法的用户名" : "不合法的用户名";
});
const saveData = () => {
emit("updateData", info.content); // 向父组件发送消息
};
const reversedContent = computed(() => { // computed返回值被包装为一个计算属性ref,可通过.value访问
return info.content.split('').reverse().join('');
});
const beforeUnloadHandler = (event) => {
event.preventDefault();
event.returnValue = '你确定要离开此页面吗?';
return event.returnValue;
};
onMounted(() => {
console.log("mounted钩子函数:组件被挂载到DOM后调用");
window.addEventListener('beforeunload', beforeUnloadHandler);
});
onUnmounted(() => {
window.removeEventListener('beforeunload', beforeUnloadHandler);
});
</script>
<template>
<div :style="info.themeStyle">
<h2>标题:{{ props.title }}</h2>
<p>描述:{{ props.desc }}</p>
<textarea style="display:block" rows="5" cols="20" v-model="info.content" placeholder="请输入内容"></textarea>
<button @click="saveData">保存</button>
<textarea style="display: block" rows="5" cols="20" v-model="reversedContent" placeholder="结果"></textarea>
<div>
<label>用户名</input></input>:</label> <input type="text" v-model="info.username" />
<div><label>用户名检测结果:{{ checkResult }}</label></div>
</div>
<HelloWorld />
<SimpleDemo />
</div>
</template>
<!-- 带有scoped属性style,其样式只影响当前组件 -->
<style scoped></style>
选项式与组合式风格的区别
Vue 的组件可以按两种不同的风格书写:组合式 API和选项式 API
选项式API
- 常用
export default {}
关键字定义,用多个选项(如data、methods、muted)的对象来描述组件的逻辑,选项所定义的属性都会暴露在函数内部的this
上,它会指向当前的组件实例 - 选项式 API 是在组合式 API 的基础上实现的,它将响应性的相关细节抽象出来,强制按照选项组织代码,在渐进增强的组件功能的场景下比较推荐采用,而且该风格对初学者也比较友好
组合式API
- 常在
<script setup>
中使用,setup
用于标识编译时要进行一些处理,该方式使用功能导入的API函数描述组件逻辑 - 其核心思想是直接在函数作用域内容定义响应式状态变量,并将多个函数中得到状态组合起来处理复杂问题,形式比较自由,需要对Vue响应式系统有深入理解才能高效使用
Vue生命周期
每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤,比如设置好数据侦听,编译模板,挂载实例到 DOM,以及在数据改变时更新 DOM,在此过程中它会运行被称为生命周期钩子的函数,让开发者有机会在特定阶段运行自己的代码,常见生命周期选项:
beforeCreate
: 在组件实例初始化完成之后立即调用,接着 props 会被定义成响应式属性,data()
和computed
等选项也开始进行处理,组合式 API 中的setup()
钩子会在所有选项式 API 钩子之前调用,beforeCreate()
也不例外
created
:在组件处理完所有与状态有关的选项后调用,此时以下内容已经设置完成:响应式数据、计算属性、方法和侦听器,然而此时挂载阶段还未开始,因此$el
属性仍不可用
beforeMount
: 在组件挂载之前调用,此时组件已经完成了其响应式状态的设置,但还没有创建 DOM 节点,它即将首次执行 DOM 渲染过程,这个钩子在服务端渲染时不会被调用
mounted
:组件被挂载之后调用,在此之前所有同步子组件、自身的DOM树都已插入到父容器中,此时可访问DOM树或DOM相关的操作,这个钩子在服务端渲染时不会被调用
beforeUpdate
:在组件即将因为一个响应式状态变更而更新其 DOM 树之前调用,可以用来在 Vue 更新 DOM 之前访问 DOM 状态
updated
:在组件因为一个响应式状态变更而更新其 DOM 树之后调用,这个钩子在服务端渲染时不会被调用
beforeUnmount
:在一个组件实例被卸载之前调用,当这个钩子被调用时,组件实例依然还保有全部的功能,这个钩子在服务端渲染时不会被调用
unmounted
:在一个组件实例被卸载之后调用,可以在这个钩子中手动清理一些副作用,例如计时器、DOM 事件监听器或者与服务器的连接,这个钩子在服务端渲染时不会被调用
以下是Vue 3.x官方给出的生命周期图示:
Vue组件常见内置指令
内容渲染
v-text
更新元素的文本内容
<span v-text="msg"></span>
<!-- 等同于 -->
<span>{{msg}}</span>
v-html
更新元素的innerHTML
,其内容直接作为普通的HTML
插入,除非是可信的HTML,否则这样做很容易导致XSS
攻击(一种恶意指令代码注入到网页导致用户看到一些非预期的页面)
const rawHtml = ref(`<div><h2>Vue用法</h2><p>关于Vue的一些描述...</p></div>`)
<span v-html="rawHtml"></span>
条件渲染
v-if、v-else-if、v-else
条件性地渲染出一块内容,一个 v-else
元素必须跟在一个 v-if
或者 v-else-if
元素后面,否则它将不会被识别
v-show
改变元素的可见性,仅切换该元素上名为 display
的 CSS 属性,它不支持在<template>
元素上使用,也不能和v-else
搭配使用
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
<div v-if='!inilized'>
<div>暂无数据</div>
<div>
列表渲染
v-for
渲染数组
指令可以基于一个数组来渲染
const items = ref([{ name: '菜单1',children:[{name:"子菜单1-1"}] }, { name: '菜单2',children:[] }])
<li v-for="item in items">
{{ item.name }}
</li>
<li v-for="(item, index) in items"> <!-- v-for第2个参数表示当前位置索引 -->
{{ name }} - {{ index }}
</li>
<li v-for="item in items"> <!-- v-for嵌套 -->
<span v-for="childItem in item.children">
{{ item.name }} {{ name }}
</span>
</li>
v-for
渲染对象
也可以基于一个对象来渲染
const info = ref({
title: '标题',
desc: "描述",
createDate: '2024-07-21'
})
<div>
<ul>
<!-- v-for 第1个参数表示属性的取值,遍历的顺序基于该对象调用Object.keys()返回来决定 -->
<li v-for="value in info">
{{ value }}
</li>
</ul>
<ul>
<!-- v-for 第2个参数表示属性名 -->
<li v-for="(value, key) in info">
{{ key }} -- {{ value }}
</li>
</ul>
<ul>
<!-- v-for第三个参数表示位置索引 -->
<li v-for="(value, key, index) in info">
{{ index }}: {{ key }} -- {{ value }}
</li>
</ul>
</div>
v-for
渲染范围值
以下渲染的数字从1到10
<span v-for="n in 10">{{ n }}</span>
v-for
渲染Set
const sets = ref(new Set(['Red', 'Orange', 'Blue']));
<p v-for="value in sets">
{{ value }}
</p> <!-- 第1个参数表示集合中元素值,第2个参数index,表示从0开始的顺序索引 -->
<p v-for="(value, index) in sets">
{{ index }}:{{ value }}
</p>
v-for
渲染Map
const maps = ref(new Map([["id", 101], ['name', 'blob'], ['age', 18]]));
<div v-for="entry in maps"> <!-- 渲染map得到的entry,entry第1个值为key,第2个值为value -->
key:{{ entry[0] }},value:{{ entry[1] }}
</div>
<div v-for="key in maps.keys()">
key:{{ key }}
</div>
属性绑定
使用v-bind
指令可响应式地绑定HTML元素的属性,若绑定的值为null
或undefined
,该属性将会从渲染的元素上移除
单个属性绑定
<div v-bind:id="dynamicId"></div>
<!-- v-bind指令的简写语法,可缺省v-bind -->
<div :id="dynamicId"></div>
<!-- vue3.4以上版本中,甚至可以进一步简写,以下与 :id="id" 相同 -->
<div :id></div>
<!-- 绑定布尔型属性 -->
<button :disabled="isButtonDisabled">Button</button>
<!-- 支持模版字符串 -->
<div :id="`list-${id}`"></div>
多个属性绑定
使用不带参数的 v-bind
可以将包含多个属性的对象绑定到一个元素上
const attrObj = ref({ id: 'container', class: 'input-area', style: 'background:#CDCDCD;' })
<div v-bind="attrObj"><label>多个属性绑定</label> </div>
class绑定
可以给:class
(v-bind:class简写)传递一个对象实现动态切换class
const isClicked = ref(false);
const isActive = ref(false);
<!-- 仅在isActive为true时,给class名称设为'title-link',若名称没有连字符,可以去掉符号''
实现的效果是当鼠标移动到div元素上,显示蓝色背景和白色字体,当点击时实现字体颜色改变
-->
<div :class="{ 'title-link': isActive, clciked: isClicked }" @click="isClicked = !isClicked"
@mouseenter="() => isActive = true" @mouseleave="() => isActive = false">
文档标题1
</div>
<!-- 可以使用条件表达式,当逻辑为true,则绑定样式,否则样式为空 -->
<div :class="isClicked ? 'title-link' : ''" @click="isClicked = !isClicked">标题2...</div>
<!-- 可以同时绑定多个 -->
<div :class="['clciked', 'title-link']">标题3...</div>
<style scoped>
.title-link {
font-weight: bold;
background-color: cornflowerblue;
color: white;
}
.clciked {
color: orange;
}
</style>
style绑定
:style
支持板顶javascript
对象值,对应style
属性
const customStyle = reactive({ backgroundColor: 'cornflowerblue', color: 'white' });
<p :style="{ 'background-color': 'cornflowerblue', 'color': 'white' }">文字描述1...</p>
<!-- 'background-color' 样式属性含有连字符时使用'',当不用''时需用驼峰命名风格 backgroundColor -->
<p :style="{ backgroundColor: 'cornflowerblue', color: 'white' }">文字描述2...</p>
<!-- 更简介的方式,直接绑定一个对象 -->
<p :style="customStyle">文字描述3...</p>
<!-- 支持:style 绑定一个包含多个样式对象的数组,这些对象会被合并后渲染到同一元素上 -->
<div :style="[customStyle, otherStyle]">内容...</div>
表单绑定
v-model
在表单输入元素或组件上创建双向绑定,常用于<input>,<select>,<textarea>
元素,v-model
会忽略任何表单元素上初始的 value
、checked
或 selected
<input v-model="text">
<!-- v-model简化了下面这种方式 -->
<input :value="text" @input="event => text = event.target.value">
多选框
input
为checkbox
类型,v-mode
为列表,当任意input
元素被选中,input
的value
值被自动添加到v-model
的列表中
const checkedNames = ref([])
<div>Checked names: {{ checkedNames }}</div>
<!-- 当点击这些多选框,value取值会被逐一添加到v-model的checkedNames数组中 -->
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames" />
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames" />
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames" />
<label for="mike">Mike</label>
单选框
input
为radio
类型,同时有value
,v-model
,当选中时value
值被更新到v-model
<div>Picked: {{ picked }}</div>
<!-- 点击单选按钮,input的value值被更新到v-model的picked上 -->
<input type="radio" id="one" value="One" v-model="picked" />
<label for="one">One</label>
<input type="radio" id="two" value="Two" v-model="picked" />
<label for="two">Two</label>
下拉框
select
-option
标签中,若option
被选中,则select
上的v-model
值被更新
const selected = ref(1);
const options = ref([{ name: '正常', value: 1 }, { name: '禁用', value: 0 }]);
<select v-model="selected">
<option v-for="option in options" :value="option.value">{{ option.name }}</option>
</select>
<label>选中:{{ selected }}</label>
复选框
使用Vue
特有的true-value
和 false-value
属性,并和 v-model
配套使用时,state
属性在被选中时设置为'yes'
,取消选中时设置为'no'
<input type="checkbox" v-model="state" true-value="yes" false-value="no" />
<label>{{ state }}</label>
v-mode修饰符
<!-- 用户输入内容中两端的空格-->
<input v-model.trim="msg" />
<!-- 用户输入自动转为数字-->
<input v-model.number="age" />
<!-- 在 "change" 事件后同步更新而不是 "input" -->
<input v-model.lazy="msg" />
事件绑定
常用@
指令(v-on
指令的缩写)监听DOM
事件,在事件触发时指定对应的JavaScript
@click
const count = ref(0)
const saveData = (event)=>{
// 符号[`]是ES6(ECMAScript 2015)中引入的一种模版字符串标记,用于创建多行字符串和内嵌表达式,对嵌入的表达式使用 ${expression}语法
alert(`保存的数据:${count.value}`)
};
const onSubmit = (msg, event) => {
if (event) { //这里可以访问原生事件
console.log(event.target.innerText);
event.preventDefault()
}
alert(msg);
}
<!-- 内联事件处理器:事件被触发时执行的内联 JavaScript 语句,通常用于简单场景 -->
<button @click="count++">点赞数{{count}}</button>
<!-- 方法事件处理器:一个指向组件上定义的方法的属性名或是路径,用于复杂场景 -->
<button @click="saveData">保存数据</button>
<!-- v-on:click 与 @click 等价 -->
<button v-on:click="saveData">保存数据</button>
<!-- 处理器方法传入一个特殊的 $event 变量,或者使用内联箭头函数 -->
<button @click="onSubmit('some data', $event)">提交1</button>
<button @click="(event) => onSubmit('some data', event)">提交2</button>
@click修饰符
@click.stop.prevent
常用于a
标签阻止默认的跳转行为
@click.once
点击实现最多只被触发一次
const onDownload = () => {
alert("触发下载...");
}
const onHandleUrl = (event) => {
alert('你被阻止访问:' + event.target.href);
}
<!-- 点击之后再次点击不会触发,除非刷新页面 -->
<a @click.once="onDownload">下载</a>
<!-- 阻止跳转行为,点击时触发事件函数处理,不会触发href地址跳转 -->
<a @click.stop.prevent="onHandleUrl" href="https://cn.vuejs.org/guide/essentials/event-handling.html">Vue链接</a>
<!-- Ctrl + 点击 -->
<div @click.ctrl="onOpenNewTab">查看</div>
此外还有其他的修饰符,比如@click.prevent.self
会阻止元素及其子元素的所有点击事件的默认行为,而 @click.self.prevent
则只会阻止对元素本身的点击事件的默认行为
@submit修饰符
@submit.prevent
阻止提交时默认的页面刷新行为
const userInfo = ref({ name: "", email: "" })
const onSubmitForm = () => {
console.log("userInfo:", userInfo.value);
};
<form @submit.prevent="onSubmitForm">
<!-- 虽然可以写为:<form @submit.prevent></form> ,这样在表单中添加按钮事件处理 <button type="submit" @click="onSubmitForm">提交</button> 不过这样的问题是失去了表单字段的自动校验能力 -->
<div>
<label for="name">Name:</label>
<input type="text" v-model="userInfo.name" required id="name" >
<!-- label 中的 for 属性值与 input 元素的 id 属性值相同,这使得点击 label 标签时,浏览器会自动聚焦到对应的输入框,这对用户体验和无障碍访问有很大帮助 -->
</div>
<div>
<label for="email">Email:</label>
<input type="email" v-model="userInfo.email" required id="email">
</div>
<button type="submit">提交</button>
</form>
按键修饰符
<!-- 输入内容时,监听到按下回车键触发事件 -->
<input @keyup.enter="onSearch" placeholder="请输入关键字~"></input>
<!-- 当按住Ctr键点击时,会触发事件函数 -->
<div @click.ctrl="onOpenNewTab">按住Ctr键点击查看</div>
<!-- 仅当按下 Ctrl 且未按任何其他键时,鼠标点击才会触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />
DOM节点的引用
Vue
虽然抽象了对大部分DOM
的直接操作,但某些场景中仍然需要访问DOM
底层元素
通常推荐的方法是使用节点的ref
属性,这样可以确保在Vue
的生命周期钩子中更安全地操作DOM
也有另外的方式如使用document
对象的方法,不过这种违背了Vue响应式和声明式编程的理念,并不是很推荐
const customRef = ref(null); // 组合式API中,可以使用ref访问DOM节点,注意此处的customRef名称必须与元素的ref指定名称一致;
onMounted(() => { // 只有在组件挂载后才能访问模板引用
if (customRef.value) {
customRef.value.innerHTML = `<div><h2>Vue用法</h2><p>关于Vue的一些描述...</p></div>`;
}
document.getElementById("customId").innerHTML = "嵌入的一些内容"; // 通过节点的id访问DOM节点
});
<div ref="customRef"></div>
<div id="customId"></div>
若在声明式API中,可以使用this.$refs.customRef
来访问上述节点的引用