| ✍️ Tangxt | ⏳ 2020-07-17 | 🏷️ directives、mixin、extends、provide/inject |
<div id="app">
<input v-focus>
<button v-t-click>btn</button>
</div>
// Register a global custom directive called `v-focus`
Vue.directive('focus', {
// When the bound element is inserted into the DOM...
inserted: function(el) {
// Focus the element
console.dir(el)
el.focus()
}
})
Vue.directive('t-click', {
inserted: function(el) {
console.dir(el)
el.onclick = () => {
alert('t-click')
}
}
})
let vm = new Vue({
el: '#app'
})
效果:

我不关注它的写法,只关注它的效果,以及它给我的感受!
<div id="hook-arguments-example" v-demo:foo.a.b="message"></div>
Vue.directive('demo', {
bind: function(el, binding, vnode) {
var s = JSON.stringify
el.innerHTML =
'name: ' + s(binding.name) + '<br>' +
'value: ' + s(binding.value) + '<br>' +
'expression: ' + s(binding.expression) + '<br>' +
'argument: ' + s(binding.arg) + '<br>' +
'modifiers: ' + s(binding.modifiers) + '<br>' +
'vnode keys: ' + Object.keys(vnode).join(', ')
}
})
let vm = new Vue({
el: '#hook-arguments-example',
data: {
message: 'hello!'
}
})
效果:

我们能拿到的数据:

Q:关于 `JSON.stringify` ?

Q: `bind` 是什么?Demo1 里边的 `inserted` 又是什么?还有我看到 `model` 有 `componentUpdated` , `show` 有 `updata` 和 `ubind` ?
它们都是 Hook Functions(钩子函数)
这些函数会在特定的时机被调用!
如:
bind :只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置inserted :被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)Q:传给那些钩子函数有哪些参数呢?
elbinding -> 一个指令如 v-my-directive.foo.bar="1 + 1" ,这里边的数据都能通过 binding.xxx 拿到!vnodeoldVnode为什么需要动态参数
<p v-pin="200">Stick me 200px from the top of the page</p>
Vue.directive('pin', {
bind: function(el, binding, vnode) {
el.style.position = 'fixed'
el.style.top = binding.value + 'px'
}
})
把这个绑定了 v-pin 指令的 p 元素固定在距离页面顶部 200 像素的位置
话说,如果我们想要距离页面页面右边的呢?
难道我又得搞个 v-right 的指令出来? -> 显然,不需要,可以使用动态参数呀!
做法:
<p v-pin:[direction]="200">I am pinned onto the page at 200px to the left.</p>
Vue.directive('pin', {
bind: function(el, binding, vnode) {
el.style.position = 'fixed'
var s = (binding.arg == 'left' ? 'left' : 'top')
el.style[s] = binding.value + 'px'
}
})
new Vue({
el: '#dynamicexample',
data: function() {
return {
direction: 'left'
}
}
})
效果:


这是函数简写,为啥叫「函数简写」,不叫「函数参数」呢?
因为如果你在 bind 和 update 时都触发相同行为,而不关心其它的钩子,那么你就可以用所谓的「函数简写」啦!而不是定义一个 {} ,然后整上两个钩子!

