前端vue框架的项目文件创建及常见Vue指令运用

前言

本文介绍前端Vue框架,先从npm工具创建的Vue项目开始,对项目结构的一些文件用途进行说明,随后对Vue文件编写所用的两种风格(选项式API组合式API风格)做了区分,同时对编写代码中常见的生命周期钩子函数做了一些概述,最后对Vue常见的内置指令包括内容渲染、条件渲染、列表渲染、属性绑定、表单绑定以及DOM的节点引用进行了详细介绍

Vue简介

Vue(发音为 /vjuː/,类似 view) 一个用于构建用户界面的JavaScript框架,基于标准的HTMLCSSJavaScript构建

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提供的npmnpxnode工具命令,安装时注意勾选将其添加到环境变量,若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-vueVue.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   # 端到端测试工具用于模拟用户操作,测试整个应用的功能不需要CypressNightwatchPlaywright√ 是否引入 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.vueHomeView.vue  

Vue文件编写及生命周期

Vue文件带有".vue"后缀,包含三个部分templatescriptstyle

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存在的一种bugconst 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元素的属性,若绑定的值为nullundefined,该属性将会从渲染的元素上移除

单个属性绑定

<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 会忽略任何表单元素上初始的 valuecheckedselected

<input v-model="text"> 
<!-- v-model简化了下面这种方式 -->
<input :value="text"  @input="event => text = event.target.value">

多选框

inputcheckbox类型,v-mode为列表,当任意input元素被选中,inputvalue值被自动添加到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>

单选框

inputradio类型,同时有valuev-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-valuefalse-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来访问上述节点的引用

参考资料

  1. Vue官方文档
  2. Vite官方中文文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/48679.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【OpenREALM学习笔记:14】单目视觉SLAM方法在UAV影像上重建三维地形的思考

最近在学习SLAM技术与测绘三维影像重建的相关知识&#xff0c;结合自己的感受&#xff0c;撰写一下对于单目视觉SLAM利用无人机影像重建三维地形的一些看法。 1. 单目视觉SLAM系统在三维地形重建中所面临的挑战有哪些&#xff1f; 单目视觉SLAM众所周知的一个问题是&#xff…

C语言联合及枚举

一.联合体 1.联合体类型的声明 像结构体一样&#xff0c;联合体也是由一个或者多个成员构成&#xff0c;这些成员可以不同的类型,但是编译器只为最大的成员分配足够的内存空间。 联合体的特点是所有成员共用同一块内存空间。所以联合体也叫:共用体,给联合体其中一个成员赋值…

AVL树超详解上

前言 学习过了二叉树以及二叉搜索树后&#xff08;不了解二叉搜索树的朋友可以先看看这篇博客&#xff0c;二叉搜索树详解-CSDN博客&#xff09;&#xff0c;我们在一般情况下对于二叉搜索树的插入与查询时间复杂度都是O(lgN)&#xff0c;是十分快的&#xff0c;但是在一些特殊…

多维时序 | Transformer+BiLSTM多变量时间序列预测(Python)

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 多维时序 | TransformerBiLSTM多变量时间序列预测&#xff08;Python&#xff09; python代码&#xff0c;pytorch框架 程序设计 完整程序和数据获取方式私信博主回复多维时序 | TransformerBiLSTM多变量时间序列预…

Ubuntu 查询未更新的包 进行手动更新

lsb_release -a 查询ubuntu的版本号&#xff0c;我这边是20.04 apt list --upgradable 查询未更新的包 sudo apt --only-upgrade install php/focal 中间遇到输入选择&#xff0c;输入y即可

linux shell脚本编程(分支语句、循环语句)

一、分支语句 1、语法结构 : if 表达式 then 命令表 fi 如果表达式为真 , 则执行命令表中的命令 ; 否则退出 if 语句 , 即执行 fi 后面的语句。 if 和 fi 是条件语句的语句括号 , 必须成对使用 ;命令表中的命令可以是一条 , 也可以是若干条。 2、语法结构为 : if 表达式 t…

pdf太大了怎么变小 pdf太大了如何变小一点

在数字化时代&#xff0c;pdf文件已成为工作与学习的重要工具。然而&#xff0c;有时我们可能会遇到pdf文件过大的问题&#xff0c;这会导致传输困难或者存储不便。别担心&#xff0c;下面我将为你介绍一些实用的技巧和工具&#xff0c;帮助你轻松减小pdf文件的大小。 方法一、…

java之利用二维数组来计算年利润和每个季度的营业额

