Vue组件通信的一些细节

88 阅读2分钟

Vue组件化

1.认识组件的嵌套

  • 如果我们一个应用程序将所有的逻辑都放在同一个组件中,那么这个组件就会非常难以维护
  • 所以组件化思想是拆分组件然后再进行组合嵌套在一起,方便维护

2.父子组件的通信

  • 在开发中很常见的就是父子通信,比如父组件有一些数据,需要子组件来进行展示

    • 就可以通过props来完成组件之间的通信
  • 那么什么是props呢?

    • props就是可以在组件上注册一些自定义的attribute
    • 父组件给这些attribute赋值,子组件通过attribute的名称获取到对应的值
  • Props有两种常见的用法

    1. 字符串数组,数组中的字符串就是attribute的名称
    2. 对象类型,对象类型我们可以在指定attribute名称的同时,指定它需要传递的类型、是否是必须的、 默认值等等.
  • App.vue

    <template>
      <div id="app">
       <!-- 第一种传递方式 --> 
        <ShowMessage :title="title" :content="content" />
         <!-- 第二种传递方式 --> 
        <ShowMessage v-bind="message" />
      </div>
    </template><script>
      import ShowMessage from "./ShowMessage.vue"
    ​
      export default {
        components: {
          ShowMessage
        },
        data() {
          return {
            title:"数据",
            conetne:"我是传递的数据",
            message: {
              title:"数据",
              conetne:"我是传递的数据",
            }
          }
        }
      }
    </script><style scoped></style>
    
  • ShowMessage.vue

    • type属性的类型有以下几个:

      String |Number|Boolean|Array|Object|Date|Function|Symbol

    <template>
      <div>
        <h2>{{title}}</h2>
        <h3>{{content}}</h3>
      </div>
    </template><script>
    ​
      export default {
        //1.第一种传入方式,由于这么无法限制数据的类型
        props: ["title","content"]
        //2.第二种传入方式,可以设置类型
        props: {
          title:: {
            type:String,
            required:true, //必须传值
            default:"123" //如果没传值就显示这个默认值
          },
          content:String
        }
      }
    </script><style scoped></style>
    

这上面⬆️的图仔细看,有各种写法,可以尝试一下,有个点💊说明一下,为什么default是一个函数在右边的图片上,为什么不是对象呢?

那是因为假设你三个组件都用同一个值,那么你只要其中改掉一个组件的值就会把其他两个组件的值也改变了,因为都指向的是同一个对象。

2.1非Prop的Attribute

  • 什么是非Prop的Attribute呢?

    • 当我们传递给一个组件某个属性,但是该属性并没有定义对应的props或者emits时,就称之为 非Prop的Attribute
    • 常见的包括class, style,id属性等...
  • 分为三种方式:

  1. 当组件有单个根节点时,非Prop的Attribute将自动添加到根节点的Attribute中

    • App.vue

      <template><ShowMessage class="smgbox" /></template>
      
    • ShowMessage.vue

      <template>
      <!-- 相当于把app.vue中的class传入到这个组件的根结点上<div class="smgbox"></div> --> 
        <div> 
          <h2>{{title}}</h2>
          <h3>{{content}}</h3>
        </div>
      </template><script>
      ​
        export default {
          //1.第一种传入方式,由于这么无法限制数据的类型
          props: ["title","content"]
          //2.第二种传入方式,可以设置类型
          props: {
            title:: {
              type:String,
              required:true, //必须传值
              default:"123" //如果没传值就显示这个默认值
            },
            content:String
          }
        }
      </script><style scoped></style>
      
  2. 如果我们不希望组件的根元素继承attribute,可以在组件中设置 inheritAttrs: false

    • 禁用attribute继承的常见情况是需要将attribute应用于根元素之外的其他元素,比如下面的h2

    • 我们可以通过 $attrs来访问所有的 非props的attribute

      • ShowMessage.vue

        <template>
        <!-- 相当于把app.vue中的class传入到这个组件的根结点上<div class="smgbox"></div> --> 
          <div>
            <!-- 第一种方式一个一个绑定 -->
            <!-- <h2 :class="$attrs.class">{{title}}</h2> -->
                <!-- 第二种方式多个绑定 -->
            <h2 v-bind="$attrs">{{title}}</h2>
            <h3>{{content}}</h3>
          </div>
        </template><script>
        ​
          export default {
            inheritAttrs:false,
            //1.第一种传入方式,由于这么无法限制数据的类型
            props: ["title","content"]
            //2.第二种传入方式,可以设置类型
            props: {
              title:: {
                type:String,
                required:true, //必须传值
                default:"123" //如果没传值就显示这个默认值
              },
              content:String
            }
          }
        </script><style scoped></style>
        
  3. 多个根结点的attribute

    • 多个根节点的attribute如果没有显示的绑定,那么会报警告,我们必须手动的指定要绑定到哪一个属性上

      • 创建一个新的组件:amultirootelem.vue

        <template>
          <h2>MultiRootElement</h2>
          <h2>MultiRootElement</h2>
          <h2 :id="$attrs.id">MultiRootElement</h2>
        </template><script>
          export default {
            
          }
        </script><style scoped></style>
        
      • App.vue

        <template>
          <div>
            <multi-root-element id="aaaa"></multi-root-element>
          </div>
        </template><script>
          import MultiRootElement from './MultiRootElement.vue';
        ​
          export default {
            components: {
              MultiRootElement
            },
            data() {
              return {
                title:"数据",
                conetne:"我是传递的数据",
                message: {
                  title:"数据",
                  conetne:"我是传递的数据",
                }
              }
            }
          }
        </script><style scoped></style>
        

3.子父通信

  • 什么情况下子组件需要传递内容到父组件呢?

    • 当子组件有一些事件发生时,比如在组件中发生了点击,父组件需要切换内容
    • 子组件有一些内容要传递给父组件的时候
  • App.vue

    <template>
      <div>
        <h2>当前计数: {{counter}}</h2>
        <CounterOperation @add="addOne" 
                           @sub="subOne"
                           @addN="addNNum">
        </CounterOperation>
      </div>
    </template><script>
      import CounterOperation from './CounterOperation.vue';
    ​
      export default {
        components: {
          CounterOperation
        },
        data() {
          return {
            counter: 0
          }
        },
        methods: {
          addOne() {
            this.counter++
          },
          subOne() {
            this.counter--
          },
          addNNum(num, name, age) {
            console.log(name, age);
            this.counter += num;
          }
        }
      }
    </script><style scoped></style>
    
  • counter-operation.vue

    <template>
      <div>
        <button @click="increment">+1</button>
        <button @click="decrement">-1</button>
    ​
        <input type="text" v-model.number="num">
        <button @click="incrementN">+n</button>
      </div>
    </template><script>
      export default {
        // emits: ["add", "sub", "addN"],
        // 对象写法的目的是为了进行参数的验证
        emits: {
          add: null,
          sub: null,
          //也可以写成payload
          //addN: payload => {}
          addN: (num, name, age) => {
            console.log(num, name, age);
            //进行判断,不通过的结果会在浏览器中显示
            if (num > 10) {
              return true
            }
            return false;
          }
        },
        data() {
          return {
            num: 0
          }
        },
        methods: {
          increment() {
            console.log("+1");
            this.$emit("add");
          },
          decrement() {
            console.log("-1");
            this.$emit("sub");
          },
          incrementN() {
            this.$emit('addN', this.num, "tim", 21);
          }
        }
      }
    </script><style scoped></style>
    

4.小案例

    1. 创建App.vue
    1. 创建TabControl组件
    1. 进行代码操作,就其实组件通信就是等价交换,父亲传给你,你也会把东西交给父亲
  • App.vue

    <template>
      <div>
        <TabControl :titles="titles" @titleClick="titleClick"></TabControl>
        
        <h2>{{contents[currentIndex]}}</h2>
      </div>
    </template><script>
      import TabControl from './TabControl.vue';
    ​
      export default {
        components: {
          TabControl
        },
        data() {
          return {
            //模拟的假数据
            titles: ["衣服", "鞋子", "裤子"],
            contents: ["衣服页面", "鞋子页面", "裤子页面"],
            //下标记录,显示哪个content内容
            currentIndex: 0
          }
        },
        methods: {
          //通过传过来的index进行对应的展示内容
          titleClick(index) {
            this.currentIndex = index;
          }
        }
      }
    </script><style scoped></style>
    
  • TabControl.vue

    <template>
      <div class="tab-control">
        <div class="tab-control-item" 
             :class="{active: currentIndex === index}"
             v-for="(title, index) in titles" 
             :key="title"
             @click="itemClick(index)">
          <span>{{title}}</span>
        </div>
      </div>
    </template><script>
      export default {
        emits: ["titleClick"],
        //通过props接收父组件传递过来的值
        props: {
          titles: {
            type: Array,
            default() {
              return []
            }
          }
        },
        data() {
          return {
            //记录下标
            currentIndex: 0
          }
        },
        methods: {
          //点击事件进行修改
          itemClick(index) {
            this.currentIndex = index;
            this.$emit("titleClick", index);
          }
        }
      }
    </script><style scoped>
      .tab-control {
        display: flex;
      }
    ​
      .tab-control-item {
        flex: 1;
        text-align: center;
      }
    ​
      .tab-control-item.active {
        color: red;
      }
    ​
      .tab-control-item.active span {
        border-bottom: 3px solid red;
        padding: 5px 10px;
      }
    </style>
    

    听着 艾志恒/Asen的 Money Focused Freestyle,学Vue压根不是问题好吧,祝大家赚💰多多