vue2中组件通信的12种方式

文章目录

vue2组件通信

1. props通信(父传子)

1.1 声明方式

在 Vue2 中, 子组件可以通过以下几种方式声明 props。

(1) 简单数组形式

当你只需要声明 props 的名称,而不关心其类型、默认值等额外信息时,可以使用简单的数组形式。

<!-- 子组件 -->


<!-- 子组件 -->
<template>
  <div>
    <p>{
    
    {
    
     message }}</p>
    <p>{
    
    {
    
     data }}</p>
  </div>
</template>

<script>
export default {
    
    
// 简单数组形式
  props: ['message', 'data']
}
</script>

在上述代码中,props 被声明为一个数组,数组中的元素就是组件可以接收的 props 名称。

(2) 对象形式

若你需要对 props 进行更细致的配置,比如指定类型、设置默认值、进行必填校验等,就可以使用对象形式。

<template>
  <div>
    <p>{
    
    {
    
     title }}</p>
    <p>{
    
    {
    
     count }}</p>
  </div>
</template>

<script>
export default {
    
    
  props: {
    
    
    title: {
    
    
      type: String,
      required: true
    },
    count: {
    
    
      type: Number,
      default: 0
    }
  }
}
</script>

在这个例子中,title 被指定为 String 类型,并且是必填项;count 是 Number 类型,若父组件未传递该 props,则默认值为 0。

只想配置类型可以简写

<template>
  <div>
    <p>{
    
    {
    
     title }}</p>
    <p>{
    
    {
    
     count }}</p>
  </div>
</template>

<script>
export default {
    
    
  props: {
    
    
    title: String,
    count: Number
  }
}
</script>

1.2 向子组件传递 props

父组件向子组件传递 props 的方式有静态传递和动态传递两种。

(1) 静态传递
<!-- 父组件 -->
<template>
  <div>
    <ChildComponent message="这是一条静态消息" />
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
    
    
  components: {
    
    
    ChildComponent
  }
}
</script>

这里,message 是一个静态值,直接写在组件标签的属性中。

(2) 动态传递
<!-- 父组件 -->
<template>
  <div>
    <ChildComponent :message="dynamicMessage" />
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
    
    
  components: {
    
    
    ChildComponent
  },
  data() {
    
    
    return {
    
    
      dynamicMessage: '这是一条动态消息'
    };
  }
}
</script>

使用 : 前缀(即 v-bind 指令的缩写)可以实现动态传递 props,dynamicMessage 是父组件 data 中的一个变量,其值可以动态改变。

1.3 props 的类型验证

Vue 2 支持对 props 进行类型验证,确保传递的数据符合预期。支持的类型包括 String、Number、Boolean、Array、Object、Date、Function、Symbol 等。

<template>
  <div>
    <p>{
    
    {
    
     user }}</p>
  </div>
</template>

<script>
export default {
    
    
  props: {
    
    
    user: {
    
    
    // 类型验证
      type: Object,
      
      validator(value) {
    
    
        return 'name' in value && 'age' in value;
      }
    }
  }
}
</script>

在这个例子中,user 被指定为 Object 类型,并且使用 validator 函数进行额外的验证,确保对象中包含 name 和 age 属性。

props 的 validator 是一个自定义验证函数,用于验证传入的 prop 值是否符合特定的要求。
使用场景:

  1. 基本类型验证:如果需要更复杂的验证逻辑,validator 可以用来替代简单的 type 验证。
  2. 自定义规则:例如,验证字符串长度、数组内容类型、对象属性等。

注意事项:

  1. 返回值必须是布尔值:validator 函数必须返回 true 或 false,否则无法正确触发验证。
  2. 执行时机:validator 在组件实例创建之前执行,因此无法访问组件实例的其他属性(如 data、computed 等)。

示例:

Vue.component('my-component', {
    
    
  props: {
    
    
    propA: {
    
    
      type: String,
      validator: function (value) {
    
    
        // 验证字符串长度是否小于等于 10
        return value.length <= 10;
      }
    },
    propB: {
    
    
      validator: function (value) {
    
    
        // 验证是否为数组且数组长度大于 0
        return Array.isArray(value) && value.length > 0;
      }
    }
  }
});

1.4 props 的单向数据流

Vue 2 遵循单向数据流原则,即数据只能从父组件流向子组件,子组件不能直接修改接收到的props。若子组件需要修改 props 的值,通常的做法是通过自定义事件向父组件发送通知,由父组件来修改数据。

<!-- 子组件 -->
<template>
  <button @click="increaseCount">增加计数</button>
</template>

<script>
export default {
    
    
  props: ['count'],
  methods: {
    
    
    increaseCount() {
    
    
    // 子传父 后面会提到
    // update:count 是自定义事件 不是特殊的写法只是一个名字,但是要与父组件中自定义事件同名
    // 这句话表示将this.count + 1 这个值作为参数传递给父组件的自定义事件并作为其形参 
      this.$emit('update:count', this.count + 1);
    }
  }
}
</script>

<!-- 父组件 -->
<template>
  <div>
    <p>当前计数: {
    
    {
    
     currentCount }}</p>
    // 这句后面的@update:count 是自定义事件, 后面是语句,将子组件传递过来的形参通过$event赋值给了currentCount
    <ChildComponent :count="currentCount" @update:count="currentCount = $event" />
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
    
    
  components: {
    
    
    ChildComponent
  },
  data() {
    
    
    return {
    
    
      currentCount: 0
    };
  }
}
</script>

在上述代码中,子组件通过 $emit 触发 update:count 事件,并将新的计数传递给父组件,父组件接收到事件后更新 currentCount 的值。

2. v-model通信(双向绑定)

在 Vue 2 中,v-model 是一个非常方便的指令,常用于实现表单输入的双向数据绑定。同时,它也可以用于自定义组件之间的通信,实现父组件和子组件之间的数据双向绑定。下面详细介绍其原理和使用方法。

2.1 基本原理

在原生表单元素上,v-model 本质上是 :value@input 事件的语法糖。例如,对于一个 input 元素:

