前端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.rhkb.cn/news/381384.html

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

相关文章

Pytorch使用前期准备

一、检查英伟达驱动和CUDA Toolkit是否正确安装 1.任务管理器性能选项卡中能正确显示显卡型号则表示显卡驱动正确安装 2. CUDA Toolkit会跟随pytorch自动安装 二、虚拟环境的准备 Miniconda — Anaconda documentationhttps://docs.anaconda.com/miniconda/ 1.安装anaconda或者…

腾讯元宝上线“3D角色梦工厂”:快速生成专属3D角色!

7月16日&#xff0c;腾讯旗下大模型应用“腾讯元宝”上线“3D角色梦工厂”&#xff0c;允许用户通过上传一张五官清晰的正面头像&#xff0c;并选择不同的角色模板&#xff0c;迅速生成个人3D角色&#xff01; 技术特点 “3D角色梦工厂”将大模型生成技术与3D应用相结合&#…

大模型(LLM)选择指南:AI解决方案的12个决策点

今天我们来看看国外各家领先的大型语言模型&#xff08;LLM&#xff09;&#xff0c;这些模型来自OpenAI、Google、Anthropic、Cohere、Meta、Mistral AI以及Databricks等不同的供应商。我们会根据几个关键因素来评估这些模型&#xff0c;包括性能&#xff08;涵盖价格、质量和…

NET 语言识别,语音控制操作、语音播报

System.Speech. 》》System.Speech.Synthesis; 语音播报 》》System.Speech.Recognition 语音识别 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Speech.Recog…

在 Windows 上运行 Linux:WSL2 完整指南(二)

系列文章目录 在 Windows 上运行 Linux&#xff1a;WSL2 完整指南&#xff08;一&#xff09;&#x1f6aa; 在 Windows 上运行 Linux&#xff1a;WSL2 完整指南&#xff08;二&#xff09;&#x1f6aa; 文章目录 系列文章目录前言四、常见问题及解决方法问题二&#xff1a;0…

昇思25天学习打卡营第17天|LLM-基于MindSpore的GPT2文本摘要

打卡 目录 打卡 环境准备 准备阶段 数据加载与预处理 BertTokenizer 部分输出 模型构建 gpt2模型结构输出 训练流程 部分输出 部分输出2&#xff08;减少训练数据&#xff09; 推理流程 环境准备 pip install -i https://pypi.mirrors.ustc.edu.cn/simple mindspo…

AV1技术学习:Affine Motion Compensation

一、Affine Model Parameter 除了传统的平移运动补偿&#xff0c;AV1 还支持仿射变换模型&#xff0c;将当前像素点 (x, y) 通过以下方式投影到参考帧中的预测像素点 (x, y). 参数 (h13, h23) 对应于平移模型中使用的常规运动向量。 参数 h11 和 h22 控制垂直和水平轴上的比例…

Spring后端框架复习总结

之前写的博客太杂,最近想把后端框架的知识点再系统的过一遍,主要是Spring Boot和Mybatis相关,带着自己的理解使用简短的话把一些问题总结一下,尤其是开发中和面试中的高频问题,基础知识点可以参考之前写java后端专栏,这篇不再赘述。 目录 Spring什么是AOP?底层原理?事务…

【HarmonyOS NEXT】网络请求 - 分页加载

分页加载关键字&#xff1a;onReachEnd 一、申请网络权限 在 module.json5 文件中&#xff0c;添加网络权限&#xff1a; {"module": {..."requestPermissions": [{"name": "ohos.permission.INTERNET","usedScene": {&qu…

K8S实战进阶

title ‘K8S实战进阶’ date 2024-04-02T16:57:3608:00 draft true 一、搭建Kubernetes集群 1.1 搭建方案 1.1.1 minikube minikube 是一个工具&#xff0c; 能让你在本地运行 Kubernetes。 minikube 在你的个人计算机&#xff08;包括 Windows、macOS 和 Linux PC&…

