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.pageX
y.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.pageX
y.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 = 30 console.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 = name
state.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; 组合式API let 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 here count: 0, }; }, getters: { doubleCount: (state) => state.count * 2, }, actions: { // all your methods here increment() { 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 = () => { // params router.push({ name: 'aItem', params: { id: '12' } }) // query router.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"]
}