<template>
  <input v-model="message">
</template>

<script>
export default {
    
    
  data() {
    
    
    return {
    
    
      message: ''
    };
  }
}
</script>

上面的代码等同于:

<template>
  <input :value="message" @input='message = $event.target.value'>
</template>

<script>
export default {
    
    
  data() {
    
    
    return {
    
    
      message: ''
    };
  }
}
</script>

2.2 在自定义组件中使用v-model

(1) 默认实现

在自定义组件中使用 v-model,子组件需要接收一个 value prop 并触发一个 input 事件。
子组件 ChildComponent.vue

<template>
// 子组件中进行三步 
// 1. :value='value'
// 2. @input="$emit('input', $event.target.value)" 这种情况下,事件一定要用input
  <input :value="value" @input="$emit('input', $event.target.value)">
</template>

<script>
export default {
    
    
// 传过来的值一定要用value接收
  props: ['value']
}
</script>

父组件 ParentComponent.vue

<template>
  <div>
    <p>父组件中的值: {
    
    {
    
     parentValue }}</p>
    // 父组件中通过v-model双向绑定一个变量
    <ChildComponent v-model="parentValue" />
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
    
    
  components: {
    
    
    ChildComponent
  },
  data() {
    
    
    return {
    
    
      parentValue: ''
    };
  }
}
</script>

在上述代码中,父组件通过 v-model 将 parentValue 传递给子组件,子组件接收 value prop 并在输入框输入内容时触发 input 事件,将新的值传递回父组件,从而实现双向数据绑定。

(2) 自定义prop和事件

默认情况下,v-model 使用 value 作为 prop 和 input 作为事件。但你可以通过 model 选项来自定义 prop 和事件的名称。
子组件 CustomChildComponent.vue

<template>
  <input :value="customValue" @input="$emit('customEvent', $event.target.value)">
</template>

<script>
export default {
    
    
  props: ['customValue'],
  // 通过model更改v-model在组件通信时双向绑定的事件名和props值
  model: {
    
    
    prop: 'customValue',
    event: 'customEvent'
  }
}
</script>

父组件 CustomParentComponent.vue

<template>
  <div>
    <p>父组件中的值: {
    
    {
    
     customParentValue }}</p>
    <CustomChildComponent v-model="customParentValue" />
  </div>
</template>

<script>
import CustomChildComponent from './CustomChildComponent.vue';

export default {
    
    
  components: {
    
    
    CustomChildComponent
  },
  data() {
    
    
    return {
    
    
      customParentValue: ''
    };
  }
}
</script>

这里通过 model 选项将 prop 名称改为 customValue,事件名称改为 customEvent,但在父组件中仍然可以使用 v-model 进行双向数据绑定。

(3) 多个v-model绑定

在 Vue 2 中,一个组件可以支持多个 v-model 绑定,通过不同的 prop 和事件来实现。
子组件 MultiModelChild.vue

<template>
  <div>
    <input :value="name" @input="$emit('update:name', $event.target.value)">
    <input :value="age" @input="$emit('update:age', $event.target.value)">
  </div>
</template>

<script>
export default {
    
    
  props: ['name', 'age']
}
</script>

父组件 MultiModelParent.vue

<template>
  <div>
    <p>姓名: {
    
    {
    
     person.name }}</p>
    <p>年龄: {
    
    {
    
     person.age }}</p>
    <MultiModelChild 
    // 通过在v-model后面加 :名字(见名知义的名字, 不知道怎么称呼这个... 理解就好) 进行多个绑定
      v-model:name="person.name" 
      v-model:age="person.age" 
    />
  </div>
</template>

<script>
import MultiModelChild from './MultiModelChild.vue';

export default {
    
    
  components: {
    
    
    MultiModelChild
  },
  data() {
    
    
    return {
    
    
      person: {
    
    
        name: '',
        age: ''
      }
    };
  }
}
</script>

在这个例子中,子组件支持两个 v-model 绑定,分别对应 name 和 age,通过不同的 update:propName 事件来更新父组件的数据。

3. .sync通信(双向绑定)

在 Vue 2 里,.sync 修饰符是一种用于实现父组件和子组件之间数据双向绑定的便捷方式。虽然在 Vue 3 中 .sync 被新的 v-model 语法糖替代,但在 Vue 2 里它有着独特的使用场景和实现机制。下面为你详细介绍 .sync 的相关内容。

3.1 基本原理

.sync 修饰符本质上是一种语法糖,它结合了 v-bind 和自定义事件,实现了数据的双向绑定。在父组件中使用 .sync 修饰符传递数据给子组件,子组件通过触发特定的自定义事件将数据的变化通知给父组件,从而更新父组件的数据。

3.2 使用步骤

(1) 父组件使用.sync传递数据

在父组件中,使用 .sync 修饰符将数据传递给子组件。示例如下:

<template>
  <div>
    <p>父组件中的 count 值: {
    
    {
    
     count }}</p>
    <!-- 使用 .sync 修饰符传递 count 数据 -->
    <ChildComponent :count.sync="count" />
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
    
    
  components: {
    
    
    ChildComponent
  },
  data() {
    
    
    return {
    
    
      count: 0
    };
  }
}
</script>

上述代码中,父组件通过 :count.sync=“count” 将 count 数据传递给子组件,同时监听子组件发出的 update:count 事件。

(2) 子组件触发自定义事件更新数据

子组件需要在合适的时机触发 update:propName 事件,将新的数据传递给父组件。示例如下:

<template>
  <div>
    <button @click="increaseCount">增加 count</button>
    <p>子组件接收到的 count 值: {
    
    {
    
     count }}</p>
  </div>
</template>

<script>
export default {
    
    
  props: ['count'],
  methods: {
    
    
    increaseCount() {
    
    
      // 触发 update:count 事件,将新的 count 值传递给父组件
      this.$emit('update:count', this.count + 1);
    }
  }
}
</script>

在这个子组件中,当用户点击按钮时,会调用 increaseCount 方法,该方法通过 this.$emit(‘update:count’, this.count + 1) 触发 update:count 事件,并将新的 count 值传递给父组件。

