Vue声明周期_axios_购物车案例

一组件从 创建 到 销毁 的整个过程就是生命周期

Vue的生命周期

 

Vue-钩子函数

 4个阶段8个方法

Vue-初始化阶段

components/Life.vue - 创建一个文件

<script>
export default {
    data(){
        return {
            msg: "hello, Vue"
        }
    },
    // 一. 初始化
    // 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)
    }
}
</script>

App.vue - 引入使用

<template>
  <div>
    <h1>1. 生命周期</h1>
 	<Life></Life>
  </div>
</template>

<script>
import Life from './components/Life'
export default {
  components: {
    Life
  }
}
</script>

Vue-挂载阶段

 components/Life.vue - 创建一个文件

<template>
  <div>
      <p>学习生命周期 - 看控制台打印</p>
      <p id="myP">{{ msg }}</p>
  </div>
</template>

<script>
export default {
    // ...省略其他代码
    
    // 二. 挂载
    // 真实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
    }
}
</script>

Vue-更新阶段

 components/Life.vue - 创建一个文件

准备ul+li循环, 按钮添加元素, 触发data改变->导致更新周期开始

<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]
        }
    },
    // ...省略其他代码

    // 三. 更新
    // 前提: 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
    }
}
</script>

Vue-销毁阶段

 components/Life.vue - 准备生命周期方法(Life组件即将要被删除)

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

 主要: App.vue - 点击按钮让Life组件从DOM上移除 -> 导致Life组件进入销毁阶段

<Life v-if="show"></Life>
<button @click="show = false">销毁组件</button>

<script>
    data(){
        return {
            show: true
        }
    },
</script>

axios-基本使用

axios({
  method: '请求方式', // get post
  url: '请求地址',
  data: {    // 拼接到请求体的参数,  post请求的参数
    xxx: xxx,
  },
  params: {  // 拼接到请求行的参数, get请求的参数
   	xxx: xxx 
  }
}).then(res => {
  console.log(res.data) // 后台返回的结果
}).catch(err => {
  console.log(err) // 后台报错返回
})

axios-获取所有图书

 components/UseAxios.vue

<template>
  <div>
    <p>1. 获取所有图书信息</p>
    <button @click="getAllFn">点击-查看控制台</button>
  </div>
</template>

<script>
// 目标1: 获取所有图书信息
// 1. 下载axios
// 2. 引入axios
// 3. 发起axios请求
import axios from "axios";
export default {
  methods: {
    getAllFn() {
      axios({
        url: "http://123.57.109.30:3006/api/getbooks",
        method: "GET", // 默认就是GET方式请求, 可以省略不写
      }).then((res) => {
        console.log(res);
      });
      // axios()-原地得到Promise对象
    },
  }
};
</script>

axios-获取某本书籍

 components/UseAxios.vue

<template>
  <div>
    <p>2. 查询某本书籍信息</p>
    <input type="text" placeholder="请输入要查询 的书名" v-model="bName" />
    <button @click="findFn">查询</button>
  </div>
</template>

<script>
import axios from "axios";
export default {
  data() {
    return {
      bName: ""
    };
  },
  methods: {
    // ...省略了查询所有的代码
    findFn() {
      axios({
        url: "/api/getbooks",
        method: "GET",
        params: { // 都会axios最终拼接到url?后面
            bookname: this.bName
        }
      }).then(res => {
          console.log(res);
      })
    }
  },
};
</script>

axios-发布书籍

components/UseAxios.vue

<template>
  <div>
    <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>
import axios from "axios";
export default {
  data() {
    return {
      bName: "",
      bookObj: { // 参数名提前和后台的参数名对上-发送请求就不用再次对接了
          bookname: "",
          author: "",
          publisher: ""
      }
    };
  },
  methods: {
    // ...省略了其他代码
    sendFn(){
       axios({
           url: "/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>

axios-全局配置

避免前缀基地址, 暴露在逻辑页面里, 统一设置

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对象
},

$refs-获取DOM

利用 ref 和 $refs 可以用于获取 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>

$refs-获取组件对象

components/Child/Demo.vue

<template>
  <div>
      <p>我是Demo组件</p>
  </div>
</template>

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

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>

$nextTick使用

components/Move.vue - 继续新增第三套代码

<template>
  <div>
      <p>1. 获取原生DOM元素</p>
      <h1 id="h" ref="myH">我是一个孤独可怜又能吃的h1</h1>
      <p>2. 获取组件对象 - 可调用组件内一切</p>
      <Demo ref="de"></Demo>
      <p>3. vue更新DOM是异步的</p>
      <p ref="myP">{{ count }}</p>
      <button @click="btn">点击count+1, 马上提取p标签内容</button>
  </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
    },
    data(){
        return {
            count: 0
        }
    },
    methods: {
        btn(){
            this.count++; // vue监测数据更新, 开启一个DOM更新队列(异步任务)
            console.log(this.$refs.myP.innerHTML); // 0

            // 原因: Vue更新DOM异步
            // 解决: this.$nextTick()
            // 过程: DOM更新完会挨个触发$nextTick里的函数体
             this.$nextTick(() => {
                console.log(this.$refs.myP.innerHTML); // 1
            })
        }
    }
}
</script>

$nextTick使用场景

components/Tick.vue

<template>
  <div>
      <input ref="myInp" type="text" placeholder="这是一个输入框" v-if="isShow">
      <button v-else @click="btn">点击我进行搜索</button>
  </div>
</template>

<script>
// 目标: 点按钮(消失) - 输入框出现并聚焦
// 1. 获取到输入框
// 2. 输入框调用事件方法focus()达到聚焦行为
export default {
    data(){
        return {
            isShow: false
        }
    },
    methods: {
        async btn(){
            this.isShow = true;
            // this.$refs.myInp.focus()
            // 原因: data变化更新DOM是异步的
            // 输入框还没有挂载到真实DOM上
            // 解决:
            // this.$nextTick(() => {
            //     this.$refs.myInp.focus()
            // })
            // 扩展: await取代回调函数
            // $nextTick()原地返回Promise对象
            await this.$nextTick()
            this.$refs.myInp.focus()
        }
    }
}
</script>

案例-购物车

项目初始化

初始化新项目, 清空不要的东西, 下载bootstrap库, 下载less模块

vue create shopcar
yarn add bootstrap
yarn add less less-loader@5.0.0 -D

import "bootstrap/dist/css/bootstrap.css" // 引入第三方包里的某个css文件

购物车-完整案例

App.vue

<template>
  <div>
    <MyHeadr colorStr="white" content="购物车案例"></MyHeadr>
    <div style="padding-top: 45px; margin-bottom: 45px">
      <MyGoods v-for="obj in arr" :key="obj.id" :goodsObj="obj"></MyGoods>
    </div>
    <MyFooter :list="arr" @changeAll="changeAllFn"></MyFooter>
  </div>
</template>

<script>
// 口诀: 父子用padding, 兄弟关系用margin
// 需求1: 初始化项目, 准备组件和第三方包
// 1.0 vue create shopcar-demo
// 1.1 清空欢迎页面
// 1.2 下载bootstrap less less-loader@5.0.0 axios
// 1.3 main.js 引入bootstrap的样式
// 1.4 分组件, 创建组件, 放入标签和样式
// 1.5 引入App.vue 使用组件拼装项目, 摆上

// 需求2: 自定义组件内值(自定义头部)
// 2.0 MyHeader 定义props, 对象格式, 每个变量的类型检查-传入值不符合的时, 控制台报错
// 2.1 使用MyHeader组件时, 向内给props各个变量传入值

// 需求3: 请求数据
// 3.0 下载axios, 在main.js中引入
// 3.1 main.js先执行, 所以给vue原型上添加了全局属性(所有.vue文件都可以通过this.$axios来访问axios方法)
// 3.2 在App.vue的created函数中,this.$axios发起了一起请求, 拿起购物车数据

import MyHeadr from "./components/MyHeader";
import MyGoods from "./components/MyGoods";
import MyFooter from "./components/MyFooter";
export default {
  data() {
    return {
      arr: [],
    };
  },
  created() {
    // console.log(this); // VueComponent创建组件对象(App.vue)
    // 实例._proto_===构造函数.prototype
    // VueComponent.prototype - 对象(都有隐士原型)
    // 对象._proto_===Vue.prototype
    this.$axios({
      url: "/api/cart",
      // method: 'GET'  // 默认就是get请求
    }).then((res) => {
      console.log(res.data.list);
      this.arr = res.data.list;
    });
  },
  methods: {
    changeAllFn(bool) {
      this.arr.forEach((obj) => (obj.goods_state = bool));
    },
  },
  components: {
    MyHeadr,
    MyGoods,
    MyFooter,
  },
};
</script>

<style>
</style>

./components/MyHeader

<template>
  <div class="my-header" :style="{ backgroundColor: bgc, color: colorStr }">
    {{ content }}
  </div>
</template>

<script>
export default {
  // props:[]
  // 数组的方式只能起名, 对象可以详细的修饰限制
  props: {
    // 格式:
    /**
     * 变量名:类型
     * 变量名:{
     *    type: 类型,
     *    default:默认值
     *    required:true // 此变量必须传入
     * }
     */
    bgc: String, // 背景颜色的字符串
    colorStr: {
      // 文字颜色字符串
      type: String,
      default: "black",
    },
    content: {
      // 标题文字
      type: String,
      required: true, // 必须传入
    },
  },
};
</script>

<style lang="less" 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>

./components/MyGoods

<template>
  <div class="my-goods-item">
    <div class="left">
      <div class="custom-control custom-checkbox">
        <input
          type="checkbox"
          class="custom-control-input"
          :id="goodsObj.id"
          v-model="goodsObj.goods_state"
        />
        <label class="custom-control-label" :for="goodsObj.id">
          <img :src="goodsObj.goods_img" alt="" />
        </label>
      </div>
    </div>
    <div class="right">
      <div class="top">{{ goodsObj.goods_name }}</div>
      <div class="bottom">
        <span class="price">¥ {{ goodsObj.goods_price }}</span>
        <span>
          <MyCount
            :count="goodsObj.goods_count"
            @changeName="changeNumFn"
          ></MyCount>
        </span>
      </div>
    </div>
  </div>
</template>

<script>
import MyCount from "./MyCount";
export default {
  props: {
    goodsObj: Object,
  },
  components: {
    MyCount,
  },
  methods: {
    changeNumFn(num) {
      this.goodsObj.goods_count = num;
    },
  },
};
</script>

<style lang="less" scoped>
.my-goods-item {
  display: flex;
  padding: 10px;
  border-bottom: 1px solid #ccc;
  .left {
    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;
      }
    }
  }
}
</style>

 在./components/MyGoods  引入./MyCount

