组件通讯(父传子、子传父、ref属性、表单双向绑定v-model、兄弟间传值Event Bus、插槽、依赖注入)

0 阅读4分钟

组件通讯(传事件、传属性、传内容)

1.父传子(父组件引入并使用子组件,通过 :属性名="父组件中要传递的属性名"传到子组件,子组件通过props接收并使用)

父组件App.js

import ComA from "./ComA.js";
export default {
  components: {
    ComA,
  },

  data() {
    return {
      title: "组件通讯",
      msg: "父组件内容",
      user: {
        name: "jack",
        age: 20,
      },
    };
  },

  methods: {
    test() {},
  },

  /*html*/
  template: `<div style="width:400px;height:400px;background-color:skyblue;">
                <h2>{{title}}</h2>
                <com-a :message="msg" :user="user"></com-a>
            </div>`,
};

子组件ComA.js

export default {
    // props: ["message", "user"],
    // props: {
    //   message:String,
    //   user: Object,
    // },
    
    props: {
      message: {
        typeString, // 类型
        default"默认值", // 默认值
        requiretrue, // 是否必传
        // 校验
        validator(val) {
          console.log("val ", val);
          if (val === "") {
            return "内容不能为空!";
          } else {
            return val;
          }
        },
      },

      userObject,
    },

    data() {
      return {
        title"子组件内容",
        count0,
        contentthis.message,
      };
    },

    created() {
      // 将外部数据赋值给内部状态content
      // this.content = this.message
    },

    methods: {
      bindConfirm() {
        this.count++;
        console.log("this.message ", this.message);
        this.content = "新内容";
      },
    },

    /*html*/
    template: `<div style="width:200px;height:300px;background-color:pink;">

                      <h2>{{title}}</h2>
                      <p>外部数据: {{ message }}</p>
                      <p>外部数据: {{ user.name }} - {{ user.age}}</p>

                      <p>内部数据: {{ count }}</p>

                      <button @click="bindConfirm">确定</button>

                      <p>外部数据赋值给内部状态content: {{content}}</p>
                </div>`,
  };

2. 子传父(父组件在使用子组件位置绑定一个自定义事件,子组件通过emits接收自定义事件,并通过$emit触发自定义事件,将参数返回给父组件)

父组件App.js

import Child from "./Child.js";
/*
 * 子传父
 *  1. 父组件使用子组件位置 绑定自定义事件 @someEvent="bindEvent"
 *  2. 子组件中定义emits选项接收自定义事件 emits:['someEvent']
 *        这步可以不写,但建议写上
 *  3. 触发自定义事件 this.$emit('someEvent',参数)
 */

export default {
  components: {
    Child,
  },

  data() {
    return {
      title"父组件",
      msg"",
    };
  },

  methods: {
    bindSomEvent(parms) {
      console.log("bindSomEvent >>> ", parms);
      this.msg = parms;
    },
  },

  /*html*/
  template: `<div style="width:400px;height:400px;background-color:skyblue;">
                <h2>{{title}}</h2>
                <p>{{ msg }}</p>
                <child @someEvent="bindSomEvent"></child>
            </div>`,
};

子组件Child.js

export default {
  emits: ["someEvent"], // 接收事件
  data() {
    return {
      title: "子组件",
      content: " 子组件数据content",
    };
  },

  methods: {
    bindConfirm(){
      this.$emit('someEvent',this.content) // 触发someEvent事件
    }
  },

  /*html*/
  template: `<div style="width:200px;height:200px;background-color:pink;">
                    <h2>{{title}}</h2>
                    <button @click="bindConfirm">传参到父组件1</button>
                    <button @click="$emit('someEvent',content)">传参到父组件2</button>
              </div>`,
};

3. ref属性操作组件(给组件标签添加ref属性,通过 this.$refs.添加的ref属性名 获取组件实例,调用其数据和方法)

父组件App.js

import ComA from "./ComA.js";
export default {
  components: {
    ComA,
  },

  data() {
    return {
      title"组件通讯",
      msg"父组件内容",
    };
  },

  methods: {
    getComA(){
      // 获取子组件实例,调用子组件方法和数据
      const comAEle = this.$refs.comaRef
      console.log('comAEle ',comAEle);
      comAEle.bindConfirm()
      console.log( 'comAEle.count',comAEle.count);
    }
  },

  /*html*/
  template: `<div style="width:400px;height:400px;background-color:skyblue;">
                <h2>{{title}}</h2>
                <com-a ref="comaRef"></com-a>
                <button @click="getComA">确定</button>
            </div>`,
};

子组件ComA.js

export default {
    data() {
      return {
        title: "子组件内容",
        count: 0,
      };
    },

    methods: {
      bindConfirm() {
        this.count++;
      },
    },

    /*html*/
    template: `<div style="width:200px;height:300px;background-color:pink;">
                      <h2>{{title}}</h2>
                      <p>{{ count }}</p>
                      <button @click="bindConfirm">加</button>
                </div>`,
  };

4. 表单组件v-model双向绑定(默认情况下props接收值固定用modelValue,emits接收事件固定用update:modelValue。当然也可以通过指定参数来更改名字)

父组件App.js

import SearchInput from "./1-SearchInput.js";
import CustomInput from './2-CustomInput.js' 
import ComputedInput from './3-ComputedInput.js'
import MyInput from './4-MyInput.js'

/**
 * 根组件
 *   搜索框组件输入内容,在父组件中获取展示
 *   子传父+子传父
 */

export default {
  components: {
    SearchInput,//原生实现
    CustomInput,//v-model实现
    ComputedInput,//v-model实现
    MyInput//更改名字

  },

  data() {
    return {
      title: "表单组件双向数据绑定",
      searchContent:'hello',
      CustomContent:'vue',
      message:'js',
      msg:'更改modleValue',
      msg1:''
    };
  },

  methods: {
     bindSearchContent(val){
       this.searchContent= val
     }
  },

  /*html*/
  template: `<div style="width:400px;height:600px;background-color:skyblue;">
                <h2>{{title}}</h2>
              
                <p>原生实现</p>
                <p>{{searchContent}}</p>
                <search-input :searchContent="searchContent" @searchEvent="bindSearchContent"></search-input>

                <p>v-model实现方法一</p>
                <p>{{CustomContent}}</p>
                <custom-input v-model="CustomContent"></custom-input>

                <p>v-model实现方法二</p>
                <p>{{message}}</p>
                <computed-input v-model="message"></computed-input>

                <p>v-model更改名字,modelValue改为title</p>
                <p>{{msg}}</p>
                <my-input v-model:title="msg" v-model:msg="msg1"></my-input>
            </div>`,
};

子组件1-SearchInput.js(原生实现)

export default {
    props:['searchContent'],
    emits:['searchEvent'],
    data() {
      return {};
    },

    /*html*/  
    template: `<div class="g-search">
                   <input type="text" name="search" :value="searchContent" @input="$emit('searchEvent', $event.target.value)" placeholder="请输入内容"/>
              </div>`,
  };

子组件2-CustomInput.js(v-model实现方法一)

export default {
    props:['modelValue'],
    emits:['update:modelValue'],

    /*html */
    template:`<div>
                 <input type="text" :value="modelValue" @input="$emit('update:modelValue',$event.target.value)" placeholder="请输入内容"/>
             </div>`
}

子组件3-ComputedInput.js(v-model实现方法二)

export default {
    props: ["modelValue"],
    emits: ["update:modelValue"]

    computed: {
      inputValue: {
        get() {
          return this.modelValue; // 计算属性值为传入的modelValue值
        },
        set(newValue) {
          this.$emit("update:modelValue", newValue);
        },
      },
    },

    /*html */
    template: `<div>
                   <input type="text" v-model="inputValue" placeholder="请输入内容"/>
               </div>`,
  };

子组件4-MyInput.js(更改名字)

export default {
    props: ["title","msg"],
    emits: ["update:title","update:msg"],

    computed: {
      inputValue: {
        get() {
          return this.title; // 计算属性值为传入的modelValue值
        },
        set(newValue) {
          this.$emit("update:title", newValue);
        },
      },
    },
 
    /*html */
    template: `<div>
                   <input type="text" v-model="inputValue" placeholder="请输入内容"/>                     
                </div>`,
  };

5. 兄弟间传值Event Bus(一般不用,可以用状态管理或依赖注入)

vue2中可以用事件总线的方式,new一个vue实例,然后在一个组件中通过$emit触发事件,在另一个组件中通过$on监听事件

image.png

vue3中提供了一个mitt第三方库

image.png

6. 插槽(父组件与子组件之间内容传递/子组件定义插槽,父组件定义插槽内容。)

(1)子组件使用slot标签接收传入的内容

默认插槽  <slot></slot>

具名插槽  <slot name=""></slot>

作用域插槽:子组件通过插槽传参到父组件

(2)父组件内容有多个标签时,使用 template标签包裹, v-slot指令指定默认名称 v-slot:default(v-slot指令可以简写 #。如v-slot:content可以简写成#content)

实际应用:子组件头部尾部确定,中间内容部分不确定,定义成插槽。多个父组件调用,传入内容是什么就显示什么。

父组件App.js

import Child from "./Child.js";
export default {
  components: {
    Child,
  },

  data() {
    return {
      title: "父组件",
    };
  },

  /*html*/
  template: `<div style="width:400px;height:400px;background-color:skyblue;">
                <h2>{{title}}</h2>
                <child>

                   <!-- 默认插槽 -->
                   <template v-slot:default>
                      <h2>标题</h2>
                      <p>插槽内容</p> 
                   </template>

                   <!-- 具名插槽 -->
                   <template v-slot:tree>
                       <p></p>
                   </template>

                   <!-- 简写 -->
                   <template #flower>
                       <p></p>
                   </template>

                   <!-- 作用域插槽
                   slotList相当于是插槽实例,可以自定义命名
                   slotList.books就可以拿到子组件数据了
                   -->
                   <template v-slot:listtemp="slotList">
                    <ul>
                        <li v-for="book in slotList.books">
                           {{book}}
                        </li>
                    </ul>
                   </template>
                </child>
            </div>`,

};

子组件Child.js

export default {
  data() {
    return {
      title: "子组件",
      list:['javascript高级编程','vue高级','css高级']
    };
  },

  /*html*/
  template: `<div style="width:200px;height:200px;background-color:pink;">
                    <h2>{{title}}</h2>
                    <slot></slot>

                    <p>绿化带</p>
                    <slot name="tree"></slot>

                    <p>绿化带</p>
                    <slot name="flower"></slot>

                    <!-- 子组件list -->
                    <slot name="listtemp" :books="list"></slot>
              </div>`,

};

7. 依赖注入(provide-inject跨组件通讯)

一般父组件改变值,子组件的值不会跟着变(要实现响应式可在暴露的时候使用computed计算属性)

父组件提供provide,子组件通过inject接收数据

父组件App.js

import Child from "./Child.js";
import { computed } from '<https://unpkg.com/vue@3/dist/vue.esm-browser.js>'
export default {
  components: {
    Child,
  },

  data() {
    return {
      title"父组件",
      msg:'这是根组件数据'
    }
  },

  // 提供数据
  provide(){
    return {
      //  message:this.msg
      messagecomputed(()=>this.msg)//实现响应式
    }
  },

  methods: {
    bindUpdateMessage(){
       this.msg = '新内容'
    }
  },

  /*html*/
  template: `<div style="width:600px;height:600px;background-color:skyblue;">
                <h2>{{title}}</h2>
                <child ></child>
                <button @click="bindUpdateMessage">确定</button>
            </div>`,

};

子组件Child.js

import Son from './Son.js'
export default {
  components:{
    Son
  },
  data() {
    return {
      title"儿子组件",
    };
  },

  /*html*/
  template: `<div style="width:400px;height:400px;background-color:pink;">
                    <h2>{{title}}</h2>
                    <son></son>
              </div>`,
};

孙组件Son.js

export default {
  // 接收数据
  // inject:['message'],

  inject: {
    msg: {//更改名字
      from'message',
      default: '默认值'
    }
  },

  data() {
    return {
      title: "孙子组件",
      sonmessage: '孙子sonmessage'
    };
  },

  /*html*/
  template: `<div style="width:200px;height:200px;background-color:green;">
                    <h2>{{title}}</h2>
                    <p>{{sonmessage}}</p>
                    <!-- <p>{{message}}</p> -->
                    <p>{{msg}}</p>
              </div>`,
};