3.3 多个.sync 修饰符的使用

一个组件可以同时使用多个 .sync 修饰符来实现多个数据的双向绑定。示例如下:
父组件

<template>
  <div>
    <p>父组件中的 name 值: {
    
    {
    
     name }}</p>
    <p>父组件中的 age 值: {
    
    {
    
     age }}</p>
    <ChildComponent :name.sync="name" :age.sync="age" />
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
    
    
  components: {
    
    
    ChildComponent
  },
  data() {
    
    
    return {
    
    
      name: '张三',
      age: 20
    };
  }
}
</script>

子组件

<template>
  <div>
    <button @click="updateName">更新 name</button>
    <button @click="updateAge">更新 age</button>
    <p>子组件接收到的 name 值: {
    
    {
    
     name }}</p>
    <p>子组件接收到的 age 值: {
    
    {
    
     age }}</p>
  </div>
</template>

<script>
export default {
    
    
  props: ['name', 'age'],
  methods: {
    
    
    updateName() {
    
    
      this.$emit('update:name', '李四');
    },
    updateAge() {
    
    
      this.$emit('update:age', 21);
    }
  }
}
</script>

在这个例子中,父组件通过两个 .sync 修饰符分别将 name 和 age 数据传递给子组件,子组件可以分别触发 update:name 和 update:age 事件来更新父组件中的对应数据。

3.4 注意事项

  • 单向数据流原则:虽然 .sync 实现了数据的双向绑定,但本质上仍然遵循 Vue 的单向数据流原则。子组件不能直接修改从父组件接收到的 props,而是通过触发自定义事件通知父组件进行修改。
  • Vue 3 中的替代:在 Vue 3 中,.sync 被新的 v-model 语法糖替代,例如 v-model:propName 可以实现类似 .sync 的功能。如果你计划将项目升级到 Vue 3,需要注意这一变化。

4. ref 通信(子传父)

在 Vue 2 里,ref 是一种非常实用的组件通信方式,它允许你直接访问子组件实例或 DOM 元素,从而进行数据交互和方法调用。以下从访问子组件实例、访问 DOM 元素以及 $refs 的特性等方面详细介绍 ref 在组件通信中的使用。

4.1 使用 ref 访问子组件实例

(1) 基本用法

在父组件中,可以通过给子组件添加 ref 属性,然后使用 this.$refs 来访问子组件的实例,进而调用子组件的方法或访问其数据。
子组件 ChildComponent.vue

<template>
  <div>
    <p>子组件的数据: {
    
    {
    
     childData }}</p>
  </div>
</template>

<script>
export default {
    
    
  data() {
    
    
    return {
    
    
      childData: '这是子组件的数据'
    };
  },
  methods: {
    
    
    childMethod() {
    
    
      console.log('子组件的方法被调用了');
    }
  }
};
</script>

父组件 ParentComponent.vue

<template>
  <div>
    <!-- 给子组件添加 ref 属性 -->
    <ChildComponent ref="childRef" />
    <button @click="callChildMethod">调用子组件的方法</button>
    <button @click="accessChildData">访问子组件的数据</button>
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
    
    
  components: {
    
    
    ChildComponent
  },
  methods: {
    
    
    callChildMethod() {
    
    
      // 通过 $refs 访问子组件实例并调用其方法
      this.$refs.childRef.childMethod();
    },
    accessChildData() {
    
    
      // 通过 $refs 访问子组件实例并获取其数据
      console.log(this.$refs.childRef.childData);
    }
  }
};
</script>

在上述代码中,父组件通过 ref=“childRef” 给子组件添加了引用,在父组件的方法里,利用 this.$refs.childRef 就可以访问子组件的实例,进而调用子组件的 childMethod 方法或获取 childData 数据。

(2) 注意事项

$refs 在 mounted 钩子之后可用:因为 $refs 是在组件挂载完成后才会被填充,所以在 mounted 钩子函数之前尝试访问 $refs 可能会得到 undefined。例如:

<script>
export default {
    
    
  created() {
    
    
    // 这里 this.$refs.childRef 可能为 undefined
    console.log(this.$refs.childRef); 
  },
  mounted() {
    
    
    // 这里可以正常访问
    console.log(this.$refs.childRef); 
  }
};
</script>

4.2 使用 ref 访问DOM元素

(1) 基本用法

除了访问子组件实例,ref 还能用于访问 DOM 元素。在模板中给 DOM 元素添加 ref 属性,然后在组件的 JavaScript 代码中通过 this.$refs 来访问该 DOM 元素。

<template>
  <div>
    <!-- 给 input 元素添加 ref 属性 -->
    <input ref="inputRef" type="text" />
    <button @click="focusInput">聚焦输入框</button>
  </div>
</template>

<script>
export default {
    
    
  methods: {
    
    
    focusInput() {
    
    
      // 通过 $refs 访问 DOM 元素并调用其方法
      this.$refs.inputRef.focus();
    }
  }
};
</script>

在这个例子中,给 input 元素添加了 ref=“inputRef”,在 focusInput 方法里,使用 this.$refs.inputRef.focus() 就可以让输入框获得焦点。

4.3 $refs 的特性

  • 动态性:$refs 是动态的,它会随着组件的创建和销毁而更新。当子组件被销毁时,对应的 $refs 引用也会被移除。
  • 非响应式:$refs 不是响应式的,也就是说,当 $refs 引用的组件或 DOM 元素发生变化时,不会触发视图的更新。因此,不要在模板中直接使用 $refs。

总之,ref 是一种非常方便的组件通信方式,尤其适用于需要直接操作子组件实例或 DOM 元素的场景,但使用时要注意其特性和使用时机。

5. $emit / v-on 通信(子传父)

在 Vue 2 里,$emit 是实现组件间通信的一种重要手段,主要用于子组件向父组件传递数据和消息。以下为你详细介绍 $emit 在组件通信中的使用方法、原理及相关示例。

5.1 基本原理

$emit 是 Vue 实例的一个方法,其作用是触发自定义事件。在子组件内部,借助 this.$emit('eventName', data) 能够触发一个名为 eventName 的自定义事件,同时可以携带额外的数据 data 传递给父组件。父组件则通过 v-on 指令(通常缩写为 @)来监听这个自定义事件,并指定相应的事件处理函数。

5.2 使用步骤

(1) 子组件触发自定义事件(使用$emit)

子组件需要在合适的时机触发自定义事件,从而将数据传递给父组件。下面是一个简单的子组件示例:

<template>
  <div>
    <!-- 点击按钮触发数据传递 -->
    <button @click="sendDataToParent">发送数据给父组件</button>
  </div>
</template>

<script>
export default {
    
    
  methods: {
    
    
    sendDataToParent() {
    
    
      const data = '这是子组件传递的数据';
      // 触发自定义事件 'customEvent' 并传递数据
      this.$emit('customEvent', data);
    }
  }
};
</script>

在上述代码中,当用户点击按钮时,会调用 sendDataToParent 方法。在该方法里,使用 this.$emit(‘customEvent’, data) 触发了一个名为 customEvent 的自定义事件,并且将数据 data 传递出去。

(2) 父组件监听自定义事件(使用v-on)

父组件需要在使用子组件的地方监听子组件触发的自定义事件,并指定相应的事件处理函数。以下是对应的父组件示例:

<template>
  <div>
    <!-- 监听子组件的自定义事件 -->
    <ChildComponent @customEvent="handleCustomEvent" />
    <p>接收到子组件的数据: {
    
    {
    
     receivedData }}</p>
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
    
    
  components: {
    
    
    ChildComponent
  },
  data() {
    
    
    return {
    
    
      receivedData: ''
    };
  },
  methods: {
    
    
    handleCustomEvent(data) {
    
    
      // 处理接收到的数据
      this.receivedData = data;
    }
  }
};
</script>

在这个父组件中,使用 @customEvent=“handleCustomEvent” 监听子组件触发的 customEvent 事件,并指定了事件处理函数 handleCustomEvent。当子组件触发该事件时,父组件的 handleCustomEvent 方法会被调用,并且可以接收到子组件传递的数据。

5.3 传递多个参数

$emit 还支持传递多个参数给父组件。以下是相应的示例:
子组件

<template>
  <div>
    <button @click="sendMultipleData">发送多个数据</button>
  </div>
</template>

<script>
export default {
    
    
  methods: {
    
    
    sendMultipleData() {
    
    
      const name = '张三';
      const age = 25;
      // 传递多个参数
      this.$emit('multipleDataEvent', name, age);
    }
  }
};
</script>

父组件

<template>
  <div>
    <ChildComponent @multipleDataEvent="handleMultipleData" />
    <p>接收到的姓名: {
    
    {
    
     receivedName }}</p>
    <p>接收到的年龄: {
    
    {
    
     receivedAge }}</p>
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
    
    
  components: {
    
    
    ChildComponent
  },
  data() {
    
    
    return {
    
    
      receivedName: '',
      receivedAge: null
    };
  },
  methods: {
    
    
    handleMultipleData(name, age) {
    
    
      this.receivedName = name;
      this.receivedAge = age;
    }
  }
};
</script>

在这个例子中,子组件通过 $emit 传递了两个参数,父组件的事件处理函数 handleMultipleData 可以接收这两个参数并进行相应的处理。

$emit 是 Vue 2 中实现子组件向父组件通信的有效方式,它使得数据能够从子组件流向父组件,同时遵循了 Vue 的单向数据流原则,保证了数据流动的清晰和可维护性。在实际开发中,你可以根据需求在子组件中触发不同的自定义事件,并在父组件中进行相应的处理。

6. $attrs 和 $listeners通信(跨层级)

在 Vue 2 里,$attrs 和 $listeners 是实现组件间通信,特别是跨层级组件通信的重要工具,它们可以让数据和事件在多层嵌套的组件结构中更便捷地传递。下面将详细介绍它们的相关概念、使用方法及示例。

6.1 基本理解

$attrs

$attrs 是一个包含了父组件传递给子组件但未在子组件的 props 选项中声明的所有属性的对象。通过它,我们可以实现非 props 属性的透传,避免在中间组件中重复定义这些属性。

listeners

$listeners 是一个包含了父组件传递给子组件的所有事件监听器的对象。利用它可以将父组件的事件监听器传递给更深层级的子组件,而无需在中间组件里重新绑定这些事件。

当存在多层嵌套的组件结构,且中间组件不需要使用某些属性或处理某些事件,仅起到传递作用时,$attrs 和 $listeners 就能发挥很大的作用,减少中间组件的代码量,提高代码的可维护性。

6.2 使用方法

以下是一个包含父组件、子组件和孙组件的三层组件结构示例,展示了如何使用 $attrs 和 $listeners 进行通信。
父组件 Parent.vue

向子组件传递了 message 和 count 两个属性,并绑定了 click 和 custom-event 两个事件,同时定义了对应的事件处理函数。

<template>
  <div>
    <!-- 向子组件传递属性和绑定事件 -->
    <ChildComponent
      message="这是一条消息"
      count="10"
      @click="handleClick"
      @custom-event="handleCustomEvent"
    />
  </div>
</template>

<script>
import ChildComponent from './Child.vue';

export default {
    
    
  components: {
    
    
    ChildComponent
  },
  methods: {
    
    
    handleClick() {
    
    
      console.log('点击事件被触发');
    },
    handleCustomEvent() {
    
    
      console.log('自定义事件被触发');
    }
  }
};
</script>

子组件 Child.vue

inheritAttrs: false:阻止 Vue 默认将未声明的属性绑定到子组件的根元素上,以便更灵活地控制属性传递。

v-bind=“$attrs”:将 $attrs 对象中的所有属性传递给孙组件。

v-on=“$listeners”:将 $listeners 对象中的所有事件监听器传递给孙组件。

