出差比较闲,先是把红宝书过了一遍,又看了尚硅谷的一揽子js,vue相关课程,大致对promise,axios,nodejs,ajax,原型链,模块化这些内容有了大概的了解,之前那些陌生而令人生畏的术语vite,vuecli,nodeman,liveserver,npm,fetch,json-server这些都显得简单了起来,归根到底只是一些js实现的工具包或是一些构建工具,甚至于vue本身都并不怎么难,只要潜心学习都很简单,难的是对js的理解和开发的经验,或者对初学者来说代码量的积累。初学者很容易纠结于选择哪种语言,先学哪个知识,甚至于纠结使用哪个编辑器,其实这是一种逃避,一种害怕失败而不停地纠结于选择的过程,如果不能克服这种情绪沉下心写代码就很难进步,做任何事情都是这样。闲言少叙,虽然现在已经使用vue3了,但解决问题的思路都是一样的,我把之前的题目翻出来看一下,觉得很简单,解决之余站在初学者角度跟大家分享一下思路,共同学习,老手勿喷,思路如果看不懂可以看尚硅谷禹神的课,语法觉得陌生自己多敲一敲js代码,Vue实例就是一个功能完备的js类,正如vue就是一个js文件一样。
第7章练习。给pane组件新增一个prop:closable的布尔值,来支持是否可以关闭这个pane.这道题目首先有点坑,要知道,一般都使用components注册子组件但这里使用了slot,这就很容易混淆,这里pane组件的
p
a
r
e
n
t
是
t
a
b
s
,
但组件标签上的数据要写在
r
o
o
t
组件。但没关系,把
c
l
o
s
a
b
l
e
定义在
r
o
o
t
组件里即可,再在
t
a
b
s
遍历时使用实例中的方法获取子组件
c
l
o
s
a
b
l
e
的值,获取子组件的值如果使用
c
o
m
p
o
n
e
n
t
s
的话可以用
p
r
o
p
s
,
也可以
parent是tabs,但组件标签上的数据要写在root组件。但没关系,把closable定义在root组件里即可,再在tabs遍历时使用实例中的方法获取子组件closable的值,获取子组件的值如果使用components的话可以用props,也可以
parent是tabs,但组件标签上的数据要写在root组件。但没关系,把closable定义在root组件里即可,再在tabs遍历时使用实例中的方法获取子组件closable的值,获取子组件的值如果使用components的话可以用props,也可以emit,还可以全局事件总线或者消息订阅,这里直接用$children组件遍历即可。
接下来就是实现这个按钮的功能,这里有个坑,题目要求使用destroy方法,但这里的文本是使用slot嵌套的,如果把pane组件破坏了,“标签一的内容”文本就会残留,没法管理。refs的语法懒得去看了,官方是不推荐直接操作dom的,建议使用v-if,那这里只好将就一下了,不然用v-if又要传来传去了。
练习2的要求更简单了,可以直接用css3的动画效果,不过要手动编写切换class的代码,也可以在基础上用vue封装的,这里用vue的。完整代码如下。
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>标签页组件</title>
<style type="text/css">
[v-cloak]{
display: none;
}
.tabs{
font-size: 14px;
color: #657180;
}
.tabs-bar:after{
content: '';
display: block;
width: 100%;
height: 1px;
background: #d7dde4;
margin-top: -1px;
}
.tabs-tab{
display: inline-block;
padding: 4px 16px;
margin-right: 6px;
background: #fff;
border: 1px solid #d7dde4;
cursor: pointer;
position: relative;
}
.tabs-tab-active{
color: #3399ff;
border-top: 1px solid #3399ff;
border-bottom: 1px solid #fff;
}
.tabs-tab-active:before{
content: '';
display: block;
height: 1px;
background: #3399ff;
position: absolute;
top: 0;
left: 0;
right: 0;
}
.tabs-content{
padding: 8px 0;
}
.v-enter-active{
animation: atguigu 1s;
}
.v-leave-active{
animation: atguigu 1s reverse;
}
@keyframes atguigu{
from{
transform: translateX(-100%);
}
to{
transform: translateX(0px);
}
}
</style>
</head>
<body>
<div id="app" v-cloak>
<tabs v-model="activeKey">
<pane label="标签一" name="1" :closable="closable">
<div id="article1">标签一的内容</div>
</pane>
<pane label="标签二" name="2" :closable="closable">
<div id="article2">标签二的内容</div>
</pane>
<pane label="标签三" name="3" :closable="closable">
<div id="article3">标签三的内容</div>
</pane>
</tabs>
</div>
<script src="vue.js"></script>
<script>
Vue.component('pane',{
name:'pane',
template:'\
\
',
props:{
name:{
type:String
},
label:{
type:String,
default:''
},
closable:{
type:Boolean
}
},
methods:{
updateNav(){
this.$parent.updateNav()
},
bye(){
this.$destroy()
}
},
watch:{
label(){
this.updateNav();
}
},
mounted(){
this.updateNav();
},
beforeDestroy(){
let refName;
refName="article"+this.name;
document.getElementById(refName).innerHTML='';
},
data:function(){
return {
show:true,
}
},
});
Vue.component('tabs',{
template:'\
\
\
\
\
\
',
data:function(){
return {
//用于渲染tabs的标题
currentValue:this.value,
navList:[],
}
},
props:{
value:{
type:[String,Number]
}
},
methods:{
destroyPane(name){
this.getTabs().forEach((pane)=>{
if(pane.name===name){
pane.bye()
}
});
this.updateNav()
},
getClosable(name){
var tabs=this.getTabs();
for(tab of tabs){
if(tab.name===name){
return tab.closable;
}
}
},
tabCls:function(item){
return [
'tabs-tab',{
'tabs-tab-active':item.name===this.currentValue
}
]
},
getTabs(){
//通过遍历子组件,得到所有的pane组件
return this.$children.filter(function(item){
return item.$options.name==='pane';
});
},
updateNav(){
this.navList=[];
//设置对this的引用,在function回调里,this指向的
//并不是Vue实例
var _this=this;
this.getTabs().forEach(function(pane,index){
_this.navList.push({
label:pane.label,
name:pane.name||index,
});
//如果没有给pane设置name,默认设置它的索引
if(!pane.name) pane.name=index;
//设置当前选中的tab的索引
if(index===0){
if(!_this.currentValue){
_this.currentValue=pane.name||index;
}
}
});
this.updateStatus();
},
updateStatus(){
var tabs=this.getTabs();
var _this=this;
tabs.forEach(function(tab){
return tab.show=tab.name===_this.currentValue;
});
},
handleChange:function(index){
var nav=this.navList[index];
var name=nav.name;
this.currentValue=name;
//这里是为了统一数据,没有实际作用this.$emit('input',name);
//这里是为了提示方法的使用,没有实际作用this.$emit('on-click',name);
}
},
watch:{
value:function(val){
this.currentValue=val;
},
currentValue:function(){
this.updateStatus();
//在更新前使用现有标签飞出的动画即可
}
}
});
var app=new Vue({
el:'#app',
data:{
activeKey:'1',
closable:true,
}
});
</script>
</body>
</html>