模板引用【ref
】
- Vue3官网-模板引用;
- 如果我们需要直接访问组件中的底层
DOM
元素,可使用vue
提供特殊的ref
属性来访问;
一、 访问模板引用
- 在视图元素上采用
ref
属性来设置需要访问的DOM
元素:- 该
ref
属性可采用 字符串 值的执行设置; - 该
ref
属性可采用v-bind:
或:ref
的形式来绑定 函数,其函数的第一个参数则为该元素;
- 该
- 如果元素的ref属性值采用的是字符串形式:
- 在组合式API
JS
中,我们需要声明一个同名的ref
变量,来获得该模板的引用; - 在选项式API
JS
中,可通过this.$refs
来访问模板的引用;
- 在组合式API
- 示例代码:
-
组合式API:
html<template> <!-- 字符串形式的 ref --> 账号输入框:<input type="text" ref="account"> <button @click="accountInputStyle">改变账号输入框的样式</button> <!-- 函数形式的 ref ,必须采用 v-bind 或 : 的形式来给ref绑定属性值 --> 密码输入框:<input type="password" :ref="passwordFn"> <button @click="passwordInputStyle">改变密码输入框的样式</button> </template> <script setup> import { ref, reactive, computed, onMounted, nextTick } from 'vue' // EXPLAIN 响应式数据 // ref 变量名 和 对应DOM元素的ref属性值 相等 let account = ref(null) let password = ref(null) // EXPLAIN 函数 const accountInputStyle = () => { // NOTE 此处设置的 style 均为行内样式 account.value.style.padding = '15px' account.value.style.caretColor = 'red' account.value.className = 'rounded' account.value.focus() } // NOTE 采用函数给ref绑定属性值,该函数的第一个参数为该元素 // NOTE 在页面渲染的时候会自动执行 // NOTE 函数式生命的 ref,不会在 this.$refs 中获取 const passwordFn = (el) => { // el 元素是密码输入框 password.value = el console.log(password.value) } const passwordInputStyle = () => { password.value.style.border = '4px solid green' password.value.style.padding = '15px' password.value.focus() } onMounted(() => {}); </script> <style scoped> .rounded { border: 4px solid purple; } </style>
-
选项式API:
html<script> export default { data: () => ({ accountEl: null, passwordEl: null }), methods: { changeAccountInputStyle() { this.accountEl = this.$refs.account // 获取账号输入框的 DOM console.log(this.accountEl) this.accountEl.style = "padding: 15px" this.accountEl.className = "rounded" this.accountEl.focus() }, passwordRef(el) { this.passwordEl = el // el 元素是密码输入框 }, changePasswordInputStyle() { console.log(this.passwordEl) console.log(this.$refs) // 函数式声明的 ref,不会在this.$refs中获取 this.passwordEl.style = "padding: 15px" this.passwordEl.className = "rounded" this.passwordEl.focus() }, } } </script> <template> <!-- ref 字符串值形式 --> 账号输入框:<input type="text" ref="account"> <button @click="changeAccountInputStyle">改变账号输入框的样式</button> <hr> <!-- ref 函数形式:元素渲染后,会立即执行该函数 --> 密码输入框:<input type="password" :ref="passwordRef"> <button @click="changePasswordInputStyle">改变密码输入框的样式</button> </template> <style> .rounded { border-radius: 15px; } </style>
-
二、 v-for
中的模板引用
-
当在
v-for
中使用模板引用时:- 如果
ref
值是 字符串 形式,在元素被渲染后包含对应整个 列表的所有元素【数组】; - 如果
ref
值是 函数 形式,则会每渲染一个列表元素就会执行对应的函数【不推荐使用】;
- 如果
-
注意 :需要
v3.2.25
及以上的版本; -
示例代码:
-
组合式API:
html<script setup> import { onMounted, ref } from "vue"; // 书本 let books = ref([ { id: 1, name: "海底两万里" }, { id: 2, name: "骆驼祥子" }, { id: 3, name: "老人与海" }, { id: 4, name: "安徒生童话" }, ]); let bookList = ref(null); onMounted(() => { console.log(bookList.value); // 获取引用的 DOM 对象,并打印,发现那么是数组, bookList.value[2].className = "error"; }); </script> <template> <ul> <li v-for="b in books" :key="b.id" ref="bookList"> {{ b.name }} </li> </ul> </template> <style> .error { border: 1px solid red; } </style>
-
选项式API:
html<script> export default { data: () => ({ books: [ { id: 1, name: "红楼梦" }, { id: 2, name: "三国演义" }, { id: 3, name: "水浒传" }, { id: 4, name: "西游记" }, ], students: [ { id: 1, name: "Jack" }, { id: 2, name: "Annie" }, { id: 3, name: "Tom" }, ], }), methods: { changeBookListStyle() { console.log(this.$refs.bookList); this.$refs.bookList[2].style = "color: red"; }, studentsRef(el) { console.log(el); }, }, }; </script> <template> <ul> <!-- 如果 ref 值是字符串形式,在元素被渲染后包含对应整个列表的所有元素【数组】 --> <li v-for="b in books" :key="b.id" ref="bookList"> {{ b.name }} </li> </ul> <button @click="changeBookListStyle">点我查看 bookList</button> <hr /> <!-- 如果ref值是函数形式,则会每渲染一个列表元素则会执行对应的函数【不推荐使用】 --> <ul> <li v-for="s in students" :key="s.id" :ref="studentsRef"> {{ s.name }} </li> </ul> </template>
-
-
运行效果:
- 选项式API:
- 组合式API:
- 选项式API:
三、 组件上的ref
-
模板引用也可以被用在一个子组件上;这种情况下引用中获得的值是组件实例;
- 如果子组件使用的选项式API,默认情况下父组件可以随意访问该子组件的数据和函数,除非在子组件使用
expose
选项来暴露特定的数据或函数,expose
值为字符串数组; - 如果子组件使用的是组合式API
<script setup>
,那么该子组件默认是私有的,则父组件无法访问该子组件,除非子组件在其中通过defineExpose
宏采用对象形式显示暴露特定的数据或函数;
- 如果子组件使用的选项式API,默认情况下父组件可以随意访问该子组件的数据和函数,除非在子组件使用
-
示例代码:
- 组合式API:
-
父组件:
html<script setup> // NOTE 组合式API中,默认情况下,子组件中的数据、函数等等都是私有的,不能访问 // NOTE 如果 子组件 通过 defineExpose 宏采用对象形式显式暴露特定的数据或函数等等 import Vue1 from '@/components/27-组件上的ref - 组合式API/1.vue' import { ref, onMounted } from 'vue' const login_vue = ref(null) const showSonData = () => { console.log(login_vue.value.account) console.log(login_vue.value.password) login_vue.value.toLogin() } onMounted(() => {}); </script> <template> <h3>登录页面</h3> <hr> <!-- 组件上的 ref 的值为该组件的实例 --> <Vue1 ref="login_vue"></Vue1> <hr> <button @click="showSonData">查看子组件的信息</button> </template>
-
子组件:
html<script setup> import { ref } from 'vue' const account = ref('admin') const password = ref('123456') const toLogin = () => { alert('登录中......') } // TODO 采用 defineExpose 将指定数据、函数等等暴露出去 defineExpose({ account, toLogin }); </script> <template> 账号:<input type="text" v-model="account"> <br> 密码:<input type="text" v-model="password"> <hr> <button @click="toLogin">登录</button> </template>
-
效果展示:
- 默认情况下(没有使用
defineEpxose
):
- 通过
defineExpose
暴露特定的属性或方法:
- 默认情况下(没有使用
-
-
选项式API:
-
父组件:
html<script> // NOTE 选项式API中,默认情况下,父组件可以随意访问子组件的数据和函数、计算属性等等 // NOTE 如果 子组件 增加 expose 选项之后,就只能访问 expose 暴露的属性和函数等等 import Vue1 from '@/components/27-组件上的ref - 选项式API/1.vue' export default { name: 'App', components: { Vue1 }, data: () => ({ login_vue: null }), methods: { showSonData () { console.log(this.login_vue.account) console.log(this.login_vue.password) this.login_vue.toLogin() } }, mounted () { // 打印出来的式子组件的ref对象 this.login_vue = this.$refs.loginVue console.log(this.login_vue) } } </script> <template> <h3>登录页面</h3> <hr> <!-- 组件上的 ref 的值为该组件的实例 --> <Vue1 ref="loginVue"></Vue1> <hr> <button @click="showSonData">查看子组件的信息</button> </template>
-
子组件:
html<script> export default { name: 'Vue1', data: () => ({ account: 'admin', password: '123456' }), methods: { toLogin () { alert('登录中......') } }, // TODO 向外暴露属性函数,增加这个选项之后,父组件只能访问该组件暴露的属性或方法等等 expose: ['account', 'password'] } </script> <template> 账号:<input type="text" v-model="account"> <br> 密码:<input type="text" v-model="password"> <hr> <button @click="toLogin">登录</button> </template> <style scoped lang='scss'> </style>
- 运行展示:
- 未增加
expose
:
- 增加
epxose
:- 子组件没有暴露
toLogin
方法,所以此处访问不了;
- 子组件没有暴露
- 未增加
-
- 组合式API: