vue3 + vite + ts基础

import x from xxx.vue报错

img
在vite-env.d.ts文件中添加如下代码:

/// <reference types="vite/client" />
declare module '*.vue' {
  import { ComponentOptions } from 'vue'
  const componentOptions: ComponentOptions
  export default componentOptions
}

组件通信

defineProps中的属性只读

在vue2中props中属性允许修改,但是提示警告,而vue3中的props属性是只读的,不能修改。

img


defineEmits: 声明触发的事件

<script setup lang="ts">
// 通过defineEmits声明自定义事件,如果包含了click事件,那么子组件的click事件将无法触发,而是将click事件作为自定义事件触发
let $emits = defineEmits(["childEvent"]);
const childEvent = () => {
  $emits("childEvent", "子组件参数");
};
</script>

mitt: 全局事件总线插件

vue2中全局事件总线通过对Vue原型对象添加一个vm实例,通过该vm实例实现全局事件,各组件使用this获取到该vm从而实现事件通信,而vue3中无法对Vue实例操作,且setup中无法通过this获取vm,因此需要借助插件实现全局事件总线。

mitt插件是一个轻量级的事件总线插件,使用起来非常简单,只需要在main.ts中引入并注册即可,然后在组件中使用即可。

bus/index.ts

import mitt from "mitt";
const $mitt = mitt();
export default $mitt;
// 组件定义事件
import $mitt from '../../bus';

$mitt.on('childEvent1', (data) => {
  console.log('子组件1中接收子组件2传递的参数:', data);
});
// 其他组件触发事件
import $mitt from "../../bus";
const childEvent1 = (data: { info: string }) => {
  $mitt.emit("childEvent1", data);
};

img


v-model: 父子组件双向数据同步

通过props + 自定义事件实现父子组件数据同步

父组件:

<script setup lang="ts">
import Child from "./Child1.vue";
import { ref } from "vue";
let money = ref(1000);
// 父组件中修改v-model数据
function updateMoney(addMoney: number) {
  money.value += addMoney;
}
</script>

<template>
  v-model: 父子组件双向通信
  <div class="parent">
    父组件:
    <div>账户金额: <input type="text" v-model="money" /></div>
    <div>
      <Child :money="money" @update:money="updateMoney"> </Child>
    </div>
  </div>
</template>

子组件:

<script setup lang="ts">
// 声明自定义事件
let $emits = defineEmits<{
  (e: "update:money", money: number): void;
}>();
// 声明props属性
defineProps<{
  money: number;
}>();
// 子组件自定义事件中触发父组件自定义事件
const addMoney = (money: number) => {
  $emits("update:money", money);
};
</script>

<template>
  <div class="child">
    子组件1: 父组件的账号金额: {{ money }}

    <button @click="addMoney(100)">修改父组件金额+100</button>
  </div>
</template>

img


通过v-model实现父子组件数据同步

父组件:

<script setup lang="ts">
import Child from "./Child1.vue";
import { ref } from "vue";
let money = ref(1000);
</script>

<template>
  v-model: 父子组件双向通信
  <div class="parent">
    父组件:
    <div>
      账户金额:
      <input type="text" v-model="money" />
      <div>
        <!-- <Child
          :modelValue="money"
          @update:modelValue="(newValue: number) => money = newValue"
        >
        </Child>-->
        <Child v-model="money"></Child>
      </div>
    </div>
  </div>
</template>

子组件:

<script setup lang="ts">
// 定义 emit
let $emits = defineEmits<{
  (e: "update:modelValue", value: number): void;
}>();
// 定义 props
let props = defineProps<{
  modelValue: number;
}>();

const addMoney = (money: number) => {
  $emits("update:modelValue", props.modelValue + money);
};
</script>

<template>
  <div class="child">
    子组件1: 父组件的账号金额: {{ modelValue }}

    <button @click="addMoney(100)">修改父组件金额+100</button>
  </div>
</template>

img

v-model原理

<Child v-model="money" />原理:
1.通过props向子组件传递一个固定为modelValue的属性,即 <Child modelValue="money" />
2.为子组件绑定一个固定为update:modelValue自定义事件,回调逻辑也是固定的,就是把双向绑定的属性值替换为新值,即 <Child @update:modelValue="(newValue) => {money = newValue}" />
3.子组件声明modelValue属性,并触发update:modelValue自定义事件,将新的值传递给父组件
4.父组件接收到子组件传递的值,替换掉双向绑定的属性值,从而触发vue从新解析模板,更新父组件和子组件使用的双向绑定属性值

