看了就懂的vue进阶(自定义指令,侦听器,过滤器,计算属性,生命周期,插槽,组件使用及互相传值)

自定义指令:

  • bind 和 inserted 的区别

    • bind在inserted之前

    • bind 时父节点el.parentNode为 null

    • inserted 时父节点el.parentNode存在。

    • bind是在vue编译解析dom之前调用,inserted在vue编译解析完dom后调用

  • 以后都优先用inserted,因为inserted里获取到的信息比bind中多一些

  • 在钩子函数内部可以通过第二个参数binding的value属性,获取到指令绑定的参数

  • 指令的修饰符可以用来做一些特定的逻辑判断处理

  • 注意:在钩子函数内部的this不是vm实例,可以通过第三个参数vnode.context获取到vm实例

  • 特别要注意:指令绑定的属性值,是动态属性绑定,会经过Vue的编译解析

  • 如果自定义指令名称有多个单词,在定义时用小驼峰,如:Vue.directive('sayHello', { ... }),在html元素上使用时用短横线,如:<div v-say-hello="xxx"></div>

全局指令和局部指令:

  • 通过Vue.directive定义的指令称为全局指令
  • 通过new Vue({ ..., directives: { ... } })定义的指令称为局部指令
  • 补充说明:一个Vue实例就是一个组件,局部指令只能在本组件内可用,全局指令在所有组件中都可用

计算属性

  • 有什么用?

    • 我们Vue的HTML模板中是可以写一些简单的表达式的,但是当逻辑比较复杂时,如果还直接写在HTML中,就显示不太合适了 比较臃肿 可读性较差,所以,这个时候就要考虑使用计算属性来重构了

    • 如果需要根据某些数据计算出一个新的结果,我们用计算属性就比较合适

  • 使用注意点:

    • 都定义在computed选项中
    • 每个计算属性就是一个函数,函数的返回值就是该计算属性的值
    • 在模板中使用计算属性时,不需要加小括号调用(加了会GG),直接把函数名当做data使用就阔以鸟
    • 计算属性时依赖数据动态计算值的,也就是说,以来的数据变了,计算属性的值就会跟着变
  • 计算属性和方法的区别

    • 计算属性有缓存(基于所依赖的数据,当数据没有发生变化时,多次使用,用的是已缓存的数据,只有当所依赖的数据发生变化后,才会重新计算)
    • 方法木有缓存,多次使用都会调用执行内部的代码

侦听器:

  • 用来监听数据的变化从而响应某种操作,适合处理异步操作

  • 注意:侦听器的名字必须和数据名一致

  • 侦听器和计算属性的区别:

    • 计算属性的值是需要同步的返回,而侦听器是不需要返回值的
    • 计算属性里面不适合做异步操作,而侦听器就适合
    • 使用场景:
      • 如果没有异步操作,又需要根据数据的变化计算出一个结果,就用计算属性
      • 如果有异步操作,或仅需要根据数据的变化响应某种操作,就用侦听器
  • 侦听器和blur事件的区别:侦听器只有当数据真正发生变化时才会触发函数,而blur事件不管数据有没有发生变化都会触发事件处理函数

过滤器:

  • 可以对数据进行过滤(格式化),通过Vue.filter定义全局过滤器,通过new Vue({ fitlers: {} })定义局部过滤器,参考全局/局部指令

  • 语法:Vue.filter('uppercase', function (originVal) { return newVal })

    • 第一个参数是过滤器名称
    • 第二个参数是处理函数
      • 处理函数的第一个参数是被过滤的源数据
      • 处理函数的返回值,就是最终过滤后的值
  • 使用:<div>{{ msg | uppercase }}</div>

  • 带参数的过滤器:

    • 定义:Vue.filter('dateformat', (originVal, pattern) => dateformat(originVal, pattern))
    • 使用:<div>{{ user.regAt | dateformat('yyyy-MM-dd hh:mm:ss') }}</div>

Vue生命周期钩子:

  • 说白了,就是Vue实例从出生到死亡的过程,Vue在一些关键的时间节点都给开发者提供了回调函数
  • 分为四个阶段:
    • 创建(只会触发一次):beforeCreate、created
    • 挂载(只会触发一次):beforeMount、mounted
    • 更新(会触发N次,只要数据有更新就会触发):beforeUpdate、updated
    • 销毁(只会触发一次):beforeDestroy、destroyed

Vue数组类型数据操作:

  • 变异方法Vue直接支持响应式(数据驱动视图)
  • 非变异方法是不支持响应式的,但是可以通过重新赋值达到触发视图更新的目的
  • 补充说明:数组的变异方法就是调用完该方法后会影响原数组的方法,反之,不会影响原数组的方法就是非变异方法

Vue自带的修改数据直接支持响应式方法:

  • Vue.set(data中的对象或数组, 键名或索引, 值)
  • vm.$set(data中的对象或数组, 键名或索引, 值) (推荐使用这种)
  • 补充说明:
    • 如果能拿到实例对象就用vm.$set,否则就用Vue.set
    • vm实例在创建时(created阶段)data中已经存在的对象属性,是支持响应式的,而在后续添加的是不支持响应式的
