【Vue】指令修饰符 && 样式绑定 && 计算属性computed && 侦听器watch

文章目录

Ⅰ. 指令补充

一、指令修饰符

所谓指令修饰符就是让指令的 功能更强大,书写更便捷

常见指令修饰符如下所示:

  1. 如何监听回车键和鼠标等?
  • @keydown.enter -> 回车按下
  • @keyup.enter -> 回车抬起
  • @mouseenter -> 鼠标放置于对应组件中
  • @mouseleave -> 鼠标离开对应组件中
  1. 如何阻止默认行为、阻止冒泡?
  • @click.prevent -> 阻止默认行为(比如链接跳转,表单提交后刷新页面等)
  • @click.stop -> 阻止冒泡
  • @click.prevent.stop -> 可以链式调用,二者都阻止
  1. v-model的三个修饰符的作用是什么?
  • .lazy -> 失去焦点再同步
  • .trim -> 去除首尾空格
  • .number -> 尝试转数字

二、v-model 用在其他表单元素上

常见的表单元素都可以用 v-model 绑定关联,作用是可以快速获取或设置表单元素的值。

它会根据控件类型自动选取正确的属性来更新元素。

markdown 复制代码
输入框   input:text     ------> value(直接绑定)
文本域   textarea       ------> value(直接绑定)

下拉菜单 select/option  ------> value(v-model 写在 select 上, 关联 option 中的 value)

单选框   input:radio    ------> value(给单选框添加 value 属性,v-model 收集选中单选框的 value)

复选框   input:checkbox ------> checked / value
    1、一个复选框,v-model绑定 布尔值,关联 checked 属性
    2、一组复选框,v-model绑定 数组,关联 value 属性,给这些复选框手动添加 value

实例代码:

xml 复制代码
<script setup>
    import { ref } from 'vue'
    // 自我介绍
    const intro = ref('')
    // 收集城市
    const city = ref('SH')
    // 收集血型
    const blood = ref('ab')
    // 是否同意用户协议
    const isAgree = ref(false)
    // 收集爱好
    const hobby = ref(['ZQ', 'PB'])
</script>

<template>
  <div>
    <!-- 文本域 -->
    <textarea
      v-model="intro"
      cols="30"
      rows="4"
      placeholder="请输入自我介绍"></textarea>
    <br />
    <br />
    
    <!-- 下拉菜单 -->
    <select v-model="city">
      <option value="BJ">北京</option>
      <option value="SH">上海</option>
      <option value="SZ">深圳</option>
      <option value="HZ">杭州</option>
    </select>
    <br />
    <br />
    
    <!-- 单选框:多个当中只能选择一个,需要给单选框手动添加 value 属性 -->
    <input
      type="radio"
      value="a"
      v-model="blood" />A
    <input
      type="radio"
      value="b"
      v-model="blood" />B
    <input
      type="radio"
      value="ab"
      v-model="blood" />AB
    <input
      type="radio"
      value="o"
      v-model="blood" />O
    <br />
    <br />
    
    <!-- 复选框:1、一个复选框,v-model绑定 布尔值,关联 checked 属性 -->
    <input
      type="checkbox"
      v-model="isAgree" />是否同意用户协议
    <br />
    <br />
    
    <!-- 复选框:2、一组复选框,v-model绑定 数组,关联 value 属性,给这些复选框手动添加 value -->
    <input
      v-model="hobby"
      type="checkbox"
      value="LQ" />篮球
    <input
      v-model="hobby"
      type="checkbox"
      value="ZQ" />足球
    <input
      v-model="hobby"
      type="checkbox"
      value="YMQ" />羽毛球
    <input
      v-model="hobby"
      type="checkbox"
      value="PPQ" />乒乓球
  </div>
</template>

Ⅱ. 样式绑定

一、基本介绍

为了方便开发者进行样式控制,Vue 扩展了 v-bind 的语法,可以针对 class 类名和 style 行内样式两个属性进行控制,进而通过数据控制元素的样式。

二、绑定 class

语法如下所示:

javascript 复制代码
// 语法:
:class = "三元表达式 / 对象"

// 1、三元表达式:
<p :class="条件 ? '类名1' : '类名2'"></p>

// 2、对象:
<p class="box" :class="{ 类名1: 布尔值1, 类名2: 布尔值2 }"></p>