<template>
  <div>
    <!-- 将 $attrs 和 $listeners 传递给孙组件 -->
    <GrandChildComponent
      v-bind="$attrs"
      v-on="$listeners"
    />
  </div>
</template>

<script>
import GrandChildComponent from './GrandChild.vue';

export default {
    
    
  components: {
    
    
    GrandChildComponent
  },
  inheritAttrs: false // 禁止将未声明的属性绑定到根元素
};
</script>

孙组件 GrandChild.vue

通过 $attrs 对象访问接收到的属性并显示。

通过 $emit 触发 click 和 custom-event 事件,这些事件会直接触发父组件中对应的事件处理函数。

<template>
  <div>
    <p>接收到的消息: {
    
    {
    
     $attrs.message }}</p>
    <p>接收到的计数: {
    
    {
    
     $attrs.count }}</p>
    <button @click="$emit('click')">触发点击事件</button>
    <button @click="$emit('custom-event')">触发自定义事件</button>
  </div>
</template>

<script>
export default {
    
    
  inheritAttrs: false // 禁止将未声明的属性绑定到根元素
};
</script>

6.3 注意事项

  • inheritAttrs 选项:在使用 $attrs 时,通常需要设置 inheritAttrs: false,这样可以避免未声明的属性被默认绑定到组件的根元素上,从而更精确地控制属性的传递。
  • 事件传递的层级:虽然 $listeners 可以实现跨层级的事件传递,但过多的层级可能会使代码逻辑变得复杂,降低代码的可维护性。因此,在使用时要根据实际情况进行合理设计。
  • 事件处理函数的作用域:事件处理函数的作用域始终是定义该函数的组件,即父组件。在多层传递过程中,不会改变事件处理函数的作用域。

7. $children 和 $parent 通信(父子间)

在 Vue 2 里,$children 和 $parent 是 Vue 实例的属性,可用于实现组件间的通信,不过这种通信方式一般用于父子组件之间,并且使用时需要谨慎,因为它会让组件之间的耦合度增加。下面分别详细介绍 $children 和 $parent 的使用方法。

7.1 $parent: 子组件访问父组件

$parent 属性指向当前组件的父组件实例,通过它,子组件可以直接访问父组件的数据、方法等。

示例代码

父组件 Parent.vue

<template>
  <div>
    <p>父组件的数据: {
    
    {
    
     parentData }}</p>
    <ChildComponent />
  </div>
</template>

<script>
import ChildComponent from './Child.vue';

export default {
    
    
  components: {
    
    
    ChildComponent
  },
  data() {
    
    
    return {
    
    
      parentData: '这是父组件的数据'
    };
  },
  methods: {
    
    
    parentMethod() {
    
    
      console.log('父组件的方法被调用了');
    }
  }
};
</script>

子组件 Child.vue

<template>
  <div>
    <button @click="accessParentData">访问父组件的数据</button>
    <button @click="callParentMethod">调用父组件的方法</button>
  </div>
</template>

<script>
export default {
    
    
  methods: {
    
    
    accessParentData() {
    
    
      // 通过 $parent 访问父组件的数据
      console.log(this.$parent.parentData);
    },
    callParentMethod() {
    
    
      // 通过 $parent 调用父组件的方法
      this.$parent.parentMethod();
    }
  }
};
</script>

7.2 $children:父组件访问子组件

$children 属性是一个数组,包含了当前组件的所有子组件实例,通过它,父组件可以直接访问子组件的数据、方法等。

示例代码

父组件 Parent.vue

<template>
  <div>
    <ChildComponent ref="childRef" />
    <button @click="accessChildData">访问子组件的数据</button>
    <button @click="callChildMethod">调用子组件的方法</button>
  </div>
</template>

<script>
import ChildComponent from './Child.vue';

export default {
    
    
  components: {
    
    
    ChildComponent
  },
  methods: {
    
    
    accessChildData() {
    
    
      // 通过 $children 访问子组件的数据
      console.log(this.$children[0].childData);
    },
    callChildMethod() {
    
    
      // 通过 $children 调用子组件的方法
      this.$children[0].childMethod();
    }
  }
};
</script>

子组件 Child.vue

<template>
  <div>
    <p>子组件的数据: {
    
    {
    
     childData }}</p>
  </div>
</template>

<script>
export default {
    
    
  data() {
    
    
    return {
    
    
      childData: '这是子组件的数据'
    };
  },
  methods: {
    
    
    childMethod() {
    
    
      console.log('子组件的方法被调用了');
    }
  }
};
</script>

7.3 注意事项

  • 耦合度问题: 使用 $parent$children 会使组件之间的耦合度增加,这意味着当组件的结构发生变化时,可能需要修改大量的代码。因此,这种通信方式只适用于简单的场景,对于复杂的组件通信,建议使用 props、$emit、事件总线或 Vuex 等方式。

  • 顺序问题: $children 数组的顺序是按照子组件在模板中声明的顺序排列的,如果子组件的顺序发生变化,可能会影响代码的正确性。因此,在使用 $children 时,最好结合 ref 来明确指定要访问的子组件。例如在上述父组件示例中,使用 this.$refs.childRef 会比 this.$children[0] 更可靠。

8. provide 和 inject 通信(跨层级)

在 Vue 2 里,provide 和 inject 是一对用于实现跨层级组件通信的选项,也就是祖先组件向后代组件传递数据,而不需要在每一级中间组件都显式地传递。下面详细介绍它们的使用方法、原理及注意事项。

8.1 原理

  • provide:是一个选项,在祖先组件中使用,它是一个对象或者返回一个对象的函数,该对象包含了要提供给后代组件的数据或方法。

  • inject:也是一个选项,在后代组件中使用,它是一个字符串数组或者一个对象,用于接收祖先组件通过 provide 提供的数据或方法。

8.2 使用方法

(1) 在祖先组件中使用provide提供数据

以下是一个简单的示例:
祖先组件

<template>
  <div>
    <ChildComponent />
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
    
    
  components: {
    
    
    ChildComponent
  },
  provide: {
    
    
    // 提供一个数据
    sharedData: '这是祖先组件提供的数据',
    // 提供一个方法
    sharedMethod: function () {
    
    
      console.log('这是祖先组件提供的方法');
    }
  }
};
</script>

