Vue2.x 核心基础(vue生命周期, axios ,$refs和$nextTick使用)

250 阅读3分钟

Vue2.x 核心基础

1. vue生命周期

1.1人的_生命周期

目标:一个人从出生到逝去的过程

image.png

1.2 Vue_生命周期

目标:从创建 到 销毁 的整个过程就是 – Vue实例的 - 生命周期

image.png

小结

1. Vue的生命周期是什么?

从Vue实例, 创建到销毁的过程

1.3 钩子函数

目标:Vue 框架内置函数,随着组件的生命周期阶段,自动执行

  • 作用: 特定的时间点,执行特定的操作
  • 场景: 组件创建完毕后,可以在created 生命周期函数中发起Ajax 请求,从而初始化 data 数据
  • 分类: 4大阶段8个方法

image.png

小结

1. 如何知道Vue生命周期到达了什么阶段?

使用钩子函数(在不同阶段会自动执行)

2. 钩子函数有哪些阶段?

  1. 初始化
  2. 挂载
  3. 更新
  4. 销毁

1.4 初始化

  1. new Vue() – Vue实例化
  2. 每个组件,也就是每个vue文件,也是一个Vue实例,它需要被创建
  3. beforeCreate – 实例创建前,此时不能操作data中的变量或methods中的方法
  4. created – 实例创建后,此时可以操作data中的变量或methods中的方法 created钩子应用场景:发送网络请求

小结

1. Vue实例创建阶段执行了哪些钩子函数?

beforeCreate / created

2. created钩子中能获取data?

能操作 data变量 或 methods方法

3. created钩子的应用场景是什么?

发送网络请求

1.5 挂载

  1. 虚拟DOM挂载成真实DOM之前
  2. beforeMount –虚拟DOM插入前, 虚拟DOM还没有插入到真实DOM中
  3. mounted – 虚拟DOM插入后,虚拟DOM和渲染的数据已经插入真实DOM上

mounted钩子应用场景:获取真实DOM

小结

1. Vue实例从创建到挂载都经历了哪些钩子函数?

beforeCreate / created / beforeMount / mounted

2. 在什么钩子函数里可以获取真实DOM?

mounted

3. mounted钩子的应用场景是什么?

获取真实DOM

1.6 更新

当 data 数据改变, 会触发组件的更新钩子

  1. beforeUpdate – 组件更新前,可以获取数据变化前的DOM
  2. updated – 组件更新后,可以获取数据变化后的DOM 只要 当 data 数据改变 – 这两个钩子会循环执行

小结

1. 什么时候执行组件 更新 钩子函数

当数据发生变化

2. 在哪可以获取更新后的DOM

在updated钩子函数里

1.7 销毁

组件被移除(例如 v-if=”false布尔值 ”),组件就会被销毁

  1. beforeDestroy:组件销毁前
  2. destroyed: 组件销毁后 我们可以在组件销毁时机,做一些优化、清理工作,比如:关闭定时器

小结

1. 一般在beforeDestroy/destroyed里做什么?

优化工作,例如:关闭定时器

2. 三个用的多的钩子?

  1. created:创建后,可以操作数据和调用方法 -> 发送网络请求
  2. mounted:挂载后,操作真实DOM -> 操作真实DOM
  3. destroyed: 销毁后。组件优化工作 -> 关闭定时器 components/Life.vue - 创建一个文件
<template>
  <div>
      <p>学习生命周期 - 看控制台打印</p>
      <p id="myP">{{ msg }}</p>
      <ul id="myUL">
          <li v-for="(val, index) in arr" :key="index">
              {{ val }}
          </li>
      </ul>
      <button @click="arr.push(1000)">点击末尾加值</button>
  </div>
</template>

