Vue 系列笔记第四篇。本文参考:>> 黑马程序员 Vue 全套视频教程


Vue 系列文章 📑📑>> 专栏阅读 <<


一、组件的生命周期


>> 两个概念:生命周期 和 生命周期函数

生命周期:是指一个组件从 创建 => 运行 => 销毁 的整个阶段,强调的是一个时间段。

生命周期函数:是由 vue 框架提供的 内置函数,会伴随着组件的生命周期,自动按次序执行。生命周期函数所强调的是时间点,也就是说如果你想在某个特定的时间点去执行某些事情,那么你就将代码写在相应的生命周期函数中即可。
 
>> 组件生命周期函数的分类
在这里插入图片描述

可以参考文末给出的 >> 生命周期图示及说明 ,进一步理解组件生命周期执行的过程。
 
>> 组件创建阶段的生命周期函数

  • beforeCreate()

创建组件实例化对象后的第一个周期函数是 beforeCreate() ,这时组件的 props、data、methods 尚未被创建,都处于 不可用状态 。因此, beforeCreate() 基本上没有什么用武之地,因此也比较少被用到。

  • created()

在初始化 props、data、methods 后(这时它们处于 可用状态 ),迎来了第二个周期函数的节点,也就是 created() 函数。 created() 函数是特别常用的,一般用来发起 AJAX 请求来请求数据,并且将请求到的数据转存在 data 中,供 template 模板渲染的时候使用。

🎈🎈 注意:这时组件的模板结构尚未生成,不能操作 DOM。

  • beforeMount()

内存中 的模板编译生成 HTML 结构后,迎来第三个生命周期函数 beforeMount() 的节点。因为此时只是在内存中生成了 HTML 结构,还没有被渲染到页面中,因此此时仍不能操作 DOM 结构。由上述原因,这个生命周期函数也很少在开发中被用到。

🎈🎈 注意:此时只是在内存中生成 HTML 结构,并没有渲染到浏览器中。

  • mounted()

内存中的 HTML 结构成功渲染到了浏览器之后,迎来第四个生命周期函数 mounted()。因为此时浏览器中已经包含 DOM 结构,因此此时可以获取和操作当前组件中的 DOM 元素。

🎈🎈 注意:最早在此时可以操作浏览器中 DOM 元素。
 
>> 组件运行阶段的生命周期函数

  • beforeUpdate()

当修改 data 中数据时,将要 根据更改后的数据重新渲染组件的模板结构时,迎来了周期函数 beforeUpdate() 的节点。此时刚刚修改完 data 数据,但是还没有重新渲染 UI 结构。

🎈🎈 注意:数据是新数据,结构是旧结构。数据变化时才执行,最少执行 0 次。

  • updated()

已经根据最新的数据,重新渲染了组件的 DOM 结构,此时迎来了周期函数 updated() 的节点。这时可以操作最新的 DOM 元素。

🎈🎈 注意:数据是新数据,结构是新结构。
 
>> 组件销毁阶段的生命周期函数

  • beforeDestrory()

将要销毁此组件,但是还尚未销毁,组件仍处于正常工作的状态,迎来 beforeDestrory() 函数的节点。

  • destroyed()

组件已经被销毁,同时,此组件在浏览器中的 DOM 结构已经被完全移除,迎来 destroryed() 函数的节点。

 

二、组件之间的数据共享


在项目开发中,组件之间的最常见的关系分为如下两种:父子关系兄弟关系 。下面我们分别来介绍 父子组件间如何数据共享兄弟组件间如何数据共享
 

1. 父向子共享数据


父组件向子组件共享数据需要使用 自定义属性 。这里的自定义属性定义在子组件内,父组件只是负责提供数据传递给子组件。参考下面代码:

	// 父组件
	<Son :msg="message" :user="userinfo"></Son>
	
	data() {
	  return {
	    message: 'hello vue.js',
	    userinfo: { name: 'zs', age: 20 }
	  }
	}
	// 子组件
	<template>
	  <div class="app-container">
	    <h5>Son 组件</h5><p>父组件传递来的 msg 值: {{ msg }}</p>
	    <p>父组件传递来的 user 值: {{ user }}</p>
	  </div>
	</template>
	
	props: ['msg', 'user']

 

