NVM-安裝及控管Node.js的版本

什麼是NVM?

NVM(Node Version Manager) 是Node.js的版本管理控制工具

什麼情況會需要/建議使用NVM?

當電腦要配置Node.js環境時,有兩種安裝方法:

  • 方法一:直接從Node.js官網下載並安裝到電腦
  • 方法二:透過NVM,下載並切換各個版本的Node.js

通常在團隊中開發大型專案,有可能會接手他人的專案程式碼,若同事的Node.js版本較舊,與自己電腦中的本不同,就必須要安裝並切換不同版本來開發。

因此一般都會使用方法二,透過NVM,即可在同一台電腦上,快速下載、切換Node.js的版本。

安裝NVM(for Mac)

參考github開源的操作說明

首先,在terminal中執行下方指令:

1
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash

接著,如圖中藍色區塊中的說明「先將terminal關閉,再次執行下方的指令」:

1
2
3
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion

輸入下方指令,確認是否安裝完畢

1
nvm --version

若成功秀出如下圖nvm版本,代表安裝成功

安裝Node.js

使用指令秀出目前所有可以使用的版本

1
nvm ls-remote

參考並選擇要下載的Node.js版本

使用下方指令安裝版本(這裡選擇以LTS v10.15.3最新的穩定版本作為示範)

1
nvm install v10.15.3

執行安裝完畢

查看目前電腦本機端中載有的版本

1
nvm list

切換不同版本(以10.15.3版本作為示範)

1
nvm use 10.15.3

Vue-(十六)狀態管理 vuex(2/2)

一個 vuex 的 Store 中有四大元素:

  • State
  • Mutations
  • Getters
  • Actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const store = new Vuex.Store({
state:{
//count:0,
//userName:"Amy",
//list:[]
},
mutations:{
addCount(state){
state.count += 1;
},
},
});

export default store;

存放狀態的 State

state 是 Vuex.Store 中儲存狀態的物件,裡面就可以存放許多自定義的屬性資料

日後會用到的資料,通常都會事先定義好屬性在 Store 中,即使是空字串、空陣列也沒關係,如果沒有事先定義好,之後就必須要用Vue.set的語法才能在 Store 中新增屬性資料。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...
...
const store = new Vuex.Store({
state:{
count:0,
userName:"",
list:[]
},
mutations:{
addCount(state){
state.count += 1;
},
setLoading(state){
//Store 中還沒有 loading 屬性,所以要用 Vue.set 去新增
Vue.set(state,'loading',false);
}
},
});
...
...

變更狀態的 Mutations

要改變 Store 中的屬性,唯一的方法是透過 Mutation,它的實作方法是在 callBack 函式中,把 state 作為參數傳進去,並在其中重新 assign 值給 state 的屬性。而 mutations 中的操作只能是「同步」操作,不能是非同步操作,如果要非同步操作只能在 actions 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
...
const store = new Vuex.Store({
state:{
count:0,
},
mutations:{
addCount(state){
state.count += 1;

//不得使用非同步操作,如:fetch
// fetch(url).then(()=>{
// state.count +=1;
// });
},
},
});
...
...

取得狀態的 Getters

Getter 其實就是 Store 中的 computed

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
...
const store = new Vuex.Store({
state:{
todos:[],
},
getters:{
itemsNotDone(state){
return state.todos.filter(item=> !item.done).length;
},
//將 getters 拿進 getter 中用
itemsDone(state,getters){
return state.todos.length - getters.itemsNotDone;
},
//getter 也可以回傳出一個函式
itemWithID(state){
return ((id)=>{
return state.todos.filter(item=>item.id===id);
})
}
},
});
...
...

在組件中要引用 getters 的做法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

//<script>
import {mapStates, mapGetters} from 'vuex';
export default {
computed:{
...mapStates(['todos']),
...mapGetters(['itemNotDone','itemWithID']),
},
methods:{
test(){
this.itemWithID('123');
},
},
};


//</script>

發出指令的 Actions

前面提過,Mutations 裡面的操作只能是同步的,但若要透過「非同步」 的方式改變 State 裡的資料,就必須使用 Actions 來做操作。可是 Action

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
...
...
const store = new Vuex.Store({
state:{
todos:[],
},
mutations:{
setTodos(state,todos){
state.todos = todos;
}
},
actions:{
fetchTodos(context,payload){
fetch(url).
then(rs => rs.json()).
then(todos=>{
context.commit('setTodos',todos);
})
},

//context 是一個包含 store 中一切的東西,
//可是 context 比 store 還多了一些東西,
//所以 context 不等同於 store。
//因為我們只需要用到 context 中的 commit
//所以這裡經常會使用到重新解構的語法:
fetchTodos({commit},payload){
...
.then(todos=>{
commit('setTodos',todos);
})
...
}
}

});
...
...

在組件中要引用 actions 的做法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//</script>
import { mapActions} from Vuex;
export default {
mounted(){
this.$store.dispatch('fetchTodos',{id:2});
},
//或是將傳入 dispatch 的參數包成一個物件的寫法
mounted(){
this.$store.dispatch({
type:'fetchTodos',//type 是固定的 key
id:2,
});
},
methods:{
...mapActions(['fetchTodos'])
}
};
//</script>

進階補充:
Actions 可以回傳一個 Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
...
...
const store = new Vuex.Store({
state:{
todos:[],
loading:false,
}
mutations:{
setTodos(state,todos){
state.todos = todos;
},
setLoading(state,loading){
state.loading = loading;
},
},
actions:{
fetchUserInfo(){
...
},
fetchTodos({commit,dispatch},payload){
return new Promise(resolve=>{
commit('setLoadeing',true)
fetch(url)
.then(rs => rs.json())
.then(todos=>{
commit('setTodos',todos);
commit('setLoading',false);
//action 中可以放另一個 action
dispatch('fetchUserInfo');
resolve();
})
})
},
},
});
...
...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//</script>
import { mapActions} from Vuex;
export default {
mounted(){
this.$store.dispatch('fetchTodos',{id:2})
.then((=>{
...
...
}))
},
methods:{
...mapActions(['fetchTodos'])
}
};
//</script>

補:因為 fetch() 本身是個 Promise,所以可以簡化寫法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
...
...
//簡化前
fetchTodos({commit,dispatch},payload){
return new Promise(resolve=>{
commit('setLoadeing',true)
fetch(url)
.then(rs => rs.json())
.then(todos=>{
commit('setTodos',todos);
commit('setLoading',false);
//action 中可以放另一個 action
dispatch('fetchUserInfo');
resolve();
})
})
},

//簡化後
fetchTodos({commit,dispatch},payload){
return fetch(url)
.then(rs => rs.json())
.then(todos=>{
commit('setTodos',todos);
commit('setLoading',false);
//action 中可以放另一個 action
dispatch('fetchUserInfo');
resolve();
})
},
...
...

