【Vue】进阶构造属性

Vue进阶属性

directives //(指令)对普通 DOM 元素进行底层操作,就会用到自定义指令
mixins // (混入)是一种分发 Vue 组件中可复用功能的非常灵活的方式。
extends //(继承)都是扩展vue组件时使用,和mixins类似,只不过使用方法不同
provide //(提供)可以把data数据提供给所有人与inject 相关使用
inject //(注入)把可以用的东西放在自己身上与provide相关

directive指令--减少DOM操作的重复

指令的作用:主要用于DOM操作

  1. Vue实例 / 组件用于数据绑定、事件监听、DOM更新
  2. Vue指令主要目的就是原生DOM操作
  3. 减少重复
  • 如果某个DOM操作你经常使用,就可以封装为指令。比如事件绑定经常做,那就写成v-on指令。
  • 如果某个DOM操作比较复杂,也可以封装为指令

1. Vue自带的指令:v-if ,v-for ,v-html等等
2. 自己造一个指令(官方文档

两种声明方式

方法1:声明一个全局指令

Vue.directive('x', directiveOptions)
//就可以在任何组件里用v-x了 

在main.js里写

//完整语法
Vue.directives("x",directiveOptions)
//举例
//在main.js里写
Vue.directive('x',{   
  inserted: function (el) {   //这里的el是一个全局元素,只要把v-x放在某个元素上,这个el就是那个元素
    el.addEventListener('click', ()=>{console.log('x')})
  }
})
//在全局任何一个dom标签都可以使用v-x调用

方法2:声明一个局部指令
在options里写,只能被那个Vue实例/组件使用

new Vue({
    ...,
    directives:{
        "x":directiveOptions
    }
})
image

或者在script里面的export default里写

关于directiveOptions

directiveOptions是个对象,里面有五个函数属性,这五个函数是钩子函数,它们规定了指令什么时候生效

  • 函数属性
  1. bind(el, info, vnode, oldVnode)
    类似created,只调用一次,指令第一次绑定到元素时调用。
  2. inserted(参数同上)
    类似mounted,被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
  3. update(参数同上) //少用
    类似 updated
  4. componentUpdated(参数同上) //少用
    用得不多,见文档
  5. unbind(参数同上)
    类似destroyed,当元素要消亡时调用。
  • 属性参数:
    el:绑定指令的那个元素
    info:是个对象,用户调用指令时,与指令相关的数据,比如监听什么事件,监听到事件执行什么函数
    vnode:虚拟节点
    oldVnode:之前的虚拟节点
举例:写一个v-on的简单的v-on2

一开始我们还是使用Vue自带的v-on: ,通过点击button是可以打印出hi的

import Vue from "vue/dist/vue.js"; // 故意使用完整版
Vue.config.productionTip = false;

new Vue({
  template: `
    <button v-on:click="hi">点我</button>
  `,
  methods: {
    hi() {
      console.log("hi");
    }
  }
}).$mount("#app");

但是现在不能用v-on来实现这个点击事件;而是使用我们新建的一个局部指令v-on2,

new Vue({
  directives: {
    on2: {
      //当元素出现在页面时,会调用bind函数,我把bind函数写成添加事件监听
      //bind和inserted都行
      bind(el, info) {
        //console.log(info); //打印出info,看看我们需要他的哪些信息
        el.addEventListener(info.arg, info.value);
      },
      //添加了事件监听,那就想办法在一定的时机删掉,不然越累积越多
      //当元素要消亡时,会调用unbind函数,我把unbind函数写成删除事件监听
      unbind(el, info) {
        el.removeEventListener(info.arg, info.value);
      }
    }
  },
  template: `
    <button v-on2:click="f1">点我</button>  //button使用了我们写的指令
  `,
  methods: {
    f1() {
      console.log("Hi");
    }
  }
}).$mount("#app");

至于info里面的arg,value是怎么来的呢,是通过console.log打印来的,就可以知道如果我们v-on2后面接的什么事件


image
  • arg是对应用户传来的方法
  • value是把用户传的方法还是什么的东西,给你算好,我们拿他的value就可以了

如果不用v-on2,我们还有方法

1646924255(1).jpg

点击h1 h3 打印出x

Mixins混入,就是复制---减少options构造选项的重复

一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
就是把共同的options构造选项复制到需要用的Vue实例/组件里

作用

减少重复

  • directives的作用是减少DOM操作的重复
  • mixins的作用是减少data、methods、钩子的重复,等构造选项的重复
  • options里的构造选项都可以先放到一个js文件,之后哪个实例/组件需要就导入并且用mixins使用就行。

智能合并
写在了共同东西里的东西被组件引用了之后,组件还可以覆盖他们,Vue会智能合并,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
发送冲突组件优先

全局的mixins:不推荐

在main.js里写

Vue.mixins({公用的的options选项})复制代码

这样所有的组件都会用这个,所以不推荐。

例子

场景描述

非完整版Vue,App.vue引用了五个组件
假设我们需要在每个组件上添加name和time
在这五个组件createddestroyed时,打出提示,并报出存活时间
请问你怎么做? 给每个组件添加data和钩子,共五次 或者使用mixins减少重复

先写组件Child1

child1组件出生就打印出“child1出生了”
那就需要个data里面name:child1
created函数
还需要个时间。那就data里面time为出生时间
在出生时记录时间(new Date),给time赋值
child1组件死亡了就打印出“child1死亡了,共生存了多少ms”
beforeDestroy
获得当前时间
那么存活时间就是当前时间-出生时间

//Child.vue
<template>
<div>Child1</div>
</template>

<script>
export default {
  data(){
    return {
      name:"Child1",
      time:undefined  //time即将用来表示出生时间
      }
  },
  //当这个组件出生了就执行created函数。
  created(){
    //把出生时间记录下来
    this.time=new Date()
    console.log(`${this.name}出生了`)
  },
  //当组件死掉之前,执行这个函数。注意不是死掉之后,死掉之后数据都没了!
  beforeDestroy(){
    //把死亡时间记录下来
    const now = new Date()
    console.log(`${this.name}死亡了,共生存了${now-this.time}ms`)
  }
}
</script>

Child1组件出生是当然的,所以created函数自动执行了。那Child1组件怎么消亡呢?
Child1组件是被App.vue所使用的。所以用App.vue来控制这个组件消亡
消亡就是把这个组件不被App.vue所用,就是把组件从DOM树里弄消失
dataChild1Visible:true(默认不消亡)
使用这个组件的时候判断一下,如果Child1Visiblefalse就不出现在DOM树里,就是消亡了。所以点击按钮的时候让Child1Visible变成false就可以控制消亡了。

Child1组件写好了,那剩下的四个组件呢?
关于每个(子)组件的共同操作,可以用mixins

新建src/mixins/log.js,把公共的东西(Child1.vue的options)剪切到log.js里面,在导出。
但是注意,以前的name:Child1被写死了,可是其他的组件不能用这个名字啊,所以把name:undefined;之后每个组件在自己里面写name:Childx,就会智能覆盖undefined,父级data会覆盖mixins里的data

const log = {
data(){
    return {
      name:undefined,  //这里的name可以写undefined,在下面判断用户要传入一个name
      time:undefined  //time即将用来表示出生时间
      }
  },
  //当这个组件出生了就执行created函数。
  created(){
    //把出生时间记录下来
    this.time=new Date()
    console.log(`${this.name}出生了`)
  },
  //当组件死掉之前,执行这个函数。注意不是死掉之后,死掉之后数据都没了!
  beforeDestroy(){
    //把死亡时间记录下来
    const now = new Date()
    console.log(`${this.name}死亡了,共生存了${now-this.time}ms`)
  }
}
export default log

每个组件如何使用(就是复制就是复制)?先引入,再放到mixins里(就是复制就是复制)。别忘了写name:Childx

//Child1.vue
<template>
<div>Child1</div>
</template>

<script>
import log from "../mixins/log.js";  //引入
export default {
  data(){
    return {
      name:"Child1"                 //智能覆盖log中的name
    }
  },
   mixins:[log]  //把公共的复制到我身体里了
}
</script>

关于(父组件)App.vue的操作
在App.vue里还是得把每个子组件的操作再做一遍的

<template>
  <div id="app">
    <Child1 v-if="Child1Visible"/>
    <button @click="Child1Visible=false">x</button>
    <Child2 v-if="Child2Visible"/>
    <button @click="Child2Visible=false">x</button>
  </div>
</template>

<script>
import Child1 from "./components/Child1.vue";
import Child2 from "./components/Child2.vue";
export default {
  name: "App",
  data() {
    return {
      Child1Visible: true,
      Child2Visible: true,
    };
  },
  components: {
    Child1,
    Child2,
  }
};
</script>
extends 继承 -也是复制(形式不一样)

extends是比mixins更抽象一点的封装
如果你的mixins里面有十个那就很麻烦,可以考虑extends一次
不过实际工作中用得很少
你可以使用Vue.extend或options.extends得到一个新的类,我们可以new MyVue

const MyVue = Vue.extend({
data(){ 
    return {name: “' ,time:undefined} 
},
created(){
if( !this . name){
    console. error('no name!')
}
this. time = new Date( )
},
beforeDestroy(){
const duration = (new Date()) - this. time 
console . log( ${ this . name}存活时间$ { duration}^ )
})
export default MyVue

image

然后我们就可以使用new MyVue(options)

Vue.extend()使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。

extends,在没有调用``Vue.extend时候继承和mixin一样

provide | inject提供和注入

祖先提供东西,后代注入东西
作用是大范围、隔N代共享信息(data、methods等)
例子:一键换肤(代码)

点击换肤按钮会切换class:${themeName}(blue|red)来切换css从而改变颜色
每个子组件都要有换肤按钮
那就把换肤按钮也写成一个组件,其他组件导入使用就行了
可是themeNameApp.vuedata,换肤按钮组件怎么拿到祖先的东西?
那就让(祖先)App.vue提供themeName
1,我们先把换肤的按钮弄出来以组件的形式,然后导入每一个Child的子组件

//这个文件叫ChangeThemeButton.vue
<template>
  <div>
    <button>换肤</button>
  </div>
</template>

2,先导入子组件,以Child1为例

<template>
  <div>Child 1
    <change-theme-button/>
    <!-- 看清楚组件的大小写,这里变了 -->
    <!-- 意思是vue支持用'-'来替换字母大写 -->
  </div>
</template>

<script>
import ChangeThemeButton from "./ChangeThemeButton.vue";
export default {
  components: {
    ChangeThemeButton
  }
};
</script>

3,我们(ChangeThemeButton.vue)怎么获得祖先(App.vue)的themeName呢?

在提供数据(祖先App.vue)的地方写入provide
在需要数据(ChangeThemeButton.vue)的地方注入inject我们需要的数据
App.js文件


image

ChangeThemeButton.vue文件

image

(子孙代)换肤按钮组件把themeName注入自己就行

但是祖先传过来的themeName到我们这只是我们复制的一个字符串。我们改了我们的字符串themeName并不会改祖先的themeName。
所以祖先得写一个可以修改祖先自己的themeName的函数提供给换肤按钮组件,这样换肤按钮组件才可以真的改themeName

所以提供一个methods方法,来改变主题,

image
image

下面是字体大小变化(直接给出代码)

App.vue

<template>
  <div :class="`app theme-${themeName} fontsize-${fontSizeName}`">
    <!-- 上面这一句是js语句:class="`app theme-${themeName}`" -->
    <!-- 两个双引号是class的值,用XML语法括起来,不是JS引号,不能删-->
    <!-- 第一个class是app,第二个class是theme-${themeName} -->
  </div>
</template>

<script>
import Child1 from "./components/Child1.vue";
import Child2 from "./components/Child2.vue";
export default {
  name: "App",
  provide() {
    return {
      themeName: this.themeName,
      changeTheme: this.changeTheme,
      changeFontSize: this.changeFontSize,
    };
  },
  data() {
    return {
      themeName: "blue", // 'red'
      fontSize: "normal", // 'big' | 'small'
    };
  },
  methods: {
    changeTheme() {
      if (this.themeName === "blue") {
        this.themeName = "red";
      } else {
        this.themeName = "blue";
      }
    },
    changeFontSize(name) {
      if (["normal", "big", "small"].indexOf(name) >= 0) {
        this.fontSizeName = name;
      }
    },
  },
  components: {
    Child1,
    Child2,
  },
};
</script>

<style>
.app.theme-blue button {
  /* 这里解释一下 .app.theme-blue和.app .theme-blue */
  /* 上面是有空格的,有空格和每空格的区别 */
  /* 没加空格意思是.app和.theme-blue这两个class同时存在 */
  /* 而加了空格就是.app里面的.theme-blue */
  background: blue;
  color: white;
}
.app.theme-blue {
  color: darkblue;
}
.app.theme-red button {
  background: red;
  color: white;
}
.app.theme-red {
  color: darkred;
}
.app button {
  font-size: inherit;
}
.app.fontSize-normal {
  font-size: 16px;
}
.app.fontSize-big {
  font-size: 20px;
}
.app.fontSize-small {
  font-size: 12px;
}
</style>

ChangeThemeButton.vue文件

<template>
  <div>
    <button @click="z">换肤</button>
    <button @click="changeFontSize('small')">小字</button>  //传入一个值给父组件的函数调用
    <button @click="changeFontSize('big')">大字</button>
    <button @click="changeFontSize('normal')">正常字</button>
  </div>
</template>
<script>
export default {
  inject: ["themeName", "changeTheme", "changeFontSize"],
  methods: {
    z() {
      this.changeTheme();
    },
  },
};
</script>

Child1.vue文件

<template>
  <div>
    Child 1
    <change-theme-button />
  </div>
</template>

<script>
import ChangeThemeButton from "./ChangeThemeButton.vue";
export default {
  components: {
    ChangeThemeButton,
  },
};
</script>

总结
directives指令

  • 全局用Vue.directive('x', {...})
  • 局部用options.directives
  • 作用是减少DOM操作相关重复代码

mixins混入

  • 全局用Vue.mixin({..})
  • 局部用options.mixins: [mixin1, mixin2]
  • 作用是减少options里的重复

extends继承/扩展

  • 全局用Vue.extend({.})
  • 局部用options.extends: {...}
  • 作用跟mixins差不多,只是形式不同

provide| inject提供和注入

  • 祖先提供东西,后代注入东西 *作用是大范围、隔N代共享信息
  • 父代的,而emit要修改子代的,因此emit是在父代里修改子代然后传回到子代(子代先给父代信息),provide | inject是在子代修改父代(父代给子代信息)
  • 注意普通传值传的就是值,如果原来的data里相应属性是个对象,那么传的就是这个对象本身,即你在子代修改父代是会真的改变的provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。传入父代的method其实也是传的函数本身

this问题

只有data和method里面属性this才有意义,因为他们将挂靠在vm实例上,每个组件的实例不同,当provide传递函数时,子代接受函数挂靠在子代实例上

inject: ["themeName", "changeTheme", "changeFontSize"],  
methods:{    
z(){  
 this.changeTheme() 
}  
}
};
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354

推荐阅读更多精彩内容