<script>
export default {
    data(){
        return {
            msg: "hello, Vue",
            arr: [5, 8, 2, 1],
            timer: null // 保存计时器
        }
    },
    // 一. 初始化
    // new Vue()以后, vue内部给实例对象添加了一些属性和方法, data和methods初始化"之前"
    beforeCreate(){
        // console.log("beforeCreate -- 执行");
        // console.log(this.msg); // undefined
    },
    // data和methods初始化以后
    // 场景: 网络请求, 注册全局事件
    created(){
        // console.log("created -- 执行");
        // console.log(this.msg); // hello, Vue

        // this.timer = setInterval(() => {
        //     console.log("哈哈哈");
        // }, 1000)
    },

    // 二. 挂载
    // 真实DOM挂载之前
    // 场景: 预处理data, 不会触发updated钩子函数
    beforeMount(){
        // console.log("beforeMount -- 执行");
        // console.log(document.getElementById("myP")); // null

        this.msg = "重新值"
    },
    // 真实DOM挂载以后
    // 场景: 挂载后真实DOM
    mounted(){
        // console.log("mounted -- 执行");
        // console.log(document.getElementById("myP")); // p
    },

    // 三. 更新
    // 前提: data数据改变才执行
    // 更新之前
    beforeUpdate(){
        // console.log("beforeUpdate -- 执行");
        // console.log(document.querySelectorAll("#myUL>li")[4]); // undefined
    },
    // 更新之后
    // 场景: 获取更新后的真实DOM
    updated(){
        // console.log("updated -- 执行");
        // console.log(document.querySelectorAll("#myUL>li")[4]); // li
    },

    // 四. 销毁
    // 前提: v-if="false" 销毁Vue实例
    // 场景: 移除全局事件, 移除当前组件, 计时器, 定时器, eventBus移除事件$off方法
    beforeDestroy(){
        // console.log('beforeDestroy -- 执行');
        clearInterval(this.timer)
    },
    destroyed(){
        // console.log("destroyed -- 执行");
    }

}
</script>

<style>

</style>

App.vue - 引入使用

<template>
  <div>
    <h1>1. 生命周期</h1>
    <Life v-if="show"></Life>
    <button @click="show = false">销毁组件</button>
  </div>
</template>

<script>
import Life from './components/Life'
export default {
  data(){
    return {
      show: true
    }
  },
  components: {
    Life
  }
}
</script>

<style>

</style>

2. axios

目标:axios 是一个专门用于发送ajax请求的库

官网: www.axios-js.com/

特点

  • 支持客户端发送Ajax请求
  • 支持Promise相关用法
  • 支持请求和响应的拦截器功能
  • axios 底层还是原生js实现, 内部通过Promise封装的

image.png

小结

1. 什么是ajax?

一种前端异步请求后端的技术

2. ajax原理?

浏览器XMLHttpRequest对象

3. axios是什么?

基于原生XMLHttpRequest+Promise封装ajax请求库

2.1 axios使用

components/UseAxios.vue

<template>
  <div>
    <p>1. 获取所有图书信息</p>
    <button @click="getAllFn">点击-查看控制台</button>
    <p>2. 查询某本书籍信息</p>
    <input type="text" placeholder="请输入要查询 的书名" v-model="bName" />
    <button @click="findFn">查询</button>
    <p>3. 新增图书信息</p>
    <div>
        <input type="text" placeholder="书名" v-model="bookObj.bookname">
    </div>
    <div>
        <input type="text" placeholder="作者" v-model="bookObj.author">
    </div>
    <div>
        <input type="text" placeholder="出版社" v-model="bookObj.publisher">
    </div>
    <button @click="sendFn">发布</button>
  </div>
</template>

<script>
// 目标1: 获取所有图书信息
// 1. 下载axios
// 2. 引入axios
// 3. 发起axios请求
import axios from "axios";
export default {
  data() {
    return {
      bName: "",
      bookObj: { // 参数名提前和后台的参数名对上-发送请求就不用再次对接了
          bookname: "",
          author: "",
          publisher: ""
      }
    };
  },
  methods: {
    getAllFn() {
      axios({
        url: "http://123.57.109.30:3006/api/getbooks",
        method: "GET", // 默认就是GET方式请求, 可以省略不写
      }).then((res) => {
        console.log(res);
      });
      // axios()-原地得到Promise对象
    },
    findFn() {
      axios({
        url: "http://123.57.109.30:3006/api/getbooks",
        method: "GET",
        params: { // 都会axios最终拼接到url?后面
            bookname: this.bName
        }
      }).then(res => {
          console.log(res);
      })
    },
    sendFn(){
       axios({
           url: "http://123.57.109.30:3006/api/addbook",
           method: "POST",
           data: {
               appkey: "7250d3eb-18e1-41bc-8bb2-11483665535a",
               ...this.bookObj
               //等同于下面
            //    bookname: this.bookObj.bookname,
            //    author: this.bookObj.author,
            //    publisher: this.bookObj.publisher
           }
       }) 
    }
  },
};
</script>

<style>
</style>

优化-全局默认配置

axios.defaults.baseURL = "http://123.57.109.30:3006"

// 所有请求的url前置可以去掉, 请求时, axios会自动拼接baseURL的地址在前面
getAllFn() {
    axios({
        url: "/api/getbooks",
        method: "GET", // 默认就是GET方式请求, 可以省略不写
    }).then((res) => {
        console.log(res);
    });
    // axios()-原地得到Promise对象
},

3. refsrefs 和 nextTick使用

3.1 获取DOM