public class TwodimensionDemo2 {public static void main(String[] args) {//创建二维数组来存储数据int [][]yearArrArr{{22,66,44},{77,33,88},{25,45,65},{11,66,99}};int yearSum0;//遍历二维数组&#xff0c;得到每一个一维数组并求和for (int i 0; i < yearArrArr.…

Linux fork、进程的退出和等待详解

初识fork函数 它从已存在进程中创建一个新进程。新进程为子进程&#xff0c;而原进程为父进程。 #include <unistd.h> pid_t fork(void); 返回值&#xff1a;子进程中返回0&#xff0c;父进程返回子进程id&#xff0c;出错返回-1 最简单的fork使用示例 #include<stdi…

每日一题,力扣leetcode Hot100之11. 盛最多的水

解法一&#xff1a; 双层循环遍历找结果&#xff0c;相当于是一个暴力求解方法&#xff0c;两层嵌套循环的时间复杂度是O(N2&#xff09;&#xff0c;所以有一些样例测试时间超时 class Solution:def maxArea(self, height: List[int]) -> int:max_result0for i in range(…

VMware 安装完,设备管理器中没有虚拟网卡(vmnet0、wmnet1、vmnet8) / 虚拟网络编辑器中没有桥接模式

问题&#xff1a;VMware 安装完&#xff0c;设备管理器中没有虚拟网卡(vmnet0、wmnet1、vmnet8) / 虚拟网络编辑器中没有桥接模式 1、确认 Device Install Service 和 Device Setup Manager 没有被禁用 Device Install Service 和 Device Setup Manager是 Windows 操作系统中…

GraphRAG参数与使用步骤 | 基于GPT-4o-mini实现更便宜的知识图谱RAG

首先给兄弟朋友们展示一下结论&#xff0c;一个文本18万多字&#xff0c;txt文本大小185K&#xff0c;采用GraphRAG,GPT-4o-mini模型&#xff0c;索引耗时差不多5分钟&#xff0c;消耗API价格0.15美元 GraphRAG介绍 GraphRAG是微软最近开源的一款基于知识图谱技术的框架&#…

“论系统安全架构设计及其应用”,写作框架,软考高级论文,系统架构设计师论文

论文真题 随着社会信息化进程的加快&#xff0c;计算机及网络已经被各行各业广泛应用&#xff0c;信息安全问题也变得愈来愈重要。它具有机密性、完整性、可用性、可控性和不可抵赖性等特征。信息系统的安全保障是以风险和策略为基础&#xff0c;在信息系统的整个生命周期中提…

73、Flink 的 DataStream API 生产实践总结

0、汇总 1.可以使用 Maven 命令、CURL 命令、IDEA 手动创建 Flink 项目&#xff1b;2.可以使用 Maven Shade 插件将必需的依赖项打包进应用程序 jar 中&#xff1b;3.应该在 Flink 集群的 lib 文件夹内配置需要的&#xff08;核心&#xff09;依赖项&#xff1b;4.应该将程序中…

【Godot4.2】GodotXML插件 - 解析和生成XML

概述 近期在研究基于Godot的XML和SVG解析&#xff0c;并且在昨天&#xff08;2024年7月20日&#xff09;编写了一个简易的SVG文件解析器。 在群友的提示下&#xff0c;知道早就存在GodotXML这样的解析器。所以今天就来测试使用并准备研究学习源代码了。和以往一样&#xff0c…

GD32 MCU是如何进入中断函数的

用过GD32 MCU的小伙伴们都知道&#xff0c;程序是顺序执行的&#xff0c;但当有中断来的时候程序会跳转到中断函数&#xff0c;执行完中断函数后程序又继续回到原来的位置继续执行&#xff0c;那么你们知道MCU是如何找到中断函数入口的吗&#xff1f; 今天我们就以GD32F303系列…

JavaScript进阶之作用域解构箭头函数

目录 一、作用域1.1 局部作用域1.2 全局作用域1.3 作用域链1.4 垃圾回收机制1.5 闭包1.6 变量提升 二、函数进阶2.1 函数提升2.2 函数参数2.3 箭头函数&#xff08;重要&#xff09; 三、解构赋值3.1 数组解构3.2 对象解构&#xff08;重要重要&#xff09; 一、作用域 1.1 局…

【BUG】已解决:ModuleNotFoundError: No module named‘ pip‘

已解决&#xff1a;ModuleNotFoundError: No module named‘ pip‘ 目录 已解决&#xff1a;ModuleNotFoundError: No module named‘ pip‘ 【常见模块错误】 【解决方案】 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰…

SpringCloud--负载均衡

目录 前言 一.负载均衡的引入 1.1问题引入 1.2代码修改实现 二.负载均衡介绍 2.1实现负载均衡 2.2负载均衡策略 2.3LoadBalancer 原理 学习专栏&#xff1a;http://t.csdnimg.cn/tntwg 前言 在前面的Eureka当中&#xff0c;我们虽然实现了从注册中心中获取url&#xf…

桌面小宠物发布一周,第一次以独立开发者的身份赚到了100块

收入数据(AppStore一周收入统计) AppStore付费工具榜第七 应用简介 桌面新宠(NewPet)&#xff0c;是我耗时半年开发的一款桌面宠物。我是被 QQ 宠物影响的那批人&#xff0c;上学时天天给 QQ 宠物喂食&#xff0c;很可惜它现在不在了。所以&#xff0c;我开发的初衷是想要在电…