(14)深入理解 Vue 组件——① 使用组件的细节点 | Vue 基础理论实操

2,033 阅读3分钟
本文版权归 “公众号 | 前端一万小时” 所有,欢迎转载!

转载请注明出处,未经同意,不可修改文章内容。

🔥🔥🔥本系列文章已在“公众号 | 前端一万小时”更新完毕,有需要的小伙伴可按需前往查看。

🔥🔥🔥“前端一万小时”两大明星专栏——“从零基础到轻松就业”、“前端面试刷题”,已于本月大改版,合二为一,干货满满,欢迎点击公众号菜单栏各模块了解。


涉及面试题:
1. Vue 组件中 data 为什么必须是一个函数?
2. Vue 的两个核心点?
3. 如何获取 DOM?

[编号:vue_14]

🔗本阶段对应的“官方文档”阅读


❓本篇需要解决的问题:

  • is标签解决 H5 标签上的 bug ;
  • 子组件里定义 data , data 值必须为一个函数;
  • ref 这个引用的使用。

1 is 特殊属性解决 H5 标签上的 bug

1.1 H5 标签元素中如何正确使用子组件

❓例如,如何使用下边这组标签:

<table>
  <tbody>
    <tr>
      <td></td>
    </tr>
  </tbody>
</table>

❌按之前学习的思路,我们可能会写出以下代码:

<body>
  <div id="root">
    <table>
      <tbody>
        <row></row>
        <row></row>
        <row></row>
      </tbody>
    </table>
  </div>
  <script>
    Vue.component("row", {
      template: "<tr><td>This is a superman</td></tr>"
    }) 
    var vm = new Vue({
      el: "#root"	
    })
  </script>
</body>

😰出现问题: 审查元素时发现 <tr>  与 <table> 处于平级,这是不合理的。 14-01.png

因为 H5 的规范中要求我们需要按以下的嵌套方式写表格标签的代码:

<table>
  <tbody>
    <tr>
      <td></td>
    </tr>
  </tbody>
</table>

💡类似还有:

<ul>
  <li> </li>
</ul>

<ol>
  <li> </li>
</ol>

<section>
  <option> </option>
</section>

1.2 解决方法:使用 is 在 H5 标签中使用子组件

这样既能保证 tr 使用 row 这个子组件,又符合 H5 的编码规范:

<body>
  <div id="root">
    <table>
      <tbody>
        <tr is="row"></tr> <!-- 🚀使用 is 在 H5 标签中使用子组件 -->
        <tr is="row"></tr>
        <tr is="row"></tr>
      </tbody>
    </table>
  </div>
  <script>
    Vue.component("row", {
      template: "<tr><td>This is a superman</td></tr>"
    }) 
    var vm = new Vue({
      el: "#root"	
    })
  </script>
</body>

2 子组件里定义 data ,值必须为一个函数

2.1 需求:在子组件定义 data 时传递内容

❌按照之前学习思路,我们可能会写出以下代码:

<script>
  Vue.component("row", {
    data: {
      content: "this is a superman"
    },
    template: "<tr><td>{{content}}</td></tr>"
  }) 
  var vm = new Vue({
    el: "#root"
  })
</script>

❗️出现报错: 14-02.png

2.2 解决方法:子组件定义 data 时,值必须为一个函数

💡分析:

子组件在调用时,不像根组件只调用一次,它可能会在同一个地方调用多次。每一个子组件在调用时,都应该有自己的数据。

而通过一个“函数”返回一个“对象”值的目的,就能让每一个子组件都拥有一个独立的数据存储,这样不会有多个子组件互相影响的情况出现。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>前端一万小时-使用组件的细节点</title>
  <script src="./vue.js"></script>
</head>
<body>
  <div id="root">
    <table>
      <tbody>
        <tr is="row"></tr>
        <tr is="row"></tr>
        <tr is="row"></tr>
      </tbody>
    </table>
  </div>
  <script>
    Vue.component("row", {
      data: function() { /*
      									 🚀在子组件里定义 data ,值必须为一个“函数”,
                         然后通过函数返回一个“对象”引用来存储值!
                          */
        return {
          content: "Hello,前端一万小时"
        }
      },
      template: "<tr><td>{{content}}</td></tr>"
    }) 
    var vm = new Vue({
      el: "#root" 
    })
  </script>
</body>
</html>

14-03.png

ref 特殊属性