Vue-(十三)自訂事件

使用 v-on 綁定自訂事件

codepen結果呈現

當內層組件要傳送資料到外層時,內層組件使用 this.$emit 提交自定義事件、資料,上層則用 v-on 或縮寫 @ 接應。

舉例說明:

  • 內層組件發出一個$emit,自定義事件為’emit-count’並帶有一個資料’this.count’
  • 外層則用 @emit-count 接應該事件,當該事件被出發時,則會執行 ‘emitCount’ 方法
    1
    2
    3
    4
    <div id="app">
    <my-counter @emit-count="emitCount"></my-counter>
    <h3>{{outerCount}}</h3>
    </div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Vue.component('my-counter',{
template:
`
<div>
<h1>{{count}}</h1>
<button @click="addOne">+1</button>
<button @click="emit">emit</button>
</div>
`,
data(){
return{
count:0,
}
},
methods:{
addOne(){
this.count += 1;
},
emit(){
this.$emit('emit-count',this.count);
},
},
});

new Vue({
el:"#app",
data:{
outerCount:0,
},
methods:{
emitCount(count){
this.outerCount = count;
},
}
})

給組件綁定原生事件

上層組件上層組件除了可以偵聽自定義事件,也可以去偵聽從內層組件發出來的原生事件,如果要偵聽「原生事件」,要加上.native 修飾符。

舉例說明:

  • 外層使用 `@click.native` 接偵聽內層物件的click事件是否被觸發,當該事件被出發時,則外層會執行 ‘clickBtn’ 方法

codepen結果呈現

1
2
3
4
5
<div id="app>
<my-button @click.native="clickBtn">

</my-button>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
Vue.component('my-button',{
template:`<button>Button</button>`,
});

new Vue({
el:"#app",
methods:{
clickBtn(){
alert('Clicked');
}
}
})

.sync修飾符

外層資料透過 props 將傳進給內層,內層經過獨立處理之後,若希望內層資料也能夠將資料更新到外層,依照前面的方法,可以在內層使用 $emit 將資料再往外傳。

舉例說明:

  • 先使用 props 傳 ‘outer-count’ 給內層,並賦值給內層的 ‘innerCount’
  • 當觸發 ‘clickSync’ 事件時,透過 $emit 將’this.innerCount’ 往外層傳送
  • 外層則透過 @sync-count 偵聽到自定義事件被觸發後,則執行 ‘syncCount’方法,方法中再重新賦值
1
2
3
4
5
<div id='app'>
<my-counter @sync-count="syncCount" :outer-count='outerCount'>
</my-counter>
<h3>{{outerCount}}</h3>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Vue.component('my-counter',{
template:`
<div>
<h1>{{innerCount}}</h1>
<button @click="innerCount+=1">+1</button>
<button @click="clickSync">sync</button>
</div>
`,
props:["outer-count"],
data(){
return {
innerCount:this.outerCount,
};
},
methods:{
clickSync(){
this.$emit('sync-count',this.innerCount)
},
},
})

new Vue({
el:"#app",
data:{
outerCount:10,
},
methods:{
syncCount(innerCount){
this.outerCount = innerCount;
}
}
})

不過,使用 .sync 修飾符,可以讓程式碼更簡潔

codepen結果呈現

舉例說明:

  • 內層透過 $emit 傳送 update:outCount事件到外層
  • 外層不使用 @ 去偵聽內層透過 $emit 傳送進來的自定義事件
  • 外層直接在傳入 props 的地方加上 .sync 修飾符, :outer-count.sync 去偵聽 ‘outer-count’的更動事件,其意義等同於 @update:outer-count="value => outerCount = count"
1
2
3
4
5
<div id='app'>
<my-counter :outer-count.sync='outerCount'>
</my-counter>
<h3>{{outerCount}}</h3>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Vue.component('my-counter',{
template:`
<div>
<h1>{{innerCount}}</h1>
<button @click="innerCount+=1">+1</button>
<button @click="clickSync">sync</button>
</div>
`,
props:["outer-count"],
data(){
return {
innerCount:this.outerCount,
};
},
methods:{
clickSync(){
this.$emit('update:outerCount',this.innerCount)
},
},
})

new Vue({
el:"#app",
data:{
outerCount:10,
},
})

自訂組件的 v-model

1
2
3
4
5
//常見 v-model 的用法
<my-counter v-model="outerCOunt"></my-counter>

//拆解上述 v-model 的意義,等同於下方程式碼
<my-counter :value="outerCount" @input='val => outerCount = val'></my-counter>

自定義 model 事件

1
2
3
4
5
6
7
8
9
10
Vue.component('my-counter',{
...
...
model:{
prop:"value",
event:"input",
}
...
...
})

codepen結果呈現

舉例說明:

1
2
3
4
5
6
7
8
<div id="app">
<!--v-model寫法: -->
<my-counter v-model="outerCount"></my-counter>

<!--意義等同於: -->
<my-counter :count="outerCount" @update:count="val=> outerCount=val"></my-counter>
<h3>{{outerCount}}</h3>
</app>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Vue.component('my-counter',{
model:{
prop:'count',
event:'update:count',
},
props:['count'],
data(){
return {
innerCount:this.count,
}
},
methods:{
syncCount(){
this.$emit('update:count',this.innerCount)
},
},
template:
`<div>
<h1>{{innerCount}}</h1>
<button @click="innerCount+=1">+1</button>
<button @click="syncCount">sync</button>
</div>
`,
});

new Vue({
el:"#app",
data:{
outerCount:10,
},
})

跨組件的溝通

上、下組件的溝通,可以透過 $emitprops來做溝通、傳遞資料,但如果組件之間並非上、下關聯,而是跨組件的關係,則有幾種溝通方式:

  1. 使用多次 $emitprops 傳遞:不建議,會造成混亂
  2. 使用 event bus
  3. 使用 Vuex

event bus

專門用來發出事件跟偵聽事件的渠道,本身是一個 Vue 實體,不綁定任何 element

codepen結果呈現

舉例說明:

  • 首先 const bus = new Vue(); 創建一個 Vue 實體來代表 event bus
  • 要推送資料給 event bus,使用 bus.$emit 發送 ‘add’ 事件給 event bus
  • 要從 event bus 接受事件、資料,則是在 mounted 生命週期階段裡,用 bus.$on 去偵聽 ‘add’ 事件
1
2
3
4
<div id="app">
<my-count></my-count>
<my-button></my-button>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const bus = new Vue();

Vue.component('my-count',{
template:'<h1>{{count}}</h1>',
data(){
return{
count:0,
};
},
mounted(){
bus.$on('add',()=>{
this.count += 1;
});
},
});

