1.前言

由于在日常开发中会有一部分前端的开发任务,会涉及到Vue的项目的搭建、迭代、构建发布等操作,所以想系统的学习一下Vue相关的知识点,本专题会依照Vue的搭建、开发基础实践、进阶用法、打包部署的顺序进行记录。

主要内容与历史文章链接如下:

  • Vue3搭建、路由配置与基本语法
  • 响应式变量、双向绑定、计算属性、侦听器
  • 工具与组件的封装、父子组件的使用、生命周期
  • 环境变量、axios配置与打包部署

本篇会在上一篇的基础上进行拓展,讲解VUE开发中需要使用到的核心功能,即:响应式变量、双向绑定、属性计算、监听器,本篇完成之后就可以做一些简单的功能开发了。

本篇所有的代码都会以组合式API进行编写。

2.响应式变量(对象)

所谓的响应式的变量,可以在变量发生变化的同时,让依赖于这个变量进行渲染的视图也同步发生变化,简单的说,就是以数据驱动视图

同时,在响应式变量中有一种特殊的使用方式v-model,可以在视图发生变化时,也同步的修改变量的值,我们把这种特殊的方式称为:双向绑定

2.1.响应式的使用

响应式变量就是在原有的变量外面包了一层代理对象,我们知道代理的作用就是用来做增强,数据驱动视图、双向绑定等操作都是由代理对象来执行的。

创建这个代理对象的方式有两种:reactiveref ,它们的区别在于reactive只能代理对象类型的变量。而对于基础数据类型stringnumber等,就需要使用 ref 来进行代理了,同时 ref 还包含了代理对象类型的变量功能。

也就是说,我们直接使用ref就可以了。


假设我们现在有一个任务清单ul,通过 v-for 循环某个数组变量arr来进行渲染,此时有一个按钮,每点击一次就往arr中添加一个元素,在不使用ref的情况下,我们查看点击的效果。

<template>
  <div class="hello">
    <ul>
      <li v-for="(value,key) in arr" :key="key">{{ value }}</li>
    </ul>
    <button @click="addArr">添加</button>
  </div>
</template>

<script setup>
let arr = ["item1", "item2", "item3"];

const addArr = function() {
  arr.push("item" + (arr.length + 1));
  console.log(arr);
};
</script>

在这里插入图片描述
从图中可以看到,数组中已经添加到了7个元素,但视图中并没有增加。


引入ref之后,再查看点击的效果。

<template>
  <div class="hello">
    <ul>
      <li v-for="(value,key) in arr" :key="key">{{ value }}</li>
    </ul>
    <button @click="addArr">添加</button>
  </div>
</template>

<script setup>
import { ref } from "vue";

let arr = ref(["item1", "item2", "item3"]);

const addArr = function() {
  arr.value.push("item" + (arr.value.length + 1));
  console.log(arr);
};
</script>

在这里插入图片描述
此时图中有两个变化:

  • 视图中的清单随数组的变化而发送变化
  • 通过console.log(arr)打印出的数据变成了一个代理对象

代理对象中有一个value值保存了原有的数组数据,我们想获取到数组数据,就需要使用.value进行解包,这也是为什么在第二段代码中使用的是arr.value.push

同时,我们可以注意到的是,在v-for="(value,key) in arr"></li>标签中,我们并没有使用 arr.value 来获取数组,这是因为在模板、条件、循环这样的语法中,ref代理对象会自动解包。

2.2.双向绑定(v-model)

双向绑定一般是用在可交互的表单元素上,例如:input , select , radio , checkbox等等,下面以 input 为例来使用一下双向绑定。

创建一个输入框,绑定inputValue这个响应式对象,在<h1>标签中通过模板展示inputValue的值,同时提供一个重置按钮,点击后通过函数将输入的变量值重置为空字符串

其中第一步是验证视图对数据的影响,第二步是验证数据对视图的影响。

<template>
  <div class="hello">
    输入框:<input v-model="inputValue" placeholder="请输入" />
    <button @click="reset">重置</button>
    <br />
    <h1>{{ inputValue }}</h1>
  </div>

</template>

<script setup>
import { ref } from "vue";

let inputValue = ref("");

const reset = function() {
  inputValue.value = "";
};
</script>

