Vue3全家桶 - Vue3 - 【8】模板引用【ref】(访问模板引用 + v-for中的模板引用 + 组件上的ref)

模板引用【ref

  • Vue3官网-模板引用
  • 如果我们需要直接访问组件中的底层DOM元素,可使用vue提供特殊的ref属性来访问;

一、 访问模板引用

  • 在视图元素上采用ref属性来设置需要访问的DOM元素:
    • ref 属性可采用 字符串 值的执行设置;
    • ref 属性可采用v-bind::ref的形式来绑定 函数,其函数的第一个参数则为该元素;
  • 如果元素的ref属性值采用的是字符串形式:
    • 在组合式API JS 中,我们需要声明一个同名的ref变量,来获得该模板的引用;
    • 在选项式API JS 中,可通过this.$refs来访问模板的引用;
  • 示例代码:
    • 组合式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:

三、 组件上的ref

  • 模板引用也可以被用在一个子组件上;这种情况下引用中获得的值是组件实例;

    • 如果子组件使用的选项式API,默认情况下父组件可以随意访问该子组件的数据和函数,除非在子组件使用expose选项来暴露特定的数据或函数,expose值为字符串数组;
    • 如果子组件使用的是组合式API<script setup>,那么该子组件默认是私有的,则父组件无法访问该子组件,除非子组件在其中通过defineExpose宏采用对象形式显示暴露特定的数据或函数;
  • 示例代码:

    • 组合式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 方法,所以此处访问不了;
相关推荐
结衣结衣.14 分钟前
C++ 类和对象的初步介绍
java·开发语言·数据结构·c++·笔记·学习·算法
汪子熙26 分钟前
Angular 服务器端应用 ng-state tag 的作用介绍
前端·javascript·angular.js
杨荧26 分钟前
【JAVA开源】基于Vue和SpringBoot的旅游管理系统
java·vue.js·spring boot·spring cloud·开源·旅游
Envyᥫᩣ34 分钟前
《ASP.NET Web Forms 实现视频点赞功能的完整示例》
前端·asp.net·音视频·视频点赞
Starry_hello world4 小时前
二叉树实现
数据结构·笔记·有问必答
Мартин.5 小时前
[Meachines] [Easy] Sea WonderCMS-XSS-RCE+System Monitor 命令注入
前端·xss
一 乐6 小时前
学籍管理平台|在线学籍管理平台系统|基于Springboot+VUE的在线学籍管理平台系统设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·学习
昨天;明天。今天。6 小时前
案例-表白墙简单实现
前端·javascript·css
数云界6 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
风清扬_jd6 小时前
Chromium 如何定义一个chrome.settingsPrivate接口给前端调用c++
前端·c++·chrome