Vue学习记录第五天
slot
如果在自定义标签中直接写入内容,内容会被替换掉,为了避免这个问题,Vue提供了slot插槽标签
<body>
<div id="app">
<modal m="1">
<p>adaf</p>
<h1 slot="title">大</h1>
<p slot='content'>白熊</p>
<button @click="fn">按钮</button>
</modal>
</div>
<template id="modal">
<div>
<slot name="title">默认标题</slot>
<slot name="content">默认内容</slot>
<slot name="default">1111111</slot>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
let modal={
template:"#modal"
};
let vm=new Vue(
{
el:"#app",
components:{
modal
},
methods:{
fn(){
alert(1)
}
}
}
)
</script>
</body>
默认值
<slot>标签中的内容为默认值,如果没有传递值,显示这个默认值,否则默认值会被替换掉
如果有很多没有指定默认值的slot,每一个都会被自定义标签内的内容替换掉
name
为slot标签附上name属性,为DOM上的自定义标签中内容附上slot属性,则可以为不同标签指定插槽
没有slot属性的都会使用default插槽
内容
自定义标签中的内容都属于父级,只有属性名属于组件,包括方法
父调用子组件的方法
复习
父传子利用属性
子级利用props接收,并且写入data(){}中
子传父利用方法
子级利用this.$emit()触发自定义方法,执行父级方法,改变父级的值
ref
前面第四天实例中的方法这部分有讲过,完全忘了
this.$refs ref用来给DOM元素或子组件注册引用信息,假如有一个标签ref="myp",通过this.$refs.myp可以进行调用
以下实例完成数据加载后,子组件隐藏
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
<loading ref="load"></loading>
<!--ref获取真实的dom-->
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
//父组件调用子组件的方法
let loading={
data(){
return {flag:true}
},
template:'<div v-show="flag">加载中</div>',
methods:{
hide(){
this.flag=false;
}
}
};
let vm=new Vue(
{
el:"#app",
data:{},
components:{
loading
},
mounted:{
//refs如果放在组件上,获取的是组件的实例,并不是组件的DOM元素
this.$refs.load.hide()
//this.$refs.load.$el.style.background="red" 改变当前元素背景颜色色
}
}
)
</script>
</body>
</html>
mounted在整个生命周期中是把编译好的HTML挂载在DOM后执行的
组件的切换以及保持
component标签
这是vue自带标签
vue自带标签还有template,transition,slot
template:没有意义的标签
transition:动画标签,用enter,enter-to,enter-active,leave,leave-active,leave-to控制动画
slot:插槽
<div id="app">
<input type="radio" v-model="r" value="polarbear">polarbear
<!--HTML中radio只要name相同,value不同就可以保证一组单选框了-->
<input type="radio" v-model="r" value="penguin">penguin
<keep-alive>
<component :is="r"></component>
</keep-alive>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
let polarbear={
template:'<div>polarbear</div>'
}
let penguin={
template:"<div>penguin</div>"
}
let vm=new Vue(
{
el:"#app",
components:{
polarbear,
penguin
},
data:{r:"polarbear"}
}
)
</script>
component可以实现多个组件使用同一个挂载点,切换不同组件
is属性
component是利用动态绑定is属性,挂载不同组件的
没有is属性会报错
切换的时候会销毁前一个组件,挂载另一个组件
keep-alive标签
为了让切换时标签存入缓存,而不是销毁,使用keep-alive标签
keep-alive标签用于缓存,为的是后面的路由做准备,如果缓存了就不会再走每个组件的created,mounted
组件生命周期的一定补充
子组件和父组件同时拥有mounted方法,会先走哪一个?
mounted方法是挂载完调用的,所以需要等待子组件挂载完,再触发父级的挂载
dom异步渲染
let child={
data(){
return {arr:[1,2,3]}
},
template:"<div>{{arr}}</div>",
mounted(){
this.arr=[4,5,6]
}
}
let vm=new Vue(
{
el:"#app",
data:{
},
components:{
child
},
mounted(){console.log(this.$refs.c.$el.innerHTML)
})
}
}
这个时候显示的console.log出来的arr并不会是4,5,6。因为挂载完毕开始触发父级mounted的时候,数据还没有改变
nextTick
官网文档说明是下次DOM更新循环结束之后执行延迟回调,修改数据之后立即使用这个方法,获取更新后的DOM
完全没看懂
稍微查一下,大致意思就是
- DOM的更新是异步的,就是说Vue中数据变化之后,并不会立即变化
- 写在同一事件中的任务是同步的
- DOM会等待这些所有的任务执行完,再更新
这就是为什么在实例中,调用DOM中的内容,会发现还没有更改
这时需要使用nextTick的回调函数,这个函数会在DOM更新完之后再调用
将Vue中 mounted改为
mounted(){
this.$nextTick(()=>{console.log(this.$refs.c.$el.innerHTML)
})}
组件的循环
使用v-for也可以使组件循环,但是由于每一个组件是不一样的,所以必须赋予key元素,为了让他们都不一样,所以使用index
实例如下,是一个拥有标题,文章内容,作者的,使用了slot,组件,父传子,组件循环知识的panel
<!--组件的循环,key是必须的-->
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<div id="app">
<panel v-for="(article,index) in articles" :type="article.type" :key="index">
<div slot="title"><span v-html="article.title"></span></div>
<div slot="content">{{article.content}}</div>
<div slot="footer" v-if="article.auth">{{article.auth}}</div>
</panel>
</div>
<template id="p">
<div class="panel" :class=[color]>
<div class="panel-heading">
<slot name="title"></slot>
</div>
<div class="panel-body">
<slot name="content"></slot></div>
<div class="panel-footer">
<slot name="footer">作者:无名</slot></div>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
let panel={
template:"#p",
props:{
type:{
type:[String],
default:"primary"
}
},
data(){
return {color:"panel-"+this.type}
}
}
let vm=new Vue(
{
el:"#app",
data:{
articles:[
{type:"primary",title:"<h1>大白熊</h1>",content:"这是一种哺乳动物",auth:"作者:polarbear"},
{type:"danger",title:"<h1>企鹅</h1>",content:"这是一种鸟类",auth:"作者:penguin"},
{type:"info",title:"<h1>海豹</h1>",content:"这是一种欧皇"}
]
},
components:{
panel
}
}
)
</script>
</body>
</html>
值得注意的点
- 不能写作 <slot class="panel-heading>这种形式,因为slot的样式会被替换掉
- props中的属性可以直接使用,但是不能改变,因为它的值是属于父组件的
EventBus(但是不用,就是为了面试了解一下,只适合极简单的形式)
同级组件,跨级组件之间相互传递信息很复杂
EventBus使用发布订阅,创建一个第三方实例,实现交互
- 在创建第一个组件时给EventBus绑定操作自己的方法
- 在第二个组件中,触发这个操作
<body>
<div id='app'>
<brother1></brother1>
<brother2></brother2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
let EventBus=new Vue;
//创建一个第三方实例,实现交互
let brother1={
template:'<div>{{color}}<button @click="change1">变绿</button></div>',
data(){
return {color:"绿色",default:'绿色'}
},
created()
{
EventBus.$on('changered',(val)=>{this.color=val})
//绑定事件
},
methods:{
change1(){
EventBus.$emit("changegreen",this.default)
}
}
}
let brother2={
template:"<div>{{color}}<button @click='change2'>变红</button></div>",
data(){
return {color:"红色",default:'红色'}
},
created(){
EventBus.$on("changegreen",(val)=>(this.color=val))
},
methods:{
change2(){
console.log(this.default)
EventBus.$emit("changered",this.default)
}
}
}
let vm=new Vue(
{
el:"#app",
components:{
brother1,
brother2
}
}
)
</script>
</body>
路由
路由的特点
访问不同的路径,返回不同的结果
前端和后端是分离的,后端值负责提供接口供前端调用,跳转都是由前端自己处理的
hash与history
hash模式:通过#,服务器不会管#后面的内容,根本不会发送到服务器,所以不会404,但是不支持SEO
history: window.history.pushState(obj, title [, url]),强制跳转,window.history.replaceState(obj, title [, url]),路由替换,会把路由历史改变,使用history刷新会真的去服务器请求,有404错误
开发使用hash,上线使用history
多页面(SPA,single page application)
切换的是组件
vue-router
使用vue-router进行多页面切换,就是把组件映射到路由,告诉vue router在哪里渲染
实例步骤
JS
- 引入Vue和Vue Router
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="./vue-router.js"></script>
一定要把vue-router写在vue的后面,因为router是基于Vue的
我的Vue-router是从网上下载直接扔在同一个文件夹的
- 定义组件
let home={template:"<div>首页</div>"};
let list={template:"<div>列表页</div>"};
- 定义路由映射表
配置路径和组件的关系,这个配置的关系就是页面级组件
let routes=[
//路由的映射表 配置路径和组件的关系
{path:' ',component:home},//默认展示的路由
//{path:'/*',component:home} 任意的都匹配到home
{path:'/home',component:home},
{path:'/list',component:list}
//配置的关系就是页面级组件
//访问'/home'就是home组件
//因为是路径,所以这里不能不写/
]
- 这里routes是一个数组
- 路径不能不写/
这里的path如果为' ',则为默认展示的路由
如果path为'*',并且有redirect组件,则表示当前路径不在routes中跳转至redirect指定的路径
- 创建router实例,传routes配置
let router=new VueRouter({
//引入vue-router/自带VueRouter类
routes:routes,
//mode:"history",会改为history模式,默认hash模式
linkActiveClass:'active'
//更改默认样式的类名,默认叫router-link-active
})
- 根据es6的规定,routes:routes可以直接缩写为routes,如果改名字了就不行了
- linkActiveClass是更改route-link的样式名,默认是router-link-active,这是router-link被激活时的样式,可以改变颜色之类的
- 创建Vue实例,通过router配置参数注入路由
let vm=new Vue(
{
el:"#app",
router:router,
mode:"history"
}
}
)
这里的Vue实例不再需要components
默认模式为hash,可以改变为hsitory
DOM
<div id="app">
<!--用a标签太局限了,只适用于hash-->
<router-link to="/home" tag="button">首页</router-link>
<router-link to="/list" tag="button">列表页</router-link>
<!--不写/如果是二级路由就会出问题-->
<!--默认为a标签,添加tag属性就会变化-->
<router-view></router-view>
<!--是一个vue-router定义的全局组件,可以直接使用-->
<!--显示视图-->
</div>
1. router-link
用于进行导航
- 默认会被渲染为a标签, 但是a标签太过于局限了,只适用于hash,使用tag属性,能够改变标签类型
- 通过to属性指定链接,链接要加/,否则如果是二级链接会出现问题
2. router-view
路由出口,路由匹配到的组件会渲染在此处
这是Vue-router定义的全局组件,用于显示视图
编程式导航
编程式导航是指利用js跳转页面
- 基于上述所学的Vue-router去创建一个导航栏
- 两个页面间可以通过按钮相互跳转
首先需要了解几个router实例方法
this.$router.push
相当于router-link标签中的to="url"
在history栈中添加一个新的记录
this.$router.replace
和push相似,但是不会像history栈添加新的记录,而是替换history记录
<router-link :to="..." replace>= router.replace(...)
this.$router.go
参数是一个整数,意思是在history记录中向前或向后多少步
负数为后退,正数为前进
记录不够用就报错
跳转的实现
在template中添加按钮,给每一个按钮绑定事件,在事件中通过this.$router.push()和 this.$router.go()进行页面跳转
<body>
<!--编程式导航,在js跳转页面-->
<div id="app">
<router-link :to="{path:'/home'" tag="a">首页</router-link>
<router-link :to="{path:'/list'" tag="a">列表页</router-link>
<router-view></router-view>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="./vue-router.js"></script>
<script>
let home={
template:"<div>首页<button @click='tolist'>去列表</button></div>",
methods:{
tolist(){
//跳到列表页是一个方法,所以使用$router
this.$router.push('/list') //强制跳转路径
console.log(window.history)
//this.$router.replace('/list') 路由替换,会把当前的历史替换掉
}
}};
let list={
template:"<div>列表页<button @click='back'>返回首页</button></div>",
methods:{
back(){
this.$router.go(-1)
//history.go(-1)返回一级
}
}
};
let routes=[
{path:' ',component:home},//默认展示的路由
//{path:'/*',component:home} 任意的都匹配到home
{path:'/home',component:home},
{path:'/list',component:list},
//{path:'*',component:home} //都匹配不到的情况,但是这个地方路径不会变,只是切换组件
{path:'*',redirect:'/home'} //路径变 组件也切换
]
let router=new VueRouter(
{
routes:routes
}
)
let vm=new Vue({
el:"#app",
router:router,
mode:"history"
//每个组件都会拥有一个名字为$router的属性(放的是方法) 还有一个名字为$route(放的都是属性)
//不用加components
})
</script>
</body>
路由的嵌套
创建一个拥有首页和详情页两个页面的路由,在详情页中又有两个路由,需要使用路由的嵌套
JS中嵌套
在routes中创建的父级路由中再创建路由表对象即可
let routes=[
{path:"/home",component:home},
{
path:"/detail",
component:detail,
children:[
//children中的路径永远不带/,如果带/表示是1级路由
{path:'profile',component:profile},
{path:'about',component:about}
]
}
]
一定要注意,这个二级路由中的路径不要带/
DOM中嵌套
路由的嵌套不要写在router-view中,写在template模板中,template模板中写的内容和之前单极路由的相似,不过是routes改为children,利用id引入组装中
<div id="app">
<router-link to="/home" tag="button">首页</router-link>
<router-link to="/detail" tag="button">详情页</router-link>
<!--路由的嵌套不能在这里写,要写在模板里-->
<router-view></router-view>
</div>
<template id="detail">
<div >
<router-link to="/detail/profile" tag="button" style="margin-top:10px">个人中心</router-link><br>
<!--这里的路径要写全部路径,不能只写二级路径-->
<router-link to="/detail/about" tag="button" style="margin-top:10px">关于我</router-link>
<router-view class="content"></router-view>
</div>
</template>
注意,这里的二级路由的路径,要写全部路径,否则找不到
全部程序
<body>
<div id="app">
<router-link to="/home" tag="button">首页</router-link>
<router-link to="/detail" tag="button">详情页</router-link>
<!--路由的嵌套不能在这里写,要写在模板里-->
<router-view></router-view>
</div>
<template id="detail">
<div >
<router-link to="/detail/profile" tag="button" style="margin-top:10px">个人中心</router-link><br>
<!--这里的路径要写全部路径,不能只写二级路径-->
<router-link to="/detail/about" tag="button" style="margin-top:10px">关于我</router-link>
<router-view class="content"></router-view>
</div>
</template>
<template id="home">
<div>
<img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1543393547132&di=49374d09562621a76c2570f91d82e7bd&imgtype=0&src=http%3A%2F%2Fi0.hdslb.com%2Fbfs%2Farchive%2F65c92640da76255ec058ab0d3691333135116875.jpg">
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="./vue-router.js"></script>
<script>
let home={
template:"#home"
}
let detail={
template:"#detail"
}
let profile={
template:"<div>profile</div>"
}
let about={
template:"<div>about</div>"
}
let routes=[
{path:"/home",component:home},
{
path:"/detail",
component:detail,
children:[
//children中的路径永远不带/,如果带/表示是1级路由
{path:'profile',component:profile},
{path:'about',component:about}
]
}
]
let router=new VueRouter(
{
routes
}
)
let vm=new Vue(
{
el:"#app",
router,
}
)
</script>
</body>
带参数路由
可以在路径中添加参数,比如商品号为111,/item/111路径最后面就是传递的参数。
想要取出这个参数,需要在路由映射表的路径中添加 :c,会产生一个{c:1}的对象,存入this.$route.params, 可以通过this.$route.params.c取出来
<body>
<div id="app">
<router-link to="/article/1">商品1</router-link>
<router-link to="/article/2">商品2</router-link>
<router-link to="/article/3">商品3</router-link>
<router-link to="/article/4">商品4</router-link>
<router-view></router-view>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="./vue-router.js"></script>
<script>
//#/article/1 就是第一篇文章
let article={template:"<div>第{{$route.params.c}}篇文章</div>"}
let routes=[
//路径参数 表示值必须要有但是值是随机的
{path:'/article/:c',component:article}
//:c表示随机的
//会产生一个{c:1}的对象=>属于$route=>this.$route.params
]
let router=new VueRouter(
{
routes
}
)
let vm=new Vue(
{
el:"#app",
router
}
)
</script>
</body>
这里组件只有一个,通过传递过来的参数改变内容
通过参数跳转
<div id="app">
<!--如果用对象作为to的属性,并且使用参数,必须给路由起名字,通过名字跳转-->
<router-link :to="{name:'pro',params:{c:1}}">商品1</router-link>
<router-link :to="{name:'pro',params:{c:2}}">商品2</router-link>
<router-link :to="{name:'pro',params:{c:3}}">商品3</router-link>
<router-link :to="{name:'pro',params:{c:4}}">商品4</router-link>
<router-view></router-view>
</div>
将参数写在路径中,注意这里的path不再是path而是name,必须在路由映射表中,给路由起名字,才能正常跳转
除此之外,还可以通过监控参数的变化而对组件进行一些操作,监控需要使用watch,监控参数的操作,而参数存入 this.$route,所以监控这个值的变化
<body>
<div id="app">
<!--如果用对象作为to的属性,并且使用参数,必须给路由起名字,通过名字跳转-->
<router-link :to="{name:'pro',params:{c:1}}">商品1</router-link>
<router-link :to="{name:'pro',params:{c:2}}">商品2</router-link>
<router-link :to="{name:'pro',params:{c:3}}">商品3</router-link>
<router-link :to="{name:'pro',params:{c:4}}">商品4</router-link>
<router-view></router-view>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="./vue-router.js"></script>
<script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
<script>
//#/article/1 就是第一篇文章
let article={
template:"<div>第{{$route.params.c}}篇文章</div>",
watch:{
this.$route(){
//路径参数发生变化,通过监控参数变化进行操作
}
}
}
let routes=[
//路径参数 表示值必须要有但是值是随机的
{path:'/article/:c',component:article,name:'pro'}
//:c表示随机的
//会产生一个{c:1}的对象=>属于$route=>this.$route.params
]
let router=new VueRouter(
{
routes
}
)
let vm=new Vue(
{
el:"#app",
router
}
)
</script>
</body>
动画路由结合
Vue动画 Vue在插入,更新,移除DOM中,提供多种不同的应用过渡,可以直接使用Animation.css这个库
在head中添加
<link href="https://cdn.bootcss.com/animate.css/3.7.0/animate.css" rel="stylesheet">
然后再transition标签中添加 enter-active-class以及leave-active-class即可添加不同的进入离开动画
<div id="app">
<router-link to="/home">首页</router-link>
<router-link to="/list">列表</router-link>
<transition enter-active-class="animated bounceInRight"
leave-active-class="animated bounceOutRight">
<keep-alive>
<router-view></router-view>
</keep-alive>
</transition>
</div>
但是会有一个非常严重的问题,动画不会在同一平面进行,列表会出现在首页下方,首页完全消失,列表跳到首页的位置
解决方法:1. 改变动画模式,在transition标签处添加mode属性,并且改为"out-in",先出去再进来 2. 使用position:absolute进行组件位置控制