<body>
  <div id="root">
    <div @click="handleClick">
      hello world
    </div>
  </div>
  <script>
    var vm = new Vue({
      el: "#root",
      methods: {
        handleClick: function() {
          alert("click")  
        }
      }
    })
  </script>
</body>

3.1 在 Vue 中如何操作 dom

虽然 Vue 不建议我们去操作 DOM ,但是在处理一些极其复杂的动画效果时,只靠 Vue 的这种双向绑定时是处理不了的。所以在有些必要的情况下,我们还是需要操作 DOM 。

🚀我们通过 ref 这种引用的形式来获取 DOM ,并进行 DOM 的操作。

❓需求: 我希望 div 在被点击的时候,我把 div 里边的内容打印出来。

💡需求分析: 我需要获得 div 所对应的 DOM 节点,然后如果能拿到 DOM 节点下的 hello wold ,那么就能够把它打印出来了。 

<body>
  <div id="root">
    <div ref="hello"
         @click="handleClick"
         >
      hello world
    </div> <!-- 1️⃣首先,我给这个 div 标签添加 ref 属性,名字叫 hello; --> 
  </div>
  <script>
    var vm = new Vue({
      el: "#root",
      methods: {
        handleClick: function() {
          
          alert(this.$refs.hello.innerHTML)   
          /*
          2️⃣这里边的 $refs 指的是父组件的 $refs 对象,使用 ref 注册的所有引用都在 $refs 中,
          而 $refs.hello 就特指名字为 hello 的这个 div 的 DOM。
           */
        }
      }
    })
  </script>
</body>

14-04.png

3.2 动态获取组件内容

❓需求①: 制作一个计数器,并且能够点击数字就按顺序 +1

代码实现

<body>
  <div id="root">
    <counter></counter>
    <counter></counter>	
  </div>
  <script>       
    Vue.component("counter", {
      template: '<div @click="handleClick">{{number}}</div>',
      data: function() {
        return {
          number: 0
        }
      },
      methods: {
        handleClick: function() {
          this.number ++ // 每点击一次,number 上的数字就会 +1。
        }
      }
    })
    var vm = new Vue({
      el: "#root"		   
    })
  </script>
</body>

vue_14-05.gif

❓需求②:对以上代码里的两个 counter 组件进行求和。

<body>
  <div id="root">
    
    <!-- 7️⃣我们给两个 counter 都添加一个 ref 引用,然后分别取名为“one”和“two”;-->
    <counter ref="one" @change="handleChange"></counter> <!-- 4️⃣同时我们会在
																								父组件“接收”子组件“触发”的 change 事件,
																								并去执行相关方法; -->
    
    <counter ref="two" @change="handleChange"></counter>
    
    总计:{{total}} <!-- 2️⃣然后通过插值表达式的形式把“数据” total 显示出来; -->
  </div>
  <script>       
    Vue.component("counter", {
      template: '<div @click="handleClick">{{number}}</div>',
      data: function() {
        return {
          number: 0
        }
      },
      methods: {
        handleClick: function() {
          this.number ++  
          
          this.$emit("change") /*
          										 3️⃣当我们点击子组件时,不仅上边的 number 会+1 ,
                               同时会“触发”一个 change 事件;
                                */
        }
      }
    })
    var vm = new Vue({
      el: "#root",
      data: {
        total: 0  // 1️⃣首先,我在 data 里定义一个 total ,并初始化为 0;
      },
      methods: {
        handleChange: function() {  
        // 5️⃣我们在父组件里定义 handleChange 这个方法;
        // 6️⃣但要实现“求和”,我们还需要返回父组件模板里边去,通过 ref 引用的方式,获取到相应 DOM;
        
        this.total = this.$refs.one.number + this.$refs.two.number
        /*
        8️⃣通过 this.$refs.one 获取“子组件” counter 的一个“引用”,
      	然后继续通过 this.$refs.one.number 拿到“one”的“数目”(同样的方式获得“two”的数目),
        最后作一个加法运算。
         */
        }
      }
    })
  </script>
</body>

vue_14-06.gif

3.3 “标签”和“子组件”里使用 ref 的区别

  • 在“标签”里使用 ref 时,我们通过 this.refs.引用的名字 获取到的是这个“标签”对应的 DOM 元素;
  • 而在“子组件”里使用 ref 时,我们通过 this.refs.引用的名字.xxx 获得到的是“子组件”的一个“引用”!

祝好,qdywxs ♥ you!