美文网首页
造轮子-nav组件

造轮子-nav组件

作者: sweetBoy_9126 | 来源:发表于2019-03-01 13:00 被阅读0次

补充知识:
如果你需要相对引用你得加一个./否则会被认为你是在引用一个第三方库
比如:

//错误引入方法
import LfNav from 'nav/nav.vue'
//正确引入方法
import LfNav from './nav/nav.vue'

首先我们需要三个组件分别是nav.vue/nav-item.vue/sub-nav.vue

最开始的结构(无子菜单的结构)

<lf-nav :selected.sync="selected">
    <lf-nav-item name="home">首页</lf-nav-item>
    <lf-nav-item name="about">关于</lf-nav-item>
    <lf-nav-item name="hire">招聘</lf-nav-item>
</lf-nav>

如何实现在父组件中slot里的子组件触发事件然后对应的在父组件中监听
思路:slot中的子组件先触发一个事件,然后在父组件中通过this.$children拿到slot里的子组件,之后遍历出每一个vm,让每一个vm都监听这个事件也就是vm.$on

  • nav.vue
<template>
    <div class="lf-nav">
        <slot></slot>
    </div>
</template>
vm.$on('add:selected',(name)=>{
    console.log(name)
    if(this.selected.indexOf(name) > -1 ){

    }else{
        let copy = JSON.parse(JSON.stringify(this.selected))
        copy.push(name)
        console.log(copy)
        this.$emit('update:selected',copy)
    }
})
  • nav-item.vue
<template>
    <div class="lf-nav-item" :class="{active: selected}" @click="onClick">
        <slot></slot>
    </div>
</template>
<script>
    export default {
        name: "LiFaNavItem",
        props: {
            name: {
                type: String,
                required: true
            }
        },
        data(){
            return {
                selected: undefined
            }
        },
        methods: {
            onClick(){
                console.log(this.name)
                this.$emit('add:selected',this.name)
            }
        }
    }
</script>

添加子菜单

对于slot来说默认的不需要加名字

  • demo.vue
<template>
    <div>
        <lf-nav :selected.sync="selected">
            <lf-nav-item name="home">首页</lf-nav-item>
            <lf-sub-nav name="about">
                <template slot="title">关于</template>
                <lf-nav-item name="girl">美女</lf-nav-item>
                <lf-nav-item name="boy">帅哥</lf-nav-item>
                <lf-nav-item name="old">老爷爷</lf-nav-item>
            </lf-sub-nav>
            <lf-nav-item name="hire">招聘</lf-nav-item>
        </lf-nav>
    </div>
</template>
  • sub-nav.vue
<template>
    <div class="lf-sub-nav">
        <span>
          //这个slot对应的上面的title
            <slot name="title"></slot>
        </span>
        <div class="popover">
        //这个是默认的不需要加名字,对应的就是上面的<lf-nav-item>
        <slot></slot>
        </div>
    </div>
</template>

遇到的问题:由于我们中间多了一层sub-nav,导致我们不能直接通知nav-item,而需要通知su-nav,让它去通知nav-item

  • demo.vue
return {
        selected: ['girl']
 }

解决方法:使用依赖注入实现跨级调用

  1. 在你的根组件里提供一个依赖root,把当前的实例给root,然后定义一个addItem的方法,接受又来接收每个后代的实例
  2. 在你所有的需要作用的后代(item)中注入这个root
  3. 直接使用this.root.addItem这个函数把每个item的实例传给根组件
  • nav.vue
data(){
    return {
        item: []
    }
},
provide(){
    return {
        root: this
    }
},
mounted() {
    this.updateChildren()
    this.listenToChildren()
},
updated() {
    this.updateChildren()
    this.listenToChildren()
},
methods: {
    addItem(vm){
        this.item.push(vm)
    },
    updateChildren: function () {
        this.item.forEach(vm => {
            if (this.selected.indexOf(vm.name) > -1) {
                vm.selected = true
            } else {
                vm.selected = false
            }
        })
    },
    listenToChildren(){
        this.item.forEach(vm=>{
            vm.$on('add:selected', (name) => {
                if(this.multiple){
                    if (this.selected.indexOf(name) <= -1) {
                        let copy = JSON.parse(JSON.stringify(this.selected))
                        copy.push(name)
                        this.$emit('update:selected', copy)
                    }
                }else{
                    this.$emit('update:selected',[name])
                }
            })
        })

    }
}
  • nav-item.vue
props: {
    name: {
        type: String,
        required: true
    }
},
data(){
    return {
        selected: undefined
    }
},
created(){
  this.root.addItem(this)
},
methods: {
    onClick(){
        this.$emit('add:selected',this.name)
    }
}
}

使用v-if遇到一个bug
我们给子菜单sub-nav组件添加一个v-if默认是false,当点击title的时候为true,但是我们发现点击子菜单里的item不能添加active的类,原因是我们sub-nav里的item实例是在created的时候添加到nav中的,而v-if为false的时候item组件并不执行created钩子,所以不会添加active
解决办法:将v-if换成v-show

实现多级菜单

bug:当我们点击关于下面的菜单的时候,他只能选中当前级下的一个,没法通知到关于被选中了

比如我们上面的我们实际上是希望关于下面的联系方式>手机>移动都被选中,这样我们再次点击关于的时候能通知到它下面这些层级都被选中

