vue3从零开始一篇文章带你学习
升级vue CLI
- 使用命令
## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 安装或者升级你的@vue/cli
npm install -g @vue/cli
## 创建
vue create vue_test
## 启动
cd vue_test
npm run serve
nvm管理node版本,区分老旧项目
node版本升级,从卸载到使用nvm管理node版本并配置vue环境(学习趟雷版)
创建vue项目
vite创建vue项目传送门
组合式函数
利用vue组合式API来封装和复用有状态逻辑的函数
1.结合官网 使用鼠标跟踪器来实现组合式API,创建mouse.js 文件,将鼠标跟踪功能给提前出来
// mouse.js
import {ref,onMounted,onUnmounted
} from 'vue'// 封装组合式函数
export function useMouse() {// 管理状态const x = ref(0)const y = ref(0)// 组合函数更改状态function updated(event) {x.value = event.pageXy.value = event.pageY}// 在生命周期商注册和卸载鼠标事件onMounted(() => {window.addEventListener('mousemove', updated)})onUnmounted(() => {window.removeEventListener('mousemove', updated)})// 将管理的状态值暴漏出去return {x,y}
}
- 在页面中使用
<template><div class="container">坐标: {{ mouse.x }} , {{ mouse.y }}</div>
</template>
<!-- setup 语法糖 -->
<script setup>
import {useMouse
} from '@/utils/mouse'
import { reactive } from 'vue';
const mouse = reactive(useMouse())
// 或者使用此解构出来
// const {
// x,
// y
// } = useMouse()
</script>
- 将添加和清除DOM事件的逻辑也给抽离出来,并在mouse里面使用
- event.js 文件
import {onMounted,onUnmounted
} from 'vue'// 封装组合式函数
export function useEventListener(target, event, callback) {// 在生命周期商注册和卸载鼠标事件// 也可以用字符串形式的 CSS 选择器来寻找目标 DOM 元素onMounted(() => {target.addEventListener(event, callback)})onUnmounted(() => {target.removeEventListener(event, callback)})
}
- mouse.js 引入使用
import {ref,
} from 'vue'
import { useEventListener } from './event'
// 封装组合式函数
export function useMouse() {// 管理状态const x = ref(0)const y = ref(0)// 组合函数更改状态function updated(event) {x.value = event.pageXy.value = event.pageY}useEventListener(window, 'mousemove', updated)// 将管理的状态值暴漏出去return {x,y}
}
vite项目
注:vue3兼容vue2语法,vue2的语法可以在vue3项目内使用,vue3不能读取vue语法内的数据
API
setup组合式 API 的入口
-
setup在生命周期执行时间高于beforeCreate, created,在vue2语法内可以直接this调用setup里面的声明变量
-
setup里面的this不是vue实例,而是undefined ,在vue3中弱化了this的使用
-
直接声明变量:let const var 的值非响应式数据,修改的话,页面也不会变化的
-
返回值,可以是值,对象,函数,它会在组件被解析前被调用
-
setup 函数的参数
setup 函数在组件创建created()之前执行,setup函数是一个函数,它也可以有返回值,类似于Vue 2中的data()和methods()的组合,接收两个参数:props 和 context- props: 是响应式的,当传入显端prop 时他会进行更新,包含父组件传递给子组件的所有数据
- context: 是一个普通的js对象,上下文对象,暴漏setup中可用的值
选项式 API 和组合式 API
官方文档传送门
组合式 API
组合式 API 通常会与 <script setup>
搭配使用 vue3.0进行响应式数据操作会使用 ref、reactive、toRef、toRefs四个核心 API 函数
-
ref 通常将基本数据类型(string,Number,Boolean…)转为响应式,也可以将引用类型转为响应式,但引用类型内的数据修改就不会及时通知页面发生变化
-
配置自动添加value在vscode设置
<template><div>{{ mease }}<div @click="bntFrom">点击</div></div> </template><script lang="ts" setup> import {ref } from 'vue'let mease = ref('张三')引用类型let obj = ref({name: "真实的"})function bntFrom(){mease.value = '李四'obj.value.name = '虚假的'console.log('点击了');} </script>
-
reactive 只能定义引用类型(obj,arry),相比较ref可以改变深层次的属性响应式 ,ref也可以改变引用对象,但深层的数据还是建议使用reactive
<template><div><h1>ref定义属性</h1>{{ mease }}<div @click="bntFrom">点击</div><h1>reactive定义</h1><p v-for="item in arry" :key="item.name">{{ item.age }}</p></div> </template><script lang="ts" setup> import {ref,reactive } from 'vue'let mease = ref('张三')let obj = ref({name: "真实的"})let arry = reactive([{name: '王二',age: 10}])function bntFrom(){mease.value = '李四'obj.value.name = '虚假的'arry[0].age = 30console.log('点击了');} </script>
- 将reactive定义的响应式重新定义一个对象,就会变成一个非响应式对象,可以使用Object.assign(obj,{name: ‘ssf’})
-
toRefs 将 reactive 定义的对象属性给解构赋值转换为响应式引用,保持对象的响应性,一般reactive 创建的对象,直接解构赋值后会失去响应式,所以就需要torefs将其给解构赋值成响应式
-
如下面代码,将对象解构赋值直接使用,也可以通过对象使用,只要值改变都会发生改变
<script setup lang="ts">
import { reactive,toRefs
} from 'vue'const parms = reactive({name: '掌声',count: 0
})
const {count
} = toRefs(parms)
</script><template><h1>{{ msg }}</h1><div class="card"><p>parms.count: {{ parms.count }}</p><button type="button" @click="count++">count is {{ count }}</button></div>
</template>
计算属性computed
- 基于缓存: 计算属性是基于它所依赖的响应式进行缓存的,只有当响应式依赖发生变化时,计算属性才会重新计算
- 声明式:计算属性根据返回值来定义,代码更清晰,更利于理解
- 自动更新:当依赖数据发生变化时,计算属性返回的值也会更新
<script setup lang="ts">
import { reactive, computed } from "vue";// 响应式数据
const state = reactive({name: "手机",price: 2000,
});
// 计算属性
const productName = computed(() => {return `优惠 ${state.name}`;
});const formattedPrice = computed(() => {return `¥${state.price.toFixed(2)}`;
});// 方法
const increasePrice = () => {state.price += 100;
};
</script><template><div><p>商品名称:{{ productName }}</p><p>商品价格:{{ formattedPrice }}</p><button @click="increasePrice">增加价格</button></div>
</template>
2. 计算属性默认是只读的,如果你想要更改计算属性的值时,你需要使用getter 和 setter 来创建
- getter 进行计算,将结果缓存起来,当参与计算的响应数据发生变化时,会触发更新机制,再次调用getter来重新计算属性的值
- setter 函数会接收一个函数值,即要修改的值
<script setup lang="ts">
import { reactive, computed } from "vue";// 响应式数据
const state = reactive({name: "手机",price: 2000,
});
// 计算属性
const productName = computed(() => {return `优惠 ${state.name}`;
});const formattedPrice = computed(() => {return `¥${state.price.toFixed(2)}`;
});const formattedNamePrice = computed({// 当依赖发生变化时再次调用,返回新的值get(){return state.name + '-' + state.price.toFixed();},// 接收用户传值并进行修改set(newValue){const [name,price] = newValue.split('-')state.name = namestate.price = Number(price)}});// 方法
const increasePrice = () => {state.price += 100;formattedNamePrice.value = '苹果手机-5600'
};
</script><template><div><p>商品名称:{{ productName }}</p><p>商品价格:{{ formattedPrice }}</p><button @click="increasePrice">增加价格</button><p>商品名称、价格:{{ formattedNamePrice }}</p></div>
</template>
监听属性
- 非缓存:监听属性不会缓存其值,每次依赖发生变化时都会执行回调函数,但他有新值和旧值
- 灵活:可以监听一个或者多个数据源,当执行复杂逻辑时,可以进行异步操作
监听ref声明值
- watch 监听值发生变化,监听ref声明的值
<script setup lang="ts">
import { ref, watch } from "vue";let couent = ref(0);// 监听
watch(couent, (newValue,oldValue) => {console.log(newValue,oldValue,'值发生变化了');});// 方法
const changeCouent = () => {couent.value +=1
};
</script><template><div><p>商品价格:{{ couent }}</p><button @click="changeCouent">增加价格</button></div>
</template><style scoped></style>
2. 监听声明的对象类型,当改变单个值时未曾发生变化,需要使用 deep:tree, 深度监听属性,立即监听的话需要使用immediate: true,
- 单个监听
watch(() => person.value.price,(newValue, oldValue) => {console.log(newValue, oldValue, "价格发生变化了");}
);
- 多个监听
watch(() => [person.value.name,person.value.price],(newValue, oldValue) => {console.log(newValue, oldValue, "价格发生变化了");}
);
- 深度监听,立即执行和监听整个对象
<script setup lang="ts">
import { ref, watch } from "vue";let person = ref({name: "lisd",price: 100,});watch(person,(newValue, oldValue) => {console.log(newValue, oldValue, "值发生变化了");},{deep: true,immediate: true,}
);
// 方法
const changeName = () => {person.value.name = '理想';
};// 方法
const changePrice = () => {person.value.price += 1;
};// 方法
const changeObj = () => {person.value = {name: "宝马",price: 345,};
};
</script><template><div><p>品牌:{{ person.name }}</p><p>价格:{{ person.price }}</p><button @click="changeName">增加价格</button><button @click="changePrice">增加价格</button><button @click="changeObj">修改整个品牌</button></div>
</template><style scoped></style>
监听 reactive
- 当改变单个值时也能监听到变化,reactive声明的对象类型会隐士的开启 deep:tree 深度监听属性,还无法关闭,立即监听的话需要使用immediate: true,
- 单个监听
watch(() => person.price,(newValue, oldValue) => {console.log(newValue, oldValue, "价格发生变化了");}
);
- 多个监听
watch(() => [person.name,person.price],(newValue, oldValue) => {console.log(newValue, oldValue, "价格发生变化了");}
);
- 监听整个对象可以不用函数写法,可以直接监听整个对象
<script setup lang="ts">
import { reactive, watch } from "vue";let person = reactive({name: "lisd",price: 100,
});watch(person,(newValue, oldValue) => {console.log(newValue, oldValue, "值发生变化了");},{immediate: true,}
);
// 方法
const changeName = () => {person.name = "理想";
};// 方法
const changePrice = () => {person.price += 1;
};// 方法
const changeObj = () => {Object.assign(person, {name: "宝马",price: 345,});
};
</script><template><div><p>品牌:{{ person.name }}</p><p>价格:{{ person.price }}</p><button @click="changeName">增加价格</button><button @click="changePrice">增加价格</button><button @click="changeObj">修改整个品牌</button></div>
</template><style scoped></style>
**注意:**监听时,单个字段时,使用函数监听值的变化,监听对象或数组时,可以直接监听,不用写函数来监听,但最好还是写函数来监听,不管是单个值还是对象
watchEffect监听全局
* 无论是哪个字段发生变化,都会触发,而watch需要写具体监听某个值
计算属性和监听属性两者使用场景
- 计算属性:当你基于组件的响应数据,生成一个新的可缓存值时,可以是使用计算属性
- 监听属性:当你数据发生变化,并且想要根据这个变化,进行一些复杂逻辑和异步操作时,可以使用监听属性
Class 与 Style 绑定 和vue的写法没什么区别
<template>//对象式绑定<div :class="{ active: isActive, 'text-danger'}">内容</div>//数组式绑定<div :class="['conter',{ active: isActive, 'text-danger'}]">内容</div>//使用对象语法动态绑定行内样式<div :class="{ color: red, fontSize: fontSize}">内容</div>//使用数组语法动态绑定行内样式<div :class="[basStyle]">内容</div>
</template><script>
export default {data() {return {isActive: true,fontSize: '12px',basStyle: {width: '20px'height: '20px'background: 'red'}}}
}
</script>
TS中:接口,泛型,自定义类型
vue 官方文档
- 接口:interface,用于定义对象是否符合特定的结构,可以用来定义,props,methods 或者 data的类型
-
对象的结构,属性名称,类型
-
定义的对象如果不符合结构,亦或者缺少属性,或者类型不匹配,ts就会报错,当然你也可以添加
?
使其变成一个可选属性,比如以下错误
-
类型声明和组合式声明
//类型let test: string | number = 1;组合式APIlet refTest = ref<string | number>("");refTest.value = "123";let reactiveTest = reactive<object>({id: 1,name: "张三",});console.log(reactiveTest);
-
符合定义对象结构写法
// 定义一个接口,用于限制posen对象的具体属性 <template><div>姓名:{{ posen.name }}-{{ posen.num }}</div> </template><script setup lang="ts"> import { reactive} from "vue";let posen = reactive<PersonInter>({id: 1,name: "张三", }); export interface PersonInter {id: string | number;name: string;num?: number; } </script><style></style>
- 自定义类型 type
自定义类型允许你为现有的类型创建一个名称,可以组合多个类型,亦或者在类型别名内添加额外的属性
-
基本类型的别名
type PersonType = {id: string | number;name: string;num?: number; }; let person: PersonType = {id: 1,name: "张三", };
-
联合类型
type Age = string | number; type ID = string | number; type PersonType = {id: ID;name: string;num?: Age; };let refTest = ref<Age>(""); refTest.value = "123";type combinationType = PersonType & { age: Age }; let combination: combinationType = {id: 1,name: "张三",age: 23, }; console.log(combination); // {id: 1, name: '张三', age: 23}
- 泛型
- 泛型,允许我们编写可重用的代码,适用于多种类型,在定义函数,接口,类的时候
- 通过泛型,可以处理任意类型的输入,并确保输入和输出类型一致
// 定义泛型
function generic<T>(arg: T): T {return arg;
}// 使用泛型
let age = generic<number>(23); // 明确指定类型
let strText = generic("hello"); // 类型推断
ref获取在dom中的使用
vue2的ref和vue3的写法不一样,可以直接声明取值,而不需要使用this.$refs.
- 组件直接使用,父组件获取子组件的内容
-
子组件
<template><div>我是子组件</div> </template><script setup lang="ts"> import { ref, defineExpose } from "vue"; let re = ref('我是上三'); let con = ref(3); defineExpose({re,con }); </script><style></style>
-
父组件
<script setup lang="ts"> import ChildComponent from "./aItem.vue"; import { ref } from "vue";// 明确 refDom 的类型 interface ChildComponentInstance {re: string; }const refDom = ref<ChildComponentInstance | null>(null); /*** changeRef函数用于检查和操作一个名为refDom的引用对象* 此函数旨在演示如何在Vue组件中处理和访问refs引用的元素或组件*/const changeRef = () => {// 检查refDom引用是否已初始化 if (refDom.value) {// 如果refDom已初始化,打印其值和特定属性console.log(refDom.value, refDom.value.re); // Proxy(Object) {re: RefImpl, con: RefImpl, __v_skip: true}[[Handler]]: Object[[Target]]: Proxy(Object)[[IsRevoked]]: false '我是上三'} else {// 如果refDom未初始化,打印提示信息console.log('refDom is not initialized');} }; </script><template><ChildComponent ref="refDom" /><el-button @click="changeRef">点击触发</el-button> </template><style scoped></style>
- 在 v-for中使用ref时,这个时候返回的 ref 就是一个包含绑定所有元素的数组或者对象了
<template><div :ref="setItemRef" v-for="item in posenList" :key="item.id">{{ item.name }}</div>
</template>
<script setup lang="ts">
import { ref,ComponentPublicInstance } from "vue";let itemRefs = ref<HTMLDivElement[]>([]);
// 设置每个元素的 ref
const setItemRef = (el: Element | ComponentPublicInstance | null) => {if (el instanceof HTMLDivElement) {itemsRef.value.push(el); // 只添加有效的 HTMLDivElement}
};
console.log("itemRefs 绑定", itemRefs.value);
</script>
组件
官方文档
动态组件:当多个组件在一个区域来回切换时,可以使用
<!-- currentTab 改变时组件也改变 -->
<component :is="activeComponentName" />
<component :is="tabs[currentTab]"></component>
组件通信
- 父组件给子组件传值
-
Props 是一种特别的
atterbutes
你可以用defineProps
函数来定义组件期望接收的props,每个props都可以指定类型、默认值、是否必需等属性,其中包含了组件传递的所有 props -
defineProps 是一个仅
<script setup>
中可用的编译宏命令,所以不需要显示的导入 -
props官方文档
-
父组件
<script setup lang="ts"> import ChildComponent from "./childItem.vue"; import { reactive } from "vue";let chPerson = reactive({ id: 1, name: "张三" }); </script><template><ChildComponent :propsVal="chPerson" car="宝马" ref="refDom" /> </template><style scoped></style>
-
子组件
<template><div><p>子组件数据: {{ person.name }}</p><p>父组件传递数据: 姓名:{{ props.propsVal.name }}-汽车:{{ car }}</p></div> </template><script setup lang="ts"> import {reactive,defineProps } from "vue";let person = reactive({name: "q2", });const props = defineProps({propsVal: {type: Object,default: () => {return {name: "q2",};},},car: {type: String,default: "奔驰",}, });// 解构 props // const { propsVal, car } = defineProps({ // propsVal: { // type: Object, // default: () => ({ // name: "q2", // }), // }, // car: { // type: String, // default: "奔驰", // }, // }); console.log(props);// 父组件传递数据</script><style></style>
注意:如果没有在
<script setup>
下,那么 props 就必须以props
选项的方式声明,props 对象会作为setup()
函数的第一个参数被传入<template><div><p>父组件传递数据: 姓名:{{ propsVal.name }}-汽车:{{ car }}</p></div> </template><script> export default {name: "childItem",props: {propsVal: {type: Object,default: () => {return {name: "q2",};},},car: {type: String,default: "奔驰",},},setup(props, context) {console.log(props, props.propsVal.name);// console.log(context.emit);// console.log(context.slots);} } </script><style></style>
-
- 子组件给父组件传值 defineEmits
-
defineEmits方法返回函数并触发,可以使子组件的值传递到父组件中
-
defineEmits 仅可用于
<script setup>
中,可以不需要导入直接使用,它返回一个等同于$emit
的emit
函数,可以在组件内抛出事件-
子组件
<template><div><el-button @click="changeName">给父组件传递数据</el-button></div> </template><script setup lang="ts"> import { reactive,defineEmits } from "vue";let person = reactive({name: "q2", });let emit = defineEmits(["childEvent"]); const changeName = () => {emit("childEvent", person); }; </script><style></style>
-
父组件
<script setup lang="ts"> import ChildComponent from "./childItem.vue";interface ChildEventData {id?: number;name?: string;[key: string]: any; // 允许扩展其他字段 }const handleChildEvent = (val: ChildEventData | {})=> {console.log(val,'子组件传递的值'); } </script><template><ChildComponent @child-event="handleChildEvent" /> </template><style scoped></style>
-
-
如果你没有在使用
<script setup>
,你可以从setup()
函数的第二个参数的emit
,抛出事件的export default {emits: ['enlarge-text'],setup(props, ctx) {ctx.emit('enlarge-text')} }
- $ref + defineExpose(obj)
-
defineExpos可以用来显式暴露组件内部的属性或方法,使得父组件可以通过 ref 访问子组件的内容
-
由于子组件的内容不会自动暴露给父组件,所以需要defineExpose 选择性地暴露内部内容,从而避免不必要的属性泄漏,同时提供更好的封装性
-
defineExpose 是专为
<script setup>
设计的,不能用于普通的<script>
或setup()
函数中 -
不建议直接暴露整个组件内部状态,应该只暴露需要的内容,从而保持组件封装性
-
父组件
<script setup lang="ts"> import ChildComponent from "./childItem.vue"; import { ref } from "vue";const refDom = ref();const changeRef = () => {// 检查refDom引用是否已初始化if (refDom.value) {// 如果refDom已初始化,打印其值和特定属性console.log(refDom.value.messe, "父组件hi", refDom.value);const { btnChild } = refDom.value;btnChild('父组件传递哦')} else {// 如果refDom未初始化,打印提示信息console.log("refDom is not initialized");} }; </script><template><ChildComponent ref="refDom" /><el-button @click="changeRef">调用子组件方法</el-button> </template><style scoped></style>
-
子组件
<template><div></div> </template><script setup lang="ts"> import { ref, reactive, defineExpose } from "vue";let person = reactive({name: "q2", }); let messe = ref("子组件消息"); const btnChild = (value: string) => {console.log("子组件按钮", value); };defineExpose({person,messe,btnChild,text: "我是子组件的text", }); </script><style></style>
-
- 兄弟组件之间通信 mitt
安装 mitt
npm install --save mitt
-
你可以封装成一个xx.ts使用
import mitt from "mitt"; export default mitt();
-
组件内使用
// 兄弟组件1 <template><div><el-button @click="btnChild">子组件按钮</el-button></div> </template><script setup lang="ts"> import { ref } from "vue"; import emittler from "@/utils/emitter";let messe = ref("你好啊"); const btnChild = (value: string) => {console.log("子组件按钮", value);emittler.emit("myEvent", messe.value); }; </script><style></style> // 兄弟组件2 <template><div></div> </template><script setup lang="ts"> import emittler from "@/utils/emitter"; emittler.on('myEvent', (message) => {console.log(message) // 输出: "你好啊" })</script><style></style>
- useAttrs + a t t r s 如果需要在子组件接受很多 p r o p s , 但你又没在 p r o p s 中定义,那么其他传递的值就会放在 ‘ attrs 如果需要在子组件接受很多props, 但你又没在 props中定义,那么其他传递的值就会放在 ` attrs如果需要在子组件接受很多props,但你又没在props中定义,那么其他传递的值就会放在‘atters`中
-
父组件传值
<script setup lang="ts"> import ChildComponent from "./childItem.vue"; import { reactive } from "vue";let chPerson = reactive({ id: 1, name: "张三" });</script><template><ChildComponent :propsVal="chPerson" :test="234" car="宝马" ref="refDom" /> </template><style scoped></style>
-
子组件接收,你可以直接
$atters
去取值,也可以声明变量去接值<template><div><p>父组件传递数据: 姓名:{{ props.propsVal.name }}-汽车:{{ $attrs.car }}</p></div> </template><script setup lang="ts"> import { defineProps,useAttrs } from "vue";const attrs = useAttrs(); console.log(attrs.car); // 宝马const props = defineProps({propsVal: {type: Object,default: () => {return {name: "q2",};},}, }); </script><style></style>
- 双向绑定 v-model + defineModel 官方文档
-
父子组件数据双向绑定 ,v-model 在组件上实现双向绑定
-
多个v-model 可以接收一个参数,我们可以通过将这个参数当成字符串给
defineModel
来接收对应的值 -
如果声明之后,那么如果你不通过字符串来获取,那你将获取不到值
-
父组件
<script setup lang="ts"> import ChildComponent from "./childItem.vue"; import { ref,reactive } from "vue"; let titleString = ref("收到反馈及时"); let chPerson = reactive({ id: 1, name: "张三" });</script><template><p>{{ chPerson }}</p><p>{{ titleString }}</p><ChildComponent v-model="chPerson" v-model:title="titleString" car="宝马" /> </template><style scoped></style>
-
子组件
<template><div><el-button type="primary" @click="brnHa">点击修改</el-button></div> </template><script setup lang="ts"> import { defineModel } from "vue"; interface Person {name: string;id: number; }const chPerson = defineModel<Person>({default: () => {return {name: "宝马",id: 1,};}, }); const stringTitle = defineModel('title',{type: String,default: '中NSA公司', }); console.log(chPerson.value,stringTitle.value); // 宝马 const brnHa = () => {stringTitle.value = '中发的时间分厘卡'chPerson.value = {name: "奔驰",id: 2,};console.log(chPerson); }; </script><style></style>
-
-
v-model 可以绑定一些内置修饰符,如
.trim
,.number
,.lazy
等,当然我们也可以自己定义一个修饰符, 通过 解构defineModel
的返回值,我们可以在子组件访问时给定义修饰符,基于修饰符可以选择性的调节值的读取和写入,我们给defineModel
传入get
,set
两个选择,根据判断修饰符来实现我们的代码逻辑,这里使用了set-
父组件
<script setup lang="ts"> import ChildComponent from "./childItem.vue"; import { ref } from "vue";let character = ref("dhasdh");</script><template><p>{{ character }}</p><ChildComponent v-model:character.capitalLetters="character" /> </template><style scoped></style>
-
子组件
<template><div><input type="text" v-model="character"></div> </template><script setup lang="ts"> import { defineModel } from "vue"; const [character,modifiers] = defineModel('character',{set: (val:string) => {// 含有capitalLetters修饰符if(modifiers.capitalLetters){return val.charAt(0).toUpperCase() + val.slice(1)}console.log(val,character,modifiers);return val} });</script><style></style>
-
- provide / Inject(提供/注入)
-
在父组件中定义值和事件
provide
(提供数据)<script setup lang="ts"> import ChildComponent from "./childItem.vue"; import { reactive,provide } from "vue";let chPerson = reactive({ id: 1, name: "张三" }); provide('sae',chPerson)const refDom = ()=>{console.log('234') } provide('abnt', refDom)</script><template><p>{{ chPerson }}</p><ChildComponent :propsVal="chPerson" :test="234" car="宝马" /> </template><style scoped></style>
-
子组件或孙子组件 中使用
inject
(获取数据)<template><div><el-button type="primary" @click="brnHa">点击修改</el-button></div> </template><script setup lang="ts"> import { inject } from "vue";const car = inject("sae", { name: "未知品牌" }); // 提供默认值避免 undefined const abnt = inject("abnt", () => {}); // 提供空函数作为默认值console.log(car); // 宝马 const brnHa = () =>{car.name = "奔驰";abnt()console.log(car); }</script><style></style>
-
注意:如果在其中一个组件修改,那么所有组件都会同步修改后的数据的
- pinia vue官方推荐的状态集中管理工具可以看下面的 vuex => pinia
vuex => pinia的使用
官方文档
-
Pinia 提供了更简洁直接的 API,并提供了组合式风格的 API,最重要的是,在使用 TypeScript 时它提供了更完善的类型推导
-
state (状态)
- 应用的数据来源,是一个响应式对象
-
getters (计算属性)
- 类似于技术属性,
getters
可以根据state的值来派生出新的值 getters
是有缓存的,只有所依赖的数据发生改变的时候才会重新计算
- 类似于技术属性,
-
actions (动作)
- 在
actions
中定义事件函数,来改变state
中的值
- 在
-
-
Pinia 的使用 选项式API
-
先下载 pinia
npm install pinia # 或者 yarn add pinia
-
在 mina.ts里引用
import { createApp } from 'vue'import App from './App.vue'import { createPinia } from 'pinia' let store = createPinia() const app = createApp(App); app .use(store) .mount('#app')
-
在
src/store
目录下创建一个 例如useStore.ts
文件import { defineStore } from "pinia";export const useertStore = defineStore("main", {state: () => {return {// all your data herecount: 0,};},getters: {doubleCount: (state) => state.count * 2,},actions: {// all your methods hereincrement() {this.count++;},}, });
-
在组件里面使用
// 父组件 <script setup lang="ts"> import ChildComponent from "./childItem.vue";import { useertStore } from "@/store/useStore" const store = useertStore(); </script><template><p>{{ store.count }}</p><ChildComponent /> </template><style scoped></style>// 子组件 <template><div><el-button type="primary" @click="btnAdd">点击添加{{store.count}}</el-button><p>{{ store.doubleCount }}</p></div> </template><script setup lang="ts"> import { useertStore } from "@/store/useStore" const store = useertStore();const btnAdd = () => {store.increment();console.log(store.count) } </script><style></style>
-
-
pinia 的使用 组合式API 在组件内用法和选项式API一样
import { defineStore } from "pinia"; import { ref, computed } from "vue"; export const useertStore = defineStore("main", () => {let count = ref(0);let doubleCount = computed(() => count.value * 2);const increment = () => {count.value++;};return { doubleCount, count, increment }; });
插槽使用slot
-
默认插槽:用于在子组件模板中定义一个位置,父组件可以在该位置插入自己的内容。
-
具名插槽:允许你在子组件模板中定义多个插槽位置,每个位置可以有自己的名字。在父组件中,你可以指定内容应该插入到哪个具名插槽。你可以
v-slot:header
也可以简写#header
-
条件插槽:我们可以通过
$slots
和v-if
来实现 -
作用域插槽:允许子组件数据传递给父组件,以便父组件可以自定义如何渲染这些数据,你可以这样写
v-slot:name="slotProps"
也可以这样简写#name="slotProps"
-
动态插槽:允许插槽的名称是动态的,以满足更多的业务需求
- 子组件声明插槽
<template><div><slot></slot><div class="container"><header><slot name="header"></slot></header><main><slot name="main" maintext="主要内容头部"></slot><div class="list"><div class="item" v-for="item in list"><slot name="item" :item="item">默认信息</slot></div></div></main><footer><template v-if="$slots.footer"><slot name="footer">我是底部</slot></template></footer></div></div> </template><script setup lang="ts"> import { reactive } from 'vue';let list = reactive([1, 2, 3]); </script><style></style>
- 父组件使用
<script setup lang="ts"> import ChildComponent from "./childItem.vue"; </script><template><ChildComponent>发生的<template #header><div>我是具名插槽</div></template><!-- 作用域插槽 --><template #main="props"><div>{{ props.maintext }}</div></template><!-- 动态插槽 --><template #item="items"><div>{{items.item}}</div></template></ChildComponent> </template><style scoped></style>
生命周期
- Vue2 的生命周期钩子代码更新到 Vue3 官方文档
- setup 是vue 3 新增的钩子函数,位于组件创建实例之前,适用于进行异步数据获取,状态管理,逻辑代码封装
beforeCreate
-> 使用setup()
: 实例创建前created
-> 使用setup()
:实例创建完毕,可以用于访问和修改数据,但还未挂载到dom元素上
beforeMount
->onBeforeMount
: 挂载前,可以用于修改dom解构
mounted
->onMounted
:挂载完毕,也就是组件渲染你完成,可以进行dom的操作和事件调用和监听
beforeUpdate
->onBeforeUpdate
: 组件将要更新到dom树之前,可以在vue更新dom之前访问dom状态
updated
->onUpdated
: 组件更新到dom树之后,用于执行依赖dom的更新操作
beforeDestroy
->onBeforeUnmount
:销毁前->卸载前, 用于清理资源
destroyed
->onUnmounted
:销毁完毕->卸载完毕,用于清理资源
errorCaptured
->onErrorCaptured
:捕获错误,在错误发生时调用
- 组件缓存
<KeepAlive>
-
<KeepAlive>
是一个内置组件,它可以在多个人组件动态切换时缓存被移除的组件实例<!-- 非活跃的组件将会被缓存! --> <KeepAlive><component :is="activeComponent" /> </KeepAlive>
-
我们可以通过
include
来制定是否需要缓存,当名称匹配时组件才会被缓存<!-- 以英文逗号分隔的字符串 --> <KeepAlive include="a,b"><component :is="view" /> </KeepAlive><!-- 正则表达式 (需使用 `v-bind`) --> <KeepAlive :include="/a|b/"><component :is="view" /> </KeepAlive><!-- 数组 (需使用 `v-bind`) --> <KeepAlive :include="['a', 'b']"><component :is="view" /> </KeepAlive>
-
exclude
来排除不缓存的组件,当匹配时不缓存,用法和include
一样 -
max 可以设置缓存组件的最大值,当缓存组件数量达到最大数值时,那么在新组件创建之前,已缓存组件中很久未曾访问的组件就会被销毁掉
-
-
生命周期
onActivated
在组件挂载时也会调用(组件从缓存中被激活时触发)onDeactivated
在组件卸载时也会调 (组件切换到其他页面时触发)
- 父子组件生命周期的先后顺序,vue2 和 vue3 变化不大,只是`beforeCreate ,created 被 setup() 取代
-
加载渲染依次顺序:
父组件:
beforeCreate
=> 父组件:created
=> 父组件:beforeMount(onBeforeMount)
=> 子组件:beforeCreate
=> 子组件:created
=> 子组件:beforeMount(onBeforeMount)
=>子组件:mounted(onMounted)
=> 父组件:`mounted(onMounted) -
更新过程中依次顺序:
父组件:
beforeUpdate(onBeforeUpdate)
=> 子组件:beforeUpdate(onBeforeUpdate)
=> 子组件:updated(onUpdated)
=> 父组件:updated(onUpdated)
-
销毁过程中依次顺序:
父组件:父组件:
beforeDestroy(onBeforeUnmount)
=> 子组件:beforeDestroy(onBeforeUnmount)
=> 子组件:destroyed(onUnmounted)
=> 父组件:destroyed(onUnmounted)
hooks
- vue hooks 是一个遵循特点规则的函数,命名以
use
起始,依托于vue 的组合式API构建,将组件逻辑拆分为独立,可复用的小块
-
useCount.ts 一个简单的计算属性
import { ref } from "vue"; export const useCount = () => {const count = ref(0);const increment = () => {count.value++;};return { count, increment }; };
<template><div><div> 计算结果:{{ count }}</div><el-button type="primary" @click="increment">添加</el-button></div> </template><script setup lang="ts"> import { useCount } from "@/hooks/useCount"; const { count, increment } = useCount(); </script><style></style>
-
多个嵌套使用
import { ref } from "vue"; export const useRide = () => {const rideNum = ref(2);const ride = (num: number) => {return rideNum.value * num;};return { rideNum, ride }; };
<template><div><div>计算结果:{{ count }}</div><el-button type="primary" @click="increment">添加</el-button><p>{{ result }}</p></div> </template><script setup lang="ts"> import { useCount } from "@/hooks/useCount"; import { useRide } from "@/hooks/useRide"; import { ref, watch } from "vue"; const { count, increment } = useCount(); const { ride } = useRide(); let result = ref(0) watch(() => count.value,(newValue, oldValue) => {console.log(newValue, oldValue);result.value = ride(newValue);} ); </script><style></style>
自定义指令
传送门
其他API
- shallowRef : 创建一个响应式数据,但只对顶层属性进行响应式处理,只跟踪引用值变化,不关心值内部属性变化
- shallowReactive:创建一个浅层响应式对象,但只对对象顶层属性进行响应式处理,对象内部属性变化不会做任何响应
- readonly:创建一个对象,对象的所以属性包括嵌套属性都只能读,不能修改
- shallowReadonly::和readonly相似,创建一个对象,对象的顶层属性只能读,不能修改,但嵌套属性是可以更改的
其他组件
- teleport:是一种能够将我们组件的HTML结构一定到指定位置的技术
<teleport to="body">html内容
</teleport>
- Suspense
安装路由
Vue Router 是 Vue.js 的官方路由。它与 Vue.js 核心深度集成,让用 Vue.js 构建单页应用变得轻而易举
-
安装路由
npm install vue-router@4 # 或者 yarn add vue-router@4
-
在src/router/index.ts创建路由实例
import {createRouter,createWebHistory,type RouteRecordRaw, } from "vue-router"; export const Layout = () => import("@/layout/index.vue"); // 静态路由 export const constantRoutes: RouteRecordRaw[] = [{path: "/",component: Layout,meta: { hidden: true },children: [{path: "/",component: () => import("@/views/index.vue"),},{path: "/aItem",component: () => import("@/views/aItem.vue"),},],}, ]; const router = createRouter({history: createWebHistory(), routes: constantRoutes,});export default router;
-
在main.ts内挂载使用
import router from './router'const app = createApp(App); app .use(router) .mount('#app')
-
路由工作模式
-
history模式(HTML5 模式):createWebHistory
-
优点:不含有
#
,显得更优雅 -
缺点:项目上线,需要服务端配合处理路径问题,如果没有适当的服务器配置,用户在浏览器中直接访问 https://example.com/user/id,就会得到一个 404 错误
const router = createRouter({history: createWebHistory(),routes: constantRoutes,});
-
-
hash模式
-
优点:不需要服务端进行特殊的处理
-
缺点:url上会出现一个
#
,在seo中影响不好const router = createRouter({history: createWebHashHistory(), //history: createWebHashHistory(),routes: constantRoutes, });
-
- RouterLink 和 RouterView
-
RouterLink
: 创建导航链接 -
RouterView
: 渲染组件,也就是当前路由显示匹配的组件<RouterLink to="/">Go to Home</RouterLink><RouterLink to="/aItem">Go About</RouterLink><RouterView />
-
携带参数跳转
<RouterLink to="/aItem?id:3">跳转</RouterLink> // 或 <router-link :to="{ path: '/aItem', params: { id: '12' } }">跳转 </router-link>
- 嵌套路由
<router-view>
嵌套一个<router-view>
,如果渲染到这个嵌套的 router-view 中,我们需要在路由中配置 children- children 配置只是另一个路由数组,就像 routes 本身一样。因此,你可以根据自己的需要,不断地嵌套视图
import {createRouter,createWebHashHistory,type RouteRecordRaw, } from "vue-router"; export const Layout = () => import("@/layout/index.vue"); // 静态路由 export const constantRoutes: RouteRecordRaw[] = [{path: "/",component: Layout,meta: { hidden: true },children: [{path: "/",component: () => import("@/views/index.vue"),},{path: "/aItem/:id",name: "aItem",component: () => import("@/views/aItem.vue"),},],}, ]; const router = createRouter({history: createWebHashHistory(),routes: constantRoutes, });export default router;
- 命名路由
- 当创建路由时我们可以给路由一个name
const routes = [{path: '/user/:username',name: 'profile', component: User}
]
- 动态路由
-
通过路径传递参数
<script setup lang="ts"> </script><template>页面二<router-link :to="{ name: 'aItem', params: { id: '12' } }">跳转aItem</router-link> </template><style scoped></style>
-
需要路由配置支持动态路径参数
import { createRouter, createWebHistory } from 'vue-router';const routes = [{path: '/aItem/:id',name: 'aItem',component: () => import('@/views/aItem.vue'),}, ];const router = createRouter({history: createWebHistory(),routes, });export default router;
- 编程时导航
我们可以通过useRouter
来访问路由
-
导航到不同位置
- 使用
router.push
方法,会给 history 栈添加一个新的记录,当用户点击返回,即返回上一页时就会回到之前的URL,此方法相当于点击<router-link :to="...">
<script setup lang="ts"> import { useRouter } from 'vue-router' const router = useRouter() const goAitem = () => {router.push({name: 'aItem',params: {id: '12'}}) } </script><template>页面二<router-link :to="{ name: 'aItem', params: { id: '12' } }">跳转aItem</router-link><el-button type="primary" @click="goAitem">跳转aItem</el-button> </template><style scoped></style>
- 使用
-
替换当前位置
-
router.replace
,它不会向history添加新的记录,会直接替换当前条目,亦或者router.push
中添加一个replace: true
<script setup lang="ts"> import { useRouter } from 'vue-router' const router = useRouter() const goAitem = () => {router.replace({name: 'aItem',params: {id: '12'}})// 亦或者 添加 replace: true,// router.push({// name: 'aItem',// replace: true,// params: {// id: '12'// }// }) } </script><template>页面二<router-link :to="{ name: 'aItem', replace: true, params: { id: '12' } }">跳转aItem</router-link><el-button type="primary" @click="goAitem">跳转aItem</el-button> </template><style scoped></style>
-
- 路由组件传参
-
传递参数
- params:是URL的一部分,通常用于传递静态数据,若使用 to的对象写法时,必须使用 name配置项,在路由里面配置,也就是动态路由
- query :参数也会附加在URL后面,用于传递敏感数据
<script setup lang="ts"> import { useRouter } from 'vue-router' const router = useRouter() const goAitem = () => {// paramsrouter.push({name: 'aItem',params: {id: '12'}})// queryrouter.push({name: 'aItem',query: {id: '12'}}) } </script><template>页面二<router-link :to="{ name: 'aItem', replace: true, params: { id: '12' } }">跳转aItem</router-link><router-link :to="{ name: 'aItem', replace: true, query: { id: '12' } }">跳转aItem</router-link><el-button type="primary" @click="goAitem">跳转aItem</el-button> </template><style scoped></style>
-
接收参数
- 通过
useRoute
来获取query
参数,useRoute
返回的是响应式的路由对象,其中query
包含了所以查询参数<script setup lang="ts"> import { useRoute } from 'vue-router'; const route = useRoute(); console.log(route,' `query`'); </script>
- 通过
-
路由 props 配置
-
当 props 设置为 true 时,route.params 将被设置为组件的 props
const routes = [{ path: '/aItem/:id',name: 'aItem',component: User, props: true } ]
-
函数模式下无论是动态路由参数还是查询参数,params, query 都可以方便地作为 props 传递到组件中
- 配置
const routes = [{path: '/aItem',component: SearchUser,props: route => ({ query: route.query.q })} ]
- 组件接收
<script setup> defineProps({id: {type: String,default: "奔驰",} }) </script>
-
部署服务器
传送门
vue创建项目使用element-plus
- 下载引用element-plus
# 选择一个你喜欢的包管理器# NPM
npm install element-plus --save# Yarn
yarn add element-plus# pnpm
pnpm install element-plus
- 在main.js内引用
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'// 引入 Element-Plus 依赖
import ElementPlus from 'element-plus'
// 引入全局 cSS 样式
import 'element-plus/dist/index.css'createApp(App)
.use(store)
.use(router)
.use(ElementPlus)
.mount('#app')
- 页面中使用,这里实现element-plus中英文切换 结合 vuex切换中英文
<template><div class='about'><el-select @change="handleClear" v-model="selectValue" placeholder="选择语言" style="width: 240px"><el-option v-for="item in langOptions" :key="item.value" :label="item.label" :value="item.value"><span style="float: left">{{ item.label }}</span><span style="float: right;color: var(--el-text-color-secondary);font-size: 13px;">{{ item.value }}</span></el-option></el-select></div>
</template><script>
import { useStore } from "vuex";
import {reactive, ref
} from 'vue'
export default {name: 'about',setup() {const selectValue = ref('')const langOptions = reactive([{value: 'en',label: 'English'},{value: 'zhCn',label: '中文'}])const store = useStore()const handleClear = (value) => {store.dispatch('provider/updateLanguage', value)}return {selectValue,langOptions,handleClear}}
};
</script>
<style lang='less' scoped></style>
- 全局实现中英文
<template><el-config-provider :locale="locale"><el-table mb-1 :data="[]" /><router-view /></el-config-provider>
</template><script>
import {computed,
} from 'vue'
import { useStore } from "vuex";export default {name: 'App',setup() {const store = useStore()const locale = computed(() => store.state.provider.language);// 返回数据return {locale,}}
}
</script>
- vuex 配置与使用
import { createStore } from 'vuex'
import provider from './modules/provider'
export default createStore({state: {},mutations: {},actions: {},modules: {provider }
})
- modules的创建provider文件
// 导入 Element Plus 中英文语言包
import zhCn from "element-plus/es/locale/lang/zh-cn";
import en from "element-plus/es/locale/lang/en";
const user = {namespaced: true,state: {language: zhCn,},mutations: {setLanguage(state, language) {if (language == "en") {state.language = en;} else {state.language = zhCn;}}},actions: {/*** 根据语言标识读取对应的语言包*/updateLanguage({ commit }, language) {commit('setLanguage', language)}},getters: {language(state) {return state.language}}
}
export default user
- 最终实现效果
- 启动项目报错
- 预转换错误:未找到预处理器依赖项“sas-embedded”。你安装了吗?尝试
npm install-D sass-embedded
。
- 按照提示安装重新启动就好
按需引入
- 按需导入,下载两款插件
unplugin-vue-components
和unplugin-auto-import
这两款插件
npm install -D unplugin-vue-components unplugin-auto-import
- 在vite.config.ts 中配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// https://vite.dev/config/
export default defineConfig({plugins: [vue(),AutoImport({resolvers: [ElementPlusResolver()],}),Components({resolvers: [ElementPlusResolver()],}),],
})
- 重启项目接下来直接引用就行了
组件名称的单个配置
-
vue3 会根据组件文件名称自动推导出name属性,item.vue name 名为item ,但当我们起个文件夹名称,但文件夹内容的文件是index.vue时就无法推导出文件内名称了
-
这在我们调试和定位问题时就不太方便了,当然我们也可以单独添加一个scrit 来去写name名称,但这种方法有点过于繁琐
``` <script lang="ts">export default {name: 'pramas'} </script> ```
-
社区推出了 unplugin-vue-define-options 来简化该操作
npm i unplugin-vue-define-options -D
// vite.config.ts import DefineOptions from 'unplugin-vue-define-options/vite' import Vue from '@vitejs/plugin-vue'export default defineConfig({plugins: [Vue(), DefineOptions()], })
-
页面中使用
<script setup lang="ts"> defineOptions({name: "pramas" }) <script>
-
三方插件在
<script lang="ts" name="pramas">
上添加name
<script setup name="pramas">let a = '虑是否' </script>
- 下载此插件可以动态修改vue文件名称
npm i vite-plugin-vue-setup-extend -D
-
-
在 vite.config.ts内引用
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import vueDefineNmae from 'vite-plugin-vue-setup-extend' export default defineConfig({plugins: [vue(),vueDefineNmae()], })
-
代码运行就可以看见我们自定义的name名称了
遇到问题
1. 淘宝镜像过期,更改淘宝镜像
- 在这里插入代码片
1.查看当前npm镜像
npm config get
2.配置新的镜像
npm config set registry https://registry.npmmirror.com
3.再次查看镜像配置情况
npm config get
2.npm run dev` 无法启动项目,显示vite不是内部或外部命令,这是系统启动vite项目,但找不到vite,意味着你未曾安装vite,你可以执行以下命令全局安装,再执行之前命令
npm install -g vite
- node 和 npm 版本不兼容,官网需要18.3或更高版本的
3.vscode打开vite创建项目引入组件报错解决
-
在src下创建此文件xxx.d.ts在你的 src 目录中,填入以下内容,帮助 TypeScript 理解 .vue 文件
declare module "*.vue" {import { defineComponent } from "vue";const Component: ReturnType<typeof defineComponent>;export default Component;}
-
如果未曾解决,将
.vue
改为vue
并重新打开项目 -
如果还未解决可以看看官网的方法
4.vue3+vite3+ts使用@alias路径别名爆红报错解决
- 在
vite.config.ts
中配置以下内容
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from "path";
// https://vite.dev/config/
export default defineConfig({plugins: [vue()],resolve: {alias: {"@": path.resolve(__dirname, "./src"),},},
})
- 如果显示找不到 path 按照以下命令安装
npm install --save-dev @types/node
- 在
tsconfig.app.json
中配置以下内容,然后重启项目
{"compilerOptions": {"baseUrl": ".","paths": {"@/*": ["src/*"],}},"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}