Vue进阶构造属性

Vue构造属性: directives、mixins、extends、provide、inject

1.Directives 指令

2种写法
1' 声明一个全局指令

Vue.directive('x',directiveOptions)

[图片上传失败...(image-85a3-1649369122915)]

例子:造出v-x,点击即打印x。
声明指令x,当元素被插入到页面中之后,我们就监听它的click事件。

1.先声明
main.js
Vue.directive('x',{ //单个directive
  inserted:function(el){
    el.addEventListener('click',()=>{console.log('x')})
  },
})

2.再使用x指令
进入App.vue组件
<template>
  <img v-x width="25%" src="./assets/logo.png">
  <HelloWorld msg="Hello Vue"/> 
//让HelloWorld也响应:进入HelloWorld.vue组件添加`<h1 v-x>`,再次点击<h1>控制台就会输出x。
</template>

你可以在任何组件里用v-x了。
inserted你把el放哪个元素上,el就是哪个元素,比如放<img>上,el就是<img>

2' 声明一个局部指令
v-x只能在该实例中用

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

例子: 只能在HelloWord.vue组件里用 x

HelloWord.vue
<template>
  <div class="hello">
    <h1 v-x>{{ msg }}</h1> //使用
  </div>
</template>
<script>
export default {
  directives: { //可声明多个directives
    'x': {
       inserted(el) {
         el.addEventListener("click", () => {console.log("x")});
      },
    },
  },
</script>

directiveOptions的其它属性

常用的钩子函数:bind、inserted、unbind

文档: 钩子函数钩子函数参数
[图片上传失败...(image-59a3ff-1649369122915)]

函数接收的参数,都是Vue传给我们的:el、info、vnode、oldVnode
1.el 就是你把v-x绑到哪个元素上去了
2.info 就是除了传元素外,你还传了哪些东西。info包含了很多详细的消息,基本上你要的东西都在第2个参数里。

原文是binding对象:
[图片上传失败...(image-47e806-1649369122915)]

3.vnode 元素对应的虚拟节点
4.oldVnode 之前的虚拟节点,有可能这个元素是从旧到新

例子: 模仿v-on,自制v-on2指令

new Vue({
  directives: {
    on2: {
      inserted(el, info) {
        //console.log(el); 打印出<button>点我</button>
        //console.log(info); 打印出一个对象
        console.log(info.arg);
        el.addEventListener(info.arg, info.value);
      },
      unbind(el, info) { //unbind用于清理垃圾,删除监听
        el.removeEventListener(info.arg, info.value);
      }
    }
  },
  template: `
    <button v-on2:click="hi">点我</button>
  `,
  methods: {
    hi() {
      console.log("hi");
    }
  }
}).$mount("#app");

addEventListener时无法确定是否是click,因为还有可能是其它事件,比如keyup。此时可以利用第2个参数拿到用户传的事件。用info得到用户传的事件

info拿到的是个对象
[图片上传失败...(image-10a298-1649369122915)]

当元素被插入到页面中时,我们就监听这个元素的事件和对应的方法。

函数简写不推荐

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

解析
1.主要用于DOM操作
平时在用Vue时从来不做DOM操作,只是改数据,数据自动更新UI。但是我们在实际开发中有时不得不写DOM操作,这时可以把DOM操作封装成一个指令,然后只传个函数给这个指令这样就可以永远不去接触DOM操作了。

new Vue({})一般会传data、methods、钩子函数主要用途就是数据绑定、事件监听、DOM更新。DOM更新不是通过DOM API更新,而是直接通过监听器去更新。如果你要进行DOM操作,就只能用Vue指令,Vue组件和Vue指令各司其职。

mixins 混入

mixins目的也是为了减少重复,就是把一些属性复制到你的组件上。
类比
1.directives的作用是减少DOM操作的重复
2.mixins的作用是减少data、methods、钩子的重复,也就是减少构造函数的重复。

示例
使用mixins减少重复

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>
    <Child3 v-if="child3Visible"/>
    <button @click="child3Visible = false">x</button>
    <Child4 v-if="child4Visible"/>
    <button @click="child4Visible = false">x</button>
    <Child5 v-if="child5Visible"/>
    <button @click="child5Visible = false">x</button>
  </div>
</template>
<script>
import Child1 from "./components/Child1.vue";
import Child2 from "./components/Child2.vue";
import Child3 from "./components/Child3.vue";
import Child4 from "./components/Child4.vue";
import Child5 from "./components/Child5.vue";
export default {
  name: "App",
  data() {
    return {
      child1Visible: true,
      child2Visible: true,
      child3Visible: true,
      child4Visible: true,
      child5Visible: true
    };
  },
  components: {
    Child1,
    Child2,
    Child3,
    Child4,
    Child5
  }
};
</script>

Child1.vue
<template>
  <div>Child1</div>
</template>
<script>
import log from "../mixins/log.js";
export default {
  data() {
    return {
      name: "Child1", //name自己传
    };
  },
  created() {
    console.log("Child 1 的 created");
  },
  mixins: [log],
};
</script>

Child2.vue
<template>
  <div>Child2</div>
</template>
<script>
import log from "../mixins/log.js";
export default {
  data() {
    return {
      name: "Child2" //name自己传
    };
  },
  mixins: [log]
};
</script>
Child3.vue、Child4.vue、Child5.vue代码略...

log.js
const log = { //log必须是对象
  data() { //2个data会智能合并
    return {
      name: undefined,
      time: undefined
    };
  },
  created() {
    if (!this.name) { 
      throw new Error("need name");
    }
    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的实现思路

五个组件都有构造函数data、created、beforeDestroy,那我们就把公共的抽到一个地方。

步骤
1.新建目录mixins、新建文件log.js,然后将共有的剪切到log.js里。
log.js代码参照上面
2.使用log
Child1.vue代码参考上面

<script>
import log from "../mixins/log.js";
export default {
  mixins: [log], //把log里的内容复制到我这里
};
</script>

mixins技巧
选项智能合并文档
也可以使用全局Vue.mixin 但不推荐使用

extends 继承、扩展

extends也是构造选项里的一个选项,跟mixins很像,也是复制减少重复但形式不同。extends更抽象高级,但还是推荐用mixins

步骤
1.新建文件MyVue.js,这不是Vue组件。

import Vue from "vue"
const MyVue = Vue.extend({ //继承Vue,MyVue就是Vue的一个扩展
  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}`)
  },
  //mixins:[log] 也可以使用mixins
})
export default MyVue

2.导入+继承

Child1.vue
<template>
  <div>Child1</div>
</template>
<script>
import MyVue from "../MyVue.js";
export default{
  extends:MyVue,
  data(){
    return {
      name:"Child1"
    }
  }
}
</script>

extends是比mixins更抽象一点的封装。如果你嫌写5次mixins麻烦,可以考虑extends一次,不过实际工作中用的很少。

provide(提供) 和 inject(注入)

例子

App.vue
<template>
  <div :class="`app theme-${themeName} fontSize-${fontSizeName}`">
//同时添加3个class
//注意: :class=""双引号是XML的双引号,不是JS的双引号。``这是JS的引号
    <Child1/>
    <button>x</button>
    <Child2/>
    <button>x</button>
    <Child3/>
    <button>x</button>
    <Child4/>
    <button>x</button>
    <Child5/>
    <button>x</button>
  </div>
</template>
<script>
import Child1 from "./components/Child1.vue";
import Child2 from "./components/Child2.vue";
import Child3 from "./components/Child3.vue";
import Child4 from "./components/Child4.vue";
import Child5 from "./components/Child5.vue";
export default {
  name: "App",
  provide() {
    return {
      themeName: this.themeName,
      fontSizeNmae: this.fontSizeName,
      changeTheme: this.changeTheme, 
      changeFontSize: this.changeFontSize
    };
  },
  data() {
    return {
      themeName: "blue", // 'red'
    //blue和red存在themeName里,点击换肤时会切换blue、red
      fontSizeName: "normal" // 'big' | 'small'
    };
  },
  methods: {
    changeTheme() {
      if (this.themeName === "blue") {
        this.themeName = "red";
      } else {
        this.themeName = "blue";
      }
    },
    changeFontSize(size) {
    //name在这3个中时下标才会 >= 0,不在下标是-1
      if (["normal", "big", "small"].indexOf(size) === -1) {
        throw new Error(`wront size: ${size}`);
      }
      this.fontSizeName = size;
    }
  },
  components: {
    Child1,
    Child2,
    Child3,
    Child4,
    Child5
  }
};
</script>
<style>
.app {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
//换肤的实现是用CSS
//如果一个元素能同时满足app和theme-blue,就设置button为蓝色
.app.theme-blue button {
  background: blue;
  color: white;
}
.app.theme-blue {
  color: darkblue;
}
.app.theme-red button {
  background: red;
  color: white;
}
.app.theme-red {
  color: darkred;
}
.app.fontSize-normal {
  font-size: 16px;
}
.app button {
  font-size: inherit;
}
.app.fontSize-small {
  font-size: 12px;
}
.app.fontSize-big {
  font-size: 20px;
}
</style>

流程
1.新建ChangeThemeButton组件:新建ChangeThemeButton.vue

ChangeThemeButton.vue
<template>
  <div>
    <button @click="changeTheme">换肤</button>
    <button @click="changeFontSize('big')">大字</button>
    <button @click="changeFontSize('small')">小字</button>
    <button @click="changeFontSize('normal')">正常字</button>
  </div>
</template>
<script>
export default {

};
  inject: ["themeName", "changeTheme", "changeFontSize"]
};
</script>

Child1.vue
<template>
  <div>Child 1
    <change-theme-button/> //这样也能找到组件,Vue会自己找
  </div>
</template>
<script>
import ChangeThemeButton from "./ChangeThemeButton.vue";
export default {
  components: { ChangeThemeButton }
};
</script>

2.App.vue完善需求实现切肤
代码参考上面App.vue

3.provide + inject
App组件(App.vue)只要切换自己的“data的themeName值”就能换肤,那如何在按钮里(ChangeThemeButton组件)改变App的data?自己只能改变自己的data,怎么才能改变别人的data?用provide实现改变别人的data。

(1)provide:在提供数据的地方写provide,提供给别人改

App.vue
<script>
export default {
  name: "App",
  provide() {
    return {
      themeName: this.themeName, //提供给别人用或改
      fontSizeNmae: this.fontSizeName, //提供给别人用或改
      changeTheme: this.changeTheme,  //提供给别人用或改
      changeFontSize: this.changeFontSize //提供给别人用或改
    };
  },

(2)inject:把别人提供的东西注入到我的组件来,使用provide

ChangeThemeButton.vue
<template>
  <div>
    <button>当前主题色:{{themeName}},换肤</button>
</template>    
<script>
export default {
  inject: ["themeName"]//注入
};
</script>

注意:provide提供的是字符串的复制品,拿到的是string,不能改,应该把改的函数传过来,拿到函数的引用调用它就行(changeTheme)。

App.vue
provide() {
    return {
      themeName: this.themeName,//提供给别人用或改
      changeTheme: this.changeTheme,//提供给别人用或改
    };
  },
methods: {
    changeTheme() {
      if (this.themeName === "blue") {
        this.themeName = "red";
      } else {
        this.themeName = "blue";
      }
    },
    
ChangeThemeButton.vue
<template>
  <div>
    <button @click="z">当前主题色:{{themeName}},换肤</button>
</template>  
<script>
export default {
  inject: ["themeName", "changeTheme"],
  methods:{
    z(){
      this.changeTheme()
    }
  }
};
</script>    

字符串拿来时已经复制了,改复制品没用,也可以用对象的引用,但不推荐。因为当这样做的人多了之后就没法控制量了,变量被到处改,无法知道变量处于什么状态,容易失控。

知识点
1.:class=""双引号是XML的双引号,不是JS的双引号。
``这是JS的引号

<div :class="`app theme-${themeName} fontSize-${fontSizeName}`">
解析:第1个class是app。第2个class是theme-${themeName},bule和red存在themeName里。
    第3个class是fontSize-${fontSizeName}

2.CSS选择器

<div :class="`app theme-${themeName}`">同时设置多个class
.app.theme-blue{ background:blue }
//如果一个元素同时满足这2个选择器,一个class为app,一个class为theme-blue,那么就设置为blue

.app.theme-blue button{ background:blue } 
//如果一个元素能同时满足app和theme-blue,那么它里面的button就是蓝色

.app .theme-blue 
//如果app里面有theme-blue
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容