注意事项:

  • class 动态绑定的是对象时,键就是类名,值就是布尔值,如果值是 true,就添加这个类,否则删除这个类。
  • 静态 class 与动态 class 共存可以共存,二者会合并。

示例代码:

javascript 复制代码
<script setup>
    import { ref } from 'vue'
    // 是否处于激活
    const isActive = ref(true)
</script>

<template>
  <div>
    <!-- 1. 三元绑定 -->
    <p :class="isActive ? 'active' : ''">Active1</p>
    
    <!-- 2. 对象绑定 -->
    <p :class="{ active: isActive }">Active2</p>
    
    <!-- 3. 静态class与动态class共存 -->
    <p class="item" :class="{ active: isActive }">Active3</p>
  </div>
</template>

<style>
.active {
  color: red;
}
</style>

案例:京东秒杀-tab栏切换导航高亮

当我们点击哪个页签时,哪个页签就高亮:

思路:

  1. 基于数据,动态渲染页签(v-for
  2. 准备一个下标记录高亮哪一个页签
  3. 基于下标动态切换 class 的类名
javascript 复制代码
<script setup>
    import { ref } from 'vue'
    // tabs 列表
    const tabs = [
      { id: 1, name: '京东秒杀' },
      { id: 2, name: '每日特价' },
      { id: 3, name: '品类秒杀' }
    ]
    // 当前高亮的下标, 默认是第一个, 下标为 0
    const currentIndex = ref(0)
</script>

<template>
  <div>
    <ul>
      <li
        v-for="(item, index) in tabs"
        :key="item.id"
        @click="currentIndex = index">
        <a
          :class="{ active: currentIndex === index }"
          href="#"
        >
            {{ item.name }}
        </a>
      </li>
    </ul>
  </div>
</template>

<style>
* {
  margin: 0;
  padding: 0;
}
ul {
  display: flex;
  border-bottom: 2px solid #e01222;
  padding: 0 10px;
}
li {
  width: 100px;
  height: 50px;
  line-height: 50px;
  list-style: none;
  text-align: center;
}
li a {
  display: block;
  text-decoration: none;
  font-weight: bold;
  color: #333333;
}
li a.active {
  background-color: #e01222;
  color: #fff;
}
</style>

三、绑定 style 样式

javascript 复制代码
<div class="box" :style="{ CSS属性名1: CSS属性值, CSS属性名2: CSS属性值 }"></div>

实例代码:

javascript 复制代码
<script setup>
    import { reactive } from 'vue'
    // 行内样式对象
    const styleObj = reactive({
      color: '#fff',
      backgroundColor: 'purple'
    })
</script>

<template>
  <div>
    <p :style="styleObj">Some Text...</p>
  </div>
</template>

案例:进度条

javascript 复制代码
<script setup>
    import { ref } from 'vue'
    // 占比
    const x = ref(0)
</script>

<template>
  <div class="progress">
    <div
      class="inner"
      :style="{ width: `${(x / 4) * 100}%` }">
      <span>{{ (x / 4) * 100 }}%</span>
    </div>
  </div>
  <button @click="x = 1">设置25%</button>
  <button @click="x = 2">设置50%</button>
  <button @click="x = 3">设置75%</button>
  <button @click="x = 4">设置100%</button>
</template>

<style>
.progress {
  height: 25px;
  width: 400px;
  border-radius: 15px;
  background-color: #272425;
  border: 3px solid #272425;
  box-sizing: border-box;
  margin-bottom: 30px;
}
.inner {
  height: 20px;
  border-radius: 10px;
  text-align: right;
  position: relative;
  background-color: #409eff;
  background-size: 20px 20px;
  box-sizing: border-box;
  transition: all 1s;
}
.inner span {
  position: absolute;
  right: -25px;
  bottom: -25px;
}
</style>

Ⅲ. 计算属性 computed

一、基本使用

基于现有的数据,计算出来的新数据;即当现有的数据变化,computed 属性会自动重新计算。

语法如下所示:

javascript 复制代码
const 新数据 = computed(() => {
  // some code ...
  return 结果
})

💥注意事项:

  1. 计算属性必须有返回值
  2. 使用和 ref/reactive 数据一样,计算属性可用于插值 ,也可配合指令使用

示例代码:(计算商品总数量)

javascript 复制代码
<script setup>
    import { computed, ref } from 'vue'
    // 商品列表(原始数据)
    const goodsList = ref([
      { id: 1, name: '篮球', num: 1 },
      { id: 2, name: '玩具', num: 3 },
      { id: 3, name: '书籍', num: 2 }
    ])
    
    // 由于这里只有原始数据,没有直接提供商品总数量
    // 但是我们可以基于现有的 goodsList 计算得到总数量
    // 那么推荐把总数量声明为 计算属性
    const totalNum = computed(() => {
        return goodsList.value.reduce((prev,item) => prev + item.num, 0)
    })
</script>

<template>
  <h3>礼物清单</h3>
  <table>
    <thead>
      <tr>
        <th>名字</th>
        <th>数量</th>
      </tr>
    </thead>
    <tbody>
      <tr
        v-for="item in goodsList"
        :key="item.id">
        <td>{{ item.name }}</td>
        <td>{{ item.num }}</td>
      </tr>
    </tbody>
  </table>
  <p>礼物总数:{{ totalNum }} 个</p>
</template>

<style>
table {
  width: 350px;
  border: 1px solid #333;
}
table th,
table td {
  border: 1px solid #333;
}
table td {
  text-align: center;
}
</style>

二、计算属性 VS 普通函数

对比点 计算属性 (computed) 普通函数
是否有缓存 有缓存,依赖的数据不变时,多次访问直接返回缓存结果 没有缓存,每次调用都会重新执行
调用方式 当作属性使用:{``{ fullName }} 当作方法调用:{``{ getFullName() }}
适用场景 依赖响应式数据,且结果需要复用、避免重复计算 一次性逻辑处理,或者不依赖缓存的情况
依赖追踪 会自动追踪依赖的响应式数据,依赖变动才会重新计算 不追踪依赖,调用时才执行
性能表现 更高效,适合复杂或多次使用的计算结果 相对低效,调用一次就执行一次
语义 表达式式的属性,像数据 行为式的函数,强调动作

三、计算属性的完整写法

  1. 计算属性的默认写法,只能读(展示数据),不能改 (导致 v-model 双向绑定不灵了)
  2. 如果要"改",需要用计算属性的完整写法
javascript 复制代码
<script setup>
    import { computed } from 'vue'
    
    // 完整写法 = get + set
    const uname = computed({
      // 使用计算属性的时候,自动触发get,get内部必须 return 计算结果
      get() {
        // 计算得到新数据
        return '姬霓太美'
      },
      
      // 给计算属性赋值的时候,自动触发set,并接收赋予的新值val
      set(val) {
        // 一段修改逻辑
        console.log(val)
      }
    })
</script>

<template>
  <input type="text" v-model="uname" />
</template>

案例:全返反选

javascript 复制代码
<script setup>
    import { computed, ref } from 'vue'
    // 计划列表
    const planList = ref([
      { id: 12, name: '跑步', done: false },
      { id: 76, name: '看书', done: false },
      { id: 31, name: '撸码', done: false },
      { id: 49, name: '追剧', done: false }
    ])
    
    // 是否全部选中
    const isAll = computed({
      // 使用计算属性自动触发 get()
      get() {
        // every: 检测每一个
        return planList.value.every((item) => item.done)
      },
      // 修改计算属性自动触发 set()
      set(val) {
        // val: 给计算属性赋予的新值,在这里就是表全选复选框的状态
        // 遍历 planList 数组,把每个小选的 done 属性与 val 保持一致即可
        planList.value.forEach((item) => (item.done = val))
      }
    })
    
    // 反选
    const onToggle = () => {
      // 遍历 planList 数组,对每个对象的 done 属性取反即可
      planList.value.forEach((item) => (item.done = !item.done))
    }
</script>

<template>
  <p>
    <span>
      <input
        v-model="isAll"
        type="checkbox"
        id="all" />
      <label for="all">全选</label>
    </span>
    <button @click="onToggle">反选</button>
  </p>
  <ul>
    <li
      v-for="item in planList"
      :key="item.id">
      <input
        type="checkbox"
        v-model="item.done" />
      <span :class="{ completed: item.done }">{{ item.name }}</span>
    </li>
  </ul>
</template>

<style lang="scss">
// npm i sass -D
// npm run dev
* {
  margin: 0;
  padding: 0;
}
ul {
  list-style: none;
}
#app {
  width: 400px;
  margin: 100px auto;
  padding: 15px 18px;
  background: plum;
  p {
    display: flex;
    justify-content: space-between;
    align-items: center;
    height: 40px;
    border-bottom: 1px solid #ccc;
    button {
      padding: 3px 6px;
    }
    input {
      margin-right: 8px;
    }
  }
  ul {
    li {
      display: flex;
      justify-content: space-between;
      align-items: center;
      height: 40px;
      border-bottom: 1px solid #ccc;
      span.completed {
        color: #ddd;
        text-decoration: line-through;
      }
    }
  }
}
</style>