Vue.component('my-button',{
template:'<button @click="add">+1</button>',
methods:{
addOne(){
bus.$emit('add')
},
},
});

new Vue({
el:"#app",
});

JavaScript-陣列處理方法

這裡整理對陣列資料的處理方法,會介紹的方法有:

  • filter()
  • find()
  • forEach()
  • map()
  • every()
  • some()
  • reduce()

初始資料

接下來的案例皆以此份資料作示範

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var memberList = [
{
name:"lin",
coin:27,
gender:"F",
account:"lin27"
},
{
name:"chen",
coin:37,
gender:"F",
account:"chen37"
},
{
name:"liu",
coin:17,
gender:"M",
account:"liu17"
},
{
name:"chien",
coin:54,
gender:"M",
account:"chien54"
},
{
name:"lin",
coin:25,
gender:"F",
account:"lin22"
},
]

Array.filter():搜尋符合條件的所有資料

filter()方法會回傳一個新的陣列,其filter()方法中的 return 後方寫入判斷條件,若條件吻合而取得 true 值,則會將該item放入新的陣列中。

filter 函式,接受一個 callback 函式,callback 可以有三個參數(item, index, array)

  • element:當前元素
  • index:當前元素的所在陣列中的位置
  • arr:已經過 filter 處理的陣列
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var femaleList = memberList.filter(function(item,index,array){
return item.gender ==="F";
})

console.log(femaleList);
// [
// {
// name:"lin",
// coin:"27",
// gender:"F"
// },
// {
// name:"chen",
// coin:"37",
// gender:"F"
// },
// ]

Array.find():搜尋第一個符合條件的資料

find()與filter()很類似,皆是用來篩選符合條件的方法,但是find()方法只會回傳一個值,且是在陣列中第一個符合條件的值。

1
2
3
4
5
6
7
8
9
10
var aFemale = memberList.find(function(item,index,array){
return item.gender==="F";
})

console.log(aFemale);
// {
// name:"lin",
// coin:"27",
// gender:"F"
// },

Array.forEach():等同於for迴圈

forEach()方法並不會回傳值,而僅在迴圈中處理到每個值時,可以對該值做執行處理,若做資料變更則會直接改動原陣列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
memberList.forEach(function(item,index,array){
item.coin += 10;
})

console.log(memberList);
// [
// {
// name:"lin",
// age:"37",
// gender:"F",
// account:"lin27"
// },
// {
// name:"chen",
// coin:"47",
// gender:"F",
// account:"chen37"
// },
// {
// name:"liu",
// coin:"27",
// gender:"M",
// account:"liu17"
// },
// {
// name:"chien",
// coin:"64",
// gender:"M",
// account:"chien54"
// },
// {
// name:"lin",
// coin:"35",
// gender:"F",
// account:"lin22"
// },
// ]

Array.every():檢查陣列資料完全符合

every()方法會回傳一個布林值為 true 或 false,every()方法中的 return 後方寫入檢查條件,用以檢查陣列中的每一筆資料是否皆符合條件。

1
2
3
4
5
6
7
8
9
var result1 = memberList.every(funciton(item,index,array){
return item.coin>10;
})
var result2 = memberList.every(funciton(item,index,array){
return item.coin>50;
})

console.log(result1);//true
console.log(result2);//false

Array.some():檢查陣列資料部分符合

some()也是用來檢查陣列中的每一筆資料,但是相較於every()較寬鬆,只要有部分資料吻合,就會獲得true。

1
2
3
4
5
var result3 = memberList.some(function(item,index,array){
item.coin > 50;
})

console.log(result3);//true

Array.map():運算組合出一個新陣列

map()方法會回傳一個等同於原先陣列長度的新陣列,新陣列中的值為重新運算、合成過的資料,資料內容則定義在return後方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var mapMember = memberList.map(function(item,index,array){
if(item.gender==="F"){
return item.account+"是女生";
}else{
return item.account+"是男生";
}
})

console.log(mapMember);
// [
// "lin27是女生",
// "chen37是女生",
// "liu17是男生",
// "chien54是男生",
// "lin22是女生"
// ]

Array.reduce():連續運算陣列中的值

reduce()方法最終會回傳陣列中每個物件接續運算後的值, reduce 函式,接受一個 callback 函式,以及一個初始值。

callback 可以有四個參數(accumulator, currentValue, currentIndex,array)

accumulator:前一個元素的回傳值
currentValue:當前元素
currentIndex:當前元素的所在陣列中的位置
array:完整陣列

1
2
3
4
5
var sumResult = memberList.reduce(function(accumulator,currentValue,currentIndex,array){
return accumulator+currentValue.coin;
},0);

console.log(sumResult);//160
1
2
3
4
5
var maxResult = memberList.reduce(function(accumulator,currentValue,currentIndex,array){
return Math.max( accumulator, currentValue.coin );
},0);

console.log(maxResult);//54

Vue-自定義directive

Vue Directive的簡介

Vue 提供許多內建指令,例如:v-onv-bindv-model,可是有些情況,這些內建指令無法滿足開發者直接對 DOM 元素進行操作,因此 Vue 提供自定義 directive 的方法來滿足這類需求。

為什麼要使用Vue Directive

雖然,也可以在 Vue 實例中的方法中直接操作 DOM 元素,但是為了秉持 Vue 的 MVVM 框架原則,使用 Vue Directive 封裝 DOM操作,可以讓 ViewModel 層僅負責操作數據, 而 View 層僅負責畫面的渲染顯示,再以數據驅動畫面,實現 ViewModel 與 View 分離的原則

因為 Vue Directive 是屬於 View 層的,所以 DOM 操作應該被封裝在 Vue Directive 裡,而不是出現在 Vue 實例中。

註冊方法

Vue 提供兩種註冊方法,包含全域以及區域註冊。在註冊並定義 Vue directiove 時,Vue 也提供許多 hook function,例如:insertedbindunbindbind是只會在綁定的元素插入父節點調用時的函式,unbind則是只有在元素被解除綁定時被調用。

全域註冊

1
2
3
4
5
6
7
8
9
10
11
12
13
Vue.directive('自定義指令名稱',{
//指令的定義
//一些options,提供許多 hook function 定義 directive 的具體操作
inserted: function(el){
//指令插入元素後的操作
},
bind:function(el,binding,vnode,oldVnode){
//綁定指令時的操作
},
unbind:function(){
//指令解除綁定時的操作
}
})
1
2
3
4
5
directive:{
name:{ //自定義指令名稱
//指令的定義
}
}

區域註冊

在欲使用的 Vue 組件中,以 directive 屬性進行註冊及定義

1
2
3
4
5
6
7
8
9
10
11
12
13
export default{
//指令的定義
//一些options,提供許多 hook function 定義 directive 的具體操作
inserted: function(el){
//指令插入元素後的操作
},
bind:function(el,binding,vnode,oldVnode){
//綁定指令時的操作
},
unbind:function(){
//指令解除綁定時的操作
}
}

1
2
3
4
import 'CustomDirective' from '../../xxx.vue';//
directive:{
'自定義指令名稱':'CustomDirective'
}

鉤子函式的參數(Directive Hook Argument)解釋

1
2
3
4
// 範例
<div id="app">
<span v-say:hello.a.b.c="{ name: 'Peter', message: 'hello!' }"></span>
</div>

el

自定義指令所綁定的元素,也就是 DOM 元素。

binding

binding 物件包含一些屬性

  • name:自定義指令的名稱,不含前綴「v-」,如上例的「say」。
  • value:指令綁定的值,如上例的「{ name: ‘Peter’, message: ‘hello!’ }」。
  • oldValue:綁定值的前一個值,僅在update 及 componentUpdated時可使用。
  • expression:綁定值的字串,如上例的「”{ name: ‘Peter’, message: ‘hello!’ }”」。
  • arg:傳給自定義指令的參數,如上例的「hello」。
  • modifiers:修飾符,如上例的「a」、「b」、「c」。

    vnode

    虛擬節點(virtual DOM)

    oldVnode

    前一個虛擬節點,僅在update 及 componentUpdated時可使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//範例:印出所有參數
Vue.directive('say', {
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(', ');
}
});

//name:"say"
//value:{ name: ‘Peter’, message: ‘hello!’ }
//expression:"{ name: ‘Peter’, message: ‘hello!’ }"
//modifiers:{"a":true,"b":true,"c":true}
//vnode keys:tag,data,children,text,elm,ns,context,functionalContext,key,componentOptions,componentInstance,parent,raw,isStatic,isRootInsert,isComment,isClobed,isOnce

使用注意

Vue-(十二)Props

上層傳來的 props

vue 組件,會使用 props 來接受上層傳來的資料。props 是一個陣列,裡面包含多個字串。

1
2
3
4
5
6
7
8
9
10
Vue.component('my-component', {
props: ['message', 'name'],
template: `
<h1>{{message}} {{name}}</h1>
`,
});

new Vue({
el: '#app',
});
1
2
3
<div id='app'>
<my-component message='hello' name='Leah'></my-component>
</div>

props 命名

假設在 component 中的 props 使用 camelCase、Pascalcase,在 html 模板中引用時,一律使用 kebab-case ,因為會需要先經過瀏覽器解析。

1
2
3
4
5
6
7
8
9
Vue.component('my-component', {
props: ['myMesssage', 'MyLastName'],
template: `
<h1>{{myMesssage}} {{MyLastName}}</h1>`,
});

new Vue({
el: '#app',
});
1
2
3
4
<div id='app'>
<my-component my-messsage='Hello' my-last-name='Lin' >
</my-component>
</div>

如果是在 template 中,即可使用 camelCase、Pascalcase。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Vue.component('my-component', {
props: ['myMesssage', 'MyLastName'],
template: `<h1>{{myMesssage}} {{MyLastName}}</h1>`,
});

new Vue({
el: '#app',
template: `
<div>
<my-component
myMesssage='Hello'
MyLastName='Lin'>
</my-component>
</div>
`,
});
1
2
3
<div id='app'>

</div>

動態綁定 props

  1. 先使用v-bind:date='today'語法,讓 tag 中的 prop 值可以綁定到 data 中的資料,所以此時的 date的值是 ‘20180606’(如果沒有使用v-bind:,直接寫 date='today',則 date的值是 ‘today’)
  2. 接著在 component ,props 屬性定義傳入的資要有 ‘date’,此時,component 即已經接到上層傳來的資料
  3. 接著在 component 中的 template 就可以引出 date 的值

陣列資料

如果是資料是陣列,在 tag 中可以用 v-for 將 data 中的 users 一個個以 text 叫出來,並用 v-bind:userusers中的值綁定給 user

1
2
3
4
<div id='app'>
<post v-for='text in users' :user='text'>
</post>
</div>

接著在 component 中,同樣在 props 屬性定義傳入的資料有 ‘user’,此時 component 即已經取得該值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
new Vue({
el: '#app',
data: {
users: ['Amy', 'Betty', 'Celine', 'Doris'],
},
components: {
post: {
props: ['user'],
template: `
<div>{{user}}<div>
`,
},
},
});

物件資料

1
2
3
4
5
<div id='app'>
<lists v-for='member in members'
:name='member.name' :gender='member.gender'>
</lists>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
new Vue({
el: '#app',
data: {
members: [
{ name: 'Amy', gender: 'F' },
{ name: 'Jack', gender: 'M' },
{ name: 'Cindy', gender: 'F' },
{ name: 'Doris', gender: 'F' },
],
},
components: {
lists: {
props: ['name', 'gender'],
template: `
<div> {{name}} <small>{{gender}}</small><div>
`,
},
},
});

如果要將一個物件裡面的所有屬性,指定給一個 component 的 props ,除了上述一個個綁定以外,有另外一個特殊用法,將 html 改寫如下:

1
2
3
4
<div id='app'>
<lists v-for='member in members' v-bind='member'>
</lists>
</div>

數值資料

在 tag 中,當要綁定 props 的資料為數值,必須要用 v-bind語法綁定(如果直接寫 number=’1’,則接受到的資料會是字串’1’)

1
2
3
4
<div id='app'>
<counter :number='1'>
</counter>
</div>
1
2
3
4
5
6
7
8
9
10
11
new Vue({
el: '#app',
components: {
counter: {
props: ['number'],
template: `
<div> {{number+1}}<div>
`,
},
},
});

單向數據流

上傳下

codepen 結果呈現

透過 props 上傳下,上層資料可以往下傳,所以上層資料改變,下層資料也會跟著變;這個資料傳遞的動作是單向的,資料並不會往上傳,所以下層數據改變,並不會影響上層。

  • 第 5 行,引入<counter/> 組件,:count='count'的意思代表,counter 組件的 props 資料中,:count的數據綁定上層'count'的資料。
1
2
3
4
5
6
<!-- html -->
<div id='app'>
<h1>{{count}}</h1>
<button @click='count+=1'>Add+1</button>
<counter :count='count'></counter>
</div>
  • 第 1-5 行是上層的資料,第 6-16 行是註冊區域組件的資料。
  • 由上傳下的單向原理解釋,當第 4 行上層 count 的數據變動,第 8 行組件中的 props 數據也會連帶跟著變動;可是,當組件內的 count 數據變動,並不會影響到上層。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
new Vue({
el: '#app',
data: {
count: 10,
},
components: {
counter: {
props: ['count'],
template: `
<div>
<h1>{{count}}</h1>
<button @click='count+=1'>Add+1</button>
</div>
`,
},
},
});