图像生成(Text-to-Image)发展脉络

这篇博客对 图像生成&#xff08;image generation&#xff09; 领域的经典工作发展进行了梳理&#xff0c;包括重要的一些改进&#xff0c;目的是帮助读者对此领域有一个整体的发展方向把握&#xff0c;并非是对每个工作的详细介绍。 脉络发展&#xff08;时间顺序&#xff0…

WGS84经纬度坐标 GCJ02火星坐标 BD09百度坐标互相转换

WGS84经纬度坐标 GCJ02火星坐标 BD09百度坐标互相转换 背景&#xff1a;uniapp做的微信小程序&#xff0c;使用到了相机拍照并获取位置坐标信息&#xff1b;在腾讯地图上展示坐标点位置信息&#xff1b; 由于业务需要我们的PC端用的不是腾讯地图&#xff0c;需要使用WGS84坐标或…

uniapp判断h5/微信小程序/app端+实战展示

文章目录 导文使用条件编译的基本语法常见的平台标识符示例实战展示使用场景举例注意事项 导文 这里是导文 当你在开发Uni-app时&#xff0c;需要根据不同的平台&#xff08;比如App端、H5端、微信小程序等&#xff09;来执行不同的代码逻辑&#xff0c;可以使用条件编译来实现…

03 Git的基本使用

第3章&#xff1a;Git的基本使用 一、创建版本仓库 一&#xff09;TortoiseGit ​ 选择项目地址&#xff0c;右键&#xff0c;创建版本库 ​ 初始化git init版本库 ​ 查看是否生成.git文件&#xff08;隐藏文件&#xff09; 二&#xff09;Git ​ 选择项目地址&#xff0c…

Redis分布式系统中的主从复制

本篇文章主要对Redis的主从复制进行讲解。主要分析复制的原理&#xff0c;包括:建立复制、全量复制、部分复制、全量复制、心跳检测等。希望本篇文章会对你有所帮助。 文章目录 一、主从复制简介 二、配置主从复制模式 断开主从复制 安全性 只读 传输延迟 三、拓扑结构 四、主…

Java开发之Java容器

#来自ゾフィー&#xff08;佐菲&#xff09; 1 总览 1.1 List ArrayList&#xff1a; Object[]数组Vector&#xff1a;Object[]数组LinkedList&#xff1a; 双向链表&#xff0c;JDK1.6 之前为循环链表&#xff0c;JDK1.7 取消了循环 1.2 Set HashSet&#xff1a;无序&#xf…

mybatis 报CannotGetJdbcConnectionException

目录 报错起因 报错截图 运行环境 数据库配置 解决思路 报错起因 在web项目上拉取代码启动web服务抛CannotGetJdbcConnectionException。 报错截图 运行环境 windows idea maven tomcat springMVC mybatis 数据库配置 urlxxx driverClassNamexxx usernamexxx pass…

docker compose 容器 编排分组

遇到问题&#xff1a;执行docker compose up -d 后docker compose 创建的容器们 在desktop-docker 中都在docker下一堆 搜索想着能不能把这个docker名字改一下&#xff0c;但是都没有找到这样的一个方案&#xff1b; 最后发现&#xff0c;我执行docker compose up -d 命令所在…

【数据结构】二叉树OJ题_对称二叉树_另一棵的子树

对称二叉树 题目 101. 对称二叉树 - 力扣&#xff08;LeetCode&#xff09; 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true示例 2&#xff1a; 输入&#xff1a;root [1,2…

Linux文件和目录常用命令

1.操作命令 查看目录内容 ls 切换目录 cd 创建和删除操作 touch rm mkdir 拷贝和移动文件 cp mv 查看文件内容 cat more grep 其他 echo 重定向 > 和 >> 管道 | 1.1 终端实用技巧 1>自动补全 在敲出 文件/目录/命令 的前几个字母之后&#xff0c;按下…