在上述代码中,祖先组件通过 provide 选项提供了一个数据 sharedData 和一个方法 sharedMethod。

(2) 在后代组件中使用 inject 接收数据

以下是对应的后代组件示例:
孙子组件

<template>
  <div>
    <p>接收到的祖先组件数据: {
    
    {
    
     sharedData }}</p>
    <button @click="callSharedMethod">调用祖先组件的方法</button>
  </div>
</template>

<script>
export default {
    
    
  inject: ['sharedData', 'sharedMethod'],
  methods: {
    
    
    callSharedMethod() {
    
    
      this.sharedMethod();
    }
  }
};
</script>

在这个后代组件中,使用 inject 选项接收了祖先组件提供的 sharedData 和 sharedMethod,并在模板中显示数据,在方法中调用方法。

8.3 使用函数形式的 provide

provide 也可以是一个返回对象的函数,这样可以动态地提供数据。示例如下:

<template>
  <div>
    <ChildComponent />
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
    
    
  components: {
    
    
    ChildComponent
  },
  data() {
    
    
    return {
    
    
      dynamicData: '动态数据'
    };
  },
  provide() {
    
    
    return {
    
    
      dynamicSharedData: this.dynamicData
    };
  }
};
</script>

在后代组件中同样可以使用 inject 接收这个动态数据。

8.4 注意事项

  • 单向数据流:provide 和 inject 主要用于单向的数据传递,也就是从祖先组件向后代组件传递。虽然后代组件可以修改接收到的数据,但这并不推荐,因为会破坏单向数据流的原则,使数据流向变得复杂,不利于代码的维护。
  • 响应式问题:如果 provide 提供的数据是响应式的(例如来自 data 选项),那么后代组件接收到的数据也是响应式的。但是,如果 provide 提供的是一个普通的对象或值,修改这个值不会触发后代组件的更新。
  • 依赖关系:使用 provide 和 inject 会使组件之间产生一定的依赖关系,因为后代组件依赖于祖先组件提供的数据。所以在使用时要注意组件的结构和数据的流向,避免过度依赖导致代码难以维护。

9. EventBus事件总线 通信(父子间,兄弟间,跨层级)

在 Vue 2 里,Event Bus(事件总线)是一种用于实现组件间通信的方式,它特别适合在非父子组件(如兄弟组件、跨层级组件)之间传递数据和消息。下面详细介绍 Event Bus 的原理、使用步骤、示例代码以及相关注意事项。

9.1 原理

Event Bus 本质上是一个 Vue 实例,它作为一个全局的事件中心,组件可以在这个实例上发布(触发)事件,也可以监听这些事件。当一个组件发布事件时,其他监听了该事件的组件会接收到通知并执行相应的操作,从而实现组件间的通信。

9.2 使用方法

(1) 创建 Event Bus

首先需要创建一个单独的 JavaScript 文件来定义 Event Bus。通常命名为 eventBus.js,示例代码如下:


// 项目中可以放在utils文件夹中 src/utils/EventBus.js

import Vue from 'vue';
// 创建一个 Vue 实例作为 Event Bus
export const eventBus = new Vue();

在这个文件中,引入 Vue 并创建了一个新的 Vue 实例 eventBus,然后将其导出,以便在其他组件中使用。

(2) 在发送组件中发布事件

在需要发送数据或消息的组件中,导入 Event Bus 并使用 $emit 方法发布事件。以下是一个示例:

<template>
  <div>
    <button @click="sendMessage">发送消息</button>
  </div>
</template>

<script>
import {
    
     eventBus } from './eventBus.js';

export default {
    
    
  methods: {
    
    
    sendMessage() {
    
    
      const message = '这是一条消息';
      // 发布一个名为 'custom-event' 的事件,并传递消息
      eventBus.$emit('custom-event', message);
    }
  }
};
</script>

在上述代码中,当用户点击按钮时,会调用 sendMessage 方法,该方法通过 eventBus.$emit(‘custom-event’, message) 发布了一个名为 custom-event 的事件,并传递了消息 message。

(3) 在接收组件中监听事件

在需要接收数据或消息的组件中,导入 Event Bus 并使用 $on 方法监听事件。以下是对应的示例:

<template>
  <div>
    <p>接收到的消息: {
    
    {
    
     receivedMessage }}</p>
  </div>
</template>

<script>
import {
    
     eventBus } from './eventBus.js';

export default {
    
    
  data() {
    
    
    return {
    
    
      receivedMessage: ''
    };
  },
  mounted() {
    
    
    // 监听 'custom-event' 事件
    eventBus.$on('custom-event', (message) => {
    
    
      // 处理接收到的消息
      this.receivedMessage = message;
    });
  },
  beforeDestroy() {
    
    
    // 组件销毁前移除事件监听,防止内存泄漏
    eventBus.$off('custom-event');
  }
};
</script>

在这个组件中,在 mounted 钩子函数中使用 eventBus.$on('custom-event', callback) 监听 custom-event 事件,并在回调函数中处理接收到的消息。同时,在 beforeDestroy 钩子函数中使用 eventBus.$off('custom-event') 移除事件监听,避免内存泄漏。

9.3 注意事项

  • 内存泄漏问题:在组件销毁时,一定要记得使用 $off 方法移除事件监听,否则可能会导致内存泄漏。因为即使组件已经销毁,事件监听仍然存在,会占用内存资源。
  • 事件命名冲突:由于 Event Bus 是全局的,不同组件可能会使用相同的事件名,从而导致事件命名冲突。为了避免这种情况,建议使用有意义且唯一的事件名,或者采用命名空间的方式来管理事件名。
  • 代码可维护性:随着项目的增大,使用 Event Bus 可能会使组件间的通信变得复杂,导致代码的可维护性降低。因此,在大型项目中,对于复杂的组件通信,建议使用 Vuex 等状态管理工具。