父子階層數據分離

codepen 結果呈現

如果不希望上層的數據跟下層的 props 資料發生連動,則可以在下層組件中的 data 自定義一個變數,並僅接住上層一開始傳來的數據做為初始值,並獨立做操作。

  • 第 5 行引入 counter 組件,:start='count'的意思代表,counter 組件的 props 資料中,:start的數據綁定上層'count'的資料。
1
2
3
4
5
6
<!-- html -->
<div id='app'>
<h1>{{count}}</h1>
<button @click='count+=1'>Add+1</button>
<counter :start='count'></counter>
</div>
  • 第 8 行 counter 組件的 props 屬性中,start 收到上層傳來的數據時,不直接在組件中的 DOM 做使用,而是先在組件中的 data 重新賦值給 count,接著才在組件中的 DOM (第 16 行) 使用 count。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
new Vue({
el: '#app',
data: {
count: 10,
},
components: {
counter: {
props: ['start'],
data() {
return {
count: this.start,
};
},
template: `
<div>
<h1>{{count}}</h1>
<button @click='count+=1'>Add+1</button>
</div>
`,
},
},
});

運算上層傳來的資料

codepen 結果呈現

如果希望下層資料能夠連動上層傳來的數據,並且直接做運算,可以使用組件中的 computed 做處理

1
2
3
4
5
6
<!-- html -->
<div id='app'>
<h1>{{count}}</h1>
<button @click='count+=1'>Add+1</button>
<counter :start='count'></counter>
</div>
  • 第 10 行的 doubleCount,是直接承接上層傳來的資料做運算所得出的結果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
new Vue({
el: '#app',
data: {
count: 10,
},
components: {
counter: {
props: ['start'],
computed: {
doubleCount() {
return this.start * 2;
},
},
template: `
<div>
<h1>{{doubleCount}}</h1>
</div>
`,
},
},
});

Props 驗證

codepen 結果呈現

型別驗證

當一個組件在多處被使用時,我們會需要事先定義好 props 的資料有哪些,另一方面也必須定義好 props 數據的資料型態,以避免傳入不符合格式型態的資料。例如:應當傳入 Number ,卻傳入 String。

1
2
3
4
<!-- html -->
<div id="app">
<counter :start="10"></counter>
</div>
  • 第 3 行 props 改傳以物件,其中物件的,key 為 props 的名稱,而 value 則使用 JaveScript 的資料型態,例:start:Number
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Vue.component('counter', {
props: {
start: Number,
},
data() {
return {
count: this.start,
};
},
template: `
<div>
<h1>{{count}}</h1>
<button @click="count+=1">+1</button>
</div>
`,
});

new Vue({
el: '#app',
});

而當資料型別錯誤時,則會 log 出錯誤訊息

資料驗證及預設值

除了指定 props 型別,也可以去驗證 props 的範圍,此外,也可以在父階層尚未賦值之前給予預設值。

  • 在第 3 行,props 中的 key pair 的 value 不直接傳入資料型態,而是改以傳入一個物件,例:start:{...},物件中再去定義 type(型別) 、default(預設值)、validator(驗證)
    • type 的值是 JavaScript 的資料型別
    • default 的值可以是純值,也可以是函式
    • validator 的值是函式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Vue.component('counter', {
props: {
start: {
type: Number,
// default:0,
default() {
return app.getSetting();
},
validator(value) {
return value <= 10;
},
},
},
data() {
return {
count: this.start,
};
},
template: `
<div>
<h1>{{count}}</h1>
<button @click="count+=1">+1</button>
</div>
`,
});

new Vue({
el: '#app',
});

如果驗證後不符合,則會 log 出錯誤

props 驗證的時間點

props 的檢驗是發生在組件建立「之前」,所以裡面不能用 this.date 或 this.methods!

Vue-(十一)組件

11-1.Vue組件的註冊與使用(影片)

Vue 的組件(component)是Vue的強大功能之一,Vue 組件預先定義好模組內容,可包含html 的視覺元素、資料、偵聽器等等,將來模組就可以重複使用,方便在開發過程的時候聚焦於一小區塊功能,在維護也比較方便。

全域組件

  1. 首先用Vue.component()方法
  2. 第一個參數是component的名字(component 命名用全小寫以 dash 分隔)
  3. 第二個參數是一個物件,其中用 templete 的字串定義組件的視覺元素(直接定義html的內容)
  4. 有個重點! component的宣告必須在 vue 實例 new之前!
1
2
3
4
5
6
7
8
9
10
11
// Global component
Vue.component('my-component',{
template:'<div>Here is content in my-component</div>'
});

new Vue({
el:'#app1',
});
new Vue({
el:'#app2',
});
  1. 因為是Global component,所以在html中,可以不受限Vue實例範圍,就可以用<my-component></my-component>去使用自定義的component
1
2
3
4
5
6
<div id='app1'>
<my-component></my-component>
</div>
<div id='app2'>
<my-component></my-component>
</div>

區域組件

local component 只能在該 Vue 實例中被使用

  1. 首先new出一個 Vue 實例
  2. 定義其中的 compinents 屬性,值為一個物件
  3. 在該物件中用key pair 作component 的命名及內容定義
  4. 下方案例中,key字串my-component為該 component 的名字
  5. 再其中的 template 屬性用字串定義組件的視覺元素(直接定義html的內容)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // Local component
    new Vue({
    el:'#app',
    components:{
    'my-component':{
    template:'<div>Here is content in my-component</div>',
    }
    }
    });
  6. 接下來在html中,就可以用<my-component></my-component>去使用自定義的component

    1
    2
    3
    <div id='app'>
    <my-component></my-component>
    </div>

11-2.DOM模板解析事項(影片)

HTML解析流程

假設將<option>tag中的<option>內容額外包成一個component,如下述的寫法:

  1. 首先,元素會先經過html瀏覽器的解析,再交給Vue實例使用。

    1
    2
    3
    4
    5
    6
    7
    Vue.component('my-component',{
    template:'<option>AAA</option>'
    });

    new Vue({
    el:'#app',
    })
  2. 所以一開始當html在解讀元素時,會因為解析到不合規範的標籤,而略過。
    在此例中,也就是說當html解讀到<select>,會預想接下來是<option>,然而卻得到<my-component></my-component>,因此被判定為不合法的情況,導致畫面無法正常如願顯示。

    1
    2
    3
    4
    5
    <div id='app'>
    <select>
    <my-component></my-component>
    </select>
    </div>

解決辦法:在templete中定義

讓瀏覽器在解讀html時,可以先略過該元素,將不合法的元素先移除,避免出現錯誤。因此:

  1. 僅給予一個元素,綁定 Vue實例

    1
    2
    <div id='app'> 
    </div>
  2. 讓html內容直接定義在templete中,避免經過html解析的流程

    1
    2
    3
    4
    new Vue({
    el:'#app',
    template:'<select><option>AAA</option><option>CCC</option></select>'
    })
  3. 綁定了Vue實例,抓取其中templete的內容,並作顯示