Ⅳ. 侦听器 watch

监视数据变化,一旦数据变化了,可以执行相应的 DOM 操作或异步操作。

语法如下所示:

javascript 复制代码
<script setup>
    import { ref, watch } from 'vue'
    
    // 搜索框关键字
    const keyword = ref('')
    
    // 监视 keyword, 只要 keyword 的值变了, 就会执行回调函数
    watch(keyword, (newVal, oldVal) => {
      // 处理逻辑
    }, { deep: false} )
</script>

<template>
  <div>
    <input
      type="text"
      v-model="keyword"
      placeholder="请输入关键字" />
  </div>
</template>

💥注意事项:如果监视的是一个对象里面的数据 ,那么就需要打开 deep:true,才能监视到!

Ⅴ. 综合案例:成绩管理

功能描述:

  1. 渲染成绩
  2. 添加成绩
  3. 删除成绩
  4. 统计总分,求平均分
  5. 数据持久化

思路分析:

  1. 渲染功能 v-for:keyv-bind: 动态绑定 class 的样式
  2. 删除功能 v-on 绑定事件,阻止 a 标签的默认行为(只是起到点击后删除功能,不要让它跳转)
  3. v-model 的修饰符 .trim.number、判断数据是否为空后再添加、添加后清空文本框的数据
  4. 使用计算属性 computed 计算总分和平均分的值
  5. 使用 localStorage 进行持久化

代码如下所示:

javascript 复制代码
<script setup>
    import { computed, reactive, ref, watch } from 'vue'
    // 本地存储 key
    const SCORE_LIST_KEY = 'score-list-key'
    // 成绩列表
    const scoreList = ref(
      // 从本地取值
      // 有值就用, 否则用默认的成绩列表
      JSON.parse(localStorage.getItem(SCORE_LIST_KEY)) || [
        { id: 19, subject: '语文', score: 94 },
        { id: 27, subject: '数学', score: 57 },
        { id: 12, subject: '英语', score: 92 }
      ]
    )
    
    // 成绩表单
    const scoreForm = reactive({
      subject: '', // 科目
      score: ''    // 分数
    })
    
    // 总分
    const totalScore = computed(() => {
      // reduce 求和
      return scoreList.value.reduce((prev,item) =>prev +item.score, 0)
    })
    
    // 平均分
    const avgScore = computed(() => {
      return scoreList.value.length
        ? totalScore.value / scoreList.value.length
        : 0
    })
    
    // 删除
    const onDel = (index) => {
      // 弹窗确认
      if (window.confirm('确认删除么?')) {
        scoreList.value.splice(index, 1)
      }
    }
    
    // 添加
    const onAdd = () => {
      // 非空校验
      if (!scoreForm.subject || !scoreForm.score)
        return alert('科目或分数不能为空')
      
      // 添加至数组
      scoreList.value.push({
        ...scoreForm,
        id: Date.now()
      })
      
      // 清空
      scoreForm.subject = scoreForm.score = ''
    }
    
    // 监听 scoreList 的变化
    watch(
      scoreList,
      (newVal) => {
        // 存本地
        localStorage.setItem(SCORE_LIST_KEY, JSON.stringify(newVal))
      },
      {
        // 深度监视
        deep: true
      }
    )
</script>

