Vue 轮子

2019-07-11  本文已影响0人  天才在右

使用JSbin学习
首先写一个tabs组件 然后用轮子代替组件

html

<html>
<head>
  <meta charset="utf-8">
  <title>Vue轮子</title>
  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
</head>
<body>
  <div id="app"></div>
</body>
</html>

JS

Vue.component('tabs',{
  props:['selectedTab'],
   template:
    `
      <div class="tabs">
        <slot>
      </div>
    `
})
Vue.component('tabs-navs',{
  name:'tabs-navs',
  template:
  `
   <div class="tabs-navs">
     <slot>
   </div>
  `
})
Vue.component('tabs-navs-item',{
  props:["name"],
  template:
  `
   <div class="tabs-navs-item">
   </div>
  `
})
Vue.component('tabs-panes',{
  name:'tabs-panes',
  template:
  `
    <div class="tabs-panes">
      <slot>
    </div>
  `
})
Vue.component('tabs-panes-item',{
  props:["name"],
  template:
  `
    <div class="tabs-panes-item">
    </div>
  `
})
var vm = new Vue({
  el:"#app",
  data:{
    selectedTab:1
  },
  template:
  `
   <tabs selectedTab="1">
     <tabs-navs>
       <tabs-navs-item name="tab1">tab1</tabs-navs-item>
       <tabs-navs-item name="tab2">tab2</tabs-navs-item>
     </tabs-navs>
     <tabs-panes>
       <tabs-panes-item name="tab1">内容1</tabs-panes-item>
       <tabs-panes-item name="tab2">内容2</tabs-navs-item>
     </tabs-panes>
   </tabs>
  `
})

注意

所以 在template编译中 文本中输入的字符也想要显示 那么在每个标签的template中都需加上<slot>标签

(其中<slot>是否要写成<slot/> 但我写成<slot> 没有报错 功能没有影响)
JS代码

Vue.component('tabs',{
  props:['selectedTab'],
   template:
    `
      <div class="tabs">
        <slot>
      </div>
    `
})
Vue.component('tabs-navs',{
  name: 'tabs-navs',
  template:
  `
   <div class="tabs-navs">
     <slot>
   </div>
  `
})
Vue.component('tabs-navs-item',{
  props:["name"],
  template:
  `
   <div class="tabs-navs-item">
     <slot>   //需要加才能显示相应内容
   </div>
  `
})
Vue.component('tabs-panes',{
  name: 'tabs-panes',
  template:
  `
    <div class="tabs-panes">
      <slot>
    </div>
  `
})
Vue.component('tabs-panes-item',{
  props:["name"],
  template:
  `
    <div class="tabs-panes-item">
      <slot>  //需要加才能显示相应内容
    </div>
  `
})
var vm = new Vue({
  el:"#app",
  data:{
    selectedTab:1
  },
  template:
  `
   <tabs selectedTab="1">
     <tabs-navs>
       <tabs-navs-item name="tab1">tab1</tabs-navs-item>
       <tabs-navs-item name="tab2">tab2</tabs-navs-item>
     </tabs-navs>
     <tabs-panes>
       <tabs-panes-item name="tab1">内容1</tabs-panes-item>
       <tabs-panes-item name="tab2">内容2</tabs-navs-item>
     </tabs-panes>
   </tabs>
  `
})

尝试用每层递进传递selectedTab 即爷爷传给爸爸 爸爸传给儿子

JS代码

Vue.component('tabs',{
  props:['selectedTab'],
   template:
    `
      <div class="tabs">
        <slot>
      </div>
    `,
  mounted(){       
    this.$children.forEach((e)=>{
      if(e.$options.name==='tabs-navs'){
        e.selectedTab = this.selectedTab
      }
    })
  }       // 爷爷传递selectedTab
})    
Vue.component('tabs-navs',{
  name:'tabs-navs',
  props:['selectedTab'],  // 爸爸接收selectedTab
  template:
  `
   <div class="tabs-navs">
     <slot>
   </div>
  `,
  mounted(){
    this.$children.forEach((e)=>{
      e.selectedTab = this.selectedTab
    })
  }     //爸爸传递selectedTab
})
Vue.component('tabs-navs-item',{
  props:["name","selectedTab"],   //儿子接受selectedTab
  template:`
   <div class="tabs-navs-item" :class={active}>
     <slot>
   </div>
  `,
  computed:{
   active(){
    return this.selectedTab === this.name
   }
  }
})