实现方法:首先通过nav.vue里的data中声明一个namePath为空数组,然后在nav-item.vue中注入这个nav的实例root,点击的时候让namePath为空数组,然后调用它的父组件里的updateNamePath方法,在sub-nav中声明这个方法,因为不止一层,有可能sub-nav.vue还有父组件所以需要在 sub-nav.vue中也调用updateNamePath然后把当前的name传给root里的namePath通过在sub-nav中判断root.namePath是否包含this.name来添加active

  • nav.vue
data(){
  namePath: []
},
provide(){
  root: this
}
  • nav-item.vue
methods: {
    onClick(){
        this.root.namePath = []
        this.$parent.updateNamePath && this.$parent.updateNamePath()
        this.$emit('add:selected',this.name)
    }
}
  • sub-nav.vue
<div class="lf-sub-nav" :class="{active}">
</div>
<script>
inject: ['root'],
props: {
  name: {
                type: String,
                required: true
            }
},
computed: {
  active(){
      return this.root.namePath.indexOf(this.name) >= 0 ? true : false
  }
},
methods: {
    updateNamePath(){
        this.$parent.updateNamePath && this.$parent.updateNamePath()
        this.root.namePath.push(this.name)
    }
}
</script>

改进:当点击sub-nav的外面的时候隐藏二级菜单
引入之前自定义的指令click-outside

  • sub-nav.vue
<div class="lf-sub-nav" :class="{active}" v-click-outside="close">
import ClickOutside from '../click-outside.js'
directives: {ClickOutside}

支持垂直导航

用户传入一个vertical来实现,然后在sub-nav中注入这个vertical

  • demo.vue
<lf-nav :selected.sync="selected" vertical>

</lf-nav>
  • nav.vue
<div class="lf-nav" :class="{vertical}">
    <slot></slot>
</div>
<script>
  props: {
    vertical: {
      type: Boolean
    }
  },
provide(){
    return {
        root: this,
        vertical: this.vertical
    }
},
</script>
  • sub-nav.vue
<transition  @enter="enter" @leave="leave"
        @after-enter="afterEnter" @after-leave="afterLeave"
>
    <div class="popover" v-show="open" :class="{vertical}">
        <slot></slot>
    </div>
</transition>
<script>
inject: ['vertical'],
methods: {
  enter(el, done){
    //先设置为auto来获取它的高度
    el.style.height = 'auto'
    let {height} = el.getBoundingClientRect()//113
    //然后让他等于0,因为高度的变化只能是数字之间0-113而不能是auto-113
    el.style.height = 0
    //之所以要加el.getBoundingClientRect(),是因为如果不加浏览器会对你的
    //多次赋值进行合并,也就是说你先赋值了0,接着赋值113,它只会记下你的最后这一次113
    //而如果你想让0也生效,就需要在它赋值后紧接着进行一个与高度有关的操作
    el.getBoundingClientRect()
    //最后让高度等于你元素自身的高度
    el.style.height = `${height}px`
    el.addEventListener('transitionend',()=>{
        done()
    })
},
leave(el,done){
    let {height} = el.getBoundingClientRect()
    el.style.height = `${height}px`
    el.getBoundingClientRect()
    el.style.height = 0
    //这里之所以要监听transitionend是因为如果直接写done的话它就会直接
    //display:none
    el.addEventListener('transitionend',()=>{
        done()
    })
},
afterEnter(el){
    el.style.height='auto'
},
afterLeave(el){
    el.style.height = 'auto'
}
}
</script>

让横竖动画分开

<template v-if="vertical">
    <transition @enter="enter" @leave="leave"
                @after-enter="afterEnter" @after-leave="afterLeave"
    >
        <div class="popover" v-show="open" :class="{vertical}">
            <slot></slot>
        </div>
    </transition>
</template>
<template v-else>
    <div class="popover" v-show="open">
        <slot></slot>
    </div>
</template>

相关文章

  • 造轮子-nav组件

    补充知识:如果你需要相对引用你得加一个./否则会被认为你是在引用一个第三方库比如: 首先我们需要三个组件分别是na...

  • 造轮子-sticky组件

    最初的api设计 上面的api就是接受一个distance参数,意思是距离顶部多少距离的时候固定住 当需要通过滚动...

  • 2019-05-31 程序员修仙进阶标准,你到哪个阶段了?

    闭门造轮子 > 使用别人的轮子 > 开门造轮子 > 分享轮子

  • 造轮子-tab组件(上)

    1. 如何解决之前遗留的bug 根据错误提示大概确定原因,toast.test.js .style 造成。 用二分...

  • 造轮子-tab组件(中)

    1. 如果给一个标签一个class,标签本身又有class,vue是默认会合并的。只有两个属性是这样一个是clas...

  • 造轮子之仿射变换

    有人说,我们不应该再造轮子;也有人说,学习怎么造轮子可以带来更深的理解。我说,用轮子有用轮子的乐趣,造轮子有造轮子...

  • 第2章 组件化选型

    1、组件化选型结论 方案是Arouter+auto-register+自己造轮子补充原因:arouter使用简单,...

  • Android模块化开发实践

    重复造轮子是肯定的 区分概念(组件化,模块化,插件化) 1.组件化封装可重用功能代码,例如网络组件,数据库组件,图...

  • 开源时代,一杯敬明天,一杯敬过往

    Reinvent the Wheel 从“我们不要重复造轮子”到,兄弟们“我们造轮子”吧 不要重复造轮子 意味着我...

  • Vue造轮子-popover组件(上)

    1. popover是什么以及难点在哪 点一下出现一个卡片,也叫气泡卡片 难点在于css的样式 2. 用户会怎么去...

网友评论

      本文标题:造轮子-nav组件

      本文链接:https://www.haomeiwen.com/subject/jwcduqtx.html