codepan-範例結果

類似使用時機

1
2
3
4
5
<ul>
<li></li>
<li></li>
...
</ul>
1
2
3
4
5
6
7
8
9
<table>
<tr/>
<td></td>
<td></td>
<td></td>
...
<tr/>
...
</ul>

11-3.data必須是函數(影片)

Vue 實例的data是一個物件,然而,Vue 組件的 data 必須是函數。這個設計邏輯的概念是,每呼叫使用一次組件,組件中的data應該是要各自獨立,所以用函數的方式可以每次都獨立新增一個獨立的data個體。

Vue實例,data共用

假設我需要有兩個計數器,若使用下述方法,因為data資料共用,所以兩個計數器的count會互相影響,無法獨立

1
2
3
4
5
6
new Vue({
el:'#app',
data:{
count:0,
}
})

1
2
3
4
5
6
<div id='app'>
<h1>{{count}}</h1>
<button @click='count+=1'>+click</button>
<h1>{{count}}</h1>
<button @click='count+=1'>+click</button>
</div>

善用組件,達到獨立data

將計數器功能拆分出來成為一個組件,Vue.component()等同於每次都會重新創造一個新的component

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Vue.component('counter-component',{
data(){
return{
count:0,
}
},
template:`
<div>
<h1>{{count}}</h1>
<button v-on:click='count+=1''>+click</button>
</div>
`,
})

new Vue({
el:'#app',
})

即是多次呼叫<counter-component></counter-component>重複使用component,其中的data也會是各自獨立

1
2
3
4
<div id='app'>
<counter-component></counter-component>
<counter-component></counter-component>
</div>

最終的執行效果,會是兩個資料獨立的計數器

11-4.合體!組件組合(影片)

資料網內傳(父傳子)

  1. 定義最外層的 todo-list 組件
  2. 因為要將資料傳進內層資料,第11行使用自定義屬性:label='todo',意思是這裡獲取出的todo資料要以label(可以使用自定義其他命名)屬性往內傳,屆時內層可以用label取出

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    Vue.component('todo-list',{
    data(){
    return{
    todos:['a','b','c']
    };
    },
    template:`
    <div>
    <todo-input></todo-input>
    <ul>
    <todo-item v-for='todo in todos' :label='todo'></todo-item>
    </ul>
    </div>
    `
    });
  3. 定義to-item 組件

  4. 第2行定義屬性,代表外層會以label屬性傳入資料
  5. 第4行一樣可以用大括號取用資料

    1
    2
    3
    4
    5
    6
    Vue.component('todo-item',{
    props:['label'],
    template:`
    <li>{{label}}</li>
    `
    });
  6. 定義 todo-input 組件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Vue.component('todo-input',{
    data(){
    return{
    text:'',
    }
    },
    template:`
    <form>
    <input type='text' v-model='text'/>
    <button type='submit'>Submit</button>
    </form>
    `
    });
  7. new 出 Vue 實例

    1
    2
    3
    new Vue({
    el:'#app',
    })
  8. 撰寫html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    <div id='app'>
    <todo-list></todo-list>
    </div>
    ```
    ### 資料往外傳(子傳父)
    欲將內層 todo-input 的資料,往外層 todo-list 傳遞
    ![](https://i.imgur.com/wkQ4vk9.png)


    7. 第14行`@submit.prevent='submit'`用意是讓提交事件不再重載頁面
    8. 重點在第9行,用`$emit`方法,定義透過這個事件傳資料至外層(父層)
    9. `$emit`方法的兩個參數,第一個代表自定義的事件命名`input`(自定義命名),第二個代表要傳遞的資料

    ```javascript=
    Vue.component('todo-input',{
    data(){
    return{
    text:'',
    }
    },
    methods:{
    submit(){
    this.$emit('input',this.text);
    this.text = '';
    },
    },
    template:`
    <form @submit.prevent='submit'>
    <input type='text' v-model='text'/>
    <button type='submit'>Submit</button>
    </form>
    `
    });
  9. 第14行,在外層(父層)templete的內層組件標籤中,用@input接受事件

  10. 在第8行,addToDo方法中的參數,就是內層傳來的資料,因此可以將之做後續處理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    Vue.component('todo-list',{
    data(){
    return{
    todos:['a','b','c']
    };
    },
    methods:{
    addTodo(text){
    this.todos.push(text);
    }
    },
    template:`
    <div>
    <todo-input @input='addTodo'></todo-input>
    <ul>
    <todo-item v-for='todo in todos' :label='todo'></todo-item>
    </ul>
    </div>
    `
    });
    ```
    結果呈現,[codepen成果](https://codepen.io/leah-lin/pen/gJjmmY)

    ## 11-5.動態組件組合[(影片)](https://hiskio.com/courses/145/lectures/5401)

    當我們在某個位置,需要有彈性地放置component,也就是說需要依據判斷值,決定該處要顯示哪個component時,則可以使用動態組件的寫法:

    1. 先創建多個conpoment,此案例中先創建了`lesson-component`以及`apply-component`這兩個組件
    2. 各自定義組件中的templete內容
    ```javascript=
    Vue.component('lesson-component',{
    template:`
    <div>
    <ul>
    <li>課程</li>
    <li>老師</li>
    <li>費用</li>
    </ul>
    </div>
    `,
    });

    Vue.component('apply-component',{
    template:`
    <form>
    <textarea></textarea>
    <button>Submit</button>
    <form>
    `
    })
  11. 創建完 component 之後,接著 new 出一個 Vue 實例

  12. Vue 實例中有一個 data 作為判斷依准,此案例中先以content作為判斷值,並且給予一個初始值

    1
    2
    3
    4
    5
    6
    new Vue({
    el:'#app',
    data:{
    content:'lesson-component',
    },
    })
  13. 接著在 html 中規劃視覺呈現,此案例中設計兩個按鈕,透過按鈕的 click 事件,下方空間會切換為不同的 component 的呈現

  14. 第4行中, :is判斷 content 當前的值,用來決定這裡要顯示什麼 component
    1
    2
    3
    4
    5
    <div id='app'>
    <button @click="content='lesson-component'">Lessons</button>
    <button @click="content='apply-component'">Apply</button>
    <component :is='content'></component>
    </div>

結果呈現,codepen成果

11-6.看不見但依舊存在的 keep-alive (影片)

接續11-5的動態組件,當前有兩個字定義的組件:lessons與apply,其中apply組件中是一個輸入框,假設在輸入框中先輸入訊息後,卻做了組件切換到lessons,之後再切換回apply時,會發現原先輸入的訊息會被清空。

