# [ 想入門,我陪你 ] Re Vue 重頭說起|Day 14:Mixin 與 Directive
###### tags: `Vue`、`Re:Vue 重頭說起`、`Alex 宅幹嘛`
## Mixins
```htmlmixed=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Day 14</title>
</head>
<body>
<div id="app">
<button @click="log">
Log
</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script>
const mixin1 = {
methods: {
log(payload) {
// 只要註解這邊 所有網站的 console.log就自動關掉了
console.log(payload);
}
}
}
new Vue({
el: '#app',
mixins: [mixin1],
methods: {
handleClick() {
this.log('clicked')
}
}
})
</script>
</body>
</html>
```
> mixin 就是為了製作共用的功能(compute/methods/watch)
> log 若要做全域的方法,也可以掛在 prototype
### Basics
用 mixin 處理 vuex 的載入資料
```javascript=
// 宣告 mixin 物件
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
// 把 mixin 載入到某個物件
var Component = Vue.extend({
mixins: [myMixin]
})
// 這 component 就有這樣的功能
var component = new Component() // => "hello from mixin!"
```
### Option Merging (14:27)
會有誰先載入,誰覆蓋誰的議題,還有缺點是很難確定功能是從哪邊來
Case 1. method & 按鈕
```htmlmixed=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Day 14</title>
</head>
<body>
<div id="app">
<button @click="log">
Log
</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script>
const mixin1 = {
methods: {
log(payload) {
console.log('1');
}
}
}
const mixin2 = {
methods: {
log(payload) {
console.log('2');
}
}
}
new Vue({
el: '#app',
mixins: [mixin1, mixin2],
methods: {
handleClick() {
this.log('clicked')
}
}
})
</script>
</body>
</html>
```
Answer: 點擊按鈕 => 2
> 載入時後蓋前,mixin2 蓋過 mixin1,所以當 mixin1 與 mixin2 有相同的 function時,且function 是 **handler**,那就是 **後蓋前**
Case 2. Hook
```htmlmixed=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Day 14</title>
</head>
<body>
<div id="app">
<button @click="log">
Log
</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script>
const mixin1 = {
created() {
console.log('c2');
},
methods: {
log(payload) {
console.log('1');
}
}
}
const mixin2 = {
created() {
console.log('c2');
},
methods: {
log(payload) {
console.log('2');
}
}
}
new Vue({
el: '#app',
mixins: [mixin1, mixin2],
methods: {
handleClick() {
this.log('clicked')
}
}
})
</script>
</body>
</html>
```
Answer: c1 c2 2
> hook 會混用,所以 c1 c2 都印出來,但是按鈕會擇一
Case 3. 元件本身放 method 與 Hook
```htmlmixed=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Day 14</title>
</head>
<body>
<div id="app">
<button @click="log">
Log
</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script>
const mixin1 = {
created() {
console.log('c2');
},
methods: {
log(payload) {
console.log('1');
}
}
}
const mixin2 = {
created() {
console.log('c2');
},
methods: {
log(payload) {
console.log('2');
}
}
}
new Vue({
el: '#app',
mixins: [mixin1, mixin2],
methods: {
handleClick() {
this.log('clicked')
},
log(payload) {
console.log('My Self');
}
},
created() {
console.log('My Created');
},
})
</script>
</body>
</html>
```
Answer: c1 c2 My Created / 點擊按鈕 =>
>Hook functions with the same name are merged into an array so that all of them will be called. Mixin hooks will be called **before** the component’s own hooks.
>解釋:我們自己component會在最後收尾(My Created),也就是權重是最後發生
Case 4.
```htmlmixed=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Day 14</title>
</head>
<body>
<div id="app">
<button @click="log">
Log
</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script>
const mixin1 = {
created() {
console.log('c2');
},
methods: {
bbb(){},
log(payload) {
console.log('1');
}
}
}
const mixin2 = {
created() {
console.log('c2');
},
methods: {
aaa(){},
log(payload) {
console.log('2');
}
}
}
new Vue({
el: '#app',
mixins: [mixin1, mixin2],
methods: {
handleClick() {
this.log('clicked')
},
log(payload) {
console.log('My Self');
}
},
created() {
console.log('My Created');
},
})
</script>
</body>
</html>
```
> `methods`, `components` and `directives`, will be merged into the same object
> 解釋:當 `methods`, `components` and `directives` 的 key 不同,不會發生覆蓋問題時,
>
### Global Mixin
全域註冊 mixin,要小心使用,不然所有模組或第三方Component可能會被影響或有衝突
```javascript=
// 全域註冊 mixin 內涵 myOption 屬性,create 時自動執行
// inject a handler for `myOption` custom option
Vue.mixin({
created: function () {
var myOption = this.$options.myOption
if (myOption) {
console.log(myOption)
}
}
})
new Vue({
myOption: 'hello!'
})
// => "hello!"
```
如果要使用建議用 Custom Option Merge Strategies 的方法,比較安全
### Custom Option Merge Strategies
```javascript=
Vue.config.optionMergeStrategies.created = function (toVal, fromVal) {
// return mergedVal
}
```

