Vue学习笔记(1)——在页面右上角实现可悬浮/隐藏的系统菜单

栏目: JavaScript · 发布时间: 6年前

内容简介:转自本文不能算一个教程,而是记录一只Vue菜鸟踩坑心路历程的笔记,对于正在踩坑的新手或许会有一点小帮助。作为第一篇正经八百的技术博客,欢迎大家发表看法,关爱菜鸟,请轻拍很久以后回来更一下,这个功能可以用Vuex轻松搞定,此文就当练习父子组件通信了吧,emmm……

转自 榕树岛

本文不能算一个教程,而是记录一只Vue菜鸟踩坑心路历程的笔记,对于正在踩坑的新手或许会有一点小帮助。作为第一篇正经八百的技术博客,欢迎大家发表看法,关爱菜鸟,请轻拍

很久以后回来更一下,这个功能可以用Vuex轻松搞定,此文就当练习父子组件通信了吧,emmm……

原文: 这是个大多数网站很常见的功能,点击页面右上角头像显示一个悬浮菜单,点击页面其他位置或再次点击头像则菜单隐藏。

Vue学习笔记(1)——在页面右上角实现可悬浮/隐藏的系统菜单
作为一个 jQuery

前端攻城狮实现这个功能可以说是很easy了,但是对只刚粗看了一遍vue文档的菜鸟来说,坑还是要亲自踩过才算圆满。

知识点

  • 组件及组件间通信
  • 计算属性

正文

1. 父组件

这里暂时只涉及系统菜单这一个功能,因此路由暂未涉及。

基本思路是:通过props将showCancel这个Boolean值传递到子组件,对父子组件分别绑定事件,来控制这个系统菜单的显示状态。其中在父组件的绑定click事件中,将传入子组件的showCancel值重置。

这里就涉及第一个小知识点——子组件调用:

首先写好等待被子组件渲染的自定义元素:

<t-header :showCancel=showCancel></t-header>
复制代码

接着 import 写好的子组件:

import THeader from "./components/t-header/t-header";
复制代码

然后在组件中注册子组件:

components: {
  THeader
}
复制代码

到这里,新入坑的同学可能会比较疑惑这几行代码是怎样把子组件对应到 <t-header> 标签的,官方文档是这样说的:

当注册组件 (或者 prop ) 时,可以使用 kebab-case (短横线分隔命名)、 camelCase (驼峰式命名) 或 PascalCase (单词首字母大写命名);

HTML 模板中,请使用 kebab-case ;

我的理解是(举例),当自定义元素为 <t-header> 时,注册组件名可以有三种写法: t-headertHeaderTHeader ,在这种情况下注册的组件会自动识别并渲染到 <t-header>

需要注意的是,以上使用的是 HTML 模板,是在单文件组件里用 <template><template/> 指定的模板;另外存在一种字符串模板,是用在组件选项里用 template: "" 指定的模板。当使用字符串模板时,自定义标签可以用三种写法,具体情况请移步官方文档组件命名约定 。 这样父组件的雏形就诞生了:

<template>
  <div id="app" @click="hideCancel">
    <t-header :showCancel=showCancel></t-header>
    <!-- <router-view/> -->
  </div>
</template>

<script>
  import THeader from "./components/t-header/t-header";
  export default {
    name: "app",
    components: {
      THeader
    },
    data() {
      return {
        showCancel: false
      };
    },
    methods: {
      hideCancel() {
        this.showCancel = false;
      }
    }
  };
</script>
复制代码

2. 子组件

子组件中 .cancel 为打开系统菜单的按钮, .cancel-div 为系统菜单,最开始是这个样子:

<template>
  <div class="header-wrapper">
    /*这里是logo和title*/
    ...
    /*这里是用户名和按钮*/
    <div class="info-wrapper">
      <span class="username">你好,管理员!</span>
      <span class="cancel" @click.stop="switchCancelBoard">
        <div class="cancel-div" v-show="showCancel">
          <ul>
            <li @click.stop="doSomething" title="用户设置">设置 </li>
            <li @click.stop="doSomething" title="退出登录">退出 </li>
          </ul>
        </div>
      </span>
    </div>
  </div>