2. 子向父共享数据


子组件向父组件共享数据使用 自定义事件 ,子组件可以使用 $emit 触发父组件的自定义事件,通过 传递参数 ,将子组件中数据传递给父组件的事件处理函数。

在父组件事件处理函数中,将从子组件获取到的数据 转存 给到父组件自身使用,这就完成了子组件向父组件的数据共享。

// 父组件
<Son @numChange="getNewCount"></Son>

data() {
  return {
    // 接收子组件传递的数据
    countFromSon: 0,
  };
},
methods: {
  getNewCount(val) {
    this.countFromSon = val;	// val 是子组件中传递来的参数
  },
},
// 子组件
<button @click="add">+1</button>

data() {
  return {
    // 希望将 count 值传递给父组件
    count: 0,
  };
},
methods: {
  add() {
    // 让子组件的 count 值自增
    this.count += 1;
    // 将结果传递给父组件
    this.$emit("numChange", this.count);
  },
},

3. 兄弟之间的共享数据


vue2.x 中,兄弟组件之间数据共享的方案是 EventBus 。具体使用步骤如下:

  1. 创建 eventBus.js 模块,并向外共享一个 Vue 的实例对象
  2. 在数据发送方,调用 bus.$emit 方法触发自定义事件
  3. 在数据接收方,调用 bus.$on 方法注册一个自定义事件

在这里插入图片描述

	// 发送方 
	const obj = { id: this.id, value: this.num - 1 };
	// 要做的事情:通过 EventBus 把 obj 对象,发送给 App.vue 组件
	bus.$emit('share', obj);
	// 中间件
	import Vue from 'vue'
	
	export default new Vue()
	// 接收方 
	created() {
	  bus.$on("share", (val) => {
	    this.list[val.id - 1].goods_count = val.value;
	  });
	},

 

三、ref 引用


1. 何为 ref 引用

ref 用来辅助开发者在 不依赖于 jQuery 的情况下,获取 DOM 元素或组件的引用。在每个 vue 的组件实例上,都包含一个 $refs 对象 ,里面存储着对应的 DOM 元素或组件的引用。

默认情况下,组件的 $refs 指向一个空对象

如下图,如果直接打印 vue 实例对象,可以发现其含有一个空的 $refs 对象。
在这里插入图片描述

>> 使用 ref 引用 DOM 元素

如果想要使用 ref 引用页面上的 DOM 元素,则可以按照如下的方式进行操作:

    <h1 ref="myref">ref 引用 DOM 元素</h1>

再次打印 vue 实例,可以发现 &refs 对象中有了属性 myref,通过 this.&refs.myref 即可获取到该 DOM 元素的引用。
在这里插入图片描述

>> 使用 ref 引用组件

	// 渲染 Left 组件 添加 ref 引用
    <Left ref="comRef"></Left>
    
    // 调用 Left 组件中的方法 resetCount
    this.$refs.comRef.resetCount();

 

2. this.$nextTick() 方法


组件的 $nextTick(callback) 方法,会把 cb 回调 推迟到下一个 DOM 更新周期之后 执行。

通俗的理解是:等组件的 DOM 更新完成之后,再执行 cb 回调函数。从而能保证 callback 回调函数可以操作到最新的 DOM 元素。

>> 来看一个 this.$nextTick() 的应用场景

点击按钮后,显示文本框,并且自动聚焦到文本框中。我们可以使用一个开关变量,结合 v-if v-else 进行判断,显示文本框时,根据文本框引用来使其获取焦点。

实现效果:点击显示输入框,并且自动聚焦
请添加图片描述

  <input type="text" v-if="inputVisible" @blur="showButton" ref="iptRef" />
  <button v-else @click="showInput">展示输入框</button>
  
  data() {
    return {
      // 控制输入框和按钮的按需切换 默认 false 展示按钮 隐藏输入框
      inputVisible: false,
    };
  },
  methods: {
    showInput() {
      this.inputVisible = true;
      // 将对 input 文本框的操作,推迟到下次 DOM 更新之后。否则页面上没有文本框。
      this.$nextTick(() => {
        this.$refs.iptRef.focus();
      });
    },
    showButton() {
      this.inputVisible = false;
    },
  },

