• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Vue3组件通信方式

武飞扬头像
呜嘶
帮助5

Vue3组件通信方式

vue2: props、自定义事件、全局事件总线$bus、pubsub、vuex、ref、slot

props

可以实现父子组件通信,在vue3中我们可以通过defineProps获取父组件传递的数据,且在组件内部不需要引入defineProps方法可直接使用。【只读】

父组件给子组件传值

<Child info="中国" :money="money"></Child>

父组件向子组件传值,如果不加:冒号,传过去的不是数值,而是一个字符串;加上:冒号,传过去的就是一个数值,不是字符串,因为加了:冒号以后引号""里面的内容就是一个js表达式

子组件接收方式一

let props = defineProps({
    info:{
        type:String,
        default:"默认参数"
    },
    money:{
        type:Number,
        default:0
    }
})

子组件接收方式二

let props = defineProps(["info","money"])

在模板中使用可省略props

自定义事件

vue2 如果直接在自定义的组件上写@click事件是不管用的,但是在事件的后面加上.native修饰符就可以直接在父组件中使用这个点击事件了。

<Child @xxx="handler1"></Child>
<template>
    <button @click="handler"></button>
</template>
<script setup lang="ts">
let $emit = defineEmits(["xxx"])
const handler = () => {
    $emit("xxx","吃饭","喝水");
}
</script>

defineEmits,不需要引入直接使用。defineEmits方法执行,传递一个数组,数组元素即为将来组件需要触发的自定义事件类型,此方执行会返回一个$emit方法用于触发自定义事件。

当点击按钮的时候,事件回调内部调用$emit方法去触发自定义事件,第一个参数为触发事件类型,第二个、三个、N个参数即为传递给父组件的数据。

<Child @xxx="handler" @click="handler1"></Child>

正常说组件标签书写@click应该为原生DOM事件,但是如果子组件内部通过defineEmits定义就变为自定义事件了。

let $emit = defineEmits(["xxx","click"])

全局事件总线

mitt 全局事件总线可以实现任意组件通信,在vue2中可以根据VM与VC关系推出全局事件总线。 但是在vue3中没有Vue构造函数,也就没有Vue.prototype.以及组合式API写法没有this, 可以使用插件mitt实现。

