Vue2.x 核心基础
1. vue生命周期
1.1人的_生命周期
目标:一个人从出生到逝去的过程
1.2 Vue_生命周期
目标:从创建 到 销毁 的整个过程就是 – Vue实例的 - 生命周期
小结
1. Vue的生命周期是什么?
从Vue实例, 创建到销毁的过程
1.3 钩子函数
目标:Vue 框架内置函数,随着组件的生命周期阶段,自动执行
- 作用: 特定的时间点,执行特定的操作
- 场景: 组件创建完毕后,可以在created 生命周期函数中发起Ajax 请求,从而初始化 data 数据
- 分类: 4大阶段8个方法
小结
1. 如何知道Vue生命周期到达了什么阶段?
使用钩子函数(在不同阶段会自动执行)
2. 钩子函数有哪些阶段?
- 初始化
- 挂载
- 更新
- 销毁
1.4 初始化
- new Vue() – Vue实例化
- 每个组件,也就是每个vue文件,也是一个Vue实例,它需要被创建
- beforeCreate – 实例创建前,此时不能操作data中的变量或methods中的方法
- created – 实例创建后,此时可以操作data中的变量或methods中的方法 created钩子应用场景:发送网络请求
小结
1. Vue实例创建阶段执行了哪些钩子函数?
beforeCreate / created
2. created钩子中能获取data?
能操作 data变量 或 methods方法
3. created钩子的应用场景是什么?
发送网络请求
1.5 挂载
- 虚拟DOM挂载成真实DOM之前
- beforeMount –虚拟DOM插入前, 虚拟DOM还没有插入到真实DOM中
- mounted – 虚拟DOM插入后,虚拟DOM和渲染的数据已经插入真实DOM上
mounted钩子应用场景:获取真实DOM
小结
1. Vue实例从创建到挂载都经历了哪些钩子函数?
beforeCreate / created / beforeMount / mounted
2. 在什么钩子函数里可以获取真实DOM?
mounted
3. mounted钩子的应用场景是什么?
获取真实DOM
1.6 更新
当 data 数据改变, 会触发组件的更新钩子
- beforeUpdate – 组件更新前,可以获取数据变化前的DOM
- updated – 组件更新后,可以获取数据变化后的DOM 只要 当 data 数据改变 – 这两个钩子会循环执行
小结
1. 什么时候执行组件 更新 钩子函数
当数据发生变化
2. 在哪可以获取更新后的DOM
在updated钩子函数里
1.7 销毁
组件被移除(例如 v-if=”false布尔值 ”),组件就会被销毁
- beforeDestroy:组件销毁前
- destroyed: 组件销毁后 我们可以在组件销毁时机,做一些优化、清理工作,比如:关闭定时器
小结
1. 一般在beforeDestroy/destroyed里做什么?
优化工作,例如:关闭定时器
2. 三个用的多的钩子?
- created:创建后,可以操作数据和调用方法 -> 发送网络请求
- mounted:挂载后,操作真实DOM -> 操作真实DOM
- 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请求的库
特点
- 支持客户端发送Ajax请求
- 支持Promise相关用法
- 支持请求和响应的拦截器功能
- axios 底层还是原生js实现, 内部通过Promise封装的
小结
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. nextTick使用
3.1 获取DOM
目标:通过id或ref属性获取原生DOM 在mounted生命周期 – 2种方式获取原生DOM标签
- 目标标签 – 添加id / ref
- 恰当时机通过获取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属性获取组件对象
- 创建Demo组件, 写一个方法
- App.vue使用Demo组件, 给ref属性-名字随意
- 恰当时机, 通过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内容
- 创建标签显示数据
2. 点击+1, 马上获取原生DOM内容
原因: Vue更新DOM是异步的
目标:等DOM更新后, 触发此方法里函数体执行
语法: this.$nextTick(函数体)
购物车案例
项目初始化
- 需求1: 从0新建项目
- 需求2: 分拆组件, 创建组件文件 效果如图:
分析:
- vue命令创建项目shopcart
- 下载bootstrap, less, less-loader@5.0.0
- main.js – 引入bootstrap样式
- 创建4个组件文件, 从笔记拿到标签和样式
- 把组件相应的引入到对应位置使用
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');