>> 问题一:如果不使用 this.$nextTick() 会怎么样呢?

这里来说明一下,当更改完 data 中数据 this.inputVisible = true;,页面还未来得及渲染,就获取了新 DOM 元素的引用 this.$refs.iptRef 。此时该引用为空,因此无法进行文本框聚焦。而使用 this.$nextTick() ,将其推迟到了 DOM 渲染完后执行,此时可以获取到最新 DOM 元素的引用,因此可以完美解决。
 
>> 问题二:为什么不能写在 updated() 中呢?

	updated() {
	  this.$refs.iptRef.focus();
	}

因为,更改数据 this.inputVisible = true; 后文本框显示,DOM 成功渲染后执行 updated 文本框获得焦点,这是没有问题的。但是当失去焦点时,data 数据会被更改 this.inputVisible = false; ,DOM 重新渲染文本框消失、按钮显示,之后再次 updated 获取文本框引用,发现其引用为空,会导致报错。
 

四、购物车案例


>> 效果展示
请添加图片描述

>> 实现步骤

  1. 初始化项目基本结构
  2. 封装 Header 组件
  3. 基于 axios 请求商品列表数据( GET 请求,地址为 https://www.escook.cn/api/cart )
  4. 封装 Footer 组件
  5. 封装 Goods 组件
  6. 封装 Counter 组件

代码过长不再给出,如果需要可以私信博主。

>> 案例总结

Ⅰ. 此案例主要是对前面所学知识的综合练习,重点利用了组件间的数据共享:父组件向子组件传值用 props 自定义属性,子组件向父组件传值要用 $emit 结合父组件事件绑定,而兄弟组件传值,需要利用 eventBue.js 作为中间件过渡。

Ⅱ. 此外还需注意计算属性的使用,计算属性需要使用 return 将值返回。虽然在 computed 中计算属性被定义成了函数,但是在使用时它当作属性值直接使用。

Ⅲ. 在利用 Ajax 请求页面数据时,必须将请求来的数据转存到组件的 data 中,否则该数据在页面中无法被访问到。此外,Ajax 请求时,要熟练使用 asyncawaitconst { data: res } 是利用解构赋值的方式,将请求到的 data 数据赋给 data,因为重名问题所以用 :res 将其重命名为 res。

    // 封装请求列表数据方法
    async initCarList() {
      // 调用 axios 的 get 方法,请求列表数据
      const { data: res } = await axios.get("https://www.escook.cn/api/cart");
      // console.log(res);
      // 请求回来的数据,在页面渲染期间要用到,必须转存到 data 中
      if (res.status === 200) {
        this.list = res.list;
      }
    },

Ⅳ. 编码规范上,在渲染组件时,按照先 vue指令 => 属性绑定 => 事件绑定 的顺序:

    <Goods
      v-for="item in list"
      :key="item.id"
      :id="item.id"
      :title="item.goods_name"
      :pic="item.goods_img"
      :price="item.goods_price"
      :state="item.goods_state"
      :count="item.goods_count"
      @state-change="getNewState"
    ></Goods>

Ⅴ. 再说一下之前没怎么使用过的数组中 reduce() 方法。

	array.reduce(function(返回值, 当前元素), 传递给函数的初始值)

本例中用于获取勾选商品的总价格和总数量,实现方法是先利用 filter() 进行数组筛选,接着配合 reduce() 方法实现累加,这里重点需要理解其简写格式:

    // 勾选商品总价格
    amount() {
      return this.list
        .filter((item) => item.goods_state)
        .reduce((total, item) => (total += item.goods_price * item.goods_count), 0);
    },
    // 获取已勾选商品的数量
    total() {
      return this.list
        .filter((item) => item.goods_state)
        .reduce((t, item) => (t += item.goods_count), 0);
    },

 

附:生命周期图示及说明



请添加图片描述

Logo

前往低代码交流专区

更多推荐