Vue
HiSKIO
<body>
<div id="app">...</div> // 受 Vue 影響
<div id="noVue">...</div> // 不受 Vue 影響
<script src="./js/vue.js"></script>
<script>
const App = {
setup () {
return {
}
}
}
Vue.createApp(App).mount('#app')
</script>
</body>
createApp()
: 創建 Vue 實體
mount()
:掛載到 DOM
body
之下用一個 div
把全部的內容包起來
setup
在 props
進來的資料被解析後,就會作為 composition API 的入口點。
在 setup
被 return 出去的任何內容都可以被組件其他地方使用。
setup
在組件創建前就執行了,因此沒有 this
。能使用的只有 props
進來的資料。
ref
利用 ref
綁定變數,進行響應式改變。
<div id="app">
<h1>{{ text }}</h1>
</div>
錯誤範例:
<script>
const App = {
setup () {
let text = 'Hello Vue'
setTimeout(() => {
text = 'Hello world'
console.log(text)
}, 1000)
return {
text
}
}
}
Vue.createApp(App).mount('#app')
</script>
ref
把接受的參數放進 value
這個 property 中,並以物件的形式返回,即可響應式地改變資料。
{{ text }}
,不需要把 .value
寫出來。正確範例:
<script>
const App = {
setup () {
const text = Vue.ref('Hello Vue')
setTimeout(() => {
text.value = 'Hello world'
console.log(text)
}, 1000)
return {
text
}
}
}
Vue.createApp(App).mount('#app')
</script>
也可以使用解構的方式使用:
<script>
const { ref } = Vue
const App = {
setup () {
const text = ref('Hello Vue')
setTimeout(() => {
text.value = 'Hello world'
console.log(text)
}, 1000)
return {
text
}
}
}
Vue.createApp(App).mount('#app')
</script>
其原理:
call by reference v.s. call by value
reactive
reactive
也可以對變數進行綁定,並達到響應式改變的功能。
reactive
只接受物件或陣列的型別變數。
<div id="app">
<h1>{{ message.text }}</h1>
</div>
<script>
const { reactive } = Vue
const App = {
setup () {
const message = reactive({ text: 'Hello Vue' })
setTimeout(() => {
message.text = 'Hello world'
console.log(message)
}, 1000)
return {
message
}
}
}
Vue.createApp(App).mount('#app')
</script>
ref
v.s reactive
在大多數情況下兩者可以互相替換使用,主要取決於個人喜好或者團隊習慣,根據具體情況去決定要使用哪個就好。
ref( )
: 可以接受任何型態的資料,但是不會對物件或陣列內部的屬性變動做監聽。reactive( )
: 只能接受物件或陣列,可以做深層的監聽,以及訪問資料不需要 .value
。
<div id="app">
<h1>{{ refMessage }}</h1>
<h1>{{ reactiveMessage.text }}</h1>
</div>
<script>
const { ref, reactive } = Vue
const App = {
setup () {
const refMessage = ref('Hello Vue')
const reactiveMessage = reactive({ text: 'Hello Vue' })
setTimeout(() => {
refMessage.value = 'Hello ref'
reactiveMessage.text = 'Hello reactive'
console.log(refMessage)
console.log(reactiveMessage)
}, 1000)
return {
refMessage,
reactiveMessage
}
}
}
Vue.createApp(App).mount('#app')
</script>
readonly
readonly
讓 reactive
或 ref
的資料變成只能讀取不能修改。適合在傳遞參數時避免修改到資料
<div id="app">
<h1>{{ number.index }}</h1>
<button @click="addNumber">Add</button>
<button @click="addNumberReadonly">Add ReadOnly</button>
</div>
<script>
const { reactive, readonly } = Vue
const App = {
setup () {
const number = reactive({ index: 0 })
const numberReadonly = readonly(number)
const addNumber = () => {
number.index++
console.log('1.number', number)
console.log('1.numberReadonly', numberReadonly)
}
// 會報 warning
const addNumberReadonly = () => {
numberReadonly.index++
console.log('2.number', number)
console.log('2.numberReadonly', numberReadonly)
}
return {
number,
numberReadonly,
addNumber,
addNumberReadonly
}
},
};
Vue.createApp(App).mount("#app")
</script>
v-for
key
的重要性當改變 v-for
中的資料時,因為有綁定 key
,所以只會針對那幾筆資料進行變動,而不會對整個陣列重新渲染。所以 key
要是唯一值。
不要拿 index
當 key
v-if
v.s v-show
v-if
:一開始就決定要不要渲染出來。
v-show
:先渲染出來,再用 display: none / block
來隱藏或顯示。
computed
<div id="app">
<a @click="handleListShow" class="title">課程列表</a>
<ul class="box" :style="{height: listHeight}">
<li v-for="(list, index) in listArray" :key="list">{{ list.name }}</li>
</ul>
</div>
<script>
const { ref, reactive, computed } = Vue
const App = {
setup () {
const isOpen = ref(true)
const listArray = reactive([
{ name: "2020 Vue3 專業職人 | 入門篇", money: 3200 },
{ name: "2020 Vue3 專業職人 | 加值篇", money: 100 },
{ name: "2020 Vue3 專業職人 | 進階篇", money: 500 },
{ name: "現代 JavaScript 職人之路|入門篇", money: 300 },
{ name: "現代 JavaScript 職人之路|中階實戰篇", money: 1600 },
{ name: "職人必修的RWD 網頁入門班", money: 900 },
{ name: "HTML5+Animate CC 網頁動畫與遊戲互動", money: 2000 },
{ name: "現代 JavaScript 職人之路|面試篇", money: 1800 },
])
const listHeight = computed(() => {
return isOpen.value ? `${listArray.length * 40}px` : '0px'
})
const handleListShow = () => {
isOpen.value = !isOpen.value
}
return {
isOpen,
listArray,
listHeight,
handleListShow,
}
},
};
Vue.createApp(App).mount("#app")
</script>
computed
v.s methods
computed
會依據計算的資料進行緩存,只要資料沒有被更改,computed
就不會被重新計算執行。但無法傳入參數進行處理。
methods
不會進行緩存,每次都會重新執行,但是可以傳入參數進行處理。
使用選擇:依照 緩存/效能 與 傳參數 決定。
以上範例可以用 methods
改寫成這樣:
<div id="app">
<a @click="handleListShow" class="title">課程列表</a>
<!-- 使用 methods 要加 () -->
<ul class="box" :style="{height: calculateListHeight()}">
<li v-for="(list, idx) in listArray" :key="list">{{list.name}}</li>
</ul>
</div>
<script>
const { ref, reactive, computed } = Vue
const App = {
setup () {
const isOpen = ref(true)
const listArray = reactive([
{ name: "2020 Vue3 專業職人 | 入門篇", money: 3200 },
{ name: "2020 Vue3 專業職人 | 加值篇", money: 100 },
{ name: "2020 Vue3 專業職人 | 進階篇", money: 500 },
{ name: "現代 JavaScript 職人之路|入門篇", money: 300 },
{ name: "現代 JavaScript 職人之路|中階實戰篇", money: 1600 },
{ name: "職人必修的RWD 網頁入門班", money: 900 },
{ name: "HTML5+Animate CC 網頁動畫與遊戲互動", money: 2000 },
{ name: "現代 JavaScript 職人之路|面試篇", money: 1800 },
])
const listHeight = computed(() => {
return isOpen.value ? `${listArray.length * 40}px` : '0px'
})
const calculateListHeight = () => {
return isOpen.value ? `${listArray.length * 40}px` : '0px'
}
const handleListShow = () => {
isOpen.value = !isOpen.value
}
return {
isOpen,
listArray,
listHeight,
handleListShow,
calculateListHeight
}
},
}
Vue.createApp(App).mount("#app")
</script>
computed
組合資料應用:
<div id="app">
<a @click="handleListShow" class="title">課程列表</a>
<ul class="box" :style="{height: listHeight}">
<li v-for="(list, idx) in newListArray" :key="list">{{ list }}</li>
</ul>
</div>
<script>
const { ref, reactive, computed } = Vue
const App = {
setup () {
const isOpen = ref(true)
const listArray = reactive([
{ name: "2020 Vue3 專業職人 | 入門篇", money: 3200 },
{ name: "2020 Vue3 專業職人 | 加值篇", money: 100 },
{ name: "2020 Vue3 專業職人 | 進階篇", money: 500 },
{ name: "現代 JavaScript 職人之路|入門篇", money: 300 },
{ name: "現代 JavaScript 職人之路|中階實戰篇", money: 1600 },
{ name: "職人必修的RWD 網頁入門班", money: 900 },
{ name: "HTML5+Animate CC 網頁動畫與遊戲互動", money: 2000 },
{ name: "現代 JavaScript 職人之路|面試篇", money: 1800 },
])
const newListArray = computed(() => {
const map = listArray.map((item, index) => {
return `${index + 1}. ${item.name} $:${item.money}`
})
return map
})
const listHeight = computed(() => {
return isOpen.value ? `${listArray.length * 40}px` : '0px'
})
const calculateListHeight = () => {
return isOpen.value ? `${listArray.length * 40}px` : '0px'
}
const handleListShow = () => {
isOpen.value = !isOpen.value
}
return {
isOpen,
newListArray,
listHeight,
handleListShow
}
},
}
Vue.createApp(App).mount("#app")
</script>
v-if
與 v-for
不推薦一起使用!v-if
的執行優先順序高於 v-for
。
正確做法:先透過 computed
把資料篩選出來後再進行 v-for
computed
的值,記得加上 .value
<div id="app">
<a @click="handleListShow" class="title">課程列表</a>
<ul class="box" :style="{height: listHeight}">
<li v-for="(list, index) in newListArray" :key="list">{{ list }}</li>
</ul>
</div>
<script>
const { ref, reactive, computed } = Vue
const App = {
setup () {
const isOpen = ref(true)
const listArray = reactive([
{ name: "2020 Vue3 專業職人 | 入門篇", money: 3200 },
{ name: "2020 Vue3 專業職人 | 加值篇", money: 100 },
{ name: "2020 Vue3 專業職人 | 進階篇", money: 500 },
{ name: "現代 JavaScript 職人之路|入門篇", money: 300 },
{ name: "現代 JavaScript 職人之路|中階實戰篇", money: 1600 },
{ name: "職人必修的RWD 網頁入門班", money: 900 },
{ name: "HTML5+Animate CC 網頁動畫與遊戲互動", money: 2000 },
{ name: "現代 JavaScript 職人之路|面試篇", money: 1800 },
])
const filteredListArray = computed(() => {
const filter = listArray.filter(item => {
return item.money > 500
})
return filter
})
const newListArray = computed(() => {
const map = filteredListArray.value.map((item, index) => {
return `${index + 1}. ${item.name} $:${item.money}`
})
return map
})
const listHeight = computed(() => {
console.log(filteredListArray)
return isOpen.value ? `${newListArray.value.length * 40}px` : '0px'
})
const handleListShow = () => {
isOpen.value = !isOpen.value
}
return {
isOpen,
newListArray,
listHeight,
handleListShow
}
}
}
Vue.createApp(App).mount("#app")
</script>
watch
watch(..., (newValue, oldValue) => {
......
})
第一個參數:要被監控的值
第二個參數:callback function
<div id="app">
<h1>refNumber: {{ refNumber }}</h1>
<h1>refString: {{ refString }}</h1>
<h1>refBoolean: {{ refBoolean }}</h1>
<button @click="changeValue">change value</button>
</div>
<script>
const { ref, watch } = Vue
const App = {
setup() {
const refNumber = ref(0)
const refString = ref('Hello')
const refBoolean = ref(true)
watch(refNumber, (newValue, oldValue) => {
console.log('refNumber newValue', newValue)
console.log('refNumber oldValue', oldValue)
})
watch(refString, (newValue, oldValue) => {
console.log('refString newValue', newValue)
console.log('refString oldValue', oldValue)
})
watch(refBoolean, (newValue, oldValue) => {
console.log('refBoolean newValue', newValue)
console.log('refBoolean oldValue', oldValue)
})
const changeValue = () => {
refNumber.value++
refString.value = refString.value + ' Vue'
refBoolean.value = !refBoolean.value
}
return {
refNumber,
refString,
refBoolean,
changeValue
}
}
}
Vue.createApp(App).mount("#app")
</script>
資料的值可以被改變,也能被監聽到。
使用 array
把要監聽的值包起來
<div id="app">
<h1>refString1: {{ refString1 }}</h1>
<h1>refString2: {{ refString2 }}</h1>
<button @click="changeValue">change value</button>
</div>
<script>
const { ref, watch } = Vue
const App = {
setup() {
const refString = ref('Hello')
const refString1 = ref('Vue')
const refString2 = ref('World'
watch([refString1, refString2], (newValue, oldValue) => {
console.log('newValue', newValue)
console.log('oldValue', oldValue)
})
const changeValue = () => {
refString1.value = refString.value + refString1.value
refString2.value = refString.value + refString2.value
}
return {
refString1,
refString2,
changeValue
}
}
}
Vue.createApp(App).mount("#app")
</script>
<div id="app">
<h1>refArray: {{ refArray }}</h1>
<h1>refObject: {{ refObject }}</h1>
<button @click="changeValue">change value</button>
</div>
<script>
const { ref, watch } = Vue
const App = {
setup() {
const refArray = ref([1, 2, 3, 4, 5])
const refObject = ref({ index: 0 })
watch(refArray, (newValue, oldValue) => {
console.log('refArray newValue', newValue)
console.log('refArray oldValue', oldValue)
})
watch(refObject, (newValue, oldValue) => {
console.log('refObject newValue', newValue)
console.log('refObject oldValue', oldValue)
})
const changeValue = () => {
refArray.value[0] = 0
refObject.value.index++
}
return {
refArray,
refObject,
changeValue
}
}
}
Vue.createApp(App).mount("#app")
</script>
資料的值可以被改變,卻無法被監聽到。
<script>
const { ref, watch } = Vue
const App = {
setup() {
const refArray = ref([1, 2, 3, 4, 5])
const refObject = ref({ index: 0 })
watch(refArray.value[0], (newValue, oldValue) => {
console.log('refArray newValue', newValue)
console.log('refArray oldValue', oldValue)
})
watch(refObject.value.index, (newValue, oldValue) => {
console.log('refObject newValue', newValue)
console.log('refObject oldValue', oldValue)
})
const changeValue = () => {
refArray.value[0] = 0
refObject.value.index++
}
return {
refArray,
refObject,
changeValue
}
}
}
Vue.createApp(App).mount("#app")
</script>
會顯示 warning:
Invalid watch source: ~~
A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.
若要監聽物件中某個 key 的值,就必須用一個 function return 該值。
<script>
const { ref, watch } = Vue
const App = {
setup() {
const refArray = ref([1, 2, 3, 4, 5])
const refObject = ref({ index: 0 })
watch(
() => refArray.value[0],
(newValue, oldValue) => {
console.log('refArray newValue', newValue)
console.log('refArray oldValue', oldValue)
}
)
watch(
() => refObject.value.index,
(newValue, oldValue) => {
console.log('refObject newValue', newValue)
console.log('refObject oldValue', oldValue)
}
)
const changeValue = () => {
refArray.value[0] = 0
refObject.value.index++
}
return {
refArray,
refObject,
changeValue
}
}
}
Vue.createApp(App).mount("#app")
</script>
<div id="app">
<h1>reactiveArray: {{ reactiveArray }}</h1>
<h1>reactiveObject: {{ reactiveObject }}</h1>
<button @click="changeValue">change value</button>
</div>
<script>
const { reactive, watch } = Vue
const App = {
setup() {
const reactiveArray = reactive([1, 2, 3, 4, 5])
const reactiveObject = reactive({ index: 0 })
watch(reactiveArray, (newValue, oldValue) => {
console.log('reactiveArray newValue', newValue)
console.log('reactiveArray oldValue', oldValue)
})
watch(reactiveObject, (newValue, oldValue) => {
console.log('reactiveObject newValue', newValue)
console.log('reactiveObject oldValue', oldValue)
})
const changeValue = () => {
reactiveArray[0] = 0
reactiveObject.index++
}
return {
reactiveArray,
reactiveObject,
changeValue
}
}
}
Vue.createApp(App).mount("#app")
</script>
雖然資料的值可以被改變,也能被監聽到,但新舊值卻是相同的。
若有需要監控舊值,則必須在第一個參數 callback function return 一個 深拷貝 的值。
<script>
const { reactive, watch } = Vue
const App = {
setup() {
const reactiveArray = reactive([1, 2, 3, 4, 5])
const reactiveObject = reactive({ index: 0 })
watch(
() => JSON.parse(JSON.stringify(reactiveArray)),
(newValue, oldValue) => {
console.log('reactiveArray newValue', newValue)
console.log('reactiveArray oldValue', oldValue)
}
)
/* Array 的深拷貝也可以用解構賦值的方式 */
/*
watch(
() => [...reactiveArray],
(newValue, oldValue) => {
console.log('reactiveArray newValue', newValue)
console.log('reactiveArray oldValue', oldValue)
},
)
*/
watch(
() => JSON.parse(JSON.stringify(reactiveObject)),
(newValue, oldValue) => {
console.log('reactiveObject newValue', newValue)
console.log('reactiveObject oldValue', oldValue)
}
)
const changeValue = () => {
reactiveArray[0] = 0
reactiveObject.index++
}
return {
reactiveArray,
reactiveObject,
changeValue
}
}
}
Vue.createApp(App).mount("#app")
</script>
<script>
const { reactive, watch } = Vue
const App = {
setup() {
const reactiveArray = reactive([1, 2, 3, 4, 5])
const reactiveObject = reactive({ index: 0 })
watch(reactiveArray[0], (newValue, oldValue) => {
console.log('reactiveArray newValue', newValue)
console.log('reactiveArray oldValue', oldValue)
})
watch(reactiveObject.index, (newValue, oldValue) => {
console.log('reactiveObject newValue', newValue)
console.log('reactiveObject oldValue', oldValue)
})
const changeValue = () => {
reactiveArray[0] = 0
reactiveObject.index++
}
return {
reactiveArray,
reactiveObject,
changeValue
}
}
}
Vue.createApp(App).mount("#app")
</script>
同 ref
會顯示 warning:
Invalid watch source: ~~
A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.
若要監聽物件中某個 key 的值,就必須用一個 function return 該值。
<script>
const { reactive, watch } = Vue
const App = {
setup() {
const reactiveArray = reactive([1, 2, 3, 4, 5])
const reactiveObject = reactive({ index: 0 })
watch(
() => reactiveArray[0],
(newValue, oldValue) => {
console.log('reactiveArray newValue', newValue)
console.log('reactiveArray oldValue', oldValue)
}
)
watch(
() => reactiveObject.index,
(newValue, oldValue) => {
console.log('reactiveObject newValue', newValue)
console.log('reactiveObject oldValue', oldValue)
}
)
const changeValue = () => {
reactiveArray[0] = 0
reactiveObject.index++
}
return {
reactiveArray,
reactiveObject,
changeValue
}
}
}
Vue.createApp(App).mount("#app")
</script>
加上第三個參數:{ deep: true }
deep
會很耗效能,因為他會針對物件中的每個 key
進行掃描。
<div id="app">
<h1>name: {{ reactiveNestedObject.user.name }}</h1>
<h1>age: {{ reactiveNestedObject.user.age }}</h1>
<button @click="changeValue">change value</button>
</div>
<script>
const { reactive, watch } = Vue
const App = {
setup() {
const reactiveNestedObject = reactive({
user: {
name: 'John',
age: 18
}
})
watch(
() => reactiveNestedObject,
(newValue, oldValue) => {
console.log('no deep')
console.log('reactiveObject newValue', newValue)
console.log('reactiveObject oldValue', oldValue)
}
)
watch(
() => reactiveNestedObject,
(newValue, oldValue) => {
console.log('deep')
console.log('reactiveObject newValue', newValue)
console.log('reactiveObject oldValue', oldValue)
},
{ deep: true }
)
const changeValue = () => {
reactiveNestedObject.user.name = 'Mary'
reactiveNestedObject.user.age = 20
}
return {
reactiveNestedObject,
changeValue
}
}
}
Vue.createApp(App).mount("#app")
</script>
只有加 deep
的才有被監控到。但注意整個物件的新舊值仍然相同。
若今天直接對 reactive
物件做深拷貝,就不需要加 deep
。
ref
直接監聽。reactive
callback function return 物件的深拷貝。ref
或 reactive
callback function return 物件中該 index / key 的值。參考資料:
watchEffect
watchEffect
與 watch
功能相似,都可以監控資料,但不同的點是:
watchEffect
functionwatchEffect
function 內被調用的值有被改變到,才會執行 watchEffect
function資料初始化時,watchEffect
就會先執行
<script>
const { ref, reactive, watchEffect } = Vue
const App = {
setup () {
const number = ref(0)
const numberObject = reactive({ index: 0 })
watchEffect(() => {
console.log('number', number.value)
})
setTimeout(() => {
number.value++
numberObject.index++
}, 1000)
return {}
},
};
Vue.createApp(App).mount("#app")
</script>
若 watchEffect
內被調用的值未改變,則 watchEffect
就不會再執行,反之亦然。
<script>
const { ref, reactive, watchEffect } = Vue
const App = {
setup () {
const number = ref(0)
const numberObject = reactive({ index: 0 })
watchEffect(() => {
console.log('number', number.value)
})
setTimeout(() => {
// number.value++
numberObject.index++
}, 1000)
return {}
},
};
Vue.createApp(App).mount("#app")
</script>
<script>
const { ref, reactive, watchEffect } = Vue
const App = {
setup () {
const number = ref(0)
const numberObject = reactive({ index: 0 })
watchEffect(() => {
console.log('number', number.value)
console.log('numberObject', numberObject.index)
})
setTimeout(() => {
// number.value++
numberObject.index++
}, 1000)
return {}
},
};
Vue.createApp(App).mount("#app")
</script>
watchEffect
可以被停用
watchStop
為自行命名的變數。只要命名變數與被執行的 function 相同,就可以停用 watchEffect
。setInterval
還是在執行!若要停止 setInterval
,就必須 clearInterval
。
<script>
const { ref, reactive, watchEffect } = Vue
const App = {
setup () {
const number = ref(0)
let timer = null
const watchStop = watchEffect(() => {
console.log(number.value)
if (number.value >= 5) {
watchStop()
clearInterval(timer)
}
})
timer = setInterval(() => {
number.value++
}, 1000)
return {}
},
};
Vue.createApp(App).mount("#app")
</script>
setup()
也是一種生命週期,
onUpdated
是因為資料改變,導致 virtual DOM 重新渲染後,才會調用。
<div id="app">
<h1>{{ number }}</h1>
</div>
<script>
const { ref, onBeforeMount, onMounted, onUpdated } = Vue
const App = {
setup() {
console.log('當 Vue 掛載到 #app 後執行')
const number = ref(0)
setTimeout(() => {
number.value = 1
console.log(number.value)
}, 3000)
onBeforeMount(() => {
// DOM 渲染前
console.log('DOM 渲染前')
})
onMounted(() => {
// DOM 渲染完成後
console.log('DOM 渲染完成後')
})
onUpdated(() => {
// 在資料更改導致 virtual DOM 重新渲染後調用
console.log('資料更改後')
})
return {
number
}
}
}
Vue.createApp(App).mount("#app")
</script>
詳細資料參閱:
Lifecycle hooks | Vue.js