「课件」原创 => Vue3【转载请标明出处】

Zero·前言

0.0 Vscode 插件

  • 别名路径跳转
  • AutoScssStruct4Vue
  • Chinese (Simplified) (简体中文) Language Pack fo
  • Code Runner
  • Aya Vue3 extension Pack
  • Live Preview
  • Live Server
  • Material Icon Theme
  • px to rem & rpx & vw (cssrem)
  • Smart Clicks
  • Vue VSCode Snippets
  • vue-helper
  • Vetur(vue3 环境下禁用)

0.1 ESLint 异常提示

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

{
  "extends": "@vue/tsconfig/tsconfig.dom.json",
  "compilerOptions": {
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",

    /* Linting */
    "noImplicitAny": false,
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedSideEffectImports": true
  },
  "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}

0.2 vite-env.d.ts

/// <reference types="vite/client" />
declare module "*.vue" {
    import type { DefineComponent } from "vue"; 
    const vueComponent: DefineComponent<{}, {}, any>; 
    export default vueComponent;
}

第一章·Vue3

1.1 Vue3 对比 Vue2 改变之处

  • 组合式API:都可以写到一堆里面,毕竟类型是不一样的,所以不用分来分去的,然后再用代码块框住了。
<script>
    data() {
        return {
            
        }
    },
    methods: { // 这就是选项式 API 
    }
</script>
  • setup():在 beforeCreated() 与 created() 两个钩子之前。

  • reactive、ref:用 reactive(复杂变量)、ref(简单变量) 将变量赋予 响应式的能力。

  • watch、computed 写法上有变化

  • 每个vue文件,不再强制要求 必须有且只能有一个 div 元素作为 根元素

  • vue3 项目用 Vite 进行打包(速度相较于webpack得到了很大的提升)

  • vue3 项目用 Pinia 来进行状态管理

  • Vue2 响应式依赖于 object.defineProperty():Object.defineProperty() 会直接修改对象的内部属性描述符(property descriptor)。每个属性都会有一组描述符来控制该属性的行为。Object.defineProperty()通过设置或修改这些描述符来实现对属性的精细控制,包括gettersetter。

  • Vue3 响应式依赖于 Proxy():你可以一次性通过代理整个对象,对所有的属性访问进行拦截,不需要手动为每个属性添加 gettersetter,所以实现响应式可能更加的简洁。而之所以采用它的原因,还有就是 Proxy 可扩展性强、比较万能。它甚至能拦截方法的调用、数组的访问等多种行为

  • 打包后体积更小

  • 主张 按需导入

1.2 创建项目

  • 安装 Vue VSCode Snippets

在这里插入图片描述

npm create vite@latest
npm i
npm run dev

在这里插入图片描述

在这里插入图片描述

  • main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'

const app = createApp(App);
app.mount('#app')

1.3 setup 的两种写法

  • 传统写法
<template>
  <div>
    <button @click="printCount">{{ count }}</button>
  </div>
</template>

<script lang="ts">
import { ref } from "vue";
export default {
  setup() {
    const count = ref(1);
    const printCount = () => {
      count.value += 1;
    };
    return {
      count,
      printCount,
    };
  },
};
</script>

<style scoped></style>
  • 简写方式
<template>
  <div>
    <button @click="printCount">{{ count }}</button>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
const count = ref(1);
const printCount = () => {
  count.value += 1; // 如果用 ref 修饰,那么 值就必须得 变量.value
};
</script>

<style scoped></style>

1.4 computed、watch

  • computed
computed: {
    aa(){
        return this.aaTemp * 2;
    }
}
const aa = computed(() => {
    return aaTemp.value * 2;
})
  • watch

浅层监听

  watch: {
    // 监听 'message' 的变化
    message(newValue, oldValue) {
      console.log(`message changed from "${oldValue}" to "${newValue}"`);
    },
    // 监听 'count' 的变化
    count(newCount, oldCount) {
      console.log(`count changed from ${oldCount} to ${newCount}`);
    }
  }
const age = ref(23);
const count = ref(1)
/*watch(age, (newVal, oldVal) => {
    console.log(`${oldVal} to ${newVal}`)
})*/
watch([age, count], (newVal, oldVal) => { // 多个监听
    console.log(`${oldVal} to ${newVal}`) // 此时这里 的 oldVal 与 newVal 返回的就是数组了 ~
})

深层监听

watch: {
    // 深度监听 `user` 对象的变化
    user: {
      handler(newUser, oldUser) {
        console.log('user object changed:', newUser, oldUser);
      },
      deep: true
      // immediate: true  // 立即执行
    }
  }
const age = ref(23);
const count = ref(1)
/*watch(age, (newVal, oldVal) => {
    console.log(`${oldVal} to ${newVal}`)
})*/
watch([age, count], (newVal, oldVal) => { // 多个监听
    console.log(`${oldVal} to ${newVal}`) // 此时这里 的 oldVal 与 newVal 返回的就是数组了 ~
}, {immediate: true, deep: true}) // 深层监听、立即执行 

监听某个属性

  watch: {
    // 监听 `user.age` 属性的变化
    'user.age'(newAge, oldAge) {
      console.log(`Age changed from ${oldAge} to ${newAge}`);
    }
  }
watch(() => user.age, (newVal, oldVal) => {
    console.log(`${oldVal} to ${newVal}`) 
}) 

如果监听的是 复杂对象,用的 reactive 修饰,则第一个参数必须 写成 箭头函数。

const age = ref(23);
const count = ref(1)
/*watch(age, (newVal, oldVal) => {
    console.log(`${oldVal} to ${newVal}`)
})*/
watch([() => age, () => count], (newVal, oldVal) => { // 多个监听
    console.log(`${oldVal} to ${newVal}`) // 此时这里 的 oldVal 与 newVal 返回的就是数组了 ~
}, {immediate: true, deep: true}) // 深层监听、立即执行 

1.5 生命周期钩子

beforeMount => onBeforeMount

mounted => onMounted

beforeUpdate => onBeforeUpdate

updated => onUpdated

beforeDestroy => onBeforeUnmount

destroyed => onUnmounted

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

<script setup>
import { onBeforeMount, onMounted, ref } from "vue";

const count = ref(0);
onBeforeMount(() => {
  alert("onBeforeMount");
});
</script>

<style lang="scss" scoped></style>

在这里插入图片描述

1.6 父子组件通信

  • 引入组件的方式

demo01.vue

<template>
  <div>我是组件01</div>
</template>

<script setup></script>

<style lang="scss" scoped></style>

App.vue

<template>
  <div>
    <demo01></demo01>
  </div>
</template>

<script setup>
import demo01 from "./components/demo01.vue";
</script>

<style lang="scss" scoped></style>

在这里插入图片描述

  • props 改为使用 defineProps() 宏函数

demo01.vue

<template>
  <div style="display: flex; flex-direction: column">
    <div v-for="(item, index) in dataList" :key="index">
      {{ item.name }}
    </div>
  </div>
</template>

<script setup>
// 无校验 props
// defineProps(["dataList"]);
// 有校验 props
defineProps({
  dataList: {
    type: String,
    required: true,
    default: [],
  },
});
</script>

<style lang="scss" scoped></style>

App.vue

<template>
  <div>
    <demo01 :dataList="dataList"></demo01>
  </div>
</template>

<script setup>
import { reactive } from "vue";
import demo01 from "./components/demo01.vue";
const dataList = reactive([
  {
    id: 1,
    name: "fq",
  },
]);
</script>

<style lang="scss" scoped></style>
  • emit 改为使用 defineEmits()

demo01.vue

<template>
  <div style="display: flex; flex-direction: column">
    <div v-for="(item, index) in dataList" :key="index">
      {{ item.name }}
    </div>
    <el-button type="primary" @click="sendMsgB">sendMsgB</el-button>
  </div>
</template>

<script setup lang="ts">
import type { PropType } from "vue";

// 无校验 props
// defineProps(["dataList"]);
// 有校验 props
interface Item {
  name: string;
}
const props = defineProps({
  dataList: {
    type: Array as PropType<Item[]>,
    required: true,
    default: [],
  },
});
const emit = defineEmits(["sendMsg", "sendMsgB"]);
const sendMsg = () => {
  emit("sendMsg", "你好");
};
const sendMsgB = () => {
  emit("sendMsgB", props.dataList);
};
</script>

<style lang="scss" scoped></style>
  • App.vue
<template>
  <demo01 @send-msg-b="onSendMsgB" :data-list="dataList"></demo01>
</template>

<script setup>
import { reactive } from "vue";
import demo01 from "./components/demo01.vue";

const dataList = reactive([
  {
    id: 1,
    name: "fq",
  },
]);

const onSendMsgB = (ret) => {
  console.log(ret);
};
</script>

<style lang="scss" scoped></style>

1.7 ref 属性

  • demo01.vue
<template>
  <div style="display: flex; flex-direction: column">
    <div v-for="(item, index) in dataList" :key="index">
      {{ item.name }}
    </div>
    <el-button type="primary" @click="sendMsgB">sendMsgB</el-button>
  </div>
</template>

<script setup lang="ts">
import type { PropType } from "vue";

// 无校验 props
// defineProps(["dataList"]);
// 有校验 props
interface Item {
  name: string;
}
const props = defineProps({
  dataList: {
    type: Array as PropType<Item[]>,
    required: true,
    default: [],
  },
});
const emit = defineEmits(["sendMsg", "sendMsgB"]);
const sendMsg = () => {
  emit("sendMsg", "你好");
};
const sendMsgB = () => {
  emit("sendMsgB", props.dataList);
};

// 暴露组件中的 数据 和 方法
defineExpose({
  dataList: props.dataList,
  sendMsg,
});
</script>

<style lang="scss" scoped></style>
  • App.vue
<template>
  <demo01
    ref="demo01Ref"
    @send-msg-b="onSendMsgB"
    @send-msg="onSendMsg"
    :data-list="dataList"
  ></demo01>
</template>

<script setup>
import { onMounted, reactive, ref } from "vue";
import demo01 from "./components/demo01.vue";

const dataList = reactive([
  {
    id: 1,
    name: "fq",
  },
]);

const onSendMsgB = (ret) => {
  console.log(ret);
};

const onSendMsg = (ret) => {
  console.log("onSendMsg", ret);
};

const demo01Ref = ref(null);
onMounted(() => {
  demo01Ref.value?.sendMsg();
});
</script>

<style lang="scss" scoped></style>

1.8 provide、inject

provide:只能是上级组件去使用,比如 父组件、爷爷组件...

inject:只能是下级组件去使用,比如 子组件...

所以,provide与inject 主要是用来 实现跨层传递的!

在这里插入图片描述

  • demo01.vue
<template>
  <div>demo01</div>
  <demo02></demo02>
</template>

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

<style lang="scss" scoped></style>
  • demo02.vue
<template>
  <div>demo02</div>
  <div>{{ fk }}</div>
</template>

<script lang="ts" setup>
import { inject } from "vue";

let fk: any = inject("fk");
</script>

<style lang="scss" scoped></style>
  • App.vue
<template>
  <div>
    <demo01></demo01>
  </div>
</template>

<script lang="ts" setup>
import { provide, ref } from "vue";
import demo01 from "./components/demo01.vue";
const fk = ref(2);
provide("fk", fk); // 向下级组件传递
</script>

<style lang="scss" scoped></style>

在这里插入图片描述

1.9 事件总线

cnpm i mitt

在这里插入图片描述

  • main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import EventBus from '../src/common/ts/EventBus.ts'
let app = createApp(App);
globalThis.$eventBus = EventBus;
app.mount("#app")
  • demo01.vue
<template>
  <div>demo01</div>
  {{ num }}
  <demo02></demo02>
</template>

<script lang="ts" setup>
import { ref } from "vue";
import demo02 from "./demo02.vue";
const num = ref(1);

globalThis.$eventBus.on("refreshDemo01", () => {
  // alert("refreshDemo01");
  num.value++;
});
</script>

<style lang="scss" scoped></style>
  • demo02.vue
<template>
  <div>demo02</div>
  <div>{{ fk }}</div>
</template>

<script lang="ts" setup>
import { inject } from "vue";

let fk: any = inject("fk");
</script>

<style lang="scss" scoped></style>
  • App.vue
<template>
  <div>
    <demo01></demo01>
    <el-button @click="handleNumPlus" type="primary">num++</el-button>
  </div>
</template>

<script lang="ts" setup>
import { provide, ref } from "vue";
import demo01 from "./components/demo01.vue";
const fk = ref(2);
provide("fk", fk); // 向下级组件传递
const handleNumPlus = () => {
  globalThis.$eventBus.emit("refreshDemo01");
};
</script>

<style lang="scss" scoped></style>

在这里插入图片描述

第二章·TypeScript

2.1 类型写法

  • 复杂类型与简单类型定义、声明
export {} // 让每个文件都是一个 单独的模块
let name:string | number = 'mqy'; // string | number 可以允许多种类型进行赋值,这也被称为 联合类型
name = 22;

interface Info { // 定义一个复杂类型
    name: string,
    age: number
} 

interface People { 
    id: string,
    info?: Info // 这个属性可以有,也可以没有, type 也支持这种写法
}

const people:People = {
    id: '01',
    info: {
        name: "SSSS",
        age: 23
    }
}

console.log(people)
  • 类型断言

它本质上是对 TypeScript 类型推断的“覆盖”,并假设开发者知道自己的代码比 TypeScript 推断出来的类型更准确(doge)。

// 我就直接断言它的类型 HTMLImageElement (img元素)
const imgDemo = document.getElementById("imgDemo") as HTMLImageElement
console.log(imgDemo.src) // 此时就支持智能提示了~ 嘻嘻
  • vue 中写法
const name = ref<string | number>('mqy')

const computedDemo = computed(() => {
    return count.value * 2; // computed 
})

const Click = (e: Event) => {
    console.log(e.target as HTMLButtonElement) // as 断言很好用
}

const autoFocusInput = ref<null | HTMLInputElement>(null)
onMounted(async () => {
    await nextTick();
    autoFocusInput.value?.focus(); // 如果报可能为 null,那么就用 ? 即可
})

// 如果是自定义的组件,那么声明上可能 写起来费劲一些 ~
<ComponentsDemo01 ref="componentsDemo01"></ComponentsDemo01>

const componentsDemo01 = ref<InstanceType<typeof ComponentsDemo01> | null>(null); // InstanceType 可以拿到一个实例的 类型,typeof ComponentsDemo01 会返回一个 ComponentsDemo01类型的实例

2.2 interface 与 type 区别

interface 主要用于定义对象的结构或接口。

type 主要用于定义更广泛的类型,除了可以定义对象结构,还可以用于联合类型、交叉类型、元组、基本类型等。

  • 定义对象的类型
// 使用 interface 定义对象类型
interface Person {
  name: string;
  age: number;
}

// 使用 type 定义对象类型
type PersonType = {
  name: string;
  age: number;
};
  • 不同的扩展方式
// 使用 interface 扩展接口
interface Employee extends Person { // 可以继承某个接口来进行扩展
  employeeId: number;
}

// 使用 type 交叉类型 即以 组合的方式来进行扩展
type EmployeeType = PersonType & { employeeId: number };
  • 是否允许重名合并
// 使用 interface 声明合并
interface Person {
  name: string;
}
interface Person {
  age: number;
}

// 合并后的 Person 接口
// interface Person {
//   name: string;
//   age: number;
// }


// 使用 type 定义同名类型会报错
type PersonType = {
  name: string;
};
type PersonType = {  // 错误:Duplicate identifier 'PersonType'.
  age: number;
};

很显然 type 不允许重名合并,但反而显得 type 更加的严谨!

  • 更契合ts

interface 主要用于定义对象类型的结构,尽管它也能与 class 结合使用,但它不支持像联合类型、交叉类型等更复杂的类型结构。所以,实际上并不是很契合 ts 对类型的定义。

// 使用 type 定义联合类型
type Status = 'active' | 'inactive';

// 使用 type 定义交叉类型
type Employee = Person & { employeeId: number };

// 使用 type 定义元组
type Point = [number, number];

// 使用 type 定义映射类型
type PersonMap = {
  [key: string]: Person;
};

反观 type 是一种更强大的工具,能够处理基本类型、联合类型、交叉类型、元组、映射类型等,几乎可以定义任何类型。

在这里插入图片描述

但可能对于 后端程序员 转 前端来说,使用传统的 interfaceclass 较多,只有逼不得已需要灵活的 type 时,才会去使用 type

2.3 props 写法

其实就是提前写好每个 prop 的类型,还有它是否必选。然后改用 withDefaults 来设置默认值

type PropsType = {
  name: string;
  age?: number;
};

const props = withDefaults(defineProps<PropsType>(), {
  age: 23
});
  • App.vue
<template>
  <div>
    <demo01 :name="'21213'"></demo01>
    <el-button @click="handleNumPlus" type="primary">num++</el-button>
  </div>
</template>

<script lang="ts" setup>
import { provide, ref } from "vue";
import demo01 from "./components/demo01.vue";
const fk = ref(2);
provide("fk", fk); // 向下级组件传递
const handleNumPlus = () => {
  globalThis.$eventBus.emit("refreshDemo01");
};
</script>

<style lang="scss" scoped></style>

2.4 emits 写法

也是如 props 的写法,提前把类型 声明好,然后让宏函数用泛型来使用这个类型。

type Emits = {
  (name: "msg", data: string): void;
};
const emit = defineEmits<Emits>();
const handleXiXi = () => {
  emit("msg", "嘻嘻");
};

2.5 pinia

pinia 是vue3 时代取代 vuex 而被发明出来的产物。

自动分模块组合式API比vuex简单易懂

cnpm i pinia
  • main.ts
import {createApp} from 'vue'
import App from './App.vue'
import {createPinia} from 'pinia'

const pinia = createPinia();
const app = createApp(App);

app.use(pinia)
app.mount('#app')

在这里插入图片描述

  • 传统写法

demo01.ts

import {defineStore} from 'pinia'


type StateType = {
    demo01Age: number
}

export const useDemo01Store = defineStore('demo01', {
    state: (): StateType => {
        return {
            demo01Age: 1
        }
    },
    actions: {
        increment() {
            this.demo01Age++
        }
    },
    getters: { // 这个实际上就是 计算属性,哈哈 ~
        doubleAge(state): number {
            return state.demo01Age*2;
        }
    }
})

demo01.vue

<template>
  <div>demo01</div>
  {{ demo01Store.demo01Age }}
  <demo02></demo02>
  <el-button @click="demo01Store.increment" type="primary">嘻嘻</el-button>
</template>

<script lang="ts" setup>
import { ref } from "vue";
import { useDemo01Store } from "../store/demo01";

const demo01Store = useDemo01Store();
</script>

<style lang="scss" scoped></style>
  • 自由式写法

demo01.ts

import {defineStore} from 'pinia'
import { computed, ref } from 'vue'

export const useDemo01Store = defineStore('demo01', () => {
    const demo01Age = ref<number>(0)

    const increment = () => {
        demo01Age.value++;
    }

    const doubleDemo01Age = computed(() => {
        return demo01Age.value * 2
    })

    return {
        demo01Age,
        increment,
        doubleDemo01Age
    }
})

demo01.vue

<template>
  <div>demo01</div>
  {{ demo01Store.demo01Age }}
  <demo02></demo02>
  <el-button @click="demo01Store.increment" type="primary">嘻嘻</el-button>
</template>

<script lang="ts" setup>
import { ref } from "vue";
import { useDemo01Store } from "../store/demo01";

const demo01Store = useDemo01Store();
</script>

<style lang="scss" scoped></style>
posted @ 2025-01-23 08:44  小哞^同^学的技术博客  阅读(23)  评论(0)    收藏  举报