组件化开发:就是把一类功能的元素组合到一起,将css(样式)/js(功能)/html(结构)包装到一起,供后续复用

Vue组件注册语法:

    // 全局组件
    Vue.component('HelloWorld', { 
      // 这里的箭头函数是直接返回一个对象,如果不加小括号,那么会当做函数体的左右花括号,有语法错误
      data: () => ({ msg: 'xxx' }),
      template: '<div>{{ msg }}</div>',
    })
    
    // HTML中使用时,如下:
    // <hello-world></hello-world>

组件注册注意事项:

  • data必须是函数,并且返回一个对象
    • 因为我们的组件定义出来后,是可能被多次使用的,如果是一个对象,我们知道,对象是属于复杂数据类型,在内存中是一小块内存(栈)指向一大块内存(堆),那么在复用时,多个组件实例之间的data是共享的,也就是说会互相影响,为了避免这个问题,必须使用函数,每次返回一个新的对象,就能保证每个组件实例有自己独立的data数据,互不影响
  • template必须只有一个根元素包裹,我们惯用的做法就是用一个div包裹所有元素
  • template优先用模板字符串(后面我们学了单文件组件之后,就不需要关心了)
  • 组件在命名时使用大驼峰命名法(和我们之后学习的单文件组件配套,如:HelloWorld.vue),但是在html中需要使用短横线的方式,如:
  • 默认情况下,组件内不可以使用父组件中的data数据(父子组件之间的作用域不同)

注册局部组件:

    new Vue({
      el: '#app',
      components: {
        HelloWorld: {
          data: () => ({ }),
          template: ``
        }
      }
    })
  • 注意:局部组件只能在注册它的父组件的HTML模板中使用,其他任何地方都不能用
  • 对于全局组件来说:组件的父子关系,在注册组件时是不能确定的,在写HTML结构时才能确定(看的就是HTML结构),比如:A组件写在B组件的HTML模板中,那么A组件就是B组件的子组件

Vue-devtools:

  • 谷歌浏览器的搞法,步骤稍微有点麻烦(因为谷歌的应用商店仓库地址在国外,需要科学上网,否则网速极差),我这里推荐大家安装火狐浏览器(一线产品,跨平台支持的很好:Windows、Mac、Mobile),安装插件so easy.
  • 注意:只有使用了Vue.js框架的页面,才能看到Vue-devtools选项卡

组件之间的数据交互:

  • 父向子传值:
<div id="app">
        
        <p>heheda</p>
        
      	<!-- 父组件在使用子组件标签时,通过属性绑定的方式传值数据给子组件 -->  
        <menu-item menu-title="传统属性-爸爸的title"></menu-item>
        <!-- 普通HTML中用短横线引用子组件props中的属性(因为HTML忽略大小写) -->
        <menu-item :menu-title="ptitle"></menu-item>
        
      </div>
      
      <script>
        
        // 定义menu-item组件
        Vue.component('MenuItem', {
          // 子组件通过props属性接收父组件传递过来的数据,可以理解成函数的形参
          props: [ 'menuTitle' ],
          data: () => ({ selfTitle: '自己的title' }),
          template: `
      			<div>
      				<!-- 模板语法中用小驼峰引用props中的属性 -->
      				{{ selfTitle }} --- {{ menuTitle }}
      			</div>
      		`,
          methods: {
            onClick() {
              // JS中用小驼峰引用props中的属性
      				console.log(this.menuTitle)
            },
          },
        })
      
        new Vue({
          el: '#app',
          data: {
            ptitle: 'Vue动态属性绑定-爸爸的title',
          },
        })
        
      </script>

补充说明:

  • props在定义时使用小驼峰命名法,和组件的命名保持一致
  • props在使用时:
    • JS中或Vue的模板语法中用小驼峰
    • 普通HTML中用短横线
  • props在定义时,可以指定固定的类型
 Vue.component('User', {
          props: {
            name: String,
            age: {
              type: Number,
              default: 18, // 默认值
              required: true, // 是否必传
            },
            isMarry: Boolean, // 这个布尔值有点特殊,只要你在HTML中加了这个属性,就是true,反之false
          },
        })
        
        // 在HTML中使用时
        // <user :name="'张三'" :age="18" :is-marry="false"></user>
  • 子向父传值:
    • 单向数据流:不能直接在组件中修改父组件传递过来的数据(这是一个开发规范,并不是语法要求,这个规范是我们的老猿惨痛的教训总结出来的),这是为了统一管理我们的数据变更,不然,当有多个子组件并且业务复杂度到达一定程度时,就不会知道到底是那个儿子改了我的数据?我的数据时通过怎样的逻辑修改的?当项目业务功能逐渐复杂时,将难以维护,Vue官方详解
    • 鉴于存在单向数据流的规范,我们子组件向父组件进行数据交互时,就不能直接操作,需要借助事件机制来通知父组件,让父组件自己操作自己的数据:
      • 子组件触发事件:vm. e m i t ( ′ 自 定 义 事 件 名 称 ′ ) , 调 用 v m . emit('自定义事件名称'),调用vm. emit()vm.emit方法可以触发自定义事件
      • 父组件监听事件:<menu-item @自定义事件名称=“事件处理函数()”>,跟我们监听@click="onClick()"是一个意思
    • 范例(带事件参数):
        <!DOCTYPE html>
        <html lang="en">
        
        <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Document</title>
        </head>
        
        <body>
        
          <div id="app">
        
            <user-name-input @name-send="onNameSend($event)"></user-name-input>
            <br>
            {{ msg }}
          </div>
        
        
          <script src="./js/vue.js"></script>
          <script>
        
        
            Vue.component('UserNameInput', {
              data: () => ({ name: '' }),
              methods: {
                send() {
                  // 触发自定义事件,用来通知父组件
                  this.$emit('name-send', this.name)
                  // 如果需要传递多个数据,可以使用数据或对象的形式
                  // this.$emit('name-send', [this.name, 'hehe', 123, 456])
                }
              },
              template: `
                <div>
                  <input type="text" v-model="name">
                  <button type="button" @click="send()">发送</button>
                </div>
              `,
            })
        
            new Vue({
              el: '#app',
              data: {
                msg: '',
              },
              methods: {
                onNameSend(eventData) {
                  console.log('<user-name-input> 子组件 触发了 name-send 事件', eventData);
                  this.msg = `hello, ${eventData} ~`
                }
              }
            })
        
          </script>
        </body>
        
        </html>
- 补充说明:
  - $event在标准的DOM事件中是标准的事件对象,在通过$emit触发的自定义事件中是事件数据
  - $emit('自定义事件类型', 事件数据)只能携带一个事件数据,如果想要传递多个,怎么办?用数据或对象即可
  • 兄弟或(祖孙)组件传值:
    • 需要借助事件中心:const eventHub = new Vue(),说白了,就是一个Vue实例,常用于但不限于兄弟组件之间的数据交互
    • 其实这个事件中心的概念类似于中介/代理人,举个栗子:房屋出租,A想要租房,B想要出租,他们都去找房产中介,A告诉中介:我要租房子,如果有人出租时,请通知我,这个动作就好比在监听事件(eventHub. o n ) , B 告 诉 中 介 : 我 要 出 租 房 子 , 如 果 有 人 想 要 租 房 , 请 通 知 我 , 这 个 动 作 也 是 在 监 听 事 件 ( e v e n t H u b . on),B告诉中介:我要出租房子,如果有人想要租房,请通知我,这个动作也是在监听事件(eventHub. onBeventHub.on),当中介收到B想要出租房子的请求时,就会通知A,告知有房源了,这个动作就好比触发事件(eventHub. e m i t ) , 当 中 介 收 到 A 想 要 租 房 子 的 请 求 时 , 也 会 通 知 B , 告 知 有 人 想 要 租 你 的 房 子 了 , 这 个 动 作 也 是 在 触 发 事 件 ( e v e n t H u b . emit),当中介收到A想要租房子的请求时,也会通知B,告知有人想要租你的房子了,这个动作也是在触发事件(eventHub. emitABeventHub.emit),如果交易达成,房产中介有可能会下架A和B的请求,这个就好比销毁事件(eventHub.$off)
插槽
  • 注意:Vue在最终渲染时,标签只会渲染内部的HTML结构,不会包含tempalte元素
  • 总结一点:无脑用标签,无脑写default默认插槽的名字,无脑指定插槽具体的名字
  • 定义组件时,在模板里面写标签挖坑
      Vue.component('HelloWorld', {
        data: () => ({ 
          user: { firstName: 'andre', lastName: 'mao' } 
        }),
        template: `
      		<div>
      			hello, <slot name="default">world</slot> ~
      			<br>
      			hello, <slot name="username" :user="user">{{ user.firstName }}</slot> ~
      		</div>
      	`
      })
  • 使用组件时,用v-slot指令填坑
      <hello-world>
      	<template v-slot:default>
        	heheda
        </template>
        <template v-slot:username="{ lastName }">
        	{{ lastName }}
        </template>
      </hello-world>
  • 补充说明:v-slot:可以缩写成#,如:#default、#username

=的区别:

  • ===:严格等于,会先比较两边的类型,只有类型相同,才会比较数据
  • ==:普通等于,如果两边的类型不同,会先进行隐式转换,再比较数据
  • = 性能高一丢丢,占用的资源少一丢丢,因为不存在隐式转换,效率会高一些,所以推荐使用严格等于:===
注意:
  • 单向数据流,在局部组件中,可以忽略,因为局部组件只能在注册它的组件中使用,所以不会暴露出去,也是属于父组件的控制范围(个人经验,官网文档无说明)
  • 写代码时,不要一直写到底再来验证,有的人写了100行代码,再回头验证,这时出错了,就一脸懵逼,不知道到底是哪一行出的错,要养成阶段性确认的编码习惯,这样可以大大缩小BUG定位
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值