发现没有显示active 那打个log看看this.selectedTab到底是个啥

打出了undefined
那接着打log 看看爸爸和爷爷谁先mounted咯
真刺激 爸爸比爷爷先mounted
虽然写代码的时候是先写了爷爷再写爸爸儿子,但是Vue是先把儿子造出来放到爸爸里面 然后 再把爸爸渲染出来放到爷爷里面 再把爷爷渲染出来放到页面当中
额。。。。所以没有办法让爷爷把selectedTab传给爸爸了 因为那个时候爸爸已经被渲染好了

既然mounted不行 那我用computed

没有更新。。。直接赋值是不会更新的吗?
既然没法更新 就得换个思路了 告辞

那就试试不直接传值 调用爸爸的一个方法看看

先后顺序没错 现在看来可行
那就如法炮制 爷爷对爸爸 爸爸对儿子 写下来 同理 <tabs-panes-item>标签中的一样写下来

最后得到JS代码

Vue.component('tabs',{
  props:['selectedTab'],
   template:
    `
      <div class="tabs">
        <slot>
      </div>
    `,
  mounted(){
    this.$children.forEach((e)=>{
      if(e.$options.name==='tabs-navs'){
        e.setSelectedTab(this.selectedTab)   //调用爸爸一个方法
      }else if(e.$options.name==='tabs-panes'){
        e.setSelectedTab(this.selectedTab)
      }
    })
  }
})
Vue.component('tabs-navs',{
  name:'tabs-navs',
  data(){
    return {
      selectedTab:undefined
    }
  },
  template:
  `
   <div class="tabs-navs">
     <slot>
   </div>
  `,
  methods:{
    setSelectedTab(tab){
      this.selectedTab = tab
      this.$children.forEach((e)=>{
        e.setSelectedTab(tab) //同理 调用儿子的方法
      })
    }
  }
})
Vue.component('tabs-navs-item',{
  props:["name"],
  data(){
    return{
      selectedTab: undefined
    }
  },
  template:`
   <div class="tabs-navs-item" :class={active}>
     <slot>
   </div>
  `,
  methods:{
    setSelectedTab(tab){
      this.selectedTab = tab
    }
  },
  computed:{
   active(){
    return this.selectedTab === this.name
   }
  }
})
Vue.component('tabs-panes',{
  name:'tabs-panes',
  data(){
    return {
      selectedTab:undefined
    }
  },
  template:
  `
    <div class="tabs-panes">
      <slot>
    </div>
  `,
  methods:{
    setSelectedTab(tab){
      this.selectedTab = tab
      this.$children.forEach((e)=>{
        e.setSelectedTab(tab)
      })
    }
  }
})
Vue.component('tabs-panes-item',{
  props:["name"],
  data(){
    return{
      selectedTab: undefined
    }
  },
  template:
  `
    <div class="tabs-panes-item" :class = {active}>
      <slot>
    </div>
  `,
  methods:{
    setSelectedTab(tab){
      this.selectedTab = tab
    }
  },
  computed:{
   active(){
    return this.selectedTab === this.name
   }
  }
})
var vm = new Vue({
  el:"#app",
  data:{
    selectedTab:'1'
  },
  template:
  `
   <tabs selectedTab="tab1">
     <tabs-navs name="tabs-navs">
       <tabs-navs-item name="tab1">tab1</tabs-navs-item>
       <tabs-navs-item name="tab2">tab2</tabs-navs-item>
     </tabs-navs>
     <tabs-panes name="tabs-panes">
       <tabs-panes-item name="tab1">内容1</tabs-panes-item>
       <tabs-panes-item name="tab2">内容2</tabs-navs-item>
     </tabs-panes>
   </tabs>
  `
})

这里用的是data没有用props了

好了 接下来就只差触发事件 事件监听 点击切换状态

子元素上触发事件 父元素上监听 发现未打出log 父元素并未监听到事件 这样不可行

看来Vue的事件是不会冒泡的(只能手动冒泡操作) 只能监听子元素本人了
JS代码