在这里插入图片描述
在这里插入图片描述


这就是VUE中双向绑定的使用,非常简单。v-model 除了可以绑定上述的这类表单元素外,还可以在组件之间传递参数,这个在后面的组件相关的文章中会讲到。

3.计算属性

3.1.金额单位转换案例

属性计算,就是绑定一个属性,当这个属性的值发生变化时,可以做一些自定义的计算获得一个新的属性。

举一个简单的例子,我们在做金额相关的需求时,在后台数据库中存入的金额单位是,但在前端视图中显示的是,所有这里会有单位转换的操作。

<template>
  <div class="hello">
    请输入金额:<input v-model.number="amount" placeholder="请输入" /><br />
    <h1>{{ amountYuan }} 元</h1>
  </div>
</template>

<script setup>
import { computed, ref } from "vue";

let amount = ref(0);

// 此处暂不考虑除法的精度丢失问题
const amountYuan = computed(() => amount.value / 100);
</script>

在这里插入图片描述


computed中的 getter 方法(() => amount.value / 100)中会自动找到需要计算的对象amount,当amount的值发生变化时,就会触发 getter 方法,并更新amountYuan这个属性的值。

需要注意的是,computed中的 getter 方法(()=> xxx)只用于计算并获取一个新的属性,不要在这个方法中做一些诸如请求服务器、修改DOM元素、修改其他属性的操作。
同时,对于生成的amountYuan这个属性,应该将其视为一个只读快照,除了 getter 方法以外,不要有其他地方修改它,以免造成一些不可预知的错误。

如果一定要做上面的操作的话,建议使用监听器而不是计算属性

4.监听器

监听器又可以叫做侦听器、监视器,都是一个意思,监听器可以对某个数据源建立监听关系,当这个数据源发生变化时,就会触发一个回调函数,在这个回调函数中可以写自己想触发的功能逻辑。

VUE中的定义监听器有两种 watchwatchEffect ,它们的区别在于:

  • watch:需要显式的指定被监听的数据源。
  • watchEffect:可以根据回调函数中使用的变量来推断被监听的对象(类似于computed)。

监听器与计算属性的区别在于,监听器不会生成一个新的快照对象,而是基于现有的变量来做操作。

4.1.watch的语法

watch 是一个函数,可以传入3个参数:监听数据源、回调函数、监听选项。其中,数据源与回调函数可以通过数组传入多个,下面是简化版的函数定义。

// 侦听单个来源
function watch<T>(
  source: WatchSource<T>,
  callback: WatchCallback<T>,
  options?: WatchOptions
): StopHandle

// 侦听多个来源
function watch<T>(
  sources: WatchSource<T>[],
  callback: WatchCallback<T[]>,
  options?: WatchOptions
): StopHandle

下面通过几个例子还尝试一下各个参数的使用方式。

4.2.单行文本参数校验

现在只有这么一个需求,有一个input框用于输入手机号,在输入时校验输入的手机号是否正确,要实现这个需求,我们只需要在文本框中绑定一个属性phoneNumber,然后通过watch监听就可以了,这是一个最简单的监听,监听数据源类型为ref,实现方法如下:

<template>
  <div class="hello">
    手机号:<input v-model="phoneNumber" placeholder="请输入手机号" />
    <br />
    <p v-if="showTip">请输入正确的手机号</p>
  </div>
</template>

<script setup>
import { ref, watch } from "vue";

const phoneNumber = ref("");
const showTip = ref(false);

const reg = /^(?:0|86|\+86)?1[3-9]\d{9}$|^$/;

watch(phoneNumber, (newValue) => {
  if (!reg.test(newValue)) {
    showTip.value = true;
    return;
  }
  showTip.value = false;
});
</script>

在这里插入图片描述


此处也可以对原有的数据源做一点处理,即使用 getter方法,例如在每个手机号前面都加上一个+86

watch(
  () => {
    return "+86" + phoneNumber.value;
  },
  // 此处的参数phone即上面的getter方法返回值
  (phone) => {
    const reg = /^(?:0|86|\+86)?1[3-9]\d{9}$|^$/;

    if (!reg.test(phone)) {
      showTip.value = true;
      return;
    }
    showTip.value = false;
  });