```javascript=
Vue.config.optionMergeStrategies.methods = function (toVal, fromVal) {
// return mergedVal
}
```

> 第一次執行沒有 toVal ,所以是 undefined,fromVal 拿到 mixin1 的 methods 的 bbb()與log()
> 第二次執行 toVal 是 bbb()與log(),fromVal 拿到 mixin2 的 methods 的 aaa()與log()
> 第三次執行 toVal 是 aaa()與log(),fromVal 拿到 new Vue 的 methods 的 handleClick()與log()
> 所以 method 的預設是只留後者不合併
```htmlmixed=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Day 14</title>
</head>
<body>
<div id="app">
<button @click="log">
Log
</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script>
// myOption 就是設定策略的 key值
Vue.config.optionMergeStrategies.myOption = function (toVal, fromVal) {
// return mergedVal
}
// 製作原始執行策略
Vue.config.optionMergeStrategies.methods = function (toVal, fromVal) {
if(!toVal) toVal = {};
console.log(toVal, fromVal)
return Object.assign(toVal, fromVal)
}
const mixin1 = {
created() {
console.log('c2');
},
methods: {
bbb(){},
log(payload) {
console.log('1');
}
}
}
const mixin2 = {
created() {
console.log('c2');
},
methods: {
aaa(){},
log(payload) {
console.log('2');
}
}
}
new Vue({
el: '#app',
mixins: [mixin1, mixin2],
methods: {
handleClick() {
this.log('clicked')
},
log(payload) {
console.log('My Self');
}
},
created() {
console.log('My Created');
},
})
</script>
</body>
</html>
```

## Custom Directives(52:20)
目的:合理操做 DOM
例子: input Focus:按一下Button 新增 input ,並希望焦點 Focus 在新的 input DOM
```htmlmixed=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Day 14</title>
</head>
<body>
<div id="app">
{{ count }}
<button @click="clickHandler">Add</button>
<my-input v-for="num in count" :key="num"></my-input>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script>
const MyInput = {
template: `
<p>
<input />
</p>
`
}
new Vue({
el: '#app',
components: { MyInput },
data() {
return {
count: 0,
}
}
methods: {
clickHandler() {
this.count++;
},
log(payload) {
console.log('My Self');
}
},
})
</script>
</body>
</html>
```
一般的JS
```javascript=
document.querySeletor('input').focus()
```
Vue 的做法: 1.) Directives 2.) Ref
:::danger
note: **autofocus** doesn’t work on mobile Safari
:::
全域註冊Directives
```javascript=
// 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
el.focus()
}
})
```
區域註冊Directives (Object 搭配 ket)
```javascript=
directives: {
focus: {
// directive definition
inserted: function (el) {
el.focus()
}
}
}
```
```htmlmixed=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Day 14</title>
</head>
<body>
<div id="app">
{{ count }}
<button @click="clickHandler">Add</button>
<my-input v-for="num in count" :key="num"></my-input>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script>
Vue.directive('focus', {
// el 綁定 input dom
inserted: function (el) {
el.focus()
}
})
const MyInput = {
template: `
<p>
<input v-focus/>
</p>
`
}
new Vue({
el: '#app',
components: { MyInput },
data() {
return {
count: 0,
}
}
methods: {
clickHandler() {
this.count++;
},
},
})
</script>
</body>
</html>
```

