前端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-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"后缀,包含三个部分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存在的一种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元素的属性,若绑定的值为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官方中文文档
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值