<template>
  <div class="my-counter">
    <button type="button" class="btn btn-light" @click="sendFn">-</button>
    <input type="number" class="form-control inp" v-model.number="val" />
    <button type="button" class="btn btn-light" @click="addFn">+</button>
  </div>
</template>

<script>
export default {
  props: {
    count: Number,
  },
  data() {
    return {
      val: 0,
    };
  },
  created() {
    this.val = this.count;
  },
  methods: {
    addFn() {
      this.val++;
    },
    sendFn() {
      if (this.val > 1) {
        this.val--;
      }
    },
  },
  watch: {
    val(newVal) {
      if (newVal < 1) {
        this.newVal = 1;
      }
      this.$emit('changeName',this.val)
    },
  },
};
</script>

<style lang="less" scoped>
.my-counter {
  display: flex;
  .inp {
    width: 45px;
    text-align: center;
    margin: 0 10px;
  }
  .btn,
  .inp {
    transform: scale(0.9);
  }
}
</style>

./components/MyFooter

<template>
  <!-- 底部 -->
  <div class="my-footer">
    <!-- 全选 -->
    <div class="custom-control custom-checkbox">
      <input
        type="checkbox"
        class="custom-control-input"
        id="footerCheck"
        v-model="isAll"
      />
      <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 {
  props: {
    list: Array,
  },
  computed: {
    isAll: {
      set(val) {
        this.$emit("changeAll", val);
      },
      get() {
        return this.list.every((obj) => obj.goods_state === true);
      },
    },
    allCount() {
      return this.list.reduce((sum, obj) => {
        if (obj.goods_state === true) {
          sum += obj.goods_count;
        }
        return sum;
      }, 0);
    },
    allPrice() {
      return this.list.reduce((sum, obj) => {
        if (obj.goods_state === true) {
          sum += obj.goods_count * obj.goods_price;
        }
        return sum;
      }, 0);
    },
  },
};
</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>

版权声明:本文为xinyue_1208原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。