vue3.4版本使用v-model更为简洁

defineModel


useAttrs辅助函数

作用1: 子组件未使用props接收父组传递的属性,将会被添加到attrs属性中,可以通过useAttrs辅助函数获取

使用场景: 组件二次封装

对按钮组件二次封装

封装一个HintButton组件,

<script setup lang="ts">
import { useAttrs } from 'vue'
let $useAttrs = useAttrs();
console.log('$useAttrs:',$useAttrs);

let $props = defineProps<{
  type: string
}>();
console.log('props:', $props);
</script>

<template>
  <div class="hint-button">
    <el-button :icon="$attrs.icon" :type="$attrs.type">{{
      $attrs.content
    }}</el-button>
  </div>
</template>

使用二次封装的按钮组件HintButton

<script setup lang="ts">
import { Delete } from "@element-plus/icons-vue";
import HintButtoon from "./HintButton.vue";
</script>

<template>
  <div>
    子组件:
    <el-button type="primary" :icon="Delete">原始按钮</el-button>
    <HintButtoon :icon="Delete" type="primary" content="二次封装按钮" />
  </div>
</template>

<style scoped>
.child {
  background-color: rgb(222, 189, 189);
  width: 200px;
  height: 200px;
  border: 1px solid blue;
  padding: 10px;
  margin: 5px;
}
</style>

img


v-bind绑定属性简写形式

$attrs对象中属性值有

{
  icon: Delete,
  type: 'primary',
}
<el-button v-bind:="$attrs"></el-button>
<el-button :="$attrs"></el-button>
<!-- 等价于 -->
<el-button :icon="$attrs.icon" :type="$attrs.type"></el-button>

作用2: 接收自定义事件和系统事件

<HintButtoon :icon="Delete" type="primary" content="二次封装按钮" title="删除按钮" @click="alert(1)" @hh="console.log(1)" />

1.当子组件只有一个根元素时,那么该元素继承父组件中为其添加的系统事件
2.子组件元素上使用:="$attrs": $attrs接收父组件为其添加的事件,并应用在该元素上
img

img


ref和$parent

ref作用: 作为dom的属性,用于获取dom节点或组件的VC实例,前提定义的获取组件变量名必须和ref属性值相同
$parent作用: 子组件内部获取父组件VC实例对象

ref函数

子组件通过defineExpose暴露属性和方法

<script setup lang="ts">
import { ref } from "vue";
const childMoney = ref(10);

const changeChildMoney = () => {
  childMoney.value -= 10;
};

defineExpose({
  childMoney,
  changeChildMoney,
});
</script>

<template>
  <div class="child">
    <div class="title">我是子组件</div>
    钱包金额: {{ childMoney }}
  </div>
</template>

父组件通过ref获取子组件VC实例对象暴露的属性和方法

<script setup lang="ts">
import { ref } from "vue";
import Child from "./Child.vue";
let parentMoney = ref(100);
let child = ref();
const changeMoney = () => {
  parentMoney.value += 2;
  console.log(child);
  child.value.changeChildMoney();
}
</script>

<template>
  <div class="parent">
    <div class="title">
      我的parent组件
    </div>
    钱包金额: {{ parentMoney }}
    <button @click="changeMoney">借子组件2块钱</button>
    <hr />
    <Child ref="child"></Child>
  </div>
</template>

父组件通过子组件的ref属性,获取子组件VC实例的属性和方法,并调用
img


使用ref属性值来区分多个相同组件的引用实例对象

<script setup lang="ts">
import { ref } from "vue";
import Child from "./Child.vue";
import Child2 from "./Child2.vue";
let parentMoney = ref(100);
// child变量名和组件Child中ref属性值相同
let child = ref();
let child2 = ref();
console.log(child);
console.log(child2);
</script>

<template>
  <div class="parent">
    <div class="title">
      我的parent组件
    </div>
    钱包金额: {{ parentMoney }}
    <hr />
    <Child ref="child"></Child>
    <Child2 ref="child2"></Child2>
  </div>
</template>

通过对相同组件的ref赋值不同值,且接收变量也不同,用来获取相同组件不同引用实例对象
img


$parent

$parent作用: 在当前子组件中获取直接父组件暴露的属性和方法

父组件暴露parentMoney属性:

<script setup lang="ts">
import { ref } from "vue";
import Child from "./Child.vue";
import Daughter from "./Daughter.vue";
let parentMoney = ref(100);
// child变量名和组件Child中ref属性值相同
let child = ref();
const changeMoney = () => {
  parentMoney.value += 2;
  console.log(child);
  child.value.changeChildMoney();
}
defineExpose({
  parentMoney
});
</script>

<template>
  <div class="parent">
    <div class="title">
      我的parent组件
    </div>
    钱包金额: {{ parentMoney }}
    <button @click="changeMoney">借子组件2块钱</button>
    <hr />
    <Child ref="child"></Child>
    <Daughter></Daughter>
  </div>
</template>

子组件事件方法中可以直接将$parent作为参数

<script setup lang="ts">
import { ref } from "vue";
const childMoney = ref(20);

const changeMoney = ($parent: any) => {
  childMoney.value += 2;
  console.log($parent);
};
defineExpose({
  childMoney
});
</script>

<template>
  <div class="child">
    <div class="title">我是子组件</div>
    钱包金额: {{ childMoney }}
    <br />
    <button @click="changeMoney($parent)">向父亲借2块钱</button>
  </div>
</template>

img

总结

ref: 父组件中获取子组件暴露的属性和方法
$parent: 子组件事件方法中将$parent作为事件参数,获取子组件的父组件暴露的属性和方法


provideinject

vue3中提供了provideinject方法,用于隔辈组件相互通信

爷爷组件:

<script setup lang="ts">
import { ref } from "vue";
import Son from "./Son.vue";
import { provide } from "vue";
let car = ref("奔驰");
provide("car", car);
</script>

<template>
  <div class="parent">
    <div class="title">我是parent组件</div>
    我的车: {{ car }}
    <Son></Son>
  </div>
</template>

孩子组件:

<script setup lang="ts">
import GrandSon from "./GrandSon.vue";
import { inject } from "vue";
const car = inject("car");
</script>

<template>
  <div class="son">
    <div class="title">我是子组件</div>
    <div>父组件的车辆: {{ car }}</div>
    <GrandSon></GrandSon>
  </div>
</template>

孙子组件:

<script setup lang="ts">
import { inject, Ref } from "vue";
let car = inject<Ref<string>>("car");

let changeCar = () => {
  console.log(car);
  if (car) {
    car.value = "保时捷";
  }
};
</script>

<template>
  <div class="grandson">
    <div class="title">我是孙子组件</div>
    <div>
      爷爷组件的车: {{ car }}
      <button @click="changeCar">修改父组件和爷爷组件的车辆</button>
    </div>
  </div>
</template>

爷爷组件提供数据,孩子和孙子都能够使用,且共享同一个内存引用,孙子修改改地址值时,爷爷和孩子的数据也会同步修改
img


pinia

选项式API简单使用(配置对象)

安装插件: npm install pinia

创建store文件夹,并在文件夹中创建index.ts文件,用于创建pinia实例对象

import { createPinia } from "pinia";
const pinia = createPinia();
export default pinia;

main.ts中引入pinia实例对象,并注册到app实例中

import { createApp } from "vue";
import App from "./App.vue";
import pinia from "./store";
const app = createApp(App);
app.use(pinia);
app.mount("#app");

store文件夹中创建modules目录,模块化区分每一个store,在modules目录下创建一个小的store名称为info.ts
img

import { defineStore } from 'pinia';
const infoStore = defineStore('info', {
  state: () => {
    return {
      info: {
        name: '测试',
        count: 100,
      }
    };
  },
  actions: {
    updateCount() {
      console.log('pinia实例对象方法中this', this);
    },
  },
  getters: {},
});
export { infoStore };

组件中使用infoStore:

import {infoStore} from '../../store/modules/info';
console.log(infoStore);
console.log(infoStore());

每一个小的store本质上是一个很函数,调用函数会返回一个proxy代理对象
img

调用sotre中的自定义的方法,查看方法中this数据结构

import { infoStore } from "../../store/modules/info";
const store = infoStore();
const changeInfoStore = () => {
  console.log(store);
  store.updateCount();
};

pinia方法中的this其实就是store的代理实例对象,可以通过this访问store中的数据
img


组合式API简单使用(配置函数)

创建一个store共享仓库数据对象,并导出

store/modules/useInfo.ts