如果希望當組件切換時,可以 keep 住訊息內容,則可以使用keep-alive標籤。
下方第4、6行就是用keep-alive標籤將第5行組件範圍包覆起來,讓組件中的內容可以不因組件切換而消失,反而會 keep 住訊息。

1
2
3
4
5
6
7
<div id='app'>
<button @click="content='lesson-component'">Lessons</button>
<button @click="content='apply-component'">Apply</button>
<keep-alive>
<component :is='content'></component>
</keep-alive>
</div>

結果呈現,codepen成果

11-7.組件命名學(影片)

1
2
3
4
5
6
// 三種命名方式
// 示範:my first component

my-first-component //kebab-case
myFirstComponent //camelCase
MyFirstComponent //PascalCase

在 html 中,只能使用 kebab-case 的命名法

  1. 假設在 Vue 中創建了一個組件,並使用「camelCase」的命名方式

    1
    2
    3
    4
    //vue.js
    Vue.component('myFirstComponent',{

    })
  2. 接著在html中呼叫使用組件,並需要轉換為「kebab-case」的命名方式才能夠辨識出組件

    1
    2
    3
    4
    <!-- html -->
    <div id='app'>
    <my-first-component></my-first-component>
    </div>

在 template 中使用,三種命名方式都可以

  1. 假設在 Vue 中創建了一個組件,並使用「camelCase」的命名方式

    1
    2
    3
    4
    //vue.js
    Vue.component('myFirstComponent',{

    })
  2. 在html中只是簡單綁定 Vue 實例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <div id='app'>
    </div>
    ```
    3. 接著在 Vue 實例中的 template 使用組件,此時要用何種命名方式都可以辨識
    ```javascript=
    new Vue({
    el:'#app',
    template:`
    <div >
    <myFirstComponent></myFirstComponent>
    </div>
    `
    })

補充:sefe-closing

  • 在 html 中,不能使用「self-closing tag 」, 僅能使用完整一組「star tag」+「end tag」

    1
    2
    3
    <div id='app'>
    <my-first-component></my-first-component>
    </div>
  • 不過在 template 中的字串模板,就可以使用「self-closing tag 」

    1
    2
    3
    4
    5
    6
    7
    8
    new Vue({
    el:'#app',
    template:`
    <div>
    <my-first-component/>
    </div>
    `
    })

Vue-(十六)狀態管理 vuex(1/2)

關於 vuex

Vue的組件溝通

在單純的情況之下,兩個上、下層Vue組件若要進行溝通,會使用:

  • 由上傳下:上層使用 v-bind ,下層用props接到v-bind傳來的屬性資料
  • 由下傳上:下層發出$emit事件,上層則用v-on偵聽該事件

    eventbus

    若專案更大一些,資料不太可能$emit一層一層慢慢傳上去,上層組件用v-on偵聽到事件並拿到資料之後,又再一層層用props慢慢傳到另一個下層組件,這樣很麻煩也很難維護,所以這種結構複雜的跨組件溝通會使用 eventbus 來傳遞資料。

eventbus 的做法是另外創立一個 Vue 實例,假設要由 A 組件傳資料到 B組件,則在 A 組件中引入 eventbus ,A 組件請求 Vue實例透過 $emit 將資料傳進eventbus,而另一方面,B組件也引入 eventbus ,再透過 $on 偵聽 eventbus 發出的事件。

然而,eventbus 依然是有限制的,例:

當「購買按鈕」被按下之後,在「購物車」中要增添一筆資料,這個情況之下,資料不應該儲存在「購買按鈕」上,也不該儲存在「購物車」中,而是應當要儲存在一個統一管理的狀態裡面,因此就需要用到 Vuex 來做狀態管理。

Vuex

Vuex 大致上是透過一個 Global state 來儲存整個網站共有的狀態,而其設計概念是由「購買按鈕」組件 commit 一個 Mutation,而這個 Mutation 會改變 State,當 State 被改變時,則會觸發有使用到 State 的「購物車」組件的視覺元件被更新,而這整個觸法的過程是「單向運作」的。

Vuex 跟 eventbus 最主要的差異在於,Vuex 的 Global state 是組件共用的狀態,而非單純儲存在某個組件內部中的狀態。

所以本質上,組件與組件之間並沒有溝通,而是透過更新 Global State的狀態,而反應畫面到另外一個組件上。

使用 Vuex 時機

  • 組件之間很上互相溝通:不用Vuex
  • 組件僅需上下傳遞就很夠用:不用Vuex
  • 組件會互相溝通,但情境很固定,次數也很少:Event Bus
  • 組件之間會跨結構傳遞狀態:Vuex
  • 需要全域狀態管理:Vuex

引入 Vuex

step 1.先以指令安裝Vuex

1
2
3
4
5
//使用npm指令
npm i -S vuex

// or 使用yarn指令
yarn add vuex

step 2.在使用 webpack 打包的專案中,於 src 資料夾中新增一個檔案 store.js

step 3.開始在檔案中創建 store 實例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import Vue from 'vue';//引入vue
import Vuex,{Store} from 'vuex';//引入vuex

Vue.use(Vuex);//註冊外掛Vuex,之後才能被使用

const store = new Store({//透過呼叫Store的建構函式,創建store實例
state:{
count:0,
},
mutations:{
addCount(state){
state.count +=1;
}
}
})

export default store;

另外一種寫法,也可以不引入{Store},而使用Vuex.Store 取得之

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);//

const store = new Vuex.Store({//使用Vuex.Store 取得之
state:{
count:0,
},
mutations:{
addCount(state){
state.count +=1;
}
}
})

export default store;

step 4.在整個專案的中使用該定義好的 store實例

第3行,在既有的專案中的 main.js引入 store.js;第5行,讓 store 成為整個專案 Vue實例中的一個屬性

1
2
3
4
5
6
7
8
9
10
11
import Vue from 'vue';
import App from './app.vue';
import store from './store.js';//use Vuex
new Vue({
store,//加入 store 屬性,讓所有的紫組件都可以使用
el: '#app',
data: {
message: '',
},
render: (h) => h(app),
});

step 5.在 app.vue中就可以直接以 this.$store 獲取並使用store

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div>
<h1>{{count}}</h1>
<button @click="addCount">Add</button>
</div>
</template>

<script>
export default {
computed: {
count() {
return this.$store.state.count;//獲取 store 的資料
}
},
methods: {
addCount() {
this.$store.commit("addCount");//commit mutation 到 store 中去更新資料
}
}
};
</script>

或是使用 Vuex 的工具函式 mapStatemapMutations

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div>
<h1>{{count}}</h1>
<button @click="addCount">Add</button>
</div>
</template>

<script>
import { mapState , mapMutations} from 'vuex';//引入 mapState 和 mapMutations
export default {
computed:mapState(['count']),
methods:mapMutations(['addCount']),
};
</script>

又或者改寫搭配 ES6 的物件 spread(…) 語法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div>
<h1>{{count}}</h1>
<button @click="addCount">Add</button>
</div>
</template>

<script>
import { mapState , mapMutations} from 'vuex';//引入 mapState 和 mapMutations
export default {
computed:{
...mapState(['count']),
},
methods:{
...mapMutations(['addCount']),
}
};
</script>

step 6.開始執行,就可以看到畫面中的計數器

Vue - 設置每頁的 title 及 圖標

過往要設置網頁的 title,直接在 html 中新增一個 tag,在其中的文字就會呈現在網頁標籤中。因此,一個基礎 html 網頁的架構大概會長這樣:

1
2
3
4
5
6
7
8
<html>
<head>
 <title>網頁標題</title>
<head>
<body>
 網頁內容
</body>
</html>

然而,通常網站不僅有一個網頁,建議每個網頁應當有一個專屬的title,以便管理。

透過 Vue Router 設置每頁的 title

step0.在自己專案中的 router.js 或 router/index.js 檔案中(依照個人在專案中的檔案命名及路徑),確定有引入 vue 及 vue-router

1
2
3
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);

step1. 加入每個路徑頁面的 meta 資料,且於meta中定義title文字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const router = new VueRouter({
routes: [
{
path: '/entrance',
name: 'entrance',
component: Entrance,
meta: {
title: 'Entrance'}
},{
path: '/login',
name: 'login',
component: Login,
meta: {
title: 'Login'}
},{
path: '/test',
name: 'test',
component: Test,
meta: {
title: 'Test'}
}]
})

step2.使用全域 navigation guards 非同步解析執行:當路徑(router object)前往(to) meta.title 時,瀏覽器的 title 為 meta.title

1
2
3
4
5
6
router.beforeEach((to, from, next) => {
if (to.meta.title) {
document.title = to.meta.title
}
next();
})

step3.最後引出該 router

1
export default router;

在 vue 專案中變更 title 圖示

step1.因為專案會使用 webpack 或 vue-cli 打包,因此將圖片放置根目錄,或是static資料夾中

step2.在 html 的 中配置圖片及文字。

1
2
3
4
5
<head>
<title>標題文字</title>
<!--href中的路徑依照專案實際放置圖示的位置。-->
<link rel="icon" href="./../static/favicon.ico" type=“image/x-icon>
</head>

step3.修改 webpack 配置檔案

1
2
3
4
5
new HtmlWebpackPlugin({
//...
favicon: path.resolve('/static/favicon.ico'), //路徑依照專案實際放置圖示的位置
//...
})

step4.重新啟用,並適時清除瀏覽器暫存資料,即可正確圖示畫面

JavaScript - let、const、var差異

過去的 JavaScript 使用”var“ 來做變數宣告,而在 ES6 中出現了”let“及”const“,它們的出現最主要是因為 區塊域(block) 的概念出現。

ES6的變異

總體而言,使用 letconst 的變數宣告方式,相較於 var 會更為嚴謹,因此建議在開發 ES6 之後的專案,使用letconst 作為變數宣吿,除了讓專案更穩定之外,也可以增加程式碼的可讀性。以下三點解釋:

  1. 區塊限制使用
  2. 變數「不」提升(hositing)
  3. 不允許重複宣告

區塊限制使用

使用 var 作變數宣告,即使在區塊內宣告,仍可以在區塊之外取用。例:

1
2
3
4
if(true){
var v1 = "hello";
}
console.log(v1);//hello

如果是使用 letconst 作變數宣告,則會受到區塊限制,無法於區塊之外取用,會報錯。例:

1
2
3
4
5
6
if(true){
let v2 = "hi";
const v3 = "holla";
}
console.log(v2);//Error:Uncaught ReferenceError: v2 is not defined
console.log(v3);//Error:Uncaught ReferenceError: v3 is not defined

變數「不」提升(hositing)

javascript的執行環境分為兩階段,會先經歷創建階段(creation),之後才正式進入程式碼執行階段(exection)。

變數提升指的是在創建階段,電腦的記憶體會為變數先空出記憶體,可是他還不知道它的值為何,會先給予一個undefined的值,而這個動作稱作「提升(hoisting)」/),等到執行階段賦值時,才會將值放進記憶體中。因此在 var 變數宣告前,取用該變數是可以的,並不會報錯。例:

1
2
console.log(value);//undefined
var value = '1234';

然而,在 ES6 的 letconst 移除了 hosting 這個不直覺的現象,在宣告之前不得取用,否則會報錯。例:

1
2
3
4
console.log(value1);//Error:Uncaught ReferenceError: Cannot access 'value1' before initialization
console.log(value2);//Error:Uncaught ReferenceError: Cannot access 'value2' before initialization
let value1 = '1234';
const value2 = '5678';

不允許重複宣告

使用 var 重複宣告,並不會報錯,後者會覆蓋掉前者。例:

1
2
3
var value = '1234';
var value = '5678';
console.log(value);//5678

而在 ES6 的 letconst 則移除了這樣不嚴謹的規則,每個變數在該區塊內為獨立宣告,不可重複宣告。例:

1
2
3
4
5
6
7
let value1 = '1234';
let value1 = '5678';
console.log(value1);//Error: Identifier 'value1' has already been declared

const value2 = '1234';
const value2 = '5678';
console.log(value2);//Error: Identifier 'value2' has already been declared

綜合變因

在區塊限制與變數不提升的綜合,會形成的錯誤。例:

1
2
3
4
5
var value = "1234";
if (true) {
value = '5678';//Error:Cannot access 'value' before initialization
let value;
}

在 if 區塊中,使用了會受區塊限制的 let ,則在該區塊中的 value 不得在宣告之前使用。

let 與 const 的區隔

const

通常const 使用在識別職(identifier),其意義為不會改變的常數,因此在一開始宣告就必須賦值,否則會出錯。例:

1
2
let value1;//undefined
const value2;//Error:Assignment to constant variable.

且若賦予一般的純值之後,即不可再被更動。

1
2
const value = '1234';
value = '5678';//Error:Assignment to constant variable.

1
2
3
4
5
6
7
8
9
const obj = {
name:'Amy',
age:'22',
};
obj = {
name:'John',
age:'25',
}
console.log(obj.name);//Error:Assignment to constant variable.

然而,如果是給予物件、陣列的值,在該變數的記憶體存放位置不變的情況之下,是可以變動的。例:

1
2
3
4
5
6
const obj = {
name:'Amy',
age:'22',
};
obj.name = 'John';
console.log(obj.name);//John

© 2020 Leah's Blog All Rights Reserved. 本站访客数人次 本站总访问量
Theme by hiero