### Hook Functions
1:07:47
- bind: called only once, when the directive is first bound to the element. This is where you can do one-time setup work.(Function 只會在被綁的時候做一次,不會多次觸發)
1:07:50
- inserted: called when the bound element has been inserted into its parent node (this only guarantees parent node presence, not necessarily in-document). (一開始DOM被塞進我們的Tree,但塞進去不代表真正的顯示或上場,只保證DOM有進到 parent Node,不代表進到網頁)
1:09:37
- update: called after the containing component’s VNode has updated, but possibly before its children have updated. The directive’s value may or may not have changed, but you can skip unnecessary updates by comparing the binding’s current and old values (see below on hook arguments).
1:10:43
- componentUpdated: called after the containing component’s VNode and the VNodes of its children have updated.
1:10:53
- unbind: called only once, when the directive is unbound from the element.
### Directive Hook Arguments (1:11:58)
大部分預設會操作 binding & update
Directive 用法有兩種,
一種是沒有傳參數直接把Directive傳上去 Ex:
```htmlmixed=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Day 14</title>
</head>
<body>
<div id="app">
{{ count }}
<button @click="clickHandler">Add</button>
<my-input v-for="num in count" :key="num"></my-input>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script>
// directive 第二個參數用物件形式
Vue.directive('focus', function (el, binding) {
console.log(binding)
// Focus element
el.focus()
})
const MyInput = {
template: `
<p>
<input v-focus/>
</p>
`
}
new Vue({
el: '#app',
components: { MyInput },
data() {
return {
count: 0,
}
}
methods: {
clickHandler() {
this.count++;
},
},
})
</script>
</body>
</html>
```
console.log(binding) 可以發現 name 跟 rawName 類似,用一個就好,modifier 與 def(你去指定的hook Ex: bind/insert/update...等),

假設directive 第二個參數用function形式(縮寫),insert 拿掉,def會變成預設 bind 與 update

> def 基本描述你針對的哪個hook做了事情
```htmlmixed=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Day 14</title>
</head>
<body>
<div id="app">
{{ count }}
<button @click="clickHandler">Add</button>
<!-- 傳入 count -->
<my-input v-for="num in count" :key="num"></my-input>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script>
// directive 第二個參數用物件形式
Vue.directive('focus', {
// 加上 binding
inserted: function (el, binding) {
console.log(binding)
// Focus element
el.focus()
}
})
const MyInput = {
props: ['count']
template: `
<p><input v-focus="count" /></p>
`
}
new Vue({
el: '#app',
components: { MyInput },
data() {
return {
count: 0,
}
}
methods: {
clickHandler() {
this.count++;
},
},
})
</script>
</body>
</html>
```
原本是 1 ,點一下變 2,第二個input被 focus,同時 console.log出現2次,因為一個是 value,一個是 oldValue

#### 剩餘的參數與屬性解釋 (1:16:52)
```javascript=
const MyInput = {
props: ['count']
template: `
<p><input v-focus:alex.test="count" /></p>
`
}
```

