Vue开发技巧总结(一)props、$attrs、单向数据流、事件修饰符、样式穿透、watch高阶使用、事件传参$event、事件侦听器、监听组件声明周期

自定义属性props

prop 定义了这个组件有哪些可配置的属性,组件的核心功能也都是它来确定的。写通用组件时,props 最好用对象的写法,这样可以针对每个属性设置类型、默认值或自定义校验属性的值,这点在组件开发中很重要,然而很多人却忽视,直接使用 props 的数组用法,这样的组件往往是不严谨的。

props: {
  type: {
    type: String,
    validator: (value) => {
      return ['success', 'warning', 'danger'].includes(value)
    },
    default: () => 'success'
  }
}

inheritAttrs与$attrs

inheritAttrs

这是2.4.0 新增的一个API,默认情况下父作用域的不被认作 props 的特性绑定将会“回退”且作为普通的 HTML 特性应用在子组件的根元素上。可通过设置 inheritAttrs 为 false,这些默认行为将会被去掉。注意:这个选项不影响 class 和 style 绑定。

  1. 父组件

    <Child1 :title="title" :message="message"></Child1>
    
  2. 子组件

    <template>
      <div>
        <div>子组件1</div>
        <Child2 v-bind="$attrs"></Child2>
      </div>
    </template>
    
    <script>
    import Child2 from './child2.vue'
    export default {
      components: {
        Child2
      },
      // inheritAttrs: false,
      props: ['title'],
      created () {
        console.log(this.$attrs)
      }
    }
    </script>
    
  3. 效果
    在这里插入图片描述

  4. 效果分析

    们看到:组件内未被注册的属性将作为普通html元素属性被渲染,如果想让属性能够向下传递,即使prop组件没有被使用,你也需要在组件上注册。这样做会使组件预期功能变得模糊不清,同时也难以维护组件的DRY。在Vue2.4.0,可以在组件定义中添加inheritAttrs:false,组件将不会把未被注册的props呈现为普通的HTML属性。但是在组件里我们可以通过其$attrs可以获取到没有使用的注册属性,如果需要,我们在这也可以往下继续传递。

5.设置了inheritAttrs: false效果
在这里插入图片描述
6. 没有申明的属性,默认挂载在组件的根元素上,可以使用inheritAttrs去除掉

$attrs
  1. 多级组件嵌套:

    例如:有一个页面由父组件,子组件,孙子组件构成

  2. 如果attrs被绑定在子组件上后,我们就可以在孙子组件里获取this.$attrs值。这个{{$attrs}}值是父组件中传递下来的props(除了子组件中props声明的)。

  3. 案例

    父组件:

    <template>
      <div>
        hello world
        <Child1 :title="title" :message="message"></Child1>
      </div>
    </template>
    

    子组件:

    <div>
      <div>子组件1</div>
      <Child2 v-bind="$attrs"></Child2>
    </div>
    

    孙子组件:

    <template>
      <div>模板2 ---- {{$attrs.message}}</div>
    </template>
    
    <script>
    export default {
      created () {
        console.log(this.$attrs)
      }
    }
    </script>
    

单向数据流

vue是单向数据流,子组件中不允许直接更改props传递过来的数据,如果需要更改,通过两种方式间接更改

  1. 操作data
  2. 通过计算属性
<template>
  <div>
    <div>子组件1</div>
    <button @click="handleClick1">通过data改变传递的props值---{{currentType}}</button>
    <button>通过computed改变传递的props值---{{currentMessage}}</button>
  </div>
</template>

<script>
export default {
  props: {
    title: {
      type: String,
      default: () => ''
    },
    message: {
      type: String,
      default: () => ''
    }
  },
  computed: { // 通过计算属性的方式间接处理props传递过来的值
    currentMessage () {
      return this.message + '...'
    }
  },
  data () {
    return {
      currentType: this.type // 通过data间接处理props传递过来的值
    }
  },
  methods: {
    handleClick1 () {
      this.currentType = '修改了标题'
    }
  }
}
</script>

以上两种方法虽可以在子组件间接修改props的值,但如果子组件想修改数据并且同步更新到父组件,却无济于事。在一些情况下,我们可能会需要对一个 prop 进行『双向绑定』,此时就推荐以下这两种方法:

  1. 使用.sync

  2. 将父组件中的数据包装成对象传递给子组件

    父组件:

    <template>
      <div>
        父组件 --- {{title}} --- {{arr}} --- {{oldValue.value}}
        <Child1 :title.sync="title" :arr.sync="arr" :oldValue="oldValue"></Child1>
      </div>
    </template>
    
    <script>
    import Child1 from './child1.vue'
    export default {
      components: {
        Child1
      },
      data () {
        return {
          title: '标题',
          arr: ['book', 'people'],
          oldValue: {
            value: '原始值'
          }
        }
      }
    }
    </script>
    

    子组件:

    <template>
      <div>
        <div>子组件1</div>
        <button @click="$emit('update:title', '标题被更改了')">通过“双向数据绑定”的形式更改props值</button>
        <button @click="arr.push('工程师')">在子组件中改变数组</button>
        <button @click="oldValue.value = '1234'">通过包装对象(数组的)的形式更改props传递的值</button>
      </div>
    </template>
    
    <script>
    export default {
      props: {
        title: {
          type: String,
          default: () => ''
        },
        arr: {
          type: Array,
          default: () => []
        },
        oldValue: {
          type: [Array, Object, String],
          default: () => ''
        }
      },
      data () {
        return {}
      },
      methods: {}
    }
    </script>
    

事件修饰符

经典案例:Vue事件分为普通事件和修饰符事件,给自定义组件原生的 click 事件

错误写法<Child1 @click="handleClick"></Child1> ,这里的 @click 是自定义事件 click,并不是原生事件 click。
正确写法<Child1 @click.native="handleClick"></Child1>

表单修饰符
  1. lazy
    在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 。你可以添加 lazy 修饰符,从而转变为使用 change事件进行同步。适用于输入完所有内容后,光标离开才更新视图的场景。
  2. trim
    如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:
  3. number
    自动将用户的输入值转为数值类型
事件修饰符
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>

<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>

样式穿透

使用 >>> 或 /deep/ 解决

<style scoped>
  .box /deep/ h2 {
    color: red;
  }

  .box >>> h4 {
    color: blue;
  }
</style>

watch高阶使用

立即执行

watch 是在监听属性改变时才会触发,有些时候,我们希望在组件创建后 watch 能够立即执行

可能想到的的方法就是在 create 生命周期中调用一次,但这样的写法不优雅,我们可以使用这样的方法

export default {
  data () {
    return {
      name: 'JOe'
    }
  },
  watch: {
    name: {
      handler: 'printData',
      immediate: true // 立即执行
    }
  },
  methods: {
    printData () {
      console.log(this.name)
    }
  }
}
深度监听

在监听对象时,对象内部的属性被改变时无法触发 watch ,我们可以为其设置深度监听

export default {
  data () {
    return {
      user: {
        name: '李四',
        age: 23
      }
    }
  },
  watch: {
    user: {
      handler: (val) => {
        console.log(val) // 使用deep才能打印出更新后的值,否则监听的值不会变化
      },
      deep: true
    }
  }
}

事件参数$event

<template>
  <div class="box">
    <input type="text" @change="handleChange" />
    <input type="text" @change="handleChange2('hello', $event)">
  </div>
</template>

<script>
export default {
  data () {
    return {}
  },
  methods: {
    handleChange (e) {
      console.log(e.target.value)
    },
    handleChange2 (msg, e) {
      console.log(msg, e.target.value)
    }
  }
}
</script>

监听组件生命周期(2种方式)

  1. 通常我们监听组件生命周期会使用 $emit ,父组件接收事件来进行通知
  2. 用 @hook 即可监听组件生命周期,组件内无需做任何改变

子组件:

export default {
  beforeCreate () {
    this.$emit('listenChildCreate')
  }
}

父组件:

<template>
  <div class="box">
    <button @click="show = !show">点击</button>
    <Child v-if="show" @hook:mounted="listenChildMounted" @hook:beforeDestroy="listenChildDestroy" @listenChildCreate="listenChildCreate"></Child>
  </div>
</template>

<script>
import Child from './Child.vue'
export default {
  components: {
    Child
  },
  data () {
    return {
      show: true
    }
  },
  methods: {
    listenChildMounted () {
      console.log('子组件挂载了')
    },
    listenChildDestroy () {
      console.log('监听到子组件销毁')
    },
    listenChildCreate () {
      console.log('监听到子组件创建')
    }
  }
}
</script>

程序化的事件侦听器

经典案例:在页面挂载时定义计时器,需要在页面销毁时清除定时器。这看起来没什么问题。但仔细一看 this.timer 唯一的作用只是为了能够在 beforeDestroy 内取到计时器序号,除此之外没有任何用处。

export default {
    mounted() {
        this.timer = setInterval(() => {
            console.log(Date.now())
        }, 1000)
    },
    beforeDestroy() {
        clearInterval(this.timer)
    }
}

如果可以的话最好只有生命周期钩子可以访问到它。这并不算严重的问题,但是它可以被视为杂物。

我们可以通过 $on 或 $once 监听页面生命周期销毁来解决这个问题:

export default {
    mounted() {
        this.creatInterval('hello')
        this.creatInterval('world')
    },
    creatInterval(msg) {
        let timer = setInterval(() => {
            console.log(msg)
        }, 1000)
        this.$once('hook:beforeDestroy', function() {
            clearInterval(timer)
        })
    }
}

使用这个方法后,即使我们同时创建多个计时器,也不影响效果。因为它们会在页面销毁后程序化的自主清除。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值