目标:通过id或ref属性获取原生DOM 在mounted生命周期 – 2种方式获取原生DOM标签

  1. 目标标签 – 添加id / ref
  2. 恰当时机通过获取DOM标签
<template>
  <div>
      <p>1. 获取原生DOM元素</p>
      <h1 id="h" ref="myH">我是一个孤独可怜又能吃的h1</h1>
  </div>
</template>

<script>
// 目标: 获取组件对象
// 1. 创建组件/引入组件/注册组件/使用组件
// 2. 组件起别名ref
// 3. 恰当时机, 获取组件对象
export default {
    mounted(){
        console.log(document.getElementById("h")); // h1
        console.log(this.$refs.myH); // h1
    }
}
</script>

<style>

</style>

3.2 获取组件对象

目标:通过ref属性获取组件对象

  1. 创建Demo组件, 写一个方法
  2. App.vue使用Demo组件, 给ref属性-名字随意
  3. 恰当时机, 通过ref属性 获取组件对象, 可调用组件对象里方法 或属性 components/Child/Demo.vue
<template>
  <div>
      <p>我是Demo组件</p>
  </div>
</template>

<script>
export default {
    methods: {
        fn(){
            console.log("demo组件内的方法被调用了");
        }
    }
}
</script>

<style>

</style>

More.vue - 获取组件对象 - 调用组件方法

<template>
  <div>
      <p>1. 获取原生DOM元素</p>
      <h1 id="h" ref="myH">我是一个孤独可怜又能吃的h1</h1>
      <p>2. 获取组件对象 - 可调用组件内一切</p>
      <Demo ref="de"></Demo>
  </div>
</template>

<script>
// 目标: 获取组件对象
// 1. 创建组件/引入组件/注册组件/使用组件
// 2. 组件起别名ref
// 3. 恰当时机, 获取组件对象
import Demo from './Child/Demo'
export default {
    mounted(){
        console.log(document.getElementById("h")); // h1
        console.log(this.$refs.myH); // h1

        let demoObj = this.$refs.de;
        demoObj.fn()
    },
    components: {
        Demo
    }
}
</script>

<style>

</style>

总结: ref定义值, 通过$refs.值 来获取dom或组件对象

3.3 $nextTick使用

目标:点击改data, 获取原生DOM内容

  1. 创建标签显示数据

image.png 2. 点击+1, 马上获取原生DOM内容

image.png 原因: Vue更新DOM是异步的

目标:等DOM更新后, 触发此方法里函数体执行

语法: this.$nextTick(函数体)

image.png

购物车案例

项目初始化

  • 需求1: 从0新建项目
  • 需求2: 分拆组件, 创建组件文件 效果如图:

分析:

  1. vue命令创建项目shopcart
  2. 下载bootstrap, less, less-loader@5.0.0
  3. main.js – 引入bootstrap样式
  4. 创建4个组件文件, 从笔记拿到标签和样式
  5. 把组件相应的引入到对应位置使用

image.png App.vue

<template>
  <div>
    <myHeader title="我的购物车" color="red" />
    <myGoods
      style="margin: 45px 0 50px 0"
      v-for="item in list"
      :obj="item"
      :key="item.id"
    />
    <myFooter :arr="list" />
  </div>
</template>
<script>
import myHeader from './components/MyHeader';
import myGoods from './components/MyGoods';
import myFooter from './components/MyFooter';
import axios from 'axios';
export default {
  data() {
    return {
      list: [],
    };
  },
  components: {
    myHeader,
    myGoods,
    myFooter,
  },
  created() {
    axios({
      method: 'get',
      url: '/api/cart',
    }).then(({ data: res }) => {
      this.list = res.list;
      console.log(this.list);
    });
  },
};
</script>
<style lang="less"></style>

MyHeader.vue

<template>
  <div class="my-header" :style="{ background: bgColor, color }">
    {{ title }}
  </div>
</template>

<script>
export default {
  props: {
    bgColor: String, // 外部插入此变量的值, 必须是字符串类型, 否则报错
    color: {
      type: String, // 约束color值的类型
      default: '#fff', // color变量默认值(外部不给 我color传值, 使用默认值)
    },
    title: {
      type: String,
      required: true, // 必须传入此变量的值
    },
  },
};
</script>

<style scoped>
.my-header {
  height: 45px;
  line-height: 45px;
  text-align: center;
  background-color: #1d7bff;
  color: #fff;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 2;
}
</style>

MyGoods.vue