Tips:
示例:
// 自定义v-on2指令
new Vue({
el: "#app",
directives: {
on2: {
// binding 是一个对象,包含指令的很多信息
inserted(el, binding) {
console.log('hi')
el.addEventListener(binding.arg, binding.value)
},
unbind(el, binding) {
el.removeEventListener(binding.arg, binding.value)
}
}
},
template: `
<button v-on2:click="hi">点我</button>
`,
methods: {
hi() {
console.log("m");
}
}
})
注意:如果你这样写
<button v-on2:click="hi()">点我</button>,不用点击就会,先log m,再log 'hi'-> 原生的@click指令,显然做了hi()这种值的处理!即不会执行hi方法,只有点击了才会执行!
el 指令所绑定的元素,可以用来直接操作DOMbinding :一个对象,包含指令的很多信息vnode :Vue编译生成的虚拟节点Vue.directive('x',directiveOptions) -> 可以在任何组件里用 v-xnew Vue({...,directives: {"x": directiveOptions}}) -> v-x 只能用在该实例中directiveOptions -> 有5个函数属性
bind(el,info,vnode,oldVnode)-类似 createdinserted(参数同上)-类似mountedupdate(参数同上)-类似updatedcomponentUpdated(参数同上)-用得不多unbind(参数同上)-类似destroyed在开发一个充满组件的应用程序时,通常会遇到两个都有着非常相似功能的组件。
那么,问题来了:
幸运的是,Vue 给了我们第三个选择:Mixins
Mixins 是封装一小部分功能的一个有效方法,而这些功能可以在多个组件之间重用。
我们要学的:
xx 组件使用 mixin 对象时,所有 mixin 的选项都将被「混合」进入该 xx 组件的本身的选项中去! -> 水(组件)混入了白糖(mixin 对象)data 旗下的也会通过递归姿势进行合并methods 、 components 、 directives ,将被合并为同一个对象。两个对象键名冲突时,优先选组件对象的键值对。Vue.extend() 也使用同样的策略进行合并示例:
var mixin = {
data: function() {
return {
message: 'hello',
mix: 'abc'
}
},
created: function() {
console.log('混入对象的钩子被调用')
},
methods: {
foo: function() {
console.log('foo')
},
conflicting: function() {
console.log('from mixin')
}
}
}
var vm = new Vue({
el: '#app',
mixins: [mixin],
data: function() {
return {
message: 'goodbye',
com: 'def'
}
},
created: function() {
console.log('组件钩子被调用')
},
methods: {
bar: function() {
console.log('bar')
},
conflicting: function() {
console.log('from self')
}
}
})
效果:

再举一个示例:
<!DOCTYPE html>
<html lang="zh-Hans">
<head>
<meta charset="utf-8">
<title>index7</title>
<style>
.red {
width: 100px;
height: 100px;
border: 3px solid lightcoral;
}
.green {
width: 100px;
height: 100px;
border: 3px solid lightgreen;
}
</style>
</head>
<body>
<div id="app">
<div>
<button @click="Child1isShow=false">child1消失</button>
<child1 v-if="Child1isShow"></child1>
</div>
<br />
<div>
<button @click="Child2isShow=false">child2消失</button>
<child2 v-if="Child2isShow"></child2>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const log = {
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() - this.time;
console.log(`${this.name}死亡了,共存活了 ${now} ms`);
}
}
Vue.component('child1', {
data() {
return {
name: 'child1'
}
},
template: `
<div class="red">Child1消失</div>
`,
mixins: [log]
})
Vue.component('child2', {
data() {
return {
name: 'child2'
}
},
template: `
<div class="green">Child2消失</div>
`,
mixins: [log]
})
new Vue({
el: '#app',
data() {
return {
Child1isShow: true,
Child2isShow: true,
};
},
})
</script>
</body>
</html>
我把
mixin写在同一个文件了,一般来说是,创建一个mixins目录,然后在该目录下创建一个log.js->mixins/log.js
效果:

示例:

虽然,全局 mixins 可用于自定义选项,然后最一些万金油的事儿,但最好还是使用插件来搞:插件 — Vue.js
插件通常用来为 Vue 添加全局功能
data 旗下的属性 -> 递归之,组件取而代之directives的作用是减少DOM操作的重复mixins的作用是减少data、methods、钩子的重复这个东西的存在,有着与mixins同样的需求
话说:
mixins,咋办呢?你可以使用 Vue.extend 或 options.extends
做法,同mixins类似,通过暴露一个extends对象到组件中使用
示例:
// MyVue是一个组件实例哈!
const MyVue = Vue.extend({
// or mixins: [log]
data() {
return {
name: '',
time: undefined
}
},
created() {
if (!this.name) { console.error('no name!') }
this.time = new Date();
console.log(`${this.name}出生了`)
},
beforeDestroy() {
const duration = (new Date()) - this.time
console.log(`${this.name}存活时间${duration}ms`)
}
})
Vue.component('child1', {
data() {
return {
name: 'child1'
}
},
template: `
<div class="red">Child1消失</div>
`,
extends: MyVue
})
Vue.component('child2', {
data() {
return {
name: 'child2'
}
},
template: `
<div class="green">Child2消失</div>
`,
extends: MyVue
})
new Vue({
el: '#app',
data() {
return {
Child1isShow: true,
Child2isShow: true,
};
},
})
效果是一样的……
讲真,我完全get不到extends的点!
const Component = Vue.extend({……}) -> new Component(options) or Vue.component('child1', {……,extends:Component}extends是比mixins更抽象一点的封装mixins 麻烦,可以考虑extends一次! -> 不过实际工作中用得很少众所周知,在组件式开发中,最大的痛点就在于组件之间的通信。在 Vue 中,Vue 提供了各种各样的组件通信方式,从基础的 props/$emit 到用于兄弟组件通信的 EventBus,再到用于全局数据管理的 Vuex。
在这么多的组件通信方式中,provide/inject 显得十分阿卡林(毫无存在感)。但是,其实 provide/inject 也有它们的用武之地
provide和inject主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。
provide 可以在祖先组件中指定我们想要提供给后代组件的数据或方法,而在任何后代组件中,我们都可以使用 inject 来接收 provide 提供的数据或方法。

话说,如何去掉那个警告呢? -> 用引用类型值,该对象的属性就好了!

根组件成为了全局状态的容器 -> 很像 React 中的 context
换肤
<!DOCTYPE html>
<html lang="zh-Hans">
<head>
<meta charset="utf-8">
<title>index9</title>
<style>
.app.theme-lightgreen button {
background: lightgreen;
color: white;
}
.app.theme-lightgreen {
color: darkcyan;
}
.app.theme-lightcoral button {
background: lightcoral;
color: white;
}
.app.theme-lightcoral {
color: darksalmon;
}
.app.fontSize-normal {
font-size: 16px;
}
.app button {
font-size: inherit;
}
.app.fontSize-small {
font-size: 12px;
}
.app.fontSize-big {
font-size: 20px;
}
</style>
</head>
<body>
<div id="xxx" :class="`app theme-${themeName} fontSize-${fontSizeName}`">
<change-button>换肤</change-button>
<br>
<button>🟡</button>
<button>🟡</button>
<button>🟡</button>
<button>🟡</button>
<button>🟡</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('change-button', {
data() {
return {}
},
template: `
<div>
<button @click="changeTheme">换肤</button>
<button @click="changeFontSize('big')">大字</button>
<button @click="changeFontSize('small')">小字</button>
<button @click="changeFontSize('normal')">正常字</button>
</div>
`,
inject: ["themeName", "changeTheme", "changeFontSize"]
})
new Vue({
el: '#xxx',
provide() {
return {
themeName: this.themeName,
fontSizeName: this.fontSizeName,
changeTheme: this.changeTheme,
changeFontSize: this.changeFontSize
}
},
data() {
return {
themeName: 'lightcoral', // 'lightgreen'
fontSizeName: 'normal' // 'big' or 'small'
}
},
methods: {
changeTheme() {
if (this.themeName === 'lightcoral') {
this.themeName = 'lightgreen'
} else {
this.themeName = 'lightcoral'
}
},
changeFontSize(size) {
if (["normal", "big", "small"].indexOf(size) === -1) {
throw new Error(`no size: ${size}`)
}
this.fontSizeName = size
}
}
})
</script>
</body>
</html>
效果:

provide/inject -> Vue 官方推荐使用 Vuex,而不是原生的 APIprovide/inject 最大的区别在于:
sync 这种破坏单向数据流的家伙),而 provide/inject 明显破坏了单向数据流原则。试想,如果有多个后代组件同时依赖于一个祖先组件提供的状态,那么只要有一个组件修改了该状态,那么所有组件都会受到影响。这一方面增加了耦合度,另一方面,使得数据变化不可控。如果在多人协作开发中,这将成为一个噩梦。provide/inject 做全局状态管理的原则:
➹:Mixins - Next-Level Vue - Vue Mastery
➹:聊聊 Vue 中 provide/inject 的应用 - 掘金
➹:43.Vue全解进阶属性(Directive、Mixin、Extends、Provide、Inject) - 掘金
➹:vue2组件之间双向数据绑定问题 - 前端笔记 - SegmentFault 思否