通过 Event Bus,可以方便地实现非父子组件之间的通信,在一些小型项目或者简单的组件通信场景中非常实用。

10. $root通信(父传子)

在 Vue 2 里,$root 是 Vue 实例的一个属性,它指向当前 Vue 应用的根实例。借助 $root,可以实现组件间的通信,不过这种通信方式一般适用于特定场景,并且使用时需要谨慎,因为可能会增加组件间的耦合度。下面详细介绍 $root 在组件通信中的使用方法、原理及注意事项。

10.1 原理

每个 Vue 应用都有一个根实例,当创建 Vue 实例时,传入的配置对象所创建的实例就是根实例。在任何一个子组件中,都可以通过 this.$root 来访问这个根实例,进而访问根实例上的数据、方法等,以此实现组件间的通信。

10.2 使用方法

(1) 根实例的定义

首先来看根实例的定义,通常在 main.js 文件中创建:

import Vue from 'vue';
import App from './App.vue';

// 创建根实例
const rootInstance = new Vue({
    
    
  data() {
    
    
    return {
    
    
      sharedData: '这是根实例的数据',
      counter: 0
    };
  },
  methods: {
    
    
    incrementCounter() {
    
    
      this.counter++;
    }
  },
  render: h => h(App)
}).$mount('#app');

在上述代码中,创建了一个根实例,包含了 sharedData 和 counter 两个数据属性,以及 incrementCounter 方法。

(2) 子组件中使用 $root 进行通信

以下是一个子组件的示例,展示如何在子组件中使用 $root 访问根实例的数据和方法:

<template>
  <div>
    <p>根实例的 sharedData: {
    
    {
    
     $root.sharedData }}</p>
    <p>根实例的 counter: {
    
    {
    
     $root.counter }}</p>
    <button @click="$root.incrementCounter()">增加计数器</button>
  </div>
</template>

<script>
export default {
    
    
  // 组件选项
};
</script>

在这个子组件中:

  • 通过 this.$root.sharedData 访问根实例的 sharedData 数据,并在模板中显示。
  • 通过 this.$root.counter 访问根实例的 counter 数据,并在模板中显示。
  • 通过 this.$root.incrementCounter() 调用根实例的 incrementCounter 方法,实现计数器的增加。

10.3 注意事项

  • 耦合度问题:使用 $root 会使组件与根实例紧密耦合,当根实例的结构或数据发生变化时,可能会影响到多个子组件。因此,这种通信方式只适用于简单的场景,对于复杂的组件通信,建议使用 props、$emit、事件总线或 Vuex 等方式。
  • 数据的可维护性:由于多个组件都可以直接访问和修改根实例的数据,可能会导致数据流向不清晰,增加代码的维护难度。所以,在使用 $root 时,要确保对数据的修改是可控的,避免出现数据不一致的问题。
  • 生命周期问题:在组件的生命周期钩子中使用 $root 时,要确保根实例已经初始化完成。一般来说,在 mounted 钩子之后使用 $root 是比较安全的

$root 可以作为一种简单的组件通信方式,但在实际开发中要谨慎使用,根据具体的业务场景选择合适的通信方案。

11. slot 插槽(父传子内容块,子传父数据)

在 Vue 2 中,slot插槽是一种强大的组件通信方式,它允许你在父组件中向子组件传递内容,实现父组件与子组件之间的内容分发和交互。下面将详细介绍 Vue 2 中slot插槽的使用以及如何通过它进行组件通信。

11.1 普通插槽(默认插槽)

普通插槽也称为默认插槽,它允许父组件向子组件传递单个内容块。

子组件(ChildComponent.vue)

<template>
  <div>
    <!-- 定义默认插槽 -->
    <slot></slot>
  </div>
</template>

<script>
export default {
    
    
  name: 'ChildComponent'
}
</script>

父组件 (ParentComponent.vue)

<template>
  <div>
    <!-- 使用子组件并传递内容 -->
    <ChildComponent>
      <p>这是通过默认插槽传递的内容</p>
    </ChildComponent>
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue'

export default {
    
    
  components: {
    
    
    ChildComponent
  }
}
</script>

在上述代码中,子组件通过<slot></slot>定义了一个默认插槽,父组件在使用子组件时,将<p>标签内的内容传递给了子组件的默认插槽。

11.2 具名插槽

具名插槽允许父组件向子组件传递多个内容块,每个内容块都可以有自己的名称。

子组件(ChildComponent.vue)

<template>
  <div>
    <!-- 定义具名插槽 -->
    <slot name="header"></slot>
    <slot></slot>
    <slot name="footer"></slot>
  </div>
</template>

<script>
export default {
    
    
  name: 'ChildComponent'
}
</script>

父组件(ParentComponent.vue)

<template>
  <div>
    <!-- 使用子组件并接收作用域插槽的数据 -->
    <ChildComponent>
      <template #default="slotProps">
        <p>姓名:{
    
    {
    
     slotProps.user.name }}</p>
        <p>年龄:{
    
    {
    
     slotProps.user.age }}</p>
      </template>
    </ChildComponent>
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue'

export default {
    
    
  components: {
    
    
    ChildComponent
  }
}
</script>

在上述代码中,子组件定义了三个插槽,分别是名为header、默认插槽和名为footer的插槽。父组件通过<template #header>和<template #footer>向对应的具名插槽传递内容,默认内容则会传递给默认插槽。

11.3 作用域插槽

作用域插槽允许子组件向父组件传递数据,父组件可以在插槽内容中使用这些数据。
子组件(ChildComponent.vue)

<template>
  <div>
    <!-- 定义作用域插槽 -->
    <slot :user="user"></slot>
  </div>
</template>

<script>
export default {
    
    
  name: 'ChildComponent',
  data() {
    
    
    return {
    
    
      user: {
    
    
        name: 'John',
        age: 25
      }
    }
  }
}
</script>

父组件(ParentComponent.vue)

<template>
  <div>
    <!-- 使用子组件并接收作用域插槽的数据 -->
    <ChildComponent>
      <template #default="slotProps">
        <p>姓名:{
    
    {
    
     slotProps.user.name }}</p>
        <p>年龄:{
    
    {
    
     slotProps.user.age }}</p>
      </template>
    </ChildComponent>
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue'