</template>
复制代码

按照踩坑之前的思路,在子组件接到 showCancel 值后用 v-show 控制显示隐藏,那么在父子组件的绑定 click 事件中只需要根据情况更改 showCancel 值就可以了,只要注意对系统菜单内几个选项的绑定事件不要触发父子组件上的绑定事件——总不能一点菜单它就没了,所以在绑定事件中用到了 .stop ,即

@click.stop="doSomething"

于是万事大吉,也就是像这样:

<script>
  export default {
    props: {
      showCancel: {
        type: Boolean
      }
    },
    methods: {
      doSomething() {},
      switchCancelBoard() {
        this.showCancel = !this.showCancel;
      }
    },
    computed: {
      ifShowCancel() {
        return this.showCancel;
      }
    }
  };
</script>
复制代码

然而第一波踩坑之后一起表明显然我还是太年轻。下面是一些 不好的示范

  • prop来的showCancel值的确可以用,点击子组件按钮的时候,

    this.showCancel=!this.showCancel

    实现了菜单的显示/隐藏,但是一打开控制台,每次点击贡献了一条报错:

    vue.esm.js?efeb:578 [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value.

    意思是:避免修改 prop 值,因为父组件一旦re-render,这个值就会被覆盖;

    另外,尽管在这个按钮上实现了显示状态的切换,但是点击其他区域的时候,并不会隐藏它,原因是: 子组件 prop 值的变化并没有影响到父组件,因此 showCancel 的值一直保持初始值没有变化,而只有在这个值被更新时才会触发子组件中相关值的更新

    ——好吧,那么老老实实的用一个计算属性接收 showCancel 值,这样实现点击子组件控制系统菜单的状态切换;

  • 获得了计算属性 ifShowCancel ,组件相应的变成了 v-show="ifShowCancel" ,我试图在绑定事件里通过 this.ifShowCancel=!this.ifShowCancel 切换菜单状态, 报错 ,得到报错信息: Computed property "ifShowCancel" was assigned to but it has no setter

    明白了, 要以直接赋值的形式改变计算属性 ifShowCancel 的值,需要一个 setter 函数 ,但是setter函数中无法修改 prop 值,因此在 getter 中也就无法通过 return this.showCancel 来更新这个计算属性,所以这个方法貌似也行不通;

    到此为止,好像路都成了堵死状态: prop 值不能改->要用计算属性;计算属性不能改->需要 setter ;而写入了 gettersetter ,计算属性的值依赖于 prop 值-> prop 值不能改。——一个堪称完美的闭环诞生了!

    走投无路之际我想起了 $emit$on 这一对。

3. 父子互相通信

前边的prop实现了从父到子的单向通信,而通过 $emit$on ,就可以实现从子组件到父组件的通信:这不能直接修改父组件的属性,但却可以触发父组件的指定绑定事件,并将一个值传入父组件。

在这一步我摒弃了点击按钮时的去操作子组件内属性的想法,既然计算属性 ifShowCancel 依赖于prop值,那么就在点击按钮时,通过 $emit 触发父组件的事件,并将需要修改的属性值传入父组件,于是:

<!--父组件自定义元素绑定switch-show事件-->
<t-header :showCancel=showCancel @switch-show="switchShow"></t-header>
复制代码
// 父组件js
methods: {
  //会被子组件$emit触发的方法
  switchShow(val) {
    this.showCancel = val;
  }
}

// 子组件js
methods: {
  //按钮上的绑定click事件
  switchCancelBoard() {
    this.$emit("switch-show", this.ifShowCancel);
  }
}
复制代码

这样处理流程就变成了:点击按钮-> ifShowCancel 值传入父组件并触发父组件事件,对 showCancel 赋值->父组件属性更新->子组件prop更新->重新compute,更新ifShowCancel值-> v-show 起作用。

另外在点击其他区域时,通过父组件绑定的click事件,就可以重置 showCancel 值,进而隐藏掉出现的系统菜单。

4. 完整代码

/*父组件*/
<template>
  <div id="app" @click="hideCancel">
    <t-header :showCancel=showCancel @switch-show="switchShow"></t-header>
    <!-- <router-view/> -->
  </div>
</template>

<script>
  import THeader from "./components/t-header/t-header";

  export default {
    name: "app",
    components: {
      THeader
    },
    data() {
      return {
        showCancel: false
      };
    },
    methods: {
      hideCancel() {
        this.showCancel = false;
      },
      switchShow(val) {
        this.showCancel = val;
      }
    }
  };
</script>

<style scope lang="stylus">
</style>
复制代码
/*子组件*/
<template>
  <div class="header-wrapper">
    <div class="title-wrapper">
      <div class="logo"></div>
      <h2 class="title">Title</h2>
    </div>
    <div class="info-wrapper">
      <span class="username">你好,管理员!</span>
      <span class="cancel" @click.stop="switchCancelBoard">
        <div class="cancel-div" v-show="ifShowCancel">
          <ul>
            <li @click.stop="doSomething" title="用户设置">设置 </li>
            <li @click.stop="doSomething" title="退出登录">退出 </li>
          </ul>
        </div>
      </span>
    </div>
  </div>
</template>

<script>
  export default {
    props: {
      showCancel: {
        type: Boolean
      }
    },
    methods: {
      doSomething() {},
      switchCancelBoard() {
        // this.ifShowCancel = !this.showCancel;
        this.$emit("switch-show", !this.ifShowCancel);
      }
    },
    computed: {
      ifShowCancel() {
        return this.showCancel;
      }
    }
  };
</script>
<style lang="stylus" rel="stylesheet/stylus" scoped>
  .header-wrapper
    background: #1C60D1
    color: #fff
    width: 100%
    height: 50px
    line-height: 50px
    position: fixed
    top: 0px
    left: 0px
    font-size: 0
    .title-wrapper
      display: block
      position: relative
      float: left
      height: 50px
      .logo
        display: inline-block
        background-image: url('./logo.png')
        background-size: 30px 30px
        background-repeat: no-repeat
        width: 30px
        height: 30px
        margin-top: 10px
      .title
        display: inline-block
        font-size: 16px
        height: 50px
        line-height: 50px
        margin: 0px auto 0px 16px
        font-weight: normal
        vertical-align: top
    .info-wrapper
      display: block
      position: relative
      float: right
      height: 50px
      width: 160px
      font-size: 0
      .username
        display: inline-block
        height: 50px
        line-height: 50px
        font-size: 14px
        vertical-align: top
      .cancel
        display: inline-block
        vertical-align: middle
        background-image: url('./cancel.png')
        background-size: 32px 32px
        cursor: pointer
        background-repeat: no-repeat
        width: 32px
        height: 32px
        .cancel-div
          position: absolute
          display: block
          width: 60px
          height: 80px
          background: #fff
          z-index: 50
          top: 40px
          right: 16px
          font-size: 14px
          color: #646464
          box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.4)
          ul
            padding-left: 0px
            margin: 0px
            li
              width: 100%
              height: 40px
              line-height: 40px
              text-align: center
              list-style-type: none
              &:hover
                background-color: #eaeaea
</style>
复制代码

以上所述就是小编给大家介绍的《Vue学习笔记(1)——在页面右上角实现可悬浮/隐藏的系统菜单》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Introduction to the Design and Analysis of Algorithms

Introduction to the Design and Analysis of Algorithms

Anany Levitin / Addison Wesley / 2011-10-10 / USD 117.00

Based on a new classification of algorithm design techniques and a clear delineation of analysis methods, Introduction to the Design and Analysis of Algorithms presents the subject in a coherent a......一起来看看 《Introduction to the Design and Analysis of Algorithms》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试