<template>
  <div class="score-case">
    <div class="table">
      <table>
        <thead>
          <tr>
            <th>编号</th>
            <th>科目</th>
            <th>成绩</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody v-if="scoreList.length">
          <tr
            v-for="(item, index) in scoreList"
            :key="item.id">
            <td>{{ index + 1 }}</td>
            <td>{{ item.subject }}</td>
            <td :class="{ red: item.score < 60 }">{{ item.score }}</td>
            <td>
              <a
                href="#"
                @click="onDel(index)"
              >删除</a
              >
            </td>
          </tr>
        </tbody>
        <tbody v-else>
          <tr>
            <td colspan="5">
              <span class="none">暂无数据</span>
            </td>
          </tr>
        </tbody>
        <tfoot>
          <tr>
            <td colspan="5">
              <span>总分: {{ totalScore }}</span>
              <span style="margin-left: 50px">平均分: {{ avgScore }}</span>
            </td>
          </tr>
        </tfoot>
      </table>
    </div>
    <form class="form">
      <div class="form-item">
        <div class="label">科目:</div>
        <div class="input">
          <input
            type="text"
            placeholder="请输入科目"
            v-model.trim="scoreForm.subject" />
        </div>
      </div>
      <div class="form-item">
        <div class="label">分数:</div>
        <div class="input">
          <input
            type="number"
            placeholder="请输入分数"
            v-model.number="scoreForm.score" />
        </div>
      </div>
      <div class="form-item">
        <div class="label"></div>
        <div class="input">
          <button
            class="submit"
            @click.prevent="onAdd">
            添加
          </button>
        </div>
      </div>
    </form>
  </div>
</template>

<style>
.score-case {
  width: 1000px;
  margin: 50px auto;
  display: flex;
}
.score-case .table {
  flex: 4;
}
.score-case .table table {
  width: 100%;
  border-spacing: 0;
  border-top: 1px solid #ccc;
  border-left: 1px solid #ccc;
}
.score-case .table table th {
  background: #f5f5f5;
}
.score-case .table table tr:hover td {
  background: #f5f5f5;
}
.score-case .table table td,
.score-case .table table th {
  border-bottom: 1px solid #ccc;
  border-right: 1px solid #ccc;
  text-align: center;
  padding: 10px;
}
.score-case .table table td.red,
.score-case .table table th.red {
  color: red;
}
.score-case .table .none {
  height: 100px;
  line-height: 100px;
  color: #999;
}
.score-case .form {
  flex: 1;
  padding: 20px;
}
.score-case .form .form-item {
  display: flex;
  margin-bottom: 20px;
  align-items: center;
}
.score-case .form .form-item .label {
  width: 60px;
  text-align: right;
  font-size: 14px;
}
.score-case .form .form-item .input {
  flex: 1;
}
.score-case .form .form-item input,
.score-case .form .form-item select {
  appearance: none;
  outline: none;
  border: 1px solid #ccc;
  width: 200px;
  height: 40px;
  box-sizing: border-box;
  padding: 10px;
  color: #666;
}
.score-case .form .form-item input::placeholder {
  color: #666;
}
.score-case .form .form-item .cancel,
.score-case .form .form-item .submit {
  appearance: none;
  outline: none;
  border: 1px solid #ccc;
  border-radius: 4px;
  padding: 4px 10px;
  margin-right: 10px;
  font-size: 12px;
  background: #ccc;
}
.score-case .form .form-item .submit {
  border-color: #069;
  background: #069;
  color: #fff;
}
</style>
相关推荐
多仔ヾ2 小时前
Vue.js 前端开发实战之 08-Vue 开发环境
vue.js
源码获取_wx:Fegn08952 小时前
计算机毕业设计|基于springboot + vue景区管理系统(源码+数据库+文档)
java·vue.js·spring boot·后端·课程设计
踢球的打工仔3 小时前
typescript-接口的基本使用(一)
android·javascript·typescript
徐小夕@趣谈前端3 小时前
NO-CRM 2.0正式上线,Vue3+Echarts+NestJS实现的全栈CRM系统,用AI重新定义和实现客户管理系统
前端·javascript·人工智能·开源·编辑器·echarts
catino3 小时前
图片、文件上传
前端
Mr Xu_4 小时前
Vue3 + Element Plus 实现点击导航平滑滚动到页面指定位置
前端·javascript·vue.js
小王努力学编程4 小时前
LangChain——AI应用开发框架(核心组件1)
linux·服务器·前端·数据库·c++·人工智能·langchain
pas1364 小时前
35-mini-vue 实现组件更新功能
前端·javascript·vue.js
前端达人4 小时前
为什么聪明的工程师都在用TypeScript写AI辅助代码?
前端·javascript·人工智能·typescript·ecmascript