export default {
    
    
  components: {
    
    
    ChildComponent
  }
}
</script>

在上述代码中,子组件通过slot的v-bind指令将user对象传递给父组件。父组件在插槽内容中使用slotProps来接收子组件传递的数据,并显示在页面上。
通过以上三种插槽的使用,你可以在 Vue 2 中实现灵活的组件通信,根据不同的需求传递内容和数据。

12. Vuex (管理全局状态的最佳实践)

12.1 Vuex核心概念

概念 作用 特点
State 存储应用级状态(数据源) 响应式数据
Mutations 唯一修改state的方法(同步操作) 通过commit触发
Actions 处理异步操作和业务逻辑,可触发mutaions 通过dispatch触发
Getters 对state的计算属性(类似组件中的computed) 缓存计算结果
Modules 模块化分割大型状态树 避免单一store臃肿

12.2 基础配置

(1) 安装与创建
// store/index.js
import Vue from 'vue'
// 导入vuex包
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    
    
  state: {
    
    
    count:0,
    user: null
  },
  mutations: {
    
    
    INCREMENT (state) {
    
    
      state.count++
    },
    SET_USER (state, user) {
    
    
      state.user = user
    }
  },
  actions: {
    
    
  	// context是固定传参,后面为自定义参数
    async login (context, credentials) {
    
    
      // api是封装的发送请求的接口
      const user = await api.login(credentials)
      context.commit('SET_USER', user)
    }
  },
  getters: {
    
    
    doubleCount: state => state.count * 2
  }
})
(2) 注入 Vue 实例
// main.js
import store from './store'

new Vue({
    
    
  store,
  render: h => h(App)
}).$mount('#app')

12.3 组件中使用Vuex

(1) 直接访问方式
<template>
  <div>
    Count: {
    
    {
    
     $store.state.count }}
    Double: {
    
    {
    
     $store.getters.doubleCount }}
    <button @click="$store.commit('INCREMENT')">+1</button>
    <button @click="$store.dispatch('login', {user: 'admin'})">Login</button>
  </div>
</template>
(2) 辅助函数方式(推荐)
<script>
import {
    
     mapState, mapGetters, mapMutations, mapActions } from 'vuex'

export default {
    
    
  computed: {
    
    
    // 展开 state 和 getters
    ...mapState(['count']),
    ...mapGetters(['doubleCount']),
    
    // 重命名映射
    ...mapState({
    
    
      userName: state => state.user.name
    })
  },
  methods: {
    
    
    // 映射 mutations/actions
    ...mapMutations(['INCREMENT']),
    ...mapActions(['login']),
    
    // 带重命名
    ...mapActions({
    
    
      userLogin: 'login' 
    })
  }
}
</script>

12.4 典型通信场景

(1) 场景1:全局用户状态管理
// store/modules/user.js
export default {
    
    
  namespaced: true,
  state: {
    
    
    token: localStorage.getItem('token') || '',
    profile: null
  },
  mutations: {
    
    
    SET_TOKEN(state, token) {
    
    
      state.token = token
      localStorage.setItem('token', token)
    },
    SET_PROFILE(state, profile) {
    
    
      state.profile = profile
    }
  },
  actions: {
    
    
    async fetchProfile({
     
      commit }) {
    
    
      const res = await axios.get('/api/profile')
      commit('SET_PROFILE', res.data)
    }
  }
}

在组件中使用:

<script>
import {
    
     mapActions, mapState } from 'vuex'

export default {
    
    
  computed: {
    
    
    ...mapState('user', ['token', 'profile'])
  },
  methods: {
    
    
    ...mapActions('user', ['fetchProfile'])
  },
  mounted() {
    
    
    if(this.token) {
    
    
      this.fetchProfile()
    }
  }
}
</script>

12.5 最佳实践

(1) 严格模式 (开发环境中启用)
const store = new Vuex.store({
    
    
  strict: process.env.NODE_ENV !== 'production'
})
(2) 模块化开发
// store/modules/cart.js
export default {
    
    
  namespaced: true,
  state: {
    
    
    items: []
  },
  mutations: {
    
    },
  actions: {
    
    }
}

// store/index.js
import cart from './modules/cart.js'
import user from './modules/user.js'
export default new Vuex.store({
    
    
  modules: {
    
    
    cart,
    user
  }
})
(3) 表单处理方案
<input :value="message" @input="updateMessage">

<script>
export default {
    
    
  computed: {
    
    
    message () {
    
    
      return this.$store.state.obj.message
    }
  },
  methods: {
    
    
    updateMessage (e) {
    
    
      this.$store.commit('UPDATE_MESSAGE', e.target.value)
    }
  }
}
</script>

12.6 与组件通信方式对比

方式 适用场景 数据流向 维护成本
Props/events 父子组件简单通信 单向
Event Bus 非父子组件简单通信 任意方向
Vuex 复杂应用状态管理 集中式管理
provide/inject 深层嵌套组件通信 祖先 → 后代

12.7 常见问题解决方案

(1) 热重载问题
// store.js
if (module.hot) {
    
    
  module.hot.accept(['./modules'], () => {
    
    
    store.hotUpdate({
    
    
      modules: {
    
    
        cart: require('./modules/cart').default,
        user: require('./modules/user').default
      }
    })
  })
}
(2) 持久化存储
// 使用 vuex-persistedstate 插件(使用前先安装插件)
import createPersistedState from 'vuex-persistedstate'

export default new Vuex.Store({
    
    
  plugins: [createPersistedState()]
})

通过合理使用 Vuex,可以实现以下优势:

  • 集中管理所有组件的共享状态
  • 状态变更可预测(通过严格的 mutation 流程)
  • 方便调试(配合 Vue DevTools 时光旅行功能)
  • 适合中大型复杂应用的状态管理需求

猜你喜欢

转载自blog.csdn.net/qq_74114417/article/details/145640993
今日推荐