npm install --save mitt
import mitt from 'mitt';
const $bus = mitt();
export default $bus;
import $bus from "../../bus";
//组合式API函数
import { onMounted ] from "vue";//组件挂载完毕的时候,当前组件绑定一个事件,接受将来兄弟组件传递的数据
onMounted(() => {//第一个参数:即为事件类型 第二个参数:即为事件回调
    $bus.on("car",(car) => {
        console.log(car);
    }
}

v-model

v-model指令可以收集表单数据(数据双向绑定),除此之外它也可以实现父子组件数据同步。而v-model是指利用props[modelValue]与自定义事件[update:modelValue]实现的。

useAttrs

在Vue3中可以利用useAttrs方法获取组件的属性与事件(包含:原生DOM事件或者自定义事件),次函数功能类似于Vue2框架中attrs属性与listeners方法。

在父组件内部使用一个子组件my-button

<Child size="small" title="标题" @click="handler"></Child>

子组件内部可以通过useAttrs方法获取组件属性与事件.因此你也发现了,它类似于props,可以接受父组件传递过来的属性与属性值。需要注意如果defineProps接受了某一个属性,useAttrs方法返回的对象身上就没有相应属性与属性值。

import { useAttrs } from 'vue';
let $attrs = useAttrs();

ref与$parent

ref 可以在父组件内部通过ref获取子组件实例VC,子组件内部的方法与响应式数据父组件可以使用。

<template>
    <Son ref="son"></Son>
</template>
<script lang="ts">
import Vue from 'vue'
import Son from 'Son'
import { onMounted, ref } from 'vue'
const son = ref()
onMounted(()=>{
    console.log(son.value);
    
})
</script>

如果想让父组件获取子组件的数据或者方法需要通过defineExpose对外暴露,因为vue3中组件内部的数据对外“关闭的”,外部不能访问.

<script lang="ts">
import {  ref } from 'vue'
let money = ref(1000)
const handler = ()=>{
    console.log(1);
}
defineExpose({
    money,
    handler
})
</script>

$parent可以获取某一个组件的父组件实例VC,因此可以使用父组件内部的数据与方法。必须子组件内部拥有一个按钮点击时候获取父组件实例,当然父组件的数据与方法需要通过defineExpose方法对外暴露。

<button @click="handler($parent)">点击</button>

provide与inject【提供和注入】

vue3提供两个方法provide与inject,可以实现隔辈组件传递参数。

provide方法用于提供数据,此方法执需要传递两个参数,分别提供数据的key与提供数据value。

注意:后代组件修改的时候,源数据也会修改。

import { provide } from 'vue'
provide('token','token')

后代组件可以通过inject方法获取数据,通过key获取存储的数值

import {infect} from 'vue'
let token = infect('token')

pinia

pinia的特点

  • 支持options api 和 composition api
  • 去除 mutations 比vuex更加精简
  • 在页面或者组件直接引用后使用, 数据来源更加清晰调试更加方便
  • 全面的TypeScript支持
  • 服务器端渲染支持
  • 轻量 压缩后的体积只有1kb左右

下载

npm install pinia --save

使用

// main.js
import { createPinia } from 'pinia'
app.use(createPinia())

//store/index.ts
import { createPinia } from 'pinia'
const store = createPinia()
export default store;
// main.js
import store from './store'
const app = create(App)
app.use(store)

核心概念与基本使用

创建Store

import { defineStore } from 'pinia';

// defineStore 第一个参数是id,必需且值唯一
export const useUserStore = defineStore('user', {
  //state返回一个函数,防止作用域污染
  state: () => {
    return {
      userInfo: {
        name: 'zhangsan',
        age: 23,
      },
      token: 'S1',
    };
  },
  getters: {
    getNewName: (state) => state.userInfo.name   'vip',
  },
  actions: {
    //更新整个对象
    updateUserInfo(userInfo: { name: string; age: number }) {
      this.userInfo = userInfo;
    },
    //更新对象中某个属性
    updateAge(age: number) {
      this.userInfo.age = age;
    },
    //更新基础数据类型
    updateToken(token: string) {
      this.token = token;
    },
  },
});

使用store

store 是一个用 reactive 包装的对象,直接解构读取state会失去响应式,因此需要storeToRefs,它将为每一个响应式属性创建引用;需通过 .value 读取解构出来的值

<template>
  <div>
    <div>姓名:{{ userInfo.name }} 年龄:{{ userInfo.age }}</div>
    <div>token:{{ token }}</div>
    <div>getter值:{{ newName }}</div>
    <button @click="handleUser">更新用户</button>
    <button @click="handleAge">更新年龄</button>
    <button @click="handleToken">更新token</button>
  </div>
</template>

<script setup lang="ts">
import { storeToRefs } from 'pinia';
import { useUserStore } from '@/store/user'; //路径别名,引入store

const userStore = useUserStore();

//storeToRefs 会跳过所有的 action 属性
const { userInfo, token, newName } = storeToRefs(userStore);

//action 属性直接解构
const { updateUserInfo, updateAge, updateToken } = userStore;

const handleUser = () => {
  updateUserInfo({ name: 'lisi', age: 24 });
};

const handleAge = () => {
  //userInfo是一个ref响应式引用,需通过.value取值
  updateAge(userInfo.value.age   1);
};

const handleToken = () => {
  updateToken('23234');
};
</script>

异步 actions

除了上面展示的同步action,action也可以是异步的

新建store/list.ts

import { defineStore } from 'pinia';

const getData = () => {
  return new Promise<number>((resolve) => {
    setTimeout(() => {
      resolve(Math.random() * 100);
    }, 200);
  });
};

export const useListStore = defineStore('list', {
  state: () => {
    return {
      list: [] as number[],
    };
  },
  actions: {
    async updateList() {
      try {
        const data = await getData();
        this.list.push(data);
      } catch {
        /* empty */
      }
    },
  },
});

store 的相互引用

每个store可看做一个hook。相互引用即调用不同hook

新建store/userSex.ts

import { defineStore } from 'pinia';
import { useUserStore } from './user';

enum Sex {
  man = '男人',
  woman = '女人',
}

export const userSexStore = defineStore('user2', {
  state: () => {
    return {
      sex: Sex.man,
    };
  },
  actions: {
    updateSex() {
      const userStore = useUserStore();  // 引用其他store
      if (userStore.userInfo.name !== 'zhangsan') this.sex = Sex.woman;
    },
  },
});

路由钩子中使用store

使用store的前提是保证pinia已被注册

import { useUserStore } from '@/store/user';

router.beforeEach((to, from) => {
  // 这样做是可行的,因为路由器是在其被安装之后开始导航的,
  // 而此时 Pinia 也已经被安装。
  const userStore = useUserStore();
  if (!userStore.token && to.path !== "/login") {
    return "/login";
  }
});

slot

插槽:默认插槽、具名插槽、作用域插槽可以实现父子组件通信.

默认插槽

在子组件内部的模板中书写slot全局组件标签

<template>
    <div>
        <slot></slot>
    </div>
</template>
<script lang="ts">
</script>

在父组件内部提供结构:Todo即为子组件,在父组件内部使用的时候,在双标签内部书写结构传递给子组件

<Todo>
    <h1>填充默认插槽</h1>
</Todo>

具名插槽

<template>
    <div>
        <h1>todo</h1>
        <slot name="a"></slot>
        <slot name="b"></slot>
    </div>
</template>
<script lang="ts">
</script>

父组件内部向指定的具名插槽传递结构。需要注意v-slot:可以替换为#

<template>
    <div>
        <Todo>
            <template #a>
                <div>填入组件a</div>
            </template>
            <template v-slot:b>
                <div>填入组件b</div>
            </template>
        </Todo>
    </div>
</template>

作用域插槽

子组件数据由父组件提供。

学新通

学新通

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfhefef
系列文章
更多 icon
同类精品
更多 icon
继续加载