「课件」原创 => 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()通过设置或修改这些描述符来实现对属性的精细控制,包括getter和setter。
- 
Vue3 响应式依赖于 Proxy():你可以一次性通过代理整个对象,对所有的属性访问进行拦截,不需要手动为每个属性添加getter和setter,所以实现响应式可能更加的简洁。而之所以采用它的原因,还有就是 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 是一种更强大的工具,能够处理基本类型、联合类型、交叉类型、元组、映射类型等,几乎可以定义任何类型。

但可能对于 后端程序员 转 前端来说,使用传统的 interface 与 class 较多,只有逼不得已需要灵活的 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>
 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号