未掌握或被遗忘的vue2 - web前端 | MrNobody = MrNobody's Blog = 保持呼吸 不要断气

# v-model 的原理

原理: v-model 本质上是一个语法糖。例如应用在输入框上,就是 Value 属性和 input 事件的和写
ps: v-model 应用于不同的表单元素 设置的属性和事件会稍有不同,例如在复选框是 checked 属性和 change 事件

作用: 提供数据的双向绑定

  1. 数据发生改变,页面会自动变 :value
  2. 页面输入改变,数据会自动变 @input

注意: $event 用于在模板中,获取事件的形参

<div id="app">
    <input type="text" v-model="text">
   <!-- v-model 等价于 value+@input 的组合 -->
    <input type="text" :value="text" @input="text = $event.target.value">
  </div>

# v-model 应用于组件

使用 v-model 来实现父组件数据和子组件双向绑定

  1. 子组件中: prop 必须通过 value 接收,事件触发必须用 input
  2. 父组件中:v-model 给组件直接绑定数据

父组件

<template>
  <div id="app">
    <!-- v-model 给组件直接绑定数据 -->
    <baseInput v-model="iptValue"></baseInput>
  </div>
</template>
<script>
export default {
  components: {
    'baseInput': () => import('@/components/baseInput.vue')
  },
  name: 'app',
  data() {
    return {
      iptValue: "o.O"
    };
  },
}
</script>

子组件

<template>
  <div class="iptBox">
    <!-- 事件触发必须用 input -->
    <input type="text" :value="value" @input="changeValue">
  </div>
</template>
<script>
export default {
  props: {
    //props 必须用 value 来接收
    value: {
      default: "awa",
      type: String
    }
  },
  methods: {
    changeValue(e) {
      // 将数据通过 input 方法传送给父组件 必须用 input 方法
      this.$emit('input', e.target.value)
    }
  }
}
</script>

# .sync 修饰符

作用: 可以实现 子组件 与 父组件 的双向绑定,简化代码,与 v-model 作用一样,语法上稍有不同

特点: prop 属性名,可以自定义,非固定为 value

本质: 就是 :属性名@update:属性名 合写

父组件

<baseDialog :visible.sync="isShow"></baseDialog>
<!-- 两者相同 -->
<baseDialog :visible="isShow" @update:visible="isShow=$event"></baseDialog>

子组件

<template>
  <div class="baseDialogMask" v-show="visible">
      <div class="baseDialogFooter">
        <button @click="closeDialog">确定</button>
        <button>取消</button>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    visible: Boolean,
    default: false
  },
  methods: {
    closeDialog() {
      // 必须为 update
      this.$emit('update:visible', false)
    }
  }
}
</script>

# 计算属性

概念: 基于现有的数据,计算出来的新属性。依赖的数据变化,自动重新计算。

语法:

  1. 声明在 computed 配置项中,一个计算属性对应一个函数
  2. 使用起来和普通属性一样使用 {{计算属性名}}
// 在 computed 中定义一个计算属性
data() {
  return {
    // 现有的数据
    list: [
      { id: 1, name: '滋蹦', num: 6 },
      { id: 2, name: '和平捍卫者', num: 5 },
      { id: 3, name: 'R99', num: 19 }
    ]
  };
},
computed: {
  // 根据现有的数据 编写求值逻辑
  totalCount() {
//reducer 逐个遍历数组元素,每一步都将当前元素的值与前一步的结果相加(该结果是之前所有步骤结果的总和)—— 直到没有更多需要相加的元素。
    var total = this.list.reduce((sum, item) => sum + item.num, 0)
    return total
  }
}
<!-- 在页面中渲染 -->
<div id="app">
  <table border="1px solid black">
    <tr>
      <th>名字</th>
      <th>数量</th>
    </tr>
    <tr v-for="item in list" :key="item.id">
      <td></td>
      <td></td>
    </tr>
  </table>
  <span>枪械总数:</span>
</div>

# vue 异步更新,$nextTick

首先来看一个案例

需求:

  1. 点击显示按钮后显示 input 盒子
  2. 让 input 立刻获取焦点
<template>
  <div id="app">
    <button @click="showInput">显示input</button>
    <div class="inputBox" ref="inp" v-show="isShow">
      <input type="text">
      <button>确定</button>
    </div>
  </div>
</template>
export default {
  data() {
    return {
      isShow: false
    }
  },
  methods: {
    showInput() {
      // 显示对话框
      this.isShow = true
      // 获取焦点
      this.$ref.inp.focus()
    }
  }
}