<template>
  <div class="my-goods-item">
    <div class="left">
      <div class="custom-control custom-checkbox">
        <input
          type="checkbox"
          class="custom-control-input"
          :id="obj.id"
          v-model="obj.goods_state"
        />
        <label class="custom-control-label" :for="obj.id">
          <img :src="obj.goods_img" alt="" />
        </label>
      </div>
    </div>
    <div class="right">
      <div class="top">{{ obj.goods_name }}</div>
      <div class="bottom">
        <span class="price">{{ obj.goods_price }}</span>
        <div class="my-counter">
          <button
            type="button"
            class="btn btn-light"
            :disabled="obj.goods_count === 1"
            @click="obj.goods_count > 1 && obj.goods_count--"
          >
            -
          </button>
          <input
            type="number"
            class="form-control inp"
            v-model.number.lazy="obj.goods_count"
          />
          <button
            type="button"
            class="btn btn-light"
            @click="obj.goods_count++"
          >
            +
          </button>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    obj: {
      type: Object,
      required: true,
    },
  },
  watch: {
    obj: {
      deep: true,
      handler(newArr) {
        if (newArr.goods_count < 1) {
          newArr.goods_count = 1;
        }
        newArr.goods_count = parseInt(newArr.goods_count);
      },
    },
  },
};
</script>

<style lang="less" scoped>
.my-goods-item {
  display: flex;
  padding: 10px;
  border-bottom: 1px solid #ccc;
  .left {
    label {
      margin-left: 6px;
    }
    img {
      width: 120px;
      height: 120px;
      margin-right: 8px;
      border-radius: 10px;
    }
    .custom-control-label::before,
    .custom-control-label::after {
      top: 50px;
    }
  }
  .right {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    .top {
      font-size: 14px;
      font-weight: 700;
    }
    .bottom {
      display: flex;
      justify-content: space-between;
      padding: 5px 0;
      align-items: center;
      .price {
        color: red;
        font-weight: bold;
      }
      .my-counter {
        display: flex;
        .inp {
          width: 45px;
          text-align: center;
          margin: 0 10px;
        }
        .btn,
        .inp {
          transform: scale(0.9);
        }
      }
    }
  }
}
</style>

MyFooter.vue

<template>
  <!-- 底部 -->
  <div class="my-footer">
    <!-- 全选 -->
    <div class="custom-control custom-checkbox">
      <input
        type="checkbox"
        class="custom-control-input"
        id="footerCheck"
        :checked="Allfn"
        @click="handleChange"
      />
      <label class="custom-control-label" for="footerCheck">全选</label>
    </div>
    <!-- 合计 -->
    <div>
      <span>合计:</span>
      <span class="price">¥ {{ allPrice }}</span>
    </div>
    <!-- 按钮 -->
    <button type="button" class="footer-btn btn btn-primary">
      结算 ( {{ allCount }} )
    </button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      aaa: 0,
    };
  },
  props: {
    arr: {
      type: Array,
      required: true,
    },
  },

  computed: {
    Allfn() {
      return this.arr.every(item => item.goods_state);
    },
    allPrice() {
      //   let price = 0;
      //   this.arr.forEach(item => {
      //     if (item.goods_state) {
      //       price += item.goods_count * item.goods_price;
      //     }
      //   });
      //array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
      // total	必需。初始值, 或者计算结束后的返回值。
      // currentValue	必需。当前元素
      // currentIndex	可选。当前元素的索引
      // arr	可选。当前元素所属的数组对象。
      return this.arr.reduce(function (pre, item) {
        if (item.goods_state) {
          pre += item.goods_count * item.goods_price;
        }
        return pre;
      }, 0);
    },
    allCount() {
      return this.arr.reduce(function (count, item) {
        if (item.goods_state) {
          count += item.goods_count;
        }
        return count;
      }, 0);
    },
  },

  methods: {
    handleChange(e) {
      const checked = e.target.checked;

      this.arr.forEach(item => {
        item.goods_state = checked;
      });
    },
  },
};
</script>

<style lang="less" scoped>
.my-footer {
  position: fixed;
  z-index: 2;
  bottom: 0;
  width: 100%;
  height: 50px;
  border-top: 1px solid #ccc;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 10px;
  background: #fff;

  .price {
    color: red;
    font-weight: bold;
    font-size: 15px;
  }
  .footer-btn {
    min-width: 80px;
    height: 30px;
    line-height: 30px;
    border-radius: 25px;
    padding: 0;
  }
}
</style>

main.js

import Vue from 'vue';
import App from './App.vue';
import 'bootstrap/dist/css/bootstrap.css'; // 引入第三方包里的某个css文件

// import less from 'less';

import axios from 'axios';
// 2. 基础地址
axios.defaults.baseURL = 'https://www.escook.cn';
// 3. axios方法添加到Vue的原型上
Vue.prototype.$axios = axios;
Vue.config.productionTip = false;

new Vue({
  render: h => h(App),
}).$mount('#app');