import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
type info = {
  id: number;
  name?: string;
  age?: number;
  sex?: string;
  phone?: string;
  address?: string;
};
const useInfoStore = defineStore('useInfo', () => {
  let item = ref([
    {
      id: 1,
      name: '张三',
      age: 18,
      sex: '男',
      phone: '123456789',
      address: '北京市',
    },
    {
      id: 2,
      name: '李四',
      age: 20,
      sex: '女',
      phone: '987654321',
    },
  ]);
  const updateName = (id: number, name: string) => {
    item.value.forEach((i: info) => {
      if (i.id === id) {
        i.name = name;
      }
    });
  };
  let allName = computed(() => {
    return item.value.reduce((prev, next) => {
      return prev + next.name;
    }, '');
  });
  // 箭头函数必须返回一个对象,对象中的数据可以提供给各个组件使用
  return {
    item,
    allName,
    updateName,
  };
});
export { useInfoStore };

组件中引用useInfoStore共享对象:

<script setup lang="ts">
import { useInfoStore } from "../../store/modules/useInfo";
const infoStore = useInfoStore();
const updateName = () => {
  infoStore.updateName(1, "李四");
};
</script>

<template>
  <div class="son2">
    <div class="title">我是子组件2</div>
    <div>获取组合式store数据: {{ infoStore.item[0].name }}</div>
    <div>更新组合式store数据: <button @click="updateName">更新</button></div>
    {{ infoStore.allName }}
  </div>
</template>

img


注意

创建的store对象时,参数ID值全局不能重复,否则无法成功创建store对象
例如: const infoStore = defineStore('info', {})info是store的ID值,全局不能重复,否则不能成功创建


插槽

插槽: 父组件中的模板内容填充子组件插槽位置,这个过程中父组件可以向子组件进行通信。

父组件:

<script setup lang="ts">
import SlotDefault from "./SlotDefault.vue";
import SlotName from "./SlotName.vue";
</script>
<template>
  <div>
    <SlotDefault>
      <p>我是默认插槽填充数据</p>
    </SlotDefault>
    <hr />

    <SlotName>
      <!-- <template #header>
        <h1>标题</h1>
      </template> -->
      <template v-slot:header>
        <h3>标题</h3>
      </template>
      <template #footer>
        <p>底部</p>
      </template>
    </SlotName>
    <hr />
  </div>
</template>

默认插槽

子组件定义默认插槽:

<script lang="ts" setup></script>
<template>
  <div>
    <h2>默认插槽</h2>
    <slot></slot>
  </div>
</template>

img


具名插槽

子组件定义具名插槽:

<script lang="ts" setup></script>
<template>
  <div>
    <h2>具名插槽</h2>
    <slot name="header"></slot>
    <slot name="footer"></slot>
  </div>
</template>

img

子组件使用name属性定义插槽名称,父组件使用v-slot指令: <template v-slot:name>将模板中的内容填充到指定插槽中,
v-slot:name指令缩写形式: #name


作用域插槽

作用域插槽: 子组件中的插槽可以像props一样将该组件中的数据传递给父组件,父组件接收到数据处理模板场景,从而实现相同的插槽由父组件决定填充不同的模板。

子组件定义插槽并传递数据给父组件:

<script lang="ts" setup>
import { ref } from "vue";
let users = ref([
  { name: "张三", age: 18, sex: 1, size: 15 },
  { name: "李四", age: 19, sex: 0, size: 17 },
  { name: "王五", age: 20, sex: 1, size: 20 },
]);
</script>
<template>
  <div>
    <h2>作用域插槽</h2>
    <ul>
      <li v-for="user in users" :key="user.name">
        <slot :user="user"></slot>
      </li>
    </ul>
  </div>
</template>

父组件使用v-slot指令接收子组件传递的props数据:

<script setup lang="ts">
import SlotScop from "./SlotScop.vue";

</script>
<template>
    <SlotScop>
      <template v-slot="{user}">
        <p :style="{fontSize: user.size + 'px'}" >{{user}}</p>
      </template>
    </SlotScop>
  </div>
</template>

img


具名作用域插槽

具名作用域插槽的工作方式也是类似的,插槽props可以作为v-slot指令的值被访问到:v-slot:name="slotProps"

<template #footer="footerProps">
  {{ footerProps }}
</template>

vue3引入element plus

自动导入element组件文档

各个组件使用文档

posted @ 2025-09-02 14:08  ethanx3  阅读(22)  评论(0)    收藏  举报