温故知新:
组件的认识:
- 组件是可复用的
- 把组件当做一种标签使用 使用一次 它内部的代码就会完整的执行一次
- 类比函数 使用组件就相当于调用函数 实参就相当于组件的属性传值
- 不用把数据全部放在数据源中,会变化更新或网络请求的数据才放在data中,固定的数据直接写在模板标签中
关于项目的资源打包问题:
热更新服务:将打包后的静态项目在内存中托管起来
项目中的本地资源打包时:
- 引用文件的资源(import)、标签中引入的资源(img-src)会启用loder去加载资源 会被webpack打包
- 放在data数据源中的网址对应的资源(如把图片等本地资源的路径放在data中) webpack不会读取data 所以webpack不打包
- data数据源中的图片应该放网络图片
编辑器写代码有波浪线
v-for:不写key会有警告
解决:绑定一个key 如果没有id就绑定for循环的下标 或者忽略不管没影响
标签的其他地方 命令窗口报错
解决:关闭webpack中配置的eslint的严格检测模式:lintOnSave:false
一、插槽v-slot
v-slot:插槽名 是具名插槽的用法
#s1 是插槽的语法糖
没有指定插槽名就是默认插入到插槽,不给插槽插入数据的话,就会使用组件的slot中的数据
插槽名不用使用引号引起来,直接写变量名
插入的内容必须是template标签或者组件 不能是原生的元素eg:
设计组件里面:
- 默认槽位:< slot> < /slot>
- 具名槽位:< slot name="s1"> < /slot>
使用组件时:
< 组件名> 尖括号中的东西插入默认槽位 < /组件名>
< 组件名>
< template v-slot:s1>插入内容必须放在这个标签中,老版本不用< template>
< template #s1>插入内容必须放在这个标签中,老版本不用< template>
< /组件名>
(1)组件的属性传值:
App.vue文件:
<template>
<div class="appbox">
<Box1 :msg="n1" :title="n2"></Box1>
<Box1 msg="msg1" title="标题1"></Box1>
</div>
</template>
<script>
import Box1 from "@/components/Box1.vue"
export default {
data() {
return {
n1: "app组件传给box组件的数据",
n2: "app组件传给box的标题"
}
},
components: {
Box1,
}
}
</script>
<style scoped="scoped">
.appbox {
width: 500px;
min-height: 300px;
background-color: honeydew;
padding: 0px;
border: 1px honeydew solid;
}
</style>
Box1.vue文件:
<template>
<div class="box">
<h1>{{title}}</h1>
<div>{{msg}}</div>
</div>
</template>
<script>
export default {
props:["msg","title"]
}
</script>
<style scoped="scoped">
.box {
width:400px;
min-height: 100px;
background-color: darkgray;
margin: 20px;
}
</style>
结果显示:
(2)插槽
父组件使用子组件时要在子组件中插入内容 在子组件中就要用插槽来装 否则不会显示出来
插槽的位置在哪 插入的内容就在哪
App.vue文件:
<template>
<div class="appbox">
<Box2 msg="msg2" title="标题2">
<b>app组件的插槽数据</b>
<!-- b标签是App组件的而非Box2的 只是通过插槽的方式插入到了Box2的模板中去-->
</Box2>
<Box2 msg="msg2" title="标题2">
<img src="./img/9.png">
</Box2>
<Box2 msg="msg2" title="标题2">
6666
</Box2>
<!-- 这种写法 并非嵌套 也不是父子组件关系 -->
<Box2 msg="msg2" title="标题2">
<Box3></Box3>
</Box2>
</div>
</template>
<script>
import Box2 from "@/components/Box2.vue"
import Box3 from "@/components/Box3.vue"
export default {
data() {
return {
}
},
components: {
Box2,
Box3
}
}
</script>
<style scoped="scoped">
.appbox {
width: 500px;
min-height: 300px;
background-color: honeydew;
padding: 0px;
border: 1px honeydew solid;
}
</style>
Box2.vue文件:
<template>
<div class="box">
<!-- 插槽的槽位 -->
<div class="s">
<slot></slot>
</div>
<h1>{{title}}</h1>
<div>{{msg}}</div>
</div>
</template>
<script>
export default {
props: ["msg", "title"]
}
</script>
<style scoped="scoped">
.box {
width: 400px;
min-height: 100px;
background-color: goldenrod;
margin: 20px;
}
.s {
width: 60px;
height: 60px;
background-color: ghostwhite;
}
.s img {
width: 60px;
height: 60px;
}
</style>
Box3.vue文件:
<template>
<i>box3</i>
</template>
结果显示:
(3)插槽
App.vue文件:
<template>
<div class="appbox">
<Box4 title="hello">
<template v-slot:s1>
<b>111111</b>
</template>
<template v-slot:s2>
<b>2222</b>
</template>
</Box4>
<Box4 title="helloBox4">
<template v-slot:s2>
<b>222222</b>
</template>
<template v-slot:s1>
<b>111111</b>
</template>
</Box4>
<Box4 title="helloBox4">
<template v-slot:s1>
<b>222222</b>
</template>
<template v-slot:s2>
<b>111111</b>
</template>
</Box4>
<Box4 title="helloBox4">
<i>Box4</i>
<template #s1>
<b>222222</b>
</template>
<template #s2>
<b>111111</b>
</template>
</Box4>
</div>
</template>
<script>
import Box4 from "@/components/Box4.vue"
export default {
data() {
return {
}
},
components: {
Box4,
}
}
</script>
<style scoped="scoped">
.appbox {
width: 500px;
min-height: 300px;
background-color: honeydew;
padding: 0px;
border: 1px honeydew solid;
}
</style>
Box4.vue文件:
<template>
<div>
<slot name="s1"></slot>
<h2>{{title}}</h2>
<slot name="s2"></slot>
<slot name="s2">不传数据到s2插槽中 就会默认显示这段文本出来</slot>
<slot></slot>
</div>
</template>
<script>
export default {
props:["title"]
}
</script>
<style>
</style>
结果显示:
二、组件的自定义事件
- 事件的三要素: 事件源 事件类型 事件监听器
如:<box v-on:myevent="fn"></box>
事件源target:box组件
事件类型type:myevent
使用组件时 绑定监听器,内部触发事件时 监听器就会调用
1.在原生组件(就是html标签)中 事件是由系统来设计触发条件的:
如:<div @click="fn">点我</div>
App.vue文件:
<template>
<div>
<button @click="clicked">点击</button>
</div>
</template>
<script>
export default {
methods:{
clicked(){
console.log("原生事件触发了")
}
}
}
</script>
结果显示:
2.在自定义组件中,事件是由自己来设计什么时候触发:
绑定事件:
<Box v-on:myevent="fn"></Box>
- Box组件是事件源
- myevent是Box组件绑定的事件类型
- fn是Box组件上面绑定的监听器
事件设计:
在Box组件内部,可以在想要的条件下去触发事件以下代码放在想触发自定义事件的地方
this.$emit("myevent","要给触发的事件的函数传入的参数")
(1)自定义事件
App.vue文件:
<template>
<div>
<Box v-on:myevent="fn"></Box>
</div>
</template>
<script>
import Box from "./Box.vue"
export default {
methods:{
fn(){
//fn在myevent事件触发后就会运行
//myevent由Box组件内部自己设计规定什么时候触发
console.log("自定义事件触发,执行了fn函数")
},
},
components:{
Box,
}
}
</script>
Box.vue文件:
<template>
<div>
<button @click="add">增加</button>
<p>{{count}}</p>
</div>
</template>
<script>
export default {
data() {
return {
count: 0
}
},
methods:{
add(){
this.count++
}
},
//侦听器:侦听count的值的变化 当count的值为5时 触发组件内的事件
watch:{
count(v){
if(v==5){
//触发组件内的事件
this.$emit("myevent")
}
}
},
//挂载完毕后就触发组件内的事件
mounted(){
this.$emit("myevent")
}
}
</script>
结果显示:
(2)组件中写的只能是自定义事件 就算与系统原生事件一模一样的写法也不能执行
App.vue文件:
<template>
<div>
<Box2 v-on:click="fn2"></Box2>
</div>
</template>
<script>
import Box2 from "./Box2.vue"
export default {
methods:{
fn2(){
console.log("Box2的自定义事件")
},
},
components:{
Box2,
}
}
</script>
Box2.vue文件:
<template>
<div>
<button @click="x">box2</button>
</div>
</template>
<script>
export default {
methods:{
x(){
this.$emit("click")
}
}
}
</script>
结果显示:
3.希望组件绑定原生事件(事件的触发条件设计由系统设计)
给事件绑定事件修饰符 .native
<mydiv @click.native="fn">点我</mydiv>//事件名必须是系统存在的事件
App.vue文件:
<template>
<div>
<Box3 v-on:click.native="fn3"></Box3>
</div>
</template>
<script>
import Box3 from "./Box3.vue"
export default {
methods:{
fn3(){
console.log("自定义事件写法的原生事件")
},
},
components:{
Box3,
}
}
</script>
Box3.vue文件:
<template>
<div>
<p>box3</p>
<button>box3</button>
</div>
</template>
结果显示:
三、组件中的网络请求
配置代理(解决跨域问题):vue的8080服务器请求egg的7001服务器
vue.config.js中:
devServer: {
//代理配置,这里只是配置,不用写代理服务器的代码(配置好了它帮我们实现)
// proxy: {
// '/xxxx': 'http://localhost:7001',
// },
proxy: {
'/xxxx': {
target: 'http://ip:7001',
secure: true, //如果代理的target是https接口,需要配置它
pathRewrite: {
'^/xxxx': '/'
}, //请求时重写pathname
//如果项目中实际请求的是:http://ip:8080/xxxx/ajax1
//8080服务器就会帮我们代理 请求:http://ip:7001/ajax1
},
},
}
(1)在组件中引入axios模块
App.vue文件:
<template>
<div>
<div>
<h2>{{obj.title}}</h2>
<p>{{obj.info}}</p>
</div>
</div>
</template>
<script>
import axios from "axios"//去node_modules中找到的axios文件夹中
export default {
data() {
return {
obj:{}
}
},
components:{},
async mounted() {
// let res=await axios("http://ip/ajax1")
let res=await axios("http://ip:8080/xxxx/ajax1")
// let res=await axios("/xxxx/ajax1")
//==>实际请求的是:http://http://ip:8080/xxxx/ajax1
//==>希望8080服务器帮我们代理 请求:http://http://ip:7001/ajax1
console.log(res)
this.obj=res.data
}
}
</script>
结果显示:
(2)配置公共url:
main.js中:
在组件的原型链上配置axios工具,并配置公共网址:
import axios from "axios"
Vue.prototype.$axios=axios
axios.defaults.baseURL="http://ip:8001/xxxx"
//配置公共url 如果这个axios去请求 ajax1 实际网址是:http://ip:8001/xxxx/ajax1
//做网络请求都是基于baseURL
//代理去请求:http://ip:7001/ajax1
App.vue文件:
<template>
<div>
<div>
<h2>{{obj.title}}</h2>
<p>{{obj.info}}</p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
obj:{}
}
},
components:{},
async mounted() {
// let res=await this.$axios("http://ip:8080/xxxx/ajax1")
let res=await this.$axios("ajax1")
//==>基于baseurl:"http://ip:8080/xxxx/ajax1"
console.log(res)
this.obj=res.data
}
}
</script>
显示结果同上
四、面试题相关
1、组件中的data设计为函数的原因:
1.组件被复用时,data数据源才不会被共用 设计成对象就会被共用 调函数调一次生成一次
2.函数的设计就像懒加载一样 当组件使用时,数据源的对象才会创建 这样设计性能更好
2、数据劫持的顺序:
this组件对象有很多属性和方法 都是劫持“别人”的:如 data methods props
给this设置成员(初始化)的顺序:属性props>方法methods>数据源data>计算属性computed>对属性监听watch
3、单向数据流:
数据由 根组件-->父组件-->子组件 ,单向修改,不能反向修改
父组件通过属性传值给子组件,子组件内部修改数据,父组件的数据不改变;父组件修改数据,会重新传值给子组件,子组件的数据更新改变
即:
- 在父传子的前提下,父组件的数据发生会通知子组件自动更新
- 子组件内部,不能直接修改父组件传递过来的props => props是只读的
4、vue加载流程
每一个组件在加载时都会调用vue内部的render函数把这个组件的template选项的模板解析为一个js对象,这个对象和DOM节点对象“长得一模一样”,就是为了后面的渲染。调render生成虚拟模板VNode.
然后是数据劫持代理监听等等:
底层设计:发布着/订阅者设计 其实就是写了一个watcher函数去订阅(监听)数据的改变(底层js语法就是Obj.defineproperty,vue3是proxy)
当数据变化以后:
当数据变更的时候,重新构造一颗新的对象树。然后用新的树和旧的树进行比较(diff),记录两棵树的差异。当第二棵树所记录的差异应用到第一棵树所构建的真正的DOM树上(patch),视图更新。(DIFF算法)
比较的过程是 一层一层的比较 父组件和父组件比较 并不会把父组件和子组件比较 同层级时 从两边到中间
比较的过程中 发现差异(组件/标签类型,文本,属性值。注释等) 就会异步地给DOM打补丁(操作页面)
5、DIFF算法
什么是DIFF?
用JavaScript对象结构表示DOM树的结构,然后用这个树构建一个真正的DOM树,插到文档中
当状态变更的时候,重新构建一颗新的对象树。然后用新的树和就的树进行比较(diff),记录两棵树的差异
当第二棵树所记录的差异应用到第一棵树所构建的真正的DOM树上(patch),视图更新。
DIFF算法的过程
当数据发生改变时,订阅者watcher就会调用patch给真实的DOM打补丁
通过sameVnode进行判断,相同则调用patchVnode方法
patchVnode做了以下操作:
找到对应的真实dom,称为el
如果都有都有文本节点且不相等,将el文本节点设置为Vnode的文本节点
如果oldVnode有子节点而VNode没有,则删除el子节点
如果oldVnode没有子节点而VNode有,则将VNode的子节点真实化后添加到el
如果两者都有子节点,则执行updateChildren函数比较子节点
updateChildren主要做了以下操作:
设置新旧VNode的头尾指针
新旧头尾指针进行比较,循环向中间靠拢,根据情况调用patchVnode进行patch重复流程、调用createElem创建一个新节点,从哈希表寻找 key一致的VNode 节点再分情况操作
6、在.vue文件中写Sass/SCSS、Less
1.引入外部写的Sass编译后的css文件
2.配置sass的加载器
<style lang="scss">
在里面写scss代码
</style>