順序的差別比較
```javascript=
const MyInput = {
props: ['count']
template: `
// 先用 arg 後用 modifer
<p><input v-focus:alex.test="count" /></p>
// 先用 modifer 後用 arg
<p><input v-focus.test:alex="count" /></p>
`
}
```
先用 modifer 後用 arg 的結果 rawName完整名字有出來,但是 modifer是 test:alex=true,所以**先用冒好,再用後綴**比較好
:::danger
Apart from el, you should treat these arguments as read-only and never modify them. If you need to share information across hooks, it is recommended to do so through element’s dataset.
如果傳遞東西,不該直接修改 element,而是透過 dataset傳遞東西
:::
1:22:50 測試 Doc 給的範例
```htmlmixed=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Day 14</title>
</head>
<body>
<div id="app">
<div id="hook-arguments-example" v-demo:foo.a.b="message"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script>
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(', ')
}
})
new Vue({
el: '#app',
components: { MyInput },
data() {
return {
message: 'Hello Vue'
}
}
methods: {
clickHandler() {
this.count++;
},
},
})
</script>
</body>
</html>
```

### Dynamic Directive Arguments (1:28:09)
Dynamic的意思就是從
```htmlmixed=
<div id="hook-arguments-example" v-demo:foo.a.b="message"></div>
```
變成,可以透過參數資料決定 Directive
```htmlmixed=
<div id="hook-arguments-example" v-demo:[foo].a.b="message"></div>
```
可以參考 Doc 範例
### Function Shorthand (1:29:16)
Function Shorthand 直接處理 bind 與 update
### Object Literals(1:29:40)
bind 與 update 也可以綁物件,Directive也可以傳物件,
Ex: Grid System Demo,變版轉換版型
先做一般 Grid System
```htmlmixed=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Day 14</title>
<style>
.col-12 {
float: left;
box-sizing: border-box;
height: 100px;
width: calc(50% - 40px);
margin-right: 10px;
margin-left: 10px;
border: 1px solid #000;
}
</style>
</head>
<body>
<div id="app">
{{windowWidth}}
<div class="col-12"></div>
<div class="col-12"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script>
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(', ')
}
})
new Vue({
el: '#app',
data() {
return {
windowWidth: 0,
}
},
mounted() {
window.addEventListener('resize', this.resizeHandler);
// 一開始拿初始值
this.resizeHandler();
},
brforeDestory() {
window.addEventListener('resize', this.resizeHandler)
},
methods: {
resizeHandler() {
this.windowWidth = window.innerWidth;
},
},
})
</script>
</body>
</html>
```


加上 Directive
```htmlmixed=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Day 14</title>
<style>
.col-12 {
float: left;
box-sizing: border-box;
height: 100px;
width: calc(50% - 40px);
margin-right: 10px;
margin-left: 10px;
border: 1px solid #000;
}
.sm\:.col-24 {
float: left;
box-sizing: border-box;
height: 100px;
width: calc(100% - 20px);
margin-right: 10px;
margin-left: 10px;
border: 1px solid #000;
}
</style>
</head>
<body>
<div id="app">
{{windowWidth
<!-- windowWidth 在 resize時一直被更新,照理來講容器也會被更新,這樣可以一直每次更新時算容器的大小-->
<div class="col-12 .sm\:.col-24" v-width="windowWidth"></div>
<div class="col-12 .sm\:.col-24" v-width="windowWidth"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script>
Vue.directive('width', function (el, binding) {
el.innerHTML = el.getBoundingClientRect().width
})
new Vue({
el: '#app',
data() {
return {
windowWidth: 0,
}
},
mounted() {
window.addEventListener('resize', this.resizeHandler);
// 一開始拿初始值
this.resizeHandler();
},
brforeDestory() {
window.addEventListener('resize', this.resizeHandler)
},
methods: {
resizeHandler() {
this.windowWidth = window.innerWidth;
},
},
})
</script>
</body>
</html>
```


> 這樣就可以透過 v-width ,隨著螢幕寬度或外部事件resize去控制所有要render寬度的DOM,去控制顯示每個的大小,而且全部DOM只需偵聽一個事件
Mixin 組合 resize listener 1:50:00