4.3.对象监听(select联动)

当监听的数据源类型为对象时,需要注意两个点:

  • 只有对象整体被修改才会触发回调函数,如果想要对象中的某个元素发生变化也能触发回调,则需要配置深层监听,即:options参数配置为{deep:true}
  • 回调函数上的参数 newValueoldValue 值是一样的,因为是同一个对象。

接下来用一个稍微复杂一点点的例子 select的联动来验证一下对象的监听,同时也是对上一篇的基本语法做一次复习。


有这么一个需求:

  • 页面表单上有3个select,编号分别为1,2,3,其待选项都为A,B,C
  • 其中select1是显示的,另外两个是隐藏的。
  • select1值为A时显示select2,不为A时将selelct2值置为空并隐藏。
  • selelct2值为A时显示select3,不为A时将selelct3值置为空并隐藏。
<template>
  <div class="hello">
    <div v-for="n in 3" v-show="form['select' + n].visible">
      选项 {{ n }}:
      <select v-model="form['select' + n].optionValue" class="select">
        <option v-for="(item,index) in selectSource" :key="index" :value="item">
          {{ item }}
        </option>
      </select>
    </div>
  </div>
</template>

<script setup>
import { ref, watch } from "vue";

const selectSource = ["A", "B", "C"];

// 表单数据
const form = ref({
  select1: { optionValue: "", visible: true, visibleTargetKey: "select2" },
  select2: { optionValue: "", visible: false, visibleTargetKey: "select3" },
  select3: { optionValue: "", visible: false, visibleTargetKey: "" }
});

watch(
  // 通过两次JSON转换获取一个新的对象,此时newValue与oldValue才会不一样
  () => JSON.parse(JSON.stringify(form.value)),
  (newValue, oldValue) => {
    // 找出是哪个值发生了变化
    let changedKey;
    for (let key in newValue) {
      if (newValue[key].optionValue !== oldValue[key].optionValue) {
        changedKey = key;
        break;
      }
    }
    // 都没有变化则return
    if (!changedKey) {
      return;
    }
    // 显示与隐藏的处理
    disposeVisible(changedKey, newValue[changedKey].optionValue, newValue[changedKey].visibleTargetKey);
  },
  { deep: true }
);

const disposeVisible = (changedKey, changedOptionValue, targetKey) => {
  if (!targetKey) {
    return;
  }
  for (let key in form.value) {
    let isVisible = changedOptionValue === "A";
    form.value[targetKey].visible = isVisible;
    if (!isVisible) {
      form.value[targetKey].optionValue = "";
    }
  }
};
</script>

在这里插入图片描述
联动效果的操作步骤比较多,这里就不一一截图说明了,感兴趣的同学可以复制上面的代码体验。


需要注意的是,深层监听{deep:true}会迭代检查对象中的元素变化,对象越大,嵌套越深,消耗的性能就越高,所以我们在日常开发的时候尽可能的减少深层监听的使用。

4.4.watchEffect的使用

我们已经提到了,watchEffect不需要显示的指定参数,而是通过回调函数中使用到的变量来进行推断,将4.1中的watch方法改写一下,将newValue去掉,在函数中直接使用phoneNumber.value,最终的效果是一致的:

watchEffect(() => {
  if (!reg.test(phoneNumber.value)) {
    showTip.value = true;
    return;
  }
  showTip.value = false;
});

5.总结

本篇主要是讲了VUE开发中经常会使用到的响应式变量、双向绑定的使用方式,并举例说明了计算属性和监听器之间的使用差别及各自的作用,有了本篇的基础,已经可以使用VUE做一些简单的开发了。

需要注意这么几个细节:

  • 响应式与双向绑定的联系与区别,v-model的作用
  • 计算属性的使用限制,生成的快照属性不能别其他功能修改
  • 监听器watchwatchEffect的区别,watchEffectcomputed的区别
  • 监听器监听数据源为对象时需要注意两个问题:深层监听newValueoldValue的值相等

如果想将VUE使用的更加优雅,可以关注下一篇博客,在下一篇中将会讲到组件的封装、组合式函数的封装、父子组件的交互等功能的使用。

Logo

前往低代码交流专区

更多推荐