Vue.component('tabs',{
  props:['selectedTab'],
   template:
    `
      <div class="tabs">
        <slot>
      </div>
    `,
  mounted(){
    this.$children.forEach((vm)=>{
      if(vm.$options.name==='tabs-navs'){
        vm.setSelectedTab(this.selectedTab)
        vm.$on('update:selectedTab',(e)=>{
          console.log('爷爷知道selectedTab为'+e)
        this.$emit('update:selectedTab',e)
      })
      }else if(vm.$options.name==='tabs-panes'){
        vm.setSelectedTab(this.selectedTab)
      }
    })
  }
})
Vue.component('tabs-navs',{
  name:'tabs-navs',
  data(){
    return {
      selectedTab:undefined
    }
  },
  template:
  `
   <div class="tabs-navs">
     <slot>
   </div>
  `,
  methods:{
    setSelectedTab(tab){
      this.selectedTab = tab
      this.$children.forEach((e)=>{
        e.setSelectedTab(tab)
      })
    }
  },
  mounted(){
    this.$children.forEach((e)=>{
      
      e.$on('update:selectedTab',(e)=>{
        console.log('爸爸知道事件触发了'+e)
        this.$emit('update:selectedTab',e)
      })
    })
  },
  created(){
   this.$on('update:selectedTab',()=>{
    
    }
   )
}
})
Vue.component('tabs-navs-item',{
  props:["name"],
  data(){
    return{
      selectedTab: undefined
    }
  },
  template:`
   <div class="tabs-navs-item" :class={active} @click = "onClick">
     <slot>
   </div>
  `,
  methods:{
    setSelectedTab(tab){
      this.selectedTab = tab
    },
    onClick(){
      this.$emit('update:selectedTab',this.name) //触发事件
      console.log('儿子触发事件')
    }
  },
  computed:{
   active(){
    return this.selectedTab === this.name
   }
  }
})
Vue.component('tabs-panes',{
  name:'tabs-panes',
  data(){
    return {
      selectedTab:undefined
    }
  },
  template:
  `
    <div class="tabs-panes">
      <slot>
    </div>
  `,
  methods:{
    setSelectedTab(tab){
      this.selectedTab = tab
      this.$children.forEach((e)=>{
        e.setSelectedTab(tab)
      })
    }
  }
})
Vue.component('tabs-panes-item',{
  props:["name"],
  data(){
    return{
      selectedTab: undefined
    }
  },
  template:
  `
    <div class="tabs-panes-item" :class = {active}>
      <slot>
    </div>
  `,
  methods:{
    setSelectedTab(tab){
      this.selectedTab = tab
    }
  },
  computed:{
   active(){
    return this.selectedTab === this.name
   }
  }
})
var vm = new Vue({
  el:"#app",
  data:{
    value: 'tab1'
  },
  template:
  `
   <tabs :selectedTab="value" @update:selectedTab = "value=$event">
     <tabs-navs name="tabs-navs">
       <tabs-navs-item name="tab1">tab1</tabs-navs-item>
       <tabs-navs-item name="tab2">tab2</tabs-navs-item>
     </tabs-navs>
     <tabs-panes name="tabs-panes">
       <tabs-panes-item name="tab1">内容1</tabs-panes-item>
       <tabs-panes-item name="tab2">内容2</tabs-navs-item>
     </tabs-panes>
   </tabs>
  `
})

现在发现点击tab2没有变化 原来是我们只在tabs的mounted做了操作 而在其updated的时候也应该做相应的操作

添加相应的JS代码

Vue.component('tabs',{
  props:['selectedTab'],
   template:
    `
      <div class="tabs">
        <slot>
      </div>
    `,
  mounted(){
    this.$children.forEach((vm)=>{
      if(vm.$options.name==='tabs-navs'){
        vm.setSelectedTab(this.selectedTab)
        vm.$on('update:selectedTab',(e)=>{
          console.log('爷爷知道selectedTab为'+e)
        this.$emit('update:selectedTab',e)
        })
      }else if(vm.$options.name==='tabs-panes'){
        vm.setSelectedTab(this.selectedTab)
      }
    })
  },
  updated(){           //任何更新后都应做相应的渲染
    this.$children.forEach((vm)=>{
      if(vm.$options.name==='tabs-navs'){
        vm.setSelectedTab(this.selectedTab)
        }else if(vm.$options.name==='tabs-panes'){
        vm.setSelectedTab(this.selectedTab)
      }
    })
  }
})