看起来代码好像没有问题,实际上立刻获取焦点是无法成功的
因为 Vue 是异步 DOM 更新 (提升性能)
这个时候就需要使用 $nextTick
它的作用是等 DOM 更新完成之后,才会触发执行此方法里的函数体
更改后代码为;

showInput() {
  this.isShow = true
  this.$nextTick(() => {
    this.$refs.inp.focus()
  })
}

当然使用 setTimeout 也可以,但是 nextTick 更加精准

# 自定义指令

# 全局注册自定义指令

还是那个案例,当页面加载时让 input 获取焦点

在 main.js 中全局注册自定义指令

// 全局注册自定义指令 focus 是自定义指令名字
Vue.directive('focus', {
  //inserted 是当前指令所绑定的元素被添加到页面去的时 会自动调用
  //el 是指令所绑定的元素
  "inserted"(el) {
    el.focus()
  }
})
<!-- v - 指令名即可使用 -->
<input type="text" v-focus>

让 input 表单只能输入正整数自定义指令

Vue.directive('numeric', {
  'inserted' (el) {
    el.addEventListener('input', function (e) {
      const input = e.target
      input.value = input.value.replace(/[^0-9]/g, '') // 只保留数字
    })
  }
})

# 自定义指令的值

在绑定指令时,可以通过 “等号” 的形式为指令 绑定 具体的参数值
语法:

  1. v - 指令名 = "指令值" 通过 等号 可以绑定指令的值
  2. 通过 binding.value 可以拿到指令的值
  3. 通过 update 钩子,可以监听指令值的变化,进行 dom 更新操作
<span v-color="'green'">嗡嗡嗡</span>
Vue.directive('color', {
  //inserted 提供的是元素被添加到页面时的逻辑
  "inserted"(el, binding) {
    el.style.color = binding.value
  },
  //update 会在指令的值修改的时候触发,提供值变化后 dom 更新的逻辑
  update(el, binding) {
    el.style.color = binding.value
  }
})

# 封装加载动画指令

一个工作中非常常用的自定义指令
场景: 实际开发过程中,发送请求需要时间,在请求的数据未返回时,页面会处于空白状态,这样会导致用户体验不好。

所以我们可以封装一个指令,实现加载效果

思路:

  1. 本质 Loading 效果就是一个蒙层,盖在了盒子上
  2. 数据请求中,开启 Loading 状态,添加蒙层
  3. 数据请求完毕,关闭 Loading 状态,移除蒙层

先准备一个 loading 类 通过伪元素定位设置宽高,实现蒙层

.loading:before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background: black url('assets/loading.gif') no-repeat center;
}

自定义 loading 指令,当获取数据时,移除 loading 遮罩层

data() {
  return {
    isLoading: true,
    list: []
  }
},
// 局部自定义指令
directives: {
  loading: {
    inserted(el, binding) {
      // 如果 isLoading 为 true 则添加 loading 类,没有则移除
      binding.value ? el.classList.add('loading') : el.classList.remove('loading')
    },
    // 当数据发生更改时触发函数
    update(el, binding) {
      binding.value ? el.classList.add('loading') : el.classList.remove('loading')
    }
  }
},
created() {
  // 模拟接收数据慢
  setTimeout(() => {
    // 关闭 loading 遮罩层
    this.isLoading = false
    this.list = list
  }, 2000);
}
<div class="box" v-loading="isLoading">
</div>

# 组件缓存 keep-alive

keep-alive 是 Vue 的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁他们

优点
在组件切换过程中,把切换出去的组件保留在内存中,防止重复渲染 DOM
减少加载时间及性能消耗,提高用户体验性

使用方式

<template>
  <div id="app">
    <keep-alive>
      <router-view></router-view>
    </keep-alive>
  </div>
</template>

keep-alive 的三个属性

  1. include 组件名数组,只有匹配的组件会被缓存
  2. exclude 组件名数组,任何匹配的组件都不会被缓存
  3. max 最多可以缓存多少组件实例

ps: 优先使用组件中的 name 来作组件名,如果组件没有配置 name 则会找文件名作组件名

<template>
  <div>
    <keep-alive :exclude="[`articleDetail`]">
      <router-view></router-view>
    </keep-alive>
  </div>
</template>
  1. keep-alive 的两个生命周期函数
  • activated 当组件被激活时触发 → 进入页面触发
  • deactivated 当组件不被使用的时候触发 → 离开页面触发
<script>
export default {
  name: 'layout',
  activated() {
    console.log('组件被激活,进入组件页面');
  },
  deactivated() {
    console.log('组件失活,离开组件页面');
  },
}
</script>