Day3
Vue生命周期 和 生命周期的四个阶段
Vue生命周期的四个阶段:
从创建到销毁的整个阶段中,Vue提供好了一系列函数(8个);
并且在经历生命周期的对应阶段时,会自动帮你调用这些函数
这8个函数称为生命周期钩子
生命周期钩子:一些在Vue生命周期过程中,会被自动调用的一系列函数
响应数据准备好之前有一个钩子:beforeCreate,响应数据准备好之后有一个钩子:created;
在created(){}函数里 就可以发送初始化渲染的请求了
模板渲染好之前一个钩子:beforeMount,模板渲染好之后一个钩子:mounted
在mounted(){}函数里 就可以操作dom了
... ...
以计数器为例:
我们先看前两个阶段
<!DOCTYPE html>
<html lang="en">
<head></head>
<body><div id="app"><h3>{{ title }}</h3><div><button @click="count--">-</button><span>{{ count }}</span><button @click="count++">+</button></div></div><script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><script>const app = new Vue({el: '#app',data: {count: 100,title: '计数器'},//1.创建阶段beforeCreate(){console.log('响应式准备好之前',this.count)//undefined},created(){console.log('响应式准备好之后',this.count)//100},//2.挂载阶段// <h3>{{ title }}</h3>beforeMount(){console.log('模板渲染好之前',document.querySelector('h3').innerHTML)//{{ title }}},mounted(){console.log('模板渲染好之后',document.querySelector('h3').innerHTML)//计数器}})</script>
</body>
</html>
再来看后两个阶段;需要修改数据才能触发
<!DOCTYPE html>
<html lang="en">
<head></head>
<body><div id="app"><h3>{{ title }}</h3><div><button @click="count--">-</button><span>{{ count }}</span><button @click="count++">+</button></div></div><script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><script>const app = new Vue({el: '#app',data: {count: 100,title: '计数器'},//1.创建阶段beforeCreate(){console.log('响应式准备好之前',this.count)//undefined},created(){console.log('响应式准备好之后',this.count)//100},//2.挂载阶段// <h3>{{ title }}</h3>beforeMount(){console.log('模板渲染好之前',document.querySelector('h3').innerHTML)//{{ title }}},mounted(){console.log('模板渲染好之后',document.querySelector('h3').innerHTML)//计数器},//3.更新阶段// <span>{{ count }}</span>beforeUpdate(){console.log('数据修改了,但视图还没更新',document.querySelector('span').innerHTML)//100},updated(){console.log('数据修改了,视图已经更新',document.querySelector('span').innerHTML)//101},//4.销毁阶段// app.$destroy() 用于卸载当前实例beforeDestroy(){console.log('卸载前')},destroyed(){console.log('卸载后')//再点击+-;count不会有变化//以前已经渲染完的dom还在;但跟vue相关的绑定取消了}})</script>
</body>
</html>
created应用 —— 新闻列表
首先,要发送初始化渲染请求,要在created这个钩子里完成
<script>// 接口地址:http://hmajax.itheima.net/api/news// 请求方式:getconst app = new Vue({el: '#app',data: {list: []},async created () {// 1. 发送请求获取数据const res = await axios.get('http://hmajax.itheima.net/api/news')console.log(res)}})
</script>
this.list = res.data.data
<!DOCTYPE html>
<html lang="en">
<head><style>* {margin: 0;padding: 0;list-style: none;}.news {display: flex;height: 120px;width: 600px;margin: 0 auto;padding: 20px 0;cursor: pointer;}.news .left {flex: 1;display: flex;flex-direction: column;justify-content: space-between;padding-right: 10px;}.news .left .title {font-size: 20px;}.news .left .info {color: #999999;}.news .left .info span {margin-right: 20px;}.news .right {width: 160px;height: 120px;}.news .right img {width: 100%;height: 100%;object-fit: cover;}</style>
</head>
<body><div id="app"><ul><li v-for="(item, index) in list" :key="item.id" class="news"><div class="left"><div class="title">{{ item.title }}</div><div class="info"><span>{{ item.source }}</span><span>{{ item.time }}</span></div></div><div class="right"><img v-bind:src="item.img" alt=""></div></li></ul></div><script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script><script>// 接口地址:http://hmajax.itheima.net/api/news// 请求方式:getconst app = new Vue({el: '#app',data: {list: []},async created () {// 1. 发送请求获取数据const res = await axios.get('http://hmajax.itheima.net/api/news')// console.log(res)// 2. 更新到 list 中,用于页面渲染 v-forthis.list = res.data.data}})</script>
</body>
</html>
mounted应用 —— 输入框获取焦点
要获取输入框,操作Dom;那就需要在mounted这个钩子里完成
<!DOCTYPE html>
<html lang="zh-CN"><head><!-- 初始化样式 --><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reset.css@2.0.2/reset.min.css"><!-- 核心样式 --><style>html,body {height: 100%;}.search-container {position: absolute;top: 30%;left: 50%;transform: translate(-50%, -50%);text-align: center;}.search-container .search-box {display: flex;}.search-container img {margin-bottom: 30px;}.search-container .search-box input {width: 512px;height: 16px;padding: 12px 16px;font-size: 16px;margin: 0;vertical-align: top;outline: 0;box-shadow: none;border-radius: 10px 0 0 10px;border: 2px solid #c4c7ce;background: #fff;color: #222;overflow: hidden;box-sizing: content-box;-webkit-tap-highlight-color: transparent;}.search-container .search-box button {cursor: pointer;width: 112px;height: 44px;line-height: 41px;line-height: 42px;background-color: #ad2a27;border-radius: 0 10px 10px 0;font-size: 17px;box-shadow: none;font-weight: 400;border: 0;outline: 0;letter-spacing: normal;color: white;}body {background: no-repeat center /cover;background-color: #edf0f5;}</style>
</head><body>
<div class="container" id="app"><div class="search-container"><img src="https://www.itheima.com/images/logo.png" alt=""><div class="search-box"><input type="text" v-model="words" id="inp"><button>搜索一下</button></div></div>
</div><script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>const app = new Vue({el: '#app',data: {words: ''},// 让input框获取焦点 inp.focus()mounted () {document.querySelector('#inp').focus()}})
</script></body></html>
Day4
scoped样式冲突
写在组件中的样式默认是全局生效;这就造成了多个组件间的样式冲突问题
那我们想让它局部有效,让样式只作用于当前组件,那就需要给组件.vue文件中的样式
加上scoped属性;像这样<style scoped>
scoped原理:
- 组件内所有标签都被添加了 data-v-hash 值 (App.vue)并且是一样的;
有相同data-v-hash值的处于同一组件范围内,eg.<div data-v-hash>
<span data-v-hash>
- css选择器都被自动加上了[data-v-hash]属性选择器 ,eg.div[data-v-hash];
样式作用时利用这个属性选择器区分关联
data函数
一个组件的data选项必须是个函数;以保证每个组件实例维护独立的一份数据对象
每次创建新的组件实例,都会重新执行一次data函数,以得到一个新对象
目标:
App.vue
<template><div id="app"><BaseCount></BaseCount><br><BaseCount></BaseCount><br><BaseCount></BaseCount></div>
</template><script>
import BaseCount from './components/BaseCount.vue'export default {components:{BaseCount}
}
</script><style></style>
BaseCount.vue
<template><div class=".base-count"><button @click="count--">-</button><span>{{ count }}</span><button @click="count++">+</button></div>
</template><script>
export default {data(){// 数据对象return {count:999}}
}
</script><style scoped>.base-count{margin:20px;}
</style>
组件通信
组件通信:组件与组件间的 数据传递
组件的数据是独立的,无法直接访问其他组件的数据;想用其他组件的数据的话,就需要进行组件通信
组件关系有两种:父子关系 ; 非父子关系
其中父子关系间的通信方案采用:props 和 $emit
父传子用:props;子传父用:$emit
下面我们先来看一下 “父组件通过props将数据传给子组件的过程”
- 父中给子组件添加属性,传值
- 子组件通过props接收(属性名要同名)
- 子组件中渲染使用
目标:
准备工作
完整代码:
App.vue
import vueConfig from 'vue.config';
<template><div style="border:3px solid #000;margin:10px;">我是App组件<!-- 3.使用 --><!-- 一. 给子组件添加属性;传值 --><Son v-bind:title="myTitle"></Son></div></template><script>
// 2.1导入
import Son from './components/Son.vue'
export default {data(){return {myTitle:'黑马程序员'}},// 2.2注册components:{Son}
}
</script><style></style>
Son.vue
<!-- 1.创建 -->
<template><div style="border:3px solid #000;margin:10px;"><!-- 三. 进行渲染使用 -->我是Son组件 {{ title }}</div>
</template><script>
export default {// 二. 子组件通过props接收属性props:['title']
}
</script><style></style>
再来看一下 “子组件利用$emit通知父组件,进行修改更新的过程”
- 子组件中通过this.$emit向父组件发送消息
- 父组件中给子组件添加消息监听(事件名要同名)
- 父组件中实现处理函数
目标:
App.vue
<template><div style="border:3px solid #000;margin:10px;">我是App组件<!-- 二.父组件为了能收到消息;对消息进行监听 --><Son :title="myTitle" v-on:changeTitle="handleChange"></Son></div>
</template><script>
import Son from './components/Son.vue'
export default {data(){return {// 将来我要修改这个数据myTitle:'学前端,来黑马!'}},methods:{// 三.定义处理函数,提供处理逻辑handleChange(newTitle){this.myTitle=newTitle}},components:{Son}
}
</script>
<style></style>
Son.vue
import vueConfig from 'vue.config';
vueConfig
<template><div style="border:3px solid #000;margin:10px;">我是Son组件 {{ title }}<!-- 添加点击事件 --><button v-on:click="changeFn">修改title</button></div></template><script>
export default {props:['title'],methods:{changeFn(){// 一.子组件向父组件发送消息通知 —— 修改标题;事件名changeTitlethis.$emit('changeTitle','传智教育')} }}
</script><style></style>
props
多属性(父传子)
目标:
完整代码:
App.vue
<template><div class="app"><UserInfo:username="username":age="age":isSingle="isSingle":car="car":hobby="hobby"></UserInfo></div>
</template><script>
import UserInfo from './components/UserInfo.vue'
export default {data() {return {username: '小帅',age: 28,isSingle: true,car: {brand: '宝马',},hobby: ['篮球', '足球', '羽毛球'],}},components: {UserInfo,},
}
</script><style>
</style>
UserInfo.vue
<template><div class="userinfo"><h3>我是个人信息组件</h3><div>姓名:{{username}}</div><div>年龄:{{age}}</div><div>是否单身:{{isSingle?'是':'否'}}</div><div>座驾:{{car.brand}}</div><div>兴趣爱好:{{hobby.join('、')}}</div></div>
</template><script>
export default {props:['username','age','isSingle','car','hobby']
}
</script><style>
.userinfo {width: 300px;border: 3px solid #000;padding: 20px;
}
.userinfo > div {margin: 20px 10px;
}
</style>
props校验
为组件的prop指定验证要求,不符合要求的,控制台就会有错误提示;帮助开发者快速发现错误
类型校验
语法:
props:{
校验的属性名: 类型(Number、String、Boolean... ...)
},
错误演示
完整代码:
App.vue
<template><div class="app"><BaseProgress :w="width"></BaseProgress></div>
</template><script>
import BaseProgress from './components/BaseProgress.vue'
export default {data() {return {width: 30,}},components: {BaseProgress,},
}
</script><style>
</style>
BaseProgress.vue
<template><div class="base-progress"><div class="inner" :style="{ width: w + '%' }"><span>{{ w }}%</span></div></div>
</template><script>
export default {props: {w: Number,},
}
</script><style scoped>
.base-progress {height: 26px;width: 400px;border-radius: 15px;background-color: #272425;border: 3px solid #272425;box-sizing: border-box;margin-bottom: 30px;
}
.inner {position: relative;background: #379bff;border-radius: 15px;height: 25px;box-sizing: border-box;left: -3px;top: -2px;
}
.inner span {position: absolute;right: 0;top: 26px;
}
</style>
非空校验、默认值、自定义校验
语法:
把更详细的验证要求,写成一个完整对象
props:{
校验的属性名: {
type:类型, <=> 其效果等价于 校验的属性名: 类型
required:true,//是否必填
default:默认值,
validator(value){ //自定义校验
return 是否通过校验
}
}
},
演示
1.required:true
2. default:默认值
3.
validator(value){
return 是否通过校验
}
应用场景:
完整代码:
App.vue
<template><div class="app"><BaseProgress :w="width"></BaseProgress></div>
</template><script>
import BaseProgress from './components/BaseProgress.vue'
export default {data() {return {width: 200,}},components: {BaseProgress,},
}
</script><style>
</style>
BaseProgress.vue
<template><div class="base-progress"><div class="inner" :style="{ width: w + '%' }"><span>{{ w }}%</span></div></div>
</template><script>
export default {// 完整写法(类型、默认值、非空、自定义校验)props: {w: {type: Number,// required: true,default: 0,validator(val) {if (val >= 100 || val <= 0) {console.error('传入的范围必须是0-100之间')return false} else {return true}},},},
}
</script><style scoped>.base-progress {height: 26px;width: 400px;border-radius: 15px;background-color: #272425;border: 3px solid #272425;box-sizing: border-box;margin-bottom: 30px;}.inner {position: relative;background: #379bff;border-radius: 15px;height: 25px;box-sizing: border-box;left: -3px;top: -2px;}.inner span {position: absolute;right: 0;top: 26px;}
</style>
prop & data
共同点:都可以给组件提供数据
区别:
data的数据是自己的(可以随便改);prop的数据是外部的(不能直接改,要遵循 单向数据流)
单向数据流:父组件的数据更新,会向下流动,进而影响子组件;这个数据流动是单向的
data自己的数据随便改
prop外部数据不能直接改 (从父组件那儿传来的)
子要想修改=> 只能通知父组件,让父组件改;父改完去影响子
子组件count不能改;count++ <=> count = count + 1
正确写法
完整代码:
App.vue
<template><div class="app"><BaseCount :count="count" @changeCount="handleChange"></BaseCount></div>
</template><script>
import BaseCount from './components/BaseCount.vue'
export default {components:{BaseCount},data(){return {count:100}},methods:{handleChange(newVal){// console.log(newVal);this.count = newVal}}
}
</script><style></style>
BaseCount.vue
<template><div class="base-count"><button @click="handleSub">-</button><span>{{ count }}</span><button @click="handleAdd">+</button></div>
</template><script>
export default {props: {count: {type: Number,},},methods: {handleSub() {// 子组件向父组件发送消息通知 —— this.$emit// 子传父 this.$emit(事件名,参数)this.$emit('changeCount', this.count - 1)},handleAdd() {this.$emit('changeCount', this.count + 1)},},
}
</script><style>
.base-count {margin: 20px;
}
</style>
非父子关系通信方案
eventbus 事件总线
适用于非父子组件间进行简易的消息传递
- 创建一个都能访问到的事件总线(就是一个空的Vue实例)
utils/EventBus.js
- 接收方,监听Bus实例的事件
- 发送方,触发Bus实例的事件
目标:
分析:
完整代码:
EventBus.js
// 创建一个都能访问到的事件总线// 导入Vue
import Vue from 'vue'
// 创建事件总线 —— 空Vue实例
const Bus = new Vue()
// 导出 —— 向外共享Bus实例
export default Bus
App.vue
<template><div><BaseA></BaseA><BaseB></BaseB></div>
</template><script>
import BaseA from './components/BaseA.vue'
import BaseB from './components/BaseB.vue'
export default {components:{BaseA,BaseB}
}
</script><style></style>
BaseA.vue
<template><div class="baseA">我是组件A(接收方)<p>{{ msg }}</p> </div>
</template><script>
import Bus from '../utils/EventBus'
export default {data(){return{msg:''}},created(){//接收方,监听Bus的事件;如果触发,就执行回调,接收参数(消息)Bus.$on('sendMsg',(msg)=>{this.msg=msg})}
}
</script><style scoped>.baseA {width:200px;height:150px;padding:10px;margin-top:10px;border:3px solid #000;border-radius:5px;font-size:20px;}
</style>
BaseB.vue
<template><div class="baseB">我是组件B(发送方)<button @click="clickSend">发布通知</button></div></template><script>import Bus from '../utils/EventBus'export default {methods:{clickSend(){// 发送方,触发Bus实例的事件;发送消息通知Bus.$emit('sendMsg','这是一个消息')}}}</script><style scoped>.baseB {width:200px;height:150px;padding:10px;margin-top:10px;border:3px solid #000;border-radius:5px;font-size:20px;}</style>
provide&inject 跨层级的共享数据
什么叫跨层级:爷爷可以直接把数据传给孙子
语法:
- 父组件通过provide提供共享数据
- 子孙组件任何一个都能通过inject直接接收
一般采用复杂类型 —— 因为是响应式;父组件修改数据子孙也跟着改
完整代码:
App.vue
<template><div class="app">我是APP组件<button @click="change">修改数据</button><SonA></SonA><SonB></SonB></div>
</template><script>
import SonA from './components/SonA.vue'
import SonB from './components/SonB.vue'
export default {provide() {return {// 简单类型 是非响应式的color: this.color,// 复杂类型 是响应式的userInfo: this.userInfo,}},data() {return {color: 'pink',userInfo: {name: 'zs',age: 18,},}},methods: {change() {// 非响应式this.color = 'red'// 响应式this.userInfo.name = 'ls'},},components: {SonA,SonB,},
}
</script><style>
.app {border: 3px solid #000;border-radius: 6px;margin: 10px;
}
</style>
SonA.vue
<template><div class="SonA">我是SonA组件<GrandSon></GrandSon></div>
</template><script>
import GrandSon from '../components/GrandSon.vue'
export default {components:{GrandSon}
}
</script><style>
.SonA {border: 3px solid #000;border-radius: 6px;margin: 10px;height: 200px;
}
</style>
SonB.vue
<template><div class="SonB">我是SonB组件</div>
</template><script>
export default {}
</script><style>
.SonB {border: 3px solid #000;border-radius: 6px;margin: 10px;height: 200px;
}
</style>
GrandSon.vue
<template><div class="grandSon">我是GrandSon{{ color }} -{{ userInfo.name }} -{{ userInfo.age }}</div>
</template><script>
export default {//inject: ['color'] 要接收谁写谁的名字inject: ['color', 'userInfo'],
}
</script><style>
.grandSon {border: 3px solid #000;border-radius: 6px;margin: 10px;height: 100px;
}
</style>