- 組合式api(Composition API)算是vue3對我們開發者來說非常有價值的一個api更新,我們先不關注具體語法,先對它有一個大的感知
選項式api和組合式api倆種風格是並存的關係 並不是非此即彼
需要大量的邏輯組合的場景,可以使用compition API進行增強
上面我們通過圖示簡單瞭解了一下vue3帶來的全新的api形式,下面我們通過一個具體的小案例更加深入的體會一下倆種api下的開發模式對比,我們先暫時忽略語法細節,只關注程式碼編寫形式
倆個獨立的功能:
通過點選按鈕來控制p的顯示和隱藏
通過點選按鈕控制p內字型顏色的變化
<template> <div> <!-- 功能一模板 --> <button @click="show">顯示</button> <button @click="hide">隱藏</button> <div v-if="showDiv">一個被控制顯隱的div</div> </div> <div> <!-- 功能二模板 --> <button @click="changeRed">紅色</button> <button @click="changeYellow">藍色</button> <div :style="`color:${fontColor}`">一個被控制字型顏色的的div</div> </div> </template> <script> export default { name: 'App', data() { return { showDiv: true, // 功能一資料 fontColor: '' // 功能二資料 } }, methods: { // 功能一方法 show() { this.showDiv = true }, hide() { this.showDiv = false }, // 功能二方法 changeRed() { this.fontColor = 'red' }, changeYellow() { this.fontColor = 'blue' } } } </script>
<template> <div> <!-- 功能一模板 --> <button @click="show">顯示</button> <button @click="hide">隱藏</button> <div v-if="showDivFlag">一個被控制顯隱的div</div> </div> <div> <!-- 功能二模板 --> <button @click="changeRed">紅色</button> <button @click="changeBlue">藍色</button> <div :style="`color:${fontColor}`">一個被控制字型顏色的的div</div> </div> </template> <script> import { ref } from 'vue' export default { name: 'App', setup() { // 功能一 const showDivFlag = ref(true) function show() { showDivFlag.value = true } function hide() { showDivFlag.value = false } // 功能二 const fontColor = ref('') function changeRed() { fontColor.value = 'red' } function changeBlue() { fontColor.value = 'blue' } return { showDivFlag, show, hide, fontColor, changeRed, changeBlue } } } </script>
在這裡可能會有疑惑,那我們現在是把功能相關的所有資料和行為放到一起維護了,如果應用很大功能很多的情況下,setup函數不會變得很大嗎?豈不是又會變得比較難維護,接下來我們就來拆解一下龐大的setup函數
<script>import { ref } from 'vue'function useShow() { const showpFlag = ref(true) function show() { showpFlag.value = true } function hide() { showpFlag.value = false } return { showpFlag, show, hide }}function useColor() { const fontColor = ref('') function changeRed() { fontColor.value = 'red' } function changeBlue() { fontColor.value = 'blue' } return { fontColor, changeRed, changeBlue }}export default { name: 'App', setup() { // 功能一 const { showpFlag, show, hide } = useShow() // 功能二 const { fontColor, changeRed, changeBlue } = useColor() return { showpFlag, show, hide, fontColor, changeRed, changeBlue } }}</script>
以上,我們通過定義功能函數,把倆個功能相關的程式碼各自抽離到一個獨立的小函數中,然後通過在setUp函數中再把倆個小功能函陣列合起來,這樣一來,我們既可以把setup函數變得清爽,又可以方便維護快速定位功能位置
到此我們沒有關注api細節,只是體會組合式api給到我們的好處,接下來我們就要深入到api細節,看看全新的api都該如何使用 ↓
export default { setup () { console.log('setup執行了') console.log(this) }, beforeCreate() { console.log('beforeCreate執行了') console.log(this) }}
- 作用:reactive是一個函數,接收一個普通的物件傳入,把物件資料轉化為響應式物件並返回
使用步驟
從vue框架中匯入reactive函數
在setup函數中呼叫reactive函數並將物件資料傳入
在setup函數中把reactive函數呼叫完畢之後的返回值以物件的形式返回出去
程式碼落地
<template> <div>{{ state.name }}</div> <div>{{ state.age }}</div> <button @click="state.name = 'pink'">改值</button> </template> <script> import { reactive } from 'vue' export default { setup () { const state = reactive({ name: 'cp', age: 18 }) return { state } } } </script>
- 作用:ref是一個函數,接受一個簡單型別或者複雜型別的傳入並返回一個響應式且可變的 ref 物件
使用步驟
從vue框架中匯出ref函數
在setup函數中呼叫ref函數並傳入資料(簡單型別或者複雜型別)
在setup函數中把ref函數呼叫完畢的返回值以物件的形式返回出去
注意:在setup函數中使用ref結果,需要通過.value 存取,模板中使用不需要加.value
<template> <div>{{ money }}</div> <button @click="changeMondy">改值</button> </template> <script> import { ref } from 'vue' export default { setup() { let money = ref(100) console.log(money.value) return { money } } } </script>
總結說明:
ref 函數可以接收一個簡單型別的值,返回一個可改變的 ref 響應式物件,從而彌補reactive函數不支援簡單型別的問題
reactive和ref函數都可以提供響應式資料的轉換,具體什麼時候需要使用哪個API社群還沒有最佳實踐,大家暫時可以使用自己熟練的API進行轉換
推薦一種寫法:只有我們明確知道要轉換的物件內部的欄位名稱我們才使用reactive,否則就一律使用ref,從而降低在語法選擇上的心智負擔
- 場景: 經過reactive函數處理之後返回的物件,如果給這個物件解構或者展開,會讓資料丟失響應式的能力,為了解決這個問題需要引入toRefs函數,使用 toRefs函數 可以保證該物件展開的每個屬性都是響應式的
還是之前的案例,如果我們想在模板中省略到state,直接書寫name和age,你可能會想到,那我在return出去的時候把state中的屬性解構出來不就好了
修改前
<template> <div>{{ state.name }}</div> <div>{{ state.age }}</div> <button @click="state.name = 'pink'">改值</button> </template> <script> import { reactive } from 'vue' export default { setup() { const state = reactive({ name: 'cp', age: 18 }) return { state } } } </script>
解構修改後
<template> <div>{{ name }}</div> <div>{{ age }}</div> <button @click="name = 'pink'">改值</button> </template> <script> import { reactive } from 'vue' export default { setup() { const state = reactive({ name: 'cp', age: 18 }) return { ...state } } } </script>
<template> <div>{{ name }}</div> <div>{{ age }}</div> <button @click="name = 'pink'">改值</button> </template> <script> import { reactive,toRefs } from 'vue' export default { setup() { const state = reactive({ name: 'cp', age: 18 }) return { ...toRefs(state) } } } </script>
作用:根據現有響應式資料經過一定的計算得到全新的資料
使用步驟
從vue框架中匯入computed 函數
在setup函數中執行computed函數,並傳入一個函數,在函數中定義計算公式
把computed函數呼叫完的執行結果放到setup的return值物件中
<template> {{ list }} {{ filterList }} <button @click="changeList">change list</button></template><script>import { computed, ref } from 'vue'export default { setup() { const list = ref([1, 2, 3, 4, 5]) // 輸入大於3的數位 const filterList = computed(() => { return list.value.filter(item => item > 3) }) // 修改list的函數 function changeList() { list.value.push(6, 7, 8) } return { list, filterList, changeList } }}</script>
作用:基於響應式資料的變化執行回撥邏輯,和vue2中的watch的功能完全一致
普通監聽
立即執行
深度監聽
使用步驟
從vue框架中匯入watch函數
在setup函數中執行watch函數開啟對響應式資料的監聽
watch函數接收三個常規引數
<template> {{ age }} <button @click="age++">change age</button></template><script>import { ref, watch } from 'vue'export default { setup() { const age = ref(18) watch(() => { // 返回你想要監聽的響應式屬性(ref產生的物件必須加.value) return age.value }, () => { // 資料變化之後的回撥函數 console.log('age發生了變化') }) return { age } }}</script>
watch的效果預設狀態下,只有監聽的資料發生變化才會執行回撥,如果你需要在一上來的時候就立刻執行一次,需要設定一下
immediate
屬性
<template> {{ age }} <button @click="age++">change age</button></template><script>import { ref, watch } from 'vue'export default { setup() { const age = ref(18) watch(() => { // 返回你想要監聽的響應式屬性(ref產生的物件必須加.value) return age.value }, () => { // 資料變化之後的回撥函數 console.log('age發生了變化') },{ immediate: true}) return { age } }}</script>
當我們監聽的資料是一個物件的時候,預設狀態下,物件內部的屬性發生變化是不會引起回撥函數執行的,如果想讓物件下面所有屬性都能得到監聽,需要開啟
deep
設定
<template> {{ name }} {{ info.age }} <button @click="name = 'pink'">change name</button> <button @click="info.age++">change age</button></template><script>import { reactive, toRefs, watch } from 'vue'export default { setup() { const state = reactive({ name: 'cp', info: { age: 18 } }) watch(() => { return state }, () => { // 資料變化之後的回撥函數 console.log('age發生了變化') }, { deep: true }) return { ...toRefs(state) } }}</script>
使用watch的時候,儘量詳細的表明你到底要監聽哪個屬性,避免使用deep引起的效能問題,比如我僅僅只是想在state物件的age屬性變化的時候執行回撥,可以這麼寫
<template> {{ name }} {{ info.age }} <button @click="name = 'pink'">change name</button> <button @click="info.age++">change age</button></template><script>import { reactive, toRefs, watch } from 'vue'export default { setup() { const state = reactive({ name: 'cp', info: { age: 18 } }) watch(() => { // 詳細的告知你要監聽誰 return state.info.age }, () => { // 資料變化之後的回撥函數 console.log('age發生了變化') }) return { ...toRefs(state) } }}</script>
使用步驟
先從vue中匯入以on打頭的生命週期勾點函數
在setup函數中呼叫生命週期函數並傳入回撥函數
生命週期勾點函數可以呼叫多次
<template> <div>生命週期函數</div> </template> <script> import { onMounted } from 'vue' export default { setup() { // 時機成熟 回撥函數自動執行 onMounted(() => { console.log('mouted生命週期執行了') }) onMounted(() => { console.log('mouted生命週期函數又執行了') }) } } </script>
選項式API | 組合式API |
---|---|
beforeCreate | 不需要(直接寫到setup函數中) |
created | 不需要(直接寫到setup函數中) |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroyed | onBeforeUnmount |
destroyed | onUnmounted |
在vue3的組合式API中,父傳子的基礎套路完全一樣,基礎思想依舊為:父傳子是通過prop進行傳入,子傳父通過呼叫自定義事件完成
實現步驟
setup函數提供倆個引數,第一個引數為props,第二個引數為一個物件context
props為一個物件,內部包含了父元件傳遞過來的所有prop資料,context物件包含了attrs,slots, emit屬性,其中的emit可以觸發自定義事件的執行從而完成子傳父
程式碼落地app.vue
<template> <son :name="name" @get-msg="getMsg"></son></template><script>import { ref } from 'vue'import Son from './components/son'export default { components: { Son }, setup() { const name = ref('cp') function getMsg(msg) { console.log(msg) } return { name, getMsg } }}</script>
components/son.vue
<template> <div> {{name}} <button @click="setMsgToSon">set</button> </div> </template> <script> export default { props: { name: { type: String } }, emits: ['get-msg'], // 宣告當前元件觸發的自定義事件 setup(props,{emit}) { console.log(props.name) function setMsgToSon(){ emit('get-msg','這是一條來自子元件的msg資訊') } return { setMsgToSon } } } </script>
通常我們使用props進行父子之間的資料傳遞,但是如果元件巢狀層級較深,一層一層往下傳遞將會變的非常繁瑣,有沒有一種手段可以把這個過程簡化一下呢,有的,就是我們馬上要學習的provide 和 inject,它們配合起來可以方便的完成跨層傳遞資料
來個需求: 爺元件中有一份資料 傳遞給孫元件直接使用
實現步驟:
頂層元件在setup方法中使用provide函數提供資料
任何底層元件在setup方法中使用inject函數獲取資料
程式碼落地爺爺元件 - app.vue
<template> <father></father></template><script>import Father from '@/components/Father'import { provide } from 'vue'export default { components: { Father }, setup() { let name = '柴柴老師' // 使用provide設定項注入資料 key - value provide('name', name) }}</script>
孫元件 - components/Son.vue
<template> 我是子元件 {{ name }}</template><script>import { inject } from 'vue'export default { setup() { const name = inject('name') return { name } }}</script>
事實上,只要是後代元件,都可以方便的獲取頂層元件提供的資料
provide預設情況下傳遞的資料不是響應式的,也就是如果對provide提供的資料進行修改,並不能響應式的影響到底層元件使用資料的地方,如果想要傳遞響應資料也非常簡單,只需要將傳遞的資料使用ref或者reactive生成即可
app.vue
<template> <father></father> <button @click="changeName">change name</button></template><script>import Father from '@/components/Father'import { provide, ref } from 'vue'export default { components: { Father }, setup() { // 使用ref轉換成響應式再傳遞 let name = ref('柴柴老師') function changeName(){ name.value = 'pink' } provide('name', name) return { changeName } }}</script>
在模板中使用ref,我們都很清楚,它一般有三種使用場景
ref + 普通dom標籤 獲取真實dom物件
ref + 元件標籤 獲取元件範例物件
ref + v-for 獲取由dom物件(範例物件)組成的陣列 (不經常使用)
實現步驟
使用ref函數傳入null建立 ref物件 => const hRef = ref(null)
模板中通過定義ref屬性等於1中建立的ref物件名稱建立關聯 => <h1 ref="hRef"></h1>
使用 =>hRef.value
程式碼落地components/RefComponent.vue
<template> 我是一個普通的元件</template>
app.vue
<template> <h1 ref="h1Ref">我是普通dom標籤</h1> <ref-comoonent ref="comRef"></ref-comoonent></template><script>import { onMounted, ref } from 'vue'import RefComoonent from '@/components/RefComponent'export default { components: { RefComoonent }, setup() { const h1Ref = ref(null) const comRef = ref(null) onMounted(() => { console.log(h1Ref.value) console.log(comRef.value) }) // 必須return return { h1Ref, comRef } }}</script>
核心功能
渲染列表資料 v-for
點選刪除當前列表 splice + index
回車新增新專案 @keyup.enter=「addTodo」 list.unshift
選擇狀態切換 v-model
多選和取消多選 計算屬性的set和get
未完成任務數量統計 computed
<template> <section class="todoapp"> <!-- 頭部輸入框區域 --> <header class="header"> <h1>todos</h1> <input class="new-todo" placeholder="請輸入要完成的任務" autofocus v-model="curTask" @keyup.enter="add" /> </header> <section class="main"> <!-- 全選切換input --> <input id="toggle-all" class="toggle-all" type="checkbox" v-model="isAll"/> <label for="toggle-all">標記所有已經完成</label> <ul class="todo-list"> <!-- 任務列表 --> <li v-for="(item, index) in list" :key="item.id"> <p class="view"> <!-- 雙向繫結 flag --> <input class="toggle" type="checkbox" v-model="item.flag" /> <label>{{ item.name }}</label> <!-- 刪除按鈕 --> <button class="destroy" @click="del(index)"></button> </p> </li> </ul> </section> <footer class="footer"> <span class="todo-count"> 還未完成的任務有:<strong>{{count}}</strong>項 </span> </footer> </section></template><script>import { computed, ref } from 'vue'export default { setup() { const list = ref([ { id: 1, name: '吃飯', flag: false }, { id: 2, name: '睡覺', flag: false }, { id: 3, name: '打豆豆', flag: true } ]) // 刪除函數 function del(index) { // index 要刪除項的下標值 // splice list.value.splice(index, 1) } const curTask = ref('') function add() { // 新增邏輯 list.value.unshift({ id: new Date(), name: curTask.value, flag: false }) curTask.value = '' } // 全選取消全選 // {name:"cp"} console.log(info.name) info.name = 'pink' const isAll = computed({ // 獲取isAll資料的時候會執行get函數 get() { // 當list列表中所有項的flag屬性都為true 就為true // every return list.value.every(item => item.flag === true) }, set(val) { // 拿到isAll最新值 遍歷一下list 把裡面的flag屬性設定為最新值 list.value.forEach(item => { item.flag = val }) } }) // 計算未完成的任務 const count = computed(()=>{ return list.value.filter(item=>item.flag === false).length }) return { list, del, curTask, add, isAll, count } }}</script><style>html, body { margin: 0; padding: 0;}button { margin: 0; padding: 0; border: 0; background: none; font-size: 100%; vertical-align: baseline; font-family: inherit; font-weight: inherit; color: inherit; -webkit-appearance: none; appearance: none; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;}body { font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif; line-height: 1.4em; background: #f5f5f5; color: #111111; min-width: 230px; max-width: 550px; margin: 0 auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; font-weight: 300;}:focus { outline: 0;}.hidden { display: none;}.todoapp { background: #fff; margin: 130px 0 40px 0; position: relative; box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);}.todoapp input::-webkit-input-placeholder { font-style: italic; font-weight: 300; color: rgba(0, 0, 0, 0.4);}.todoapp input::-moz-placeholder { font-style: italic; font-weight: 300; color: rgba(0, 0, 0, 0.4);}.todoapp input::input-placeholder { font-style: italic; font-weight: 300; color: rgba(0, 0, 0, 0.4);}.todoapp h1 { position: absolute; top: -140px; width: 100%; font-size: 80px; font-weight: 200; text-align: center; color: #b83f45; -webkit-text-rendering: optimizeLegibility; -moz-text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;}.new-todo, .edit { position: relative; margin: 0; width: 100%; font-size: 24px; font-family: inherit; font-weight: inherit; line-height: 1.4em; color: inherit; padding: 6px; border: 1px solid #999; box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); box-sizing: border-box; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;}.new-todo { padding: 16px 16px 16px 60px; border: none; background: rgba(0, 0, 0, 0.003); box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03);}.main { position: relative; z-index: 2; border-top: 1px solid #e6e6e6;}.toggle-all { width: 1px; height: 1px; border: none; /* Mobile Safari */ opacity: 0; position: absolute; right: 100%; bottom: 100%;}.toggle-all + label { width: 60px; height: 34px; font-size: 0; position: absolute; top: -52px; left: -13px; -webkit-transform: rotate(90deg); transform: rotate(90deg);}.toggle-all + label:before { content: "❯"; font-size: 22px; color: #e6e6e6; padding: 10px 27px 10px 27px;}.toggle-all:checked + label:before { color: #737373;}.todo-list { margin: 0; padding: 0; list-style: none;}.todo-list li { position: relative; font-size: 24px; border-bottom: 1px solid #ededed;}.todo-list li:last-child { border-bottom: none;}.todo-list li.editing { border-bottom: none; padding: 0;}.todo-list li.editing .edit { display: block; width: calc(100% - 43px); padding: 12px 16px; margin: 0 0 0 43px;}.todo-list li.editing .view { display: none;}.todo-list li .toggle { text-align: center; width: 40px; /* auto, since non-WebKit browsers doesn't support input styling */ height: auto; position: absolute; top: 0; bottom: 0; margin: auto 0; border: none; /* Mobile Safari */ -webkit-appearance: none; appearance: none;}.todo-list li .toggle { opacity: 0;}.todo-list li .toggle + label { background-image: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: center left;}.todo-list li .toggle:checked + label { background-image: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E");}.todo-list li label { word-break: break-all; padding: 15px 15px 15px 60px; display: block; line-height: 1.2; transition: color 0.4s; font-weight: 400; color: #4d4d4d;}.todo-list li.completed label { color: #cdcdcd; text-decoration: line-through;}.todo-list li .destroy { display: none; position: absolute; top: 0; right: 10px; bottom: 0; width: 40px; height: 40px; margin: auto 0; font-size: 30px; color: #cc9a9a; margin-bottom: 11px; transition: color 0.2s ease-out;}.todo-list li .destroy:hover { color: #af5b5e;}.todo-list li .destroy:after { content: "×";}.todo-list li:hover .destroy { display: block;}.todo-list li .edit { display: none;}.todo-list li.editing:last-child { margin-bottom: -1px;}.footer { padding: 10px 15px; height: 20px; text-align: center; font-size: 15px; border-top: 1px solid #e6e6e6;}.footer:before { content: ""; position: absolute; right: 0; bottom: 0; left: 0; height: 50px; overflow: hidden; box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6, 0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6, 0 17px 2px -6px rgba(0, 0, 0, 0.2);}.todo-count { float: left; text-align: left;}.todo-count strong { font-weight: 300;}.filters { margin: 0; padding: 0; list-style: none; position: absolute; right: 0; left: 0;}.filters li { display: inline;}.filters li a { color: inherit; margin: 3px; padding: 3px 7px; text-decoration: none; border: 1px solid transparent; border-radius: 3px;}.filters li a:hover { border-color: rgba(175, 47, 47, 0.1);}.filters li a.selected { border-color: rgba(175, 47, 47, 0.2);}.clear-completed, html .clear-completed:active { float: right; position: relative; line-height: 20px; text-decoration: none; cursor: pointer;}.clear-completed:hover { text-decoration: underline;}.info { margin: 65px auto 0; color: #4d4d4d; font-size: 11px; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); text-align: center;}.info p { line-height: 1;}.info a { color: inherit; text-decoration: none; font-weight: 400;}.info a:hover { text-decoration: underline;}/* Hack to remove background from Mobile Safari. Can't use it globally since it destroys checkboxes in Firefox */@media screen and (-webkit-min-device-pixel-ratio: 0) { .toggle-all, .todo-list li .toggle { background: none; } .todo-list li .toggle { height: 40px; }}@media (max-width: 430px) { .footer { height: 50px; } .filters { bottom: 10px; }}</style>
以上就是超詳細!圖文講解Vue3的組合式API!的詳細內容,更多請關注TW511.COM其它相關文章!