一、v-model進階
復習 v-model
v-model: 雙向數據綁定指令
數據 <-> 視圖: 數據和視圖相互影響, 因此被稱為雙向數據綁定指令
1> 數據變了, 視圖也會跟著變 (數據驅動視圖)
2> 視圖變了, 數據也會跟著變
1. v-model 原理
v-model只是一個語法糖,? 比較好用, 可以減少代碼量, v-model 作用再原生的 input 上本質就是給input傳了value屬性(v-bind綁定value屬性,從而數據可以影響視圖),并且監聽了input事件,通過input事件拿到輸入框的值(實現視圖影響數據)
本質:? :value = "數據" + @input="數據?= 輸入框的值" (v-bind,v-on)
我們先使用v-bind/: 實現數據影響視圖
注意: input 里面的value值是輸入框里面的默認值
現在我們實現視圖影響數據, 把輸入框里面輸入值, 然后影響數據(msg里面會變),此時我們需要監聽輸入框, 給它加一個監聽事件(監聽視圖的變化, 然后數據也跟著變)
而這個輸入框的值,需要這么拿: input是一個原生事件, 原生事件在觸發的是有一個事件對象的,此時需要通過$event來獲取這個事件對象,$event.target拿到的就是這個input標簽本身,$event.target.value拿到的就是原生input的值
<script setup>
import { ref } from 'vue';
const msg = ref('')
</script><template><!-- v-model 實現雙向數據綁定 --><p>{{ msg }}</p><!-- <input type="text" v-model="msg" /> --><!-- 自己實現雙向數據綁定 --><!--v-bind + @input (v-on) --><br><!-- 此時v-bind只能夠數據影響視圖 --><!-- 使用原生事件, 獲取到input里面的值然后通過賦值操作影響數據 --><input :value="msg"@input="msg=$event.target.value"></template><style scoped></style>
2. v-model用在組件上
v-model作用在組件標簽上:
<Xxx v-model="數據"/>,Xxx是一個組件/自定義標簽
上述代碼完全等同于
<Xxx :modelValue = "數據" @update:modelValue = "數據 = 新值"/>
注意: 在組件上使用v-model,其實就是要實現處理 modelValue屬性 和 update:modelValue 事件?
關于為什么在子組件不能使用v-model, v-bind是單向的,因此不影響
我們此處需要,每次選擇哪個城市, 就把它的value值傳給父組件里面的activeId
其中父組件的$event是emit傳過來的參數, 子組件的$event是事件對象,$event.target 是原生DOM標簽, $event.target.value 是標簽的值
App.vue
<script setup>
//導入組件
import MySelect from './component/MySelect.vue';
import { ref } from 'vue'
//收集當前選中的城市 id
const activeId = ref('333')
</script>
<template><!-- 使用組件 --><!-- <MySelect v-model="activeId" /> --><!-- 原理 --><!-- 父傳子接 --><!-- 組件標簽上的 $event 指的是 emit 傳遞來的參數 --><MySelect :modelValue="activeId" @update:modelValue="activeId=$event"/>
</template><style scoped></style>
MySelect.vue
<script setup>
const props = defineProps({//接收父傳子的 modelValue屬性modelValue: String
})
const emit = defineEmits()
</script>
<template><!-- 原生標簽上的 $event 就是事件對象 --><!-- $event.target: select 原生DOM --><!-- $event.target.value: 選中的 option 的 value--><select :value="props.modelValue" @change="emit('update:modelValue', $event.target.value)"><option value="111">長沙</option><option value="222">上海</option><option value="333">廣州</option><option value="444">蘇州</option></select>
</template><style scoped></style>
3. 使用 defineModel() 簡化上述代碼
v-model用在組件上, 子組件代碼可以簡化:
子組件用 defineModel() 接收父傳子的 v-model 數據, defineModel() 返回一個 ref 響應式數據, 這個響應式數據的初始值就是 父傳遞給子的 v-model 數據的值, 并且該數據可讀可寫, 子組件可以直接修改這個響應式數據, 會同步到父組件的數據當中.
今后組件上用v-model指令的格式如下:
父: <Xxx v-model = "父的響應式數據"/>
子: const model = defineModel()? 子可以對model 做讀寫操作
注意: defineModel() 只是一個語法糖(比較好用), 本質還是一下倆部分:?
1> const props = defineProps({modelValue:String})
2> const emit defineEmits()? emit('update:modelValue'.新值)
具體代碼
二、ref
ref 屬性: 和之前學習的 ref 函數不一樣
1. ref 屬性是什么: 是作用在標簽上的屬性, 是vue新增的, 原生不具備的這個屬性
2. ref 屬性的作用: 用來獲取原生 DOM 或 組件實例(進而調用組件提供的方法)
3. 使用步驟
1> 準備一個 ref 數據, 并綁定在想獲取的標簽上, <Xxx ref = "ref 響應式數據"/>
2> 恰當時機,通過 ref.value 來獲取原生DOM 或組件實例
4. 以獲取原生DOM為例
具體代碼
<script setup>
import { onMounted, ref } from 'vue';
//準備 ref 響應式數據
const divRef = ref(null)
//組件掛載后
onMounted(() => {// ref 拿到原生 DOM div// console.log(divRef.value)// 設置字體顏色(DOM操作)divRef.value.style.color = 'blue'
})
</script>
<template><!-- 給目標元素添加 ref 屬性并綁定數據 --><div ref="divRef">xiaohei</div>
</template><style scoped></style>
5. 小案例: 繪制一個簡單的圖表
1> 準備MyChart組件, 在這個組件總來繪制圖表, 使用在App中
2> 在組件中, 準備一個盒子, 給盒子設置寬高
3> 在組件掛載后, 獲取這個div, 繪制圖表
4>繪制圖表
1. 首先我們要用到一個第三方庫: ECharts?
獲取 ECharts - 入門篇 - 使用手冊 - Apache ECharts
npm install echarts
2. 在MyChart組件中, 組件掛載后獲取盒子, 其他的代碼抄官方的即可
具體代碼
App.vue
<script setup>
import MyChart from './component/MyChart.vue';
</script><template><MyChart />
</template><style scoped></style>
MyChart.vue
<script setup>
import * as echarts from 'echarts'
import { onMounted, ref } from 'vue';
const divRef = ref(null)
onMounted(() => {//基于準備好的dom, 初始化echars實例const myChart = echarts.init(divRef.value)//繪制圖表myChart.setOption({title: {text: 'ECharts 入門示例'},tooltip: {},xAxis: {data: ['襯衫', '羊毛衫', '雪紡衫', '褲子', '高跟鞋', '襪子']},yAxis: {},series: [{name: '銷量',type: 'bar',data: [5, 20, 36, 10, 10, 20]}]})
})
</script><template><div class="chart-box" ref="divRef"></div>
</template><style scoped>
.chart-box {width: 400px;height: 300px;margin: 100px auto;border: 3px solid #000;
}
</style>
6. 調用組件的方法
ref屬性: 獲取組件實例, 進而是為了調用組件給我們提供的方法
1> 準備MyForm表單組件, 主要需要提供一個檢驗的方法
2> 在App中使用MyForm組件, 添加登錄按鈕, 今后想知道點擊了登錄按鈕之后, 只校驗成功了還是失敗了, 進而可以做后續的工作
我們子組件可以使用 defineExpose 來把屬性和方法進行導出, 然后父組件就可以導入子組件然后進行使用
整體流程
相當于 ref屬性值鏈接的就是一個子組件的組件實例, 子組件的組件實例里面, 使用defineExpose方法新增了組件的屬性和方法, 我們就可以在父組件里面來進行使用了
具體代碼
MyForm.vue
<script setup>
//表單組件提供的校驗方法
const validate = () => {return Math.random() > 0.5 ? true : false
}
//需要對外暴露.類似于導出
defineExpose({ccc: 100,validate
})
</script><template><div class="form-box">賬號: <input type="text"><br /><br />密碼: <input type="password"><br /><br /></div>
</template><style scoped></style>
App.vue
<script setup>
import MyForm from './component/MyForm.vue';
import { onMounted, ref } from 'vue';
const formRef = ref(null)
onMounted(() => {console.log(formRef.value)//獲取 MyForm 組件實例//調用默認導出的方法console.log(formRef.value.validate())console.log(formRef.value.ccc)
})
//登錄
const onLogin = () => {//做表單校驗if (formRef.value.validate()) {console.log('ok')} else {console.log('不 ok')}
}
</script>
<template><div class="login-box"><!-- 組件上使用 ref,通過子組件使用defineExpose暴露屬性/方法從而進行使用 --><MyForm ref="formRef" /><button @click="onLogin">登錄</button></div>
</template><style scoped></style>
三、nextTick()
1. 什么是 nextTick(): 是 vue3 給我們提供的一個方法
2. nextTick()的作用: 當數據變了, 想獲取更新后的 DOM, 需要把代碼寫在這個方法的回調中
需求:
編輯標題, 編輯框自動聚焦
1> 點擊編輯, 顯示編輯框
2> 讓編輯框, 立刻獲取焦點
點擊編輯顯示編輯框和確認框
讓編輯框, 立刻獲取焦點, 此時出現一個問題
? ? 問題: 當數據變了, 發現獲取 DOM 拿不到
? ? 原因: vue3中, 當數據變了, DOM 的更新是異步的;
? ? 也就是數據變了, 想立即獲取最新的 DOM 是拿不到的, 此時DOM并沒有更新
解決方案: 利用 nextTick()這個方法,因為在nextTick()函數的回調中DOM會進行更新
3. 什么時候用這個方法(用的很少)
當數據變了, 想DOM操作, 如果直接拿不到, 就可以在nextTick回調中獲取.
具體代碼:
App.vue
<script setup>
import { ref, nextTick } from 'vue';
const inputRef = ref(null)
//控制是否顯示輸入框
const isShowEdit = ref(false)
//點擊編輯按鈕
const onEdit = () => {isShowEdit.value = true;// 問題: 當數據變了, 發現獲取 DOM 拿不到// 原因: vue3中, 當數據變了, DOM 的更新是異步的; // 數據變了, 想立即獲取最新的 DOM 是拿不到的, 此時DOM并沒有更新console.log(inputRef.value)//null//解決: 利用 nextTick()這個方法,因為在nextTick()函數的回調中DOM會進行更新nextTick(() => {console.log(inputRef.value)inputRef.value.focus()})
}
</script>
<template><div class="box"><h3>大標題</h3><button @click="onEdit">編輯</button></div><!-- 使用v-if,默認是不顯示 --><div v-if="isShowEdit"><input type="text" ref="inputRef"><button>確認</button></div>
</template>
<style scoped>
.box {display: flex;align-items: center;width: 200px;height: 40px;justify-content: space-between;
}
</style>