删除测试的log 以下是Vue轮子完整代码
JS

Vue.component('tabs',{
  props:['selectedTab'],
   template:
    `
      <div class="tabs">
        <slot>
      </div>
    `,
  mounted(){
    this.$children.forEach((vm)=>{
      if(vm.$options.name==='tabs-navs'){
        vm.setSelectedTab(this.selectedTab)
        vm.$on('update:selectedTab',(e)=>{
        this.$emit('update:selectedTab',e)
        })
      }else if(vm.$options.name==='tabs-panes'){
        vm.setSelectedTab(this.selectedTab)
      }
    })
  },
  updated(){
    this.$children.forEach((vm)=>{
      if(vm.$options.name==='tabs-navs'){
        vm.setSelectedTab(this.selectedTab)
        }else if(vm.$options.name==='tabs-panes'){
        vm.setSelectedTab(this.selectedTab)
      }
    })
  }
})
Vue.component('tabs-navs',{
  name:'tabs-navs',
  data(){
    return {
      selectedTab:undefined
    }
  },
  template:
  `
   <div class="tabs-navs">
     <slot>
   </div>
  `,
  methods:{
    setSelectedTab(tab){
      this.selectedTab = tab
      this.$children.forEach((e)=>{
        e.setSelectedTab(tab)
      })
    }
  },
  mounted(){
    this.$children.forEach((e)=>{
      
      e.$on('update:selectedTab',(e)=>{
        this.$emit('update:selectedTab',e)
      })
    })
  },
  created(){
   this.$on('update:selectedTab',()=>{
    
    }
   )
}
})
Vue.component('tabs-navs-item',{
  props:["name"],
  data(){
    return{
      selectedTab: undefined
    }
  },
  template:`
   <div class="tabs-navs-item" :class={active} @click = "onClick">
     <slot>
   </div>
  `,
  methods:{
    setSelectedTab(tab){
      this.selectedTab = tab
    },
    onClick(){
      this.$emit('update:selectedTab',this.name) //触发事件
    }
  },
  computed:{
   active(){
    return this.selectedTab === this.name
   }
  }
})
Vue.component('tabs-panes',{
  name:'tabs-panes',
  data(){
    return {
      selectedTab:undefined
    }
  },
  template:
  `
    <div class="tabs-panes">
      <slot>
    </div>
  `,
  methods:{
    setSelectedTab(tab){
      this.selectedTab = tab
      this.$children.forEach((e)=>{
        e.setSelectedTab(tab)
      })
    }
  }
})
Vue.component('tabs-panes-item',{
  props:["name"],
  data(){
    return{
      selectedTab: undefined
    }
  },
  template:
  `
    <div class="tabs-panes-item" :class = {active}>
      <slot>
    </div>
  `,
  methods:{
    setSelectedTab(tab){
      this.selectedTab = tab
    }
  },
  computed:{
   active(){
    return this.selectedTab === this.name
   }
  }
})   
///上面是轮子///
var vm = new Vue({
  el:"#app",
  data:{
    value: 'tab1'
  },
  template:
  `
   <tabs :selectedTab="value" @update:selectedTab = "value=$event">
     <tabs-navs name="tabs-navs">
       <tabs-navs-item name="tab1">tab1</tabs-navs-item>
       <tabs-navs-item name="tab2">tab2</tabs-navs-item>
     </tabs-navs>
     <tabs-panes name="tabs-panes">
       <tabs-panes-item name="tab1">内容1</tabs-panes-item>
       <tabs-panes-item name="tab2">内容2</tabs-navs-item>
     </tabs-panes>
   </tabs>
  `
})

HTML

<html>
<head>
  <meta charset="utf-8">
  <title>Vue轮子</title>
  <script src="https://cdn.bootcss.com/vue/2.6.9/vue.min.js"></script>
</head>
<body>
  <div id="app"></div>
</body>
</html>

css

.active{
  background: red;
}

jsbin预览链接
jsbin源码链接(进入点击run with js按钮)

上一篇下一篇

猜你喜欢

热点阅读