端口控制 + 跨域
在文件中
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path';
// /config/
export default defineConfig({
plugins: [vue()],
base: "./",//打包路径
resolve: {
alias: {
'@': path.resolve(__dirname, './src')//设置别名
}
},
server: {
open: true,//启动项目自动弹出浏览器
port: 4000,//启动端口
proxy: {
'/api': {
target: 'http://localhost:3001', //实际请求地址
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
},
}
}
// server: {
// port: 4000,//启动端口
// open: true,
// proxy: {
// // 跨域简单写法写法
// '/api': 'http://123.56.85.24:5000'//代理网址
// },
// cors: true
// },
})
拦截器
import Axios from 'axios'
const axios = Axios.create({
headers: {
'Content-Type': 'application/json'
},
timeout: 60000, // 超时
baseURL: '' // 请求接口地址,这里使用本项目地址,因为我们是前后端分离,后面需要在里面配置代理,实际请求得地址不是这个。
})
// 请求拦截
axios.interceptors.request.use(req => {
if (req.method === 'get') {
const url = req.url
const t = new Date().getTime()
if (url.indexOf('?') >= 0) {
req.url = `${url}&t=${t}`
} else {
req.url = `${url}?t=${t}`
}
}
return req
})
// 响应拦截
axios.interceptors.response.use(
response => {
return response.data
},
error => {
// 响应失败统一处理
const { response } = error
if (response) {
switch (response.status) {
case 400:
window.$vm.$message.error('请求无效')
break
case 401:
window.$vm.$message.error({ message: '尚未登录请重新登录' })
break
case 403:
window.$vm.$message.error('您没有权限这样做,请联系管理员')
break
case 404:
window.$vm.$message.error('请求未找到')
break
case 500:
window.$vm.$message.error('系统异常')
break
case 504:
window.$vm.$message.error('请求超时,请稍后再试')
break
default:
window.$vm.$message.error('系统异常')
break
}
}
return Promise.reject(error)
}
)
export default axios
api
import request from '../utils/request' // 引入封装得axios
// 登录
export function cellphone (data) {
return request({
url: '/api/login/cellphone',
method: 'POST',
data:{
phone:'19829215473',
password:'lsh112233'
}
})
}
Element plus 引入
-
npm install element-plus --save
-
按需导入(自动导入):
npm install -D unplugin-vue-components unplugin-auto-import -
在 或 中写入
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default {
plugins: [
// ...
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
}
ref
import {ref,isRef,shallowRef,triggerRef,customRef} from "vue";
/**
* ref() 响应式
* isRef() 判断是否为ref
* shallowRef() 浅层响应式 注意:与ref混用时,失效
* triggerRef() 强制收集依赖,shallowRef与ref混用时失效就是由于,ref底层代码中包含triggerRef
* customRef() 自定义一个ref
* 例:function MyRef<T>(value: T) {
return customRef((track, trigger) => {
return {
get() {
track();
return value;
},
set(newValue) {
value = newValue;
trigger();
},
};
});
}
const obj = ref<string>("大家好");
*/
直接使用ref
<div ref="dom">哈哈哈</div>
const dom = ref<HTMLDivElement>();
(?.innerHTML); // 注意:刚开始加载时间听不到的
reactive
<template>
<div>
<form>
<input v-model="" type="text" />
<br />
<input v-model="" type="text" />
<br />
<button @="play">提交</button>
</form>
<ul>
<li v-for="(item, index) in list" :key="index">{{ item }}</li>
</ul>
<button @click="addList">新增</button>
</div>
</template>
<script lang="ts" setup>
import { reactive, readonly, shallowReactive } from "vue";
/**
* ref 支持所有类型 reactive 只支持object类型
* ref 取值 赋值 都需要加.value reactive 不需要.value
* reactive proxy 不能直接赋值 解决方法:方法一: 可以使用push + 解构的方式 ;方法二:可以添加一个对象,把数组作为一个属性去解决
* readonly 只读的
* shallowReactive 浅层响应式(只响应一层) 注意:会被reactive影响
*/
type M = {
name: string;
age: number;
};
const form = reactive<M>({
name: "张三",
age: 18,
});
const play = () => {
(form);
};
const list = reactive<string[]>([]);
const addList = () => {
let res: string[] = ["张三", "李四", "王麻子"];
(...res);
};
</script>
<style scoped lang="scss"></style>
toRef toRefs toRaw
<!-- name -->
<template>
<div>{{ user }}</div>
<button @click="updateUser">修改</button>
</template>
<script lang="ts" setup>
import { reactive, toRef, ToRefs, toRaw } from "vue";
/**
* toRef 只能修改响应式对象的值 非响应式视图毫无变化
* toRefs 相当于toRef的集合
* toRaw 使元素由响应式变为普通的
*/
type m = {
name: string;
age: number;
};
const user = reactive<m>({
name: "李四",
age: 18,
});
const aaa = toRef(user, "name");
// toRefs实现原码
const toRefs = <T extends object>(object: T) => {
const map: any = {};
for (let key in object) {
map[key] = toRef(object, key);
}
return map;
};
// end
let { name, age } = toRefs(user);
const updateUser = () => {
// = "占山";
= "大王";
};
</script>
<style scoped lang="scss"></style>
computed
<!-- name -->
<template>
<div>
<input type="text" v-model="name1" />
<input type="text" v-model="name2" />
<div>
{{ name3 }}
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, reactive, ref } from "vue";
/**
*
*/
let name1 = ref<string>("");
let name2 = ref<string>("");
// 方法一
// const name3 = computed(() => {
// return + ;
// });
// 方法二
const name3 = computed({
get() {
return + ;
},
set() {
+ ;
},
});
</script>
<style scoped lang="scss"></style>
watch
<!-- name -->
<template>
<div>
<input type="text" v-model="name1" />
<input type="text" v-model="name2" />
<input type="text" v-model="" />
{{ name3 }}
</div>
</template>
<script lang="ts" setup>
import { computed, reactive, ref, watch } from "vue";
/**
*
*/
interface bbb {
ccc: String;
}
interface aaa {
bbb: bbb;
}
interface text {
aaa: aaa;
}
const name1 = ref<string>("");
const name2 = ref<string>("");
const name3 = ref<text>({
aaa: {
bbb: {
ccc: "123",
},
},
});
// 情况一:监听单个
// watch(name1, (newValue, oldValue) => {
// (newValue, oldValue);
// });
// 情况二:监听多个 --- 以数组的形式书写,同时newValue, oldValue返回值也是数组
// watch([name1, name2], (newValue, oldValue) => {
// (newValue, oldValue);
// });
// 情况三:监听对象 --- 如果为ref则需要开启第三个属性 deep: true,
// --- 如果为reactive则不需要开启第三个属性
// --- 注意:监听到的对象都是新的返回值
// watch(
// name3,
// (newValue, oldValue) => {
// (newValue, oldValue);
// },
// {
// deep: true,
// immediate: true, // 立即执行一次
// flush: "post", // pre 默认值 组件更新前调用 sync 同步调用 post 组件更新后调用
// }
// );
// 情况四:监听对象的某个属性,需要声明reactive,同时写成一个类似get的形式把要监听的属性返回
watch(
() => ,
(newValue, oldValue) => {
(newValue, oldValue);
}
);
</script>
<style scoped lang="scss"></style>
watchEffect – 高级侦听器
立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数。
用法:
import { watchEffect } from ‘vue’
setup()
{
watchEffect(函数(){
依赖追踪:
同步调用:
追踪所有依赖
异步调用:
只有在第一个 await 正常工作前访问到的 property 才会被追踪。
如下:只会追踪作为依赖
const response = await fetch()
= await ()
})
(1)停止侦听
隐式停止:
当watchEffect在组件的setup()函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。
显示停止:
const xx=watchEffect(...)
();
(2)清除副作用
watchEffect传入参数
watchEffect((onInvalidate)=>{
onInvalidate(()=>{
执行时机:
在副作用即将重新执行时
如果在setup()或生命周期钩子函数中使用了 watchEffect, 则在卸载组件时
})
})
(3)异步副作用以及清楚
副作用函数是一个异步函数时,清理函数必须要在Promise被resolve之前被注册,同时Vue依赖这个返回的Promise来自动处理Promise链上的潜在错误
watchEffect(async () => {
onInvalidate(() => {
/* ... */
})
= await fetchData()
})
(4)副作用需要同步或在组件更新之前
watchEffect(
() => {
...
},
{
flush: 'sync', 'pre'在DOM更新前运行,'post'在DOM更新后运行,'sync'强制效果始终同步触发,默认为'pre'
}
)
(5)调试侦听器的行为(只能在开发模式下工作)
watchEffect(
() => {
...
},
{
onTrack(e){
当一个 reactive对象属性或一个 ref 作为依赖被追踪时触发
包含了值
debugger
},
onTrigger(e) {
依赖项变更导致副作用被触发时
包含了值
debugger
}
}
)
(2)watchPostEffect
和flush:‘post’ 效果一致,作为别名使用
(3)watchSyncEffect
和flush:‘sync’ 效果一致,作为别名使用
(4)卸载watchEffect时机
异步回调创建一个侦听器,那么它不会绑定到当前组件上,必须手动停止它,以防内存泄漏,同步则不用
需要手动卸载:
setTimeout(() => {
watchEffect(() => {})
}, 100)
生命周期
注:当作为组件引入时,大体与vue2一致,但不需要在computed中声明
<!-- name -->
<template>
<div></div>
</template>
<script lang="ts" setup>
import {
ref,
reactive,
toRefs,
onBeforeUnmount,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onUnmounted,
onRenderTracked,
onRenderTriggered,
} from "vue";
// beforCreated created setup语法糖是没有这两个生命周期的 set代替
("setup");
// 创建
onBeforeMount(() => {
("创建之前");
});
onMounted(() => {
("创建完成");
});
// 更新
onBeforeUpdate(() => {
("更新之前");
});
onUpdated(() => {
("更新完成");
});
// 销毁
onBeforeUnmount(() => {
("销毁之前");
});
onUnmounted(() => {
("销毁完成");
});
// 收集依赖
onRenderTracked((e) => {
();
});
onRenderTriggered((e) => {
(e);
});
</script>
<style scoped lang="scss"></style>
父传子
父组件
<!-- name -->
<template>
<div>
我是父组件
<br />
——————————————————————————————————————————————————————
<A :text="'123'"></A>
</div>
</template>
<script lang="ts" setup>
import { computed, reactive, ref, watch, watchEffect } from "vue";
import A from "./components/";
/**
*
*/
</script>
<style scoped lang="scss"></style>
子组件
<!-- name -->
<template>
<div>
我是子组件 <br />
{{ text }}
</div>
</template>
<script lang="ts" setup>
import {
ref
} from "vue";
// 方法一 接受父组件传过来的值defineProps
const content = defineProps({
text: {
type: String,
default: "abc",
},
});
// 方法二 ts特有的默认值withDefaults
const content = withDefaults(
defineProps<{
text: string;
}>(),
{
text: () => "你好", // 默认值
}
);
();
</script>
<style scoped lang="scss"></style>
子传父
父组件
<!-- name -->
<template>
<div>
我是父组件
<br />
——————————————————————————————————————————————————————
<A @on-click="name"></A>
</div>
</template>
<script lang="ts" setup>
import { computed, reactive, ref, watch, watchEffect } from "vue";
import A from "./components/";
/**
*
*/
const name = (name: string) => {
(name);
};
</script>
<style scoped lang="scss"></style>
子组件
<!-- name -->
<template>
<div>
我是子组件 <br />
<button @click="send">传值</button>
</div>
</template>
<script lang="ts" setup>
import {
ref
} from "vue";
// 方法一 给父组件传值defineEmits
const emit = defineEmits(["on-click"]);
// 方法二 ts特有
const emit = defineEmits<{
(e: "on-click", name: string): void;
}>();
const send = () => {
emit("on-click", "子传父");
};
</script>
<style scoped lang="scss"></style>
子组件向父组件暴露一些属性
子组件
<!-- name -->
<template>
<div>我是子组件 <br /></div>
</template>
<script lang="ts" setup>
import {
ref,
} from "vue";
defineExpose({
name: "张三",
open: (): void => {
("123");
},
});
</script>
<style scoped lang="scss"></style>
父组件
<!-- name -->
<template>
<div>
我是父组件
<br />
——————————————————————————————————————————————————————
<A ref="waterFall"></A>
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, reactive, ref, watch, watchEffect } from "vue";
import A from "./components/";
/**
*
*/
const waterFall = ref<InstanceType<typeof A>>();
onMounted(() => {
(?.name);
?.open();
});
</script>
<style scoped lang="scss"></style>
局部组件
- 在components中书写.vue文件
- 在对应页面引入
<template>
<div>
<A></A>
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, reactive, ref, watch, watchEffect } from "vue";
import A from "./components/";
/**
*
*/
</script>
全局组件
- 在components中书写.vue文件
- 在中声明
import { createApp } from 'vue'
import './'
import HelloWorldVue from './components/' // 在中引入
import App from './'
const app = createApp(App)
('A', HelloWorldVue) // 声明
('#app')
- 在对应页面引入
<template>
<div>
<A></A>
</div>
</template>
可选链操作符
例:
let a = {};
(); // underfind
(); // 报错
(?.length); // underfind 可选链操作符:如果后面的一层读不到的话会反一个underfind
动态组件
- 方法一
<template>
<div style="display: flex; cursor: pointer">
<div
@click="play(item)"
v-for="(item, index) in data"
style="margin-left: 30px"
>
<div>{{ }}</div>
</div>
</div>
<component :is="aaa"></component>
</template>
<script lang="ts" setup>
import {
computed,
onMounted,
reactive,
ref,
watch,
watchEffect,
markRaw,
shallowRef,
} from "vue";
import AVue from "./components/expame/";
import BVue from "./components/expame/";
import CVue from "./components/expame/";
/**
*
*/
const aaa = shallowRef(AVue);
const data = reactive([
{
name: "A组件",
com: markRaw(AVue),
},
{
name: "B组件",
com: markRaw(BVue),
},
{
name: "C组件",
com: markRaw(CVue),
},
]);
const play = (item: object) => {
= ;
};
</script>
<style scoped lang="scss"></style>
- 方法2
<!-- name -->
<template>
<div style="display: flex; cursor: pointer">
<div
@click="play(item)"
v-for="(item, index) in data"
style="margin-left: 30px"
>
<div>{{ }}</div>
</div>
</div>
<component :is="aaa"></component>
</template>
<script lang="ts">
import AVue from "./components/expame/";
import BVue from "./components/expame/";
import CVue from "./components/expame/";
export default {
components: {
AVue,
BVue,
CVue,
},
};
</script>
<script lang="ts" setup>
import {
computed,
onMounted,
reactive,
ref,
watch,
watchEffect,
markRaw,
shallowRef,
} from "vue";
/**
*
*/
const aaa = shallowRef("AVue");
const data = reactive([
{
name: "A组件",
com: "AVue",
},
{
name: "B组件",
com: "BVue",
},
{
name: "C组件",
com: "CVue",
},
]);
const play = (item: object) => {
= ;
};
</script>
<style scoped lang="scss"></style>
slot 插槽
匿名插槽
// 父组件
<template>
<div>
<mySlot>
<template v-slot>
<div>匿名插槽</div>
</template>
</mySlot>
</div>
</template>
<script lang="ts" setup>
import mySlot from "./components/mySlot/";
</script>
// 子组件
<template>
<div>
<slot></slot>
</div>
</template>
具名插槽
// 父组件
<template>
<div>
<mySlot>
<template v-slot:slot_1>
<div>具名插槽</div>
</template>
</mySlot>
</div>
</template>
<script lang="ts" setup>
import mySlot from "./components/mySlot/";
</script>
// 子组件
<template>
<div>
<header>
<slot name="slot_1"></slot>
</header>
</div>
</template>
作用域插槽
// 父组件
<!-- name -->
<template>
<div>
<mySlot>
<template v-slot="{ date }">
<div>{{ date }}</div>
</template>
</mySlot>
</div>
</template>
<script lang="ts" setup>
import mySlot from "./components/mySlot/";
</script>
// 子组件
<!-- name -->
<template>
<div>
<!-- 作用域插槽 -->
<!-- 在子组件动态绑定参数 派发给父组件的slot去使用 -->
<main>
<div v-for="(item, index) in A" :key="index">
<slot :date="item"></slot>
</div>
</main>
</div>
</template>
<script lang="ts" setup>
import { reactive} from "vue";
type aaa = {
name: string;
age: number;
};
let A = reactive<aaa[]>([
{
name: "李四",
age: 18,
},
{
name: "张三",
age: 19,
},
{
name: "王五",
age: 20,
},
]);
</script>
异步组件(代码优化)
顶层 await
在setup语法糖里面 使用方法
< script setup > 中可以使用顶层 await。结果代码会被编译成 async setup()
const { data } = await <Data>("./");
父组件引用子组件 通过defineAsyncComponent加载异步配合import 函数模式便可以分包
<script setup lang="ts">
import { reactive, ref, markRaw, toRaw, defineAsyncComponent } from 'vue'
const Dialog = defineAsyncComponent(() => import('../../components/Dialog/'))
//完整写法
const AsyncComp = defineAsyncComponent({
// 加载函数
loader: () => import('./'),
// 加载异步组件时使用的组件
loadingComponent: LoadingComponent,
// 展示加载组件前的延迟时间,默认为 200ms
delay: 200,
// 加载失败后展示的组件
errorComponent: ErrorComponent,
// 如果提供了一个 timeout 时间限制,并超时了
// 也会显示这里配置的报错组件,默认值是:Infinity
timeout: 3000
})
Suspense
- Suspense:是 vue3 中新增的组件,类似于 keep-alive 不需要任何的引入,可以直接进行使用。自带两个 slot 分别为 default、fallback。顾名思义,当要加载的组件不满足状态时,Suspense 将回退到 fallback状态一直到加载的组件满足条件,才会进行渲染。
- 最为常见的使用场景,配合 vue3 中 defineAsyncComponent 来加载异步组件时,进行 loading 的处理。
<!-- name -->
<template>
<div>
<Suspense>
<template #default>
<SyncVue></SyncVue>
</template>
<template #fallback>
<skeleton></skeleton>
</template>
</Suspense>
</div>
</template>
<!-- <script lang="ts">
import mySlot from "./components/mySlot/";
export default {};
</script> -->
<script lang="ts" setup>
// import Sync from "./components/expame/";
import {
computed,
onMounted,
reactive,
ref,
watch,
watchEffect,
markRaw,
shallowRef,
defineAsyncComponent,
} from "vue";
import skeleton from "./components/expame/";
const SyncVue = defineAsyncComponent(
() => import("./components/expame/")
);
/**
* 引入
* defineAsyncComponent 异步组件
*
* Suspense:是 vue3 中新增的组件,类似于 keep-alive 不需要任何的引入,可以直接进行使用。自带两个 slot 分别为 default、fallback。顾名思义,当要加载的组件不满足状态时,Suspense 将回退到 fallback状态一直到加载的组件满足条件,才会进行渲染。
* 最为常见的使用场景,配合 vue3 中 defineAsyncComponent 来加载异步组件时,进行 loading 的处理。
*/
</script>
<style scoped lang="scss"></style>
Teleport传送组件
Teleport Vue 3.0新特性之一。
Teleport 是一种能够将我们的模板渲染至指定DOM节点,不受父级style、v-show等属性影响,但data、prop数据依旧能够共用的技术;类似于 React 的 Portal。
主要解决的问题 因为Teleport节点挂载在其他指定的DOM节点下,完全不受父级style样式影响
使用方法
通过to 属性 插入指定元素位置 to=“body” 便可以将Teleport 内容传送到指定位置
<Teleport to="body">
<Loading></Loading>
</Teleport>
也可以自定义传送位置 支持 class id等 选择器
<div ></div>
<div class="modal"></div>
<template>
<div class="dialog">
<header class="header">
<div>我是弹框</div>
<el-icon>
<CloseBold />
</el-icon>
</header>
<main class="main">
我是内容12321321321
</main>
<footer class="footer">
<el-button size="small">取消</el-button>
<el-button size="small" type="primary">确定</el-button>
</footer>
</div>
</template>
<script setup lang='ts'>
import { ref, reactive } from 'vue'
</script>
<style lang="less" scoped>
.dialog {
width: 400px;
height: 400px;
background: #141414;
display: flex;
flex-direction: column;
position: absolute;
left: 50%;
top: 50%;
margin-left: -200px;
margin-top: -200px;
.header {
display: flex;
color: #CFD3DC;
border-bottom: 1px solid #636466;
padding: 10px;
justify-content: space-between;
}
.main {
flex: 1;
color: #CFD3DC;
padding: 10px;
}
.footer {
border-top: 1px solid #636466;
padding: 10px;
display: flex;
justify-content: flex-end;
}
}
</style>
多个使用场景
<Teleport to=".modal1">
<Loading></Loading>
</Teleport>
<Teleport to=".modal2">
<Loading></Loading>
</Teleport>
动态控制teleport
使用disabled 设置为 true则 to属性不生效 false 则生效
<teleport :disabled="true" to='body'>
<A></A>
</teleport>
内置组件keep-alive
有时候我们不希望组件被重新渲染影响使用体验;或者处于性能考虑,避免多次重复渲染降低性能。而是希望组件可以缓存下来,维持当前的状态。这时候就需要用到keep-alive组件。
开启keep-alive 生命周期的变化
初次进入时: onMounted> onActivated
退出后触发 deactivated
再次进入:
只会触发 onActivated
事件挂载的方法等,只执行一次的放在 onMounted中;组件每次进去执行的方法放在 onActivated中
<!-- 基本 -->
<keep-alive>
<component :is="view"></component>
</keep-alive>
<!-- 多个条件判断的子组件 -->
<keep-alive>
<comp-a v-if="a > 1"></comp-a>
<comp-b v-else></comp-b>
</keep-alive>
<!-- 和 `<transition>` 一起使用 -->
<transition>
<keep-alive>
<component :is="view"></component>
</keep-alive>
</transition>
include 和 exclude
<keep-alive :include="" :exclude="" :max=""></keep-alive>
include 和 exclude 允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示:
max
<keep-alive :max="10">
<component :is="view"></component>
</keep-alive>
Transition
- 过度单个元素
<!-- name -->
<template>
<div>
<button @click="flag = !flag">变</button>
<Transition name="fade">
<div v-if="flag" class="box"></div>
</Transition>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const flag = ref<boolean>(true);
/**
*Vue 提供了两个内置组件,可以帮助你制作基于状态变化的过渡和动画:
<Transition> 会在一个元素或组件进入和离开 DOM 时应用动画。本章节会介绍如何使用它。
<TransitionGroup> 会在一个 v-for 列表中的元素或组件被插入,移动,或移除时应用动画。我们将在下一章节中介绍。
*/
</script>
<style scoped>
.box {
width: 200px;
height: 200px;
background-color: red;
}
/* 进入之前 */
.fade-enter-from {
width: 0;
height: 0;
}
/* 过度曲线 */
.fade-enter-active {
transition: all 1.5s ease;
}
/* 进入结束 */
.fade-enter-to {
width: 200px;
height: 200px;
}
/* 离开之前 */
.fade-leave-from {
width: 200px;
height: 200px;
}
/* 过度曲线 */
.fade-leave-active {
transition: all 1.5s ease;
}
/* 进入结束 */
.fade-leave-to {
width: 0;
height: 0;
}
</style>
Transition自定义类名
<!-- name -->
<template>
<div>
<button @click="flag = !flag">变</button>
<Transition name="fade" enter-from-class="e-from">
<div v-if="flag" class="box"></div>
</Transition>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const flag = ref<boolean>(true);
/**
* 自定义类名:
* 1. enter-from-class="自定义类名"
* 2. enter-active-class="自定义类名"
* 3. enter-to-class="自定义类名"
* 4. leave-from-class="自定义类名"
* 5. leave-active-class="自定义类名"
* 6. leave-to-class="自定义类名"
*
*/
</script>
<style scoped>
.box {
width: 200px;
height: 200px;
background-color: red;
}
.e-from {
width: 0;
height: 0;
}
.fade-enter-active {
transition: all 1.5s ease;
}
.fade-enter-to {
width: 200px;
height: 200px;
}
.fade-leave-from {
width: 200px;
height: 200px;
}
.fade-leave-active {
transition: all 1.5s ease;
}
.fade-leave-to {
width: 0;
height: 0;
}
</style>
transtions结合
- 安装:npm install -S
<template>
<div>
<button @click="flag = !flag">变</button>
<Transition
name="fade"
enter-from-class="animate__animated animate__fadeOut"
enter-to-class="animate__animated animate__fadeOut"
>
<div v-if="flag" class="box"></div>
</Transition>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import "";
const flag = ref<boolean>(true);
/**
* 1. 引入 import "";
* 2. 在Transtion中使用
*/
</script>
Transtion的duration属性
<template>
<div>
<button @click="flag = !flag">变</button>
<Transition
:duration="{ enter: '50', leave: '500' }"
name="fade"
enter-from-class="animate__animated animate__fadeOut"
enter-to-class="animate__animated animate__fadeOut"
>
<div v-if="flag" class="box"></div>
</Transition>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import "";
const flag = ref<boolean>(true);
/**
* 1. :duration="500" // 定义动画时长
* 2. :duration="{ enter: '50', leave: '500' }" // 单独定义动画时长
*/
</script>
Transtion声明周期
<!-- name -->
<template>
<div>
<button @click="flag = !flag">变</button>
<Transition
name="fade"
@before-enter="enterFrom"
@enter="enterActive"
@after-enter="enterTo"
@enter-cancelled="enterCancelled"
@before-leave="leaveFrom"
@leave="leaveActive"
@after-leave="leaveTo"
@leave-cancelled="leaveCancelled"
>
<div v-if="flag" class="box"></div>
</Transition>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import "";
const flag = ref<boolean>(true);
/**
* 1.@before-enter="对象名" // 在元素被插入到 DOM 之前被调用 -- 用这个来设置元素的 "enter-from" 状态
* 2.@enter="对象名" // 在元素被插入到 DOM 之后的下一帧被调用 -- 用这个来开始进入动画
* 3.@after-enter="对象名" // 当进入过渡完成时调用。
* 4.@enter-cancelled="对象名" // -- 过度效果被打断
*
* 5.@before-leave="对象名" // 在 leave 钩子之前调用 -- 大多数时候,你应该只会用到 leave 钩子
* 6.@leave="对象名" // 在离开过渡开始时调用 -- 用这个来开始离开动画
* 7.@after-leave="对象名" // 在离开过渡完成,且元素已从 DOM 中移除时调用
* 8.@leave-cancelled="对象名" // -- 过度效果被打断
*/
const enterFrom = (el: Element) => {
("开始之前");
};
const enterActive = (el: Element, done: Function) => {
("过度曲线");
// 调用回调函数 done 表示过渡结束
// 如果与 CSS 结合使用,则这个回调是可选参数
setTimeout(() => {
done();
}, 1000);
};
const enterTo = (el: Element) => {
("过度结束");
};
const enterCancelled = (el: Element) => {
("过度效果被打断");
};
const leaveFrom = (el: Element) => {
("结束之前");
};
const leaveActive = (el: Element, done: Function) => {
("过度曲线");
// 调用回调函数 done 表示过渡结束
// 如果与 CSS 结合使用,则这个回调是可选参数
setTimeout(() => {
done();
}, 1000);
};
const leaveTo = (el: Element) => {
("过度结束");
};
const leaveCancelled = (el: Element) => {
("过度效果被打断");
};
</script>
<style scoped>
.box {
width: 200px;
height: 200px;
background-color: red;
}
/* 进入之前 */
.fade-enter-from {
width: 0;
height: 0;
}
/* 过度曲线 */
.fade-enter-active {
transition: all 1.5s ease;
}
/* 进入结束 */
.fade-enter-to {
width: 200px;
height: 200px;
}
/* 离开之前 */
.fade-leave-from {
width: 200px;
height: 200px;
}
/* 过度曲线 */
.fade-leave-active {
transition: all 1.5s ease;
}
/* 进入结束 */
.fade-leave-to {
width: 0;
height: 0;
}
</style>
Transtion 结合动画库jsap使用
<template>
<div>
<button @click="flag = !flag">变</button>
<Transition
@before-enter="enterFrom"
@enter="enterActive"
@leave="leaveActive"
>
<div v-if="flag" class="box"></div>
</Transition>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import gsap from "gsap";
const flag = ref<boolean>(true);
/**
* 1. 下载 npm install jsap -S
* 2. 引入 import gsap from "gsap";
* 3. 使用
*/
const enterFrom = (el: Element) => {
// ("开始之前");
(el, {
height: 0,
width: 0,
});
};
const enterActive = (el: Element, done: ) => {
(el, {
height: 200,
width: 200,
onComplete: done,
});
};
const leaveActive = (el: Element, done: ) => {
(el, {
height: 0,
width: 0,
onComplete: done,
});
};
</script>
Transition 的 appear属性
<template>
<div>
<button @click="flag = !flag">变</button>
<Transition
appear
appear-from-class="from"
appear-active-class="active"
appear-to-class="to"
>
<div v-if="flag" class="box"></div>
</Transition>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const flag = ref<boolean>(true);
/**
* 1. 在进入时执行动画效果
* 2. 可以结合使用
*/
</script>
<style scoped>
.box {
width: 200px;
height: 200px;
background-color: red;
}
.from {
width: 0;
height: 0;
}
.active {
transition: all 1.5s ease;
}
.to {
width: 200px;
height: 200px;
}
</style>
Transition-group过度列表
- 默认情况下,它不会渲染一个包裹元素,但是你可以通过 tag attribute 指定渲染一个元素。
- 过渡模式不可用,因为我们不再相互切换特有的元素。
- 内部元素总是需要提供唯一的 key attribute 值。
- CSS 过渡的类将会应用在内部的元素中,而不是这个组/容器本身。
<transition-group>
<div style="margin: 10px;" :key="item" v-for="item in list">{{ item }</div>
</transition-group>
const list = reactive<number[]>([1, 2, 4, 5, 6, 7, 8, 9])
const Push = () => {
(123)
}
const Pop = () => {
()
}
< transition-group > 组件还有一个特殊之处。除了进入和离开,它还可以为定位的改变添加动画。只需了解新增的 v-move 类就可以使用这个新功能,它会应用在元素改变定位的过程中。像之前的类名一样,它的前缀可以通过 name attribute 来自定义,也可以通过 move-class attribute 手动设置
<template>
<div>
<button @click="shuffle">Shuffle</button>
<transition-group class="wraps" name="mmm" tag="ul">
<li class="cell" v-for="item in items" :key="">{{ }}</li>
</transition-group>
</div>
</template>
<script setup lang='ts'>
import _ from 'lodash'
import { ref } from 'vue'
let items = ref((null, { length: 81 } as number[]).map((_, index) => {
return {
id: index,
number: (index % 9) + 1
}
}))
const shuffle = () => {
= _.shuffle()
}
</script>
<style scoped lang="less">
.wraps {
display: flex;
flex-wrap: wrap;
width: calc(25px * 10 + 9px);
.cell {
width: 25px;
height: 25px;
border: 1px solid #ccc;
list-style-type: none;
display: flex;
justify-content: center;
align-items: center;
}
}
.mmm-move {
transition: transform 0.8s ease;
}
</style>
<template>
<div>
<button @click="change">变</button>
<transition-group move-class="mmm" class="wraps" tag="div">
<!-- 当key值绑定index时动画不生效。原因是dom是动态的,每次都会重新渲染 -->
<div class="items" v-for="(item, index) in arr" :key="">
{{ }}
</div>
</transition-group>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from "vue";
import _ from "lodash";
let arr = ref(
(null, { length: 81 } as number[]).map((_, index) => {
return {
id: index,
num: (index % 9) + 1,
};
})
);
(arr);
// 改变排列
const change = () => {
(arr);
= _.shuffle();
(arr);
};
/**
* 1.平移动画 move-class
*/
</script>
<style>
.wraps {
display: flex;
flex-wrap: wrap;
width: calc(25px * 9 + 18px);
}
.items {
height: 25px;
width: 25px;
border: 1px solid #ccc;
}
.mmm {
transition: all 1s;
}
</style>
Vue 也同样可以给数字 Svg 背景颜色等添加过度动画 今天演示数字变化
<template>
<div>
<input step="20" v-model="" type="number" />
<div>{{ (0) }}</div>
</div>
</template>
<script setup lang='ts'>
import { reactive, watch } from 'vue'
import gsap from 'gsap'
const num = reactive({
tweenedNumber: 0,
current:0
})
watch(()=>, (newVal) => {
// to(选择器,状态对象) 是指从原有状态 变化成参数里的状态对象
(num, {
duration: 1,
tweenedNumber: newVal
})
})
</script>
<style>
</style>
依赖注入Provide / Inject
provide 可以在祖先组件中指定我们想要提供给后代组件的数据或方法,而在任何后代组件中,我们都可以使用 inject 来接收 provide 提供的数据或方法。
- 祖先组件(声明provide)
<!-- name -->
<template>
<div>
<div>爷爷<button @click="playColor">变红</button></div>
<div class="box"></div>
<A></A>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, watch, provide } from "vue";
import A from "./components/";
const color = ref<string>("pink");
const playColor = () => {
= "red";
};
provide("color", color);
/**
*
*/
</script>
<style scoped>
.box {
width: 100px;
height: 100px;
background-color: v-bind(color);
}
</style>
- 后代组件(调用inject)
<!-- 组件A -->
<template>
<div>
<div>爸爸</div>
<div class="box"></div>
<B></B>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, watch, inject } from "vue";
import B from "./";
import type { Ref } from "vue";
const color = inject<Ref<string>>("color");
/**
*
*/
</script>
<style scoped>
.box {
width: 100px;
height: 100px;
background-color: v-bind(color);
}
</style>
<!-- 组件B -->
<template>
<div>
<div>孩子</div>
<div class="box"></div>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, watch, inject } from "vue";
import type { Ref } from "vue";
const color = inject<Ref<string>>("color");
/**
*
*/
</script>
<style scoped>
.box {
width: 100px;
height: 100px;
background-color: v-bind(color);
}
</style>
后代修改祖先传的值
可以添加readonly使后代组件无法修改祖先传的值:provide(“color”, readonly(color));
- 祖先组件(声明provide)
<!-- name -->
<template>
<div>
<div>爷爷<button @click="playColor">变红</button></div>
<div class="box"></div>
<A></A>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, watch, provide } from "vue";
import A from "./components/";
const color = ref<string>("pink");
const playColor = () => {
= "red";
};
provide("color", color);
/**
*
*/
</script>
<style scoped>
.box {
width: 100px;
height: 100px;
background-color: v-bind(color);
}
</style>
- 后代组件
<!-- 后代 -->
<template>
<div>
<div>孩子</div>
<div class="box"></div>
<button @click="playColor">变回来</button>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, watch, inject } from "vue";
import type { Ref } from "vue";
const color = inject<Ref<string>>("color");
const playColor = () => {
// 添加感叹号的原因,color有string和number两种类型,进行类型断言
// 另一种解决方法是:const color = inject<Ref<string>>("color",ref("red"));
color!.value = "pink";
};
/**
*
*/
</script>
<style scoped>
.box {
width: 100px;
height: 100px;
background-color: v-bind(color);
}
</style>
兄弟组件传参
方法一:借用父组件中转
父组件
<!-- name -->
<template>
<div>
<A @bbb="Q1"></A>
<B :Q2="qqq"></B>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from "vue";
import A from "./components/";
import B from "./components/";
const qqq = ref<string>("");
const Q1 = (e: string) => {
= e;
};
/**
*
*/
</script>
<style scoped>
.box {
}
</style>
传参组件
<!-- name -->
<template>
<div></div>
</template>
<script lang="ts" setup>
import { ref, reactive, toRefs } from "vue";
const emit = defineEmits(["bbb"]);
const aaa = () => {
emit("bbb", "我来自A组件");
};
aaa();
</script>
<style scoped lang="less"></style>
接参组件
<!-- name -->
<template>
<div>{{ qqq.Q2 }}</div>
</template>
<script lang="ts" setup>
import { ref, reactive, toRefs } from "vue";
const qqq = withDefaults(
defineProps<{
Q2: string;
}>(),
{
Q2: () => "",
}
);
</script>
<style scoped lang="less"></style>
方法二:使用Event Bus
原码实现:
type BusClass = {
emit: (name: string) => void
on: (name: string, callback: Function) => void
}
type PramsKey = string | number | symbol
type List = {
[key: PramsKey]: Array<Function>
}
class Bus implements BusClass {
list: List
constructor() {
= {}
}
emit(name: string, ...args: Array<any>) {
let eventName: Array<Function> = [name]
(fn => {
(this, args)
})
}
on(name: string, callback: Function) {
// 如果之前有就直接用,如果没有就创建一个空的数组
let fn: Array<Function> = [name] || []
(callback)
[name] = fn
}
}
export default new Bus()
父组件
<!-- name -->
<template>
<div>
<A></A>
<B></B>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from "vue";
import A from "./components/";
import B from "./components/";
/**
*
*/
</script>
<style scoped>
.box {
}
</style>
兄弟组件
<!-- 发送-->
<template>
<div>
<button @click="play">发送</button>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, toRefs } from "vue";
import Bus from "../Bus";
let bb = "我来自A组件";
// 因为在 setup 语法糖中的代码最先执行, 所以监听端会在DOM创建之前就执行。但是事件发布端, 需要和点击事件绑定, 所以在 emit 可以调用回调函数。
const play = () => {
("aa", bb);
};
</script>
<style scoped lang="less"></style>
<!-- 接收-->
<template>
<div>{{ bb }}</div>
</template>
<script lang="ts" setup>
import { ref, reactive, toRefs } from "vue";
import Bus from "../Bus";
const bb = ref("");
("aa", (e: string) => {
= e;
});
</script>
<style scoped lang="less"></style>
方法二:Mitt(对于event Bus的封装)
在vue3中 o n , on, on,off 和 $once 实例方法已被移除,组件实例不再实现事件触发接口,因此大家熟悉的EventBus便无法使用了。然而我们习惯了使用EventBus,对于这种情况我们可以使用Mitt库(其实就是我们视频中讲的发布订阅模式的设计)
1.安装
npm install mitt -S
初始化
全局总线,vue 入口文件 中挂载全局属性
import { createApp } from 'vue'
import App from './'
import mitt from 'mitt'
const Mit = mitt()
//TypeScript注册
// 由于必须要拓展ComponentCustomProperties类型才能获得类型提示
declare module "vue" {
export interface ComponentCustomProperties {
$Bus: typeof Mit
}
}
const app = createApp(App)
//Vue3挂载全局API
.$Bus = Mit
('#app')
3使用方法通过emit派发, on 方法添加事件,off 方法移除,clear 清空所有
A组件派发(emit)
<template>
<div>
<h1>我是A</h1>
<button @click="emit1">emit1</button>
<button @click="emit2">emit2</button>
</div>
</template>
<script setup lang='ts'>
import { getCurrentInstance } from 'vue'
const instance = getCurrentInstance();
const emit1 = () => {
instance?.proxy?.$('on-num', 100)
}
const emit2 = () => {
instance?.proxy?.$('*****', 500)
}
</script>
<style>
</style>
B组件监听(on)
<template>
<div>
<h1>我是B</h1>
</div>
</template>
<script setup lang='ts'>
import { getCurrentInstance } from 'vue'
const instance = getCurrentInstance()
instance?.proxy?.$('on-num', (num) => {
(num,'===========>B')
})
</script>
<style>
</style>
监听所有事件( on(“*”) )
instance?.proxy?.$('*',(type,num)=>{
(type,num,'===========>B')
})
移除监听事件(off)
const Fn = (num: any) => {
(num, '===========>B')
}
instance?.proxy?.$('on-num',Fn)//listen
instance?.proxy?.$('on-num',Fn)//unListen
清空所有监听(clear)
instance?.proxy?.$()
TSX
- 安装 (装不上,下来找原因)
npm install @vitejs/plugin-vue-jsx -D
自动引入插件
- 安装
npm i -D unplugin-auto-import
- 配置 文件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
import AutoImport from 'unplugin-auto-import/vite' // 引入
// /config/
export default defineConfig({
plugins: [vue(),
// 声明
AutoImport({
imports: ['vue'], // 语言
dts: 'src/' // 配置信息位置
})],
resolve: {
alias: {
"@": resolve(__dirname, 'src'), // 路径别名
},
extensions: ['.js', '.json', '.ts'] // 使用路径别名时想要省略的后缀名,可以自己 增减
}
})
演示
<template>
<button @click="play">测试</button>
<div>{{ cs }}</div>
</template>
<script lang="ts" setup>
const play = () => {
= !;
};
const cs = ref<boolean>(false);
</script>
v-model
其实是一个语法糖,由props与emit组合而成
1.默认值的改变
prop:value -> modelValue;
事件:input -> update:modelValue;
v-bind 的 .sync 修饰符和组件的 model 选项已移除
新增 支持多个v-model
新增 支持自定义 修饰符 Modifiers
单v-model
父子组件传值
<!-- 父组件-->
<template>
<div>父组件</div>
<div>
<button @click="play">开关 -- {{ aaa }}</button>
</div>
<VModel v-model="aaa"></VModel>
</template>
<script lang="ts" setup>
import VModel from "@/components/";
const aaa = ref<boolean>(true);
const play = () => {
= !;
};
</script>
<style scoped></style>
<!-- 子组件-->
<template>
<div>我是子组件</div>
<div><button @click="play">子组件开关</button></div>
<div v-if="modelValue">
<input type="text" />
</div>
</template>
<script lang="ts" setup>
defineProps({
modelValue: {
type: Boolean,
default: true,
},
});
const emit = defineEmits(["update:modelValue"]);
const play = () => {
emit("update:modelValue", false);
};
</script>
<style scoped lang="less"></style>
多个v-model
<template>
<div>父组件</div>
<div>
<button @click="play">开关 -- {{ aaa }} -- {{ bbb }}</button>
</div>
<VModel v-model="aaa" v-model:two="bbb"></VModel>
</template>
<script lang="ts" setup>
import VModel from "@/components/";
const aaa = ref<boolean>(true);
const bbb = ref<string>("hello world!");
const play = () => {
= !;
};
</script>
<style scoped></style>
<template>
<div>我是子组件</div>
<div><button @click="play">子组件开关</button></div>
<div v-if="modelValue">
<input type="text" @input="change" :value="two" />
</div>
</template>
<script lang="ts" setup>
defineProps({
modelValue: {
type: Boolean,
default: true,
},
two: {
type: String,
default: "",
},
});
// 声明
// const;
const emit = defineEmits(["update:modelValue", "update:two"]);
// end
const play = () => {
emit("update:modelValue", false);
};
// 传给父组件
const change = (e: Event) => {
let target = as HTMLInputElement;
emit("update:two", );
};
// end
</script>
<style scoped lang="less"></style>
自定义修饰符
添加到组件 v-model 的修饰符将通过 modelModifiers prop 提供给组件。在下面的示例中,我们创建了一个组件,其中包含默认为空对象的 modelModifiers prop
<script setup lang='ts'>
type Props = {
modelValue: boolean,
title?: string,
modelModifiers?: {
default: () => {}
}
titleModifiers?: {
default: () => {}
}
}
const propData = defineProps<Props>()
const emit = defineEmits(['update:modelValue', 'update:title'])
const close = () => {
();
emit('update:modelValue', false)
emit('update:title', '我要改变')
}
directive-自定义指令(属于破坏性更新)
Vue中有v-if,v-for,v-bind,v-show,v-model 等等一系列方便快捷的指令 今天一起来了解一下vue里提供的自定义指令
Vue3指令的钩子函数
created 元素初始化的时候
beforeMount 指令绑定到元素后调用 只调用一次
mounted 元素插入父级dom调用
beforeUpdate 元素被更新之前调用
update 这个周期方法被移除 改用updated
beforeUnmount 在元素被移除前调用
unmounted 指令被移除后调用 只调用一次
- 页面加载触发:created beforeMount Mounted
- 出现触发:created beforeMount Mounted
- 关闭触发:beforeUnmount unmounted
- 修改触发:beforeUpdate updated
注:子组件最外层只能有一个div
mounted(...arr: Array<any>) {
("初始化完成");
(arr);
/**
* arr
* 第一个值指的是当前绑定指令元素
* 第二个值:dir ==> 传的东西都放在里面
* 第三个值:vNode ==> 当前组件的虚拟Dom
* 第四个值:prevVNode ==> 上一个组件的虚拟Dom
*/
},
<template>
<div>父组件</div>
<div>
<button @click="on_off = !on_off">开关</button>
<button @click="text = 'hello world!'">修改</button>
</div>
<!--
{ background: 'red' } // 传值
aaa // 自定义参数
bbb // 自定义修饰符
注:传递的值在每一个钩子函数中都可以看到
-->
<A v-moveDirective:="{ background: 'red' }"></A>
</template>
<script lang="ts" setup>
import { Directive, DirectiveBinding } from "vue";
import A from "./components/";
type Dir = { background: string;};
const text = ref<string>("Directive");
const on_off = ref<boolean>(true);
// 指令生命周期
const vMoveDirective: Directive = {
created() {("初始化");},
beforeMount() {("初始化一次前");},
mounted(el: HTMLElement, dir: DirectiveBinding<Dir>) {
("初始化完成");
(el);
();
= ;
},
beforeUpdate() {("修改前");},
updated() {("修改完成");},
beforeUnmount() {("关闭前");},
unmounted() {("关闭");},
};
// end
</script>
函数简写
只在 mounted 和 updated 时触发
<template>
<div>
<input v-model="value" type="text" />
<A v-move="{ background: value }"></A>
</div>
</template>
<script setup lang='ts'>
import A from './components/'
import { ref, Directive, DirectiveBinding } from 'vue'
let value = ref<string>('')
type Dir = {
background: string
}
const vMove: Directive = (el, binding: DirectiveBinding<Dir>) => {
=
}
</script>
练习
思路:监听鼠标按下的事件,当鼠标按下后监听鼠标移动,并动态改变元素位置,最后鼠标松开关闭事件
<!-- name -->
<template>
<div style="position: relative; height: 100vh; width: 100vw">
<div
v-move
style="
position: absolute;
width: 100px;
height: 100px;
background-color: pink;
"
>
父组件
</div>
</div>
</template>
<script lang="ts" setup>
import { Directive, DirectiveBinding } from "vue";
// addEventListener() 方法用于向指定元素添加事件句柄。
// MouseEvent接口指用户与指针设备(如鼠标)交互时发生的事件。
// mousedown在元素上按下任意鼠标按键。
// mouseup在元素上释放任意鼠标按键。
// mousemove指针在元素内移动时持续触发。
// click在元素上按下并释放任意鼠标按键。
const vMove: Directive<any, void> = (
el: HTMLElement,
bingding: DirectiveBinding
) => {
let box: HTMLElement = el as HTMLDivElement;
const mouseDown = (e: MouseEvent) => {
let X = - ;
let Y = - ;
const move = (e: MouseEvent) => {
= - X + "px";
= - Y + "px";
};
("mousemove", move);
("mouseup", () => {
("mousemove", move);
});
};
("mousedown", mouseDown);
};
</script>
<style scoped></style>
自定义的hook
Vue3 的 hook函数 相当于 vue2 的 mixin, 不同在与 hooks 是函数
Vue3 的 hook函数 可以帮助我们提高代码的复用性, 让我们能在不同的组件中都利用 hooks 函数
Vue3 hook 库Get Started | VueUse : /guide/
案例
import { onMounted } from 'vue'
type Options = {
el: string
}
type Return = {
Baseurl: string | null
}
export default function (option: Options): Promise<Return> {
return new Promise((resolve) => {
onMounted(() => {
const file: HTMLImageElement = () as HTMLImageElement;
= ():void => {
resolve({
Baseurl: toBase64(file)
})
}
})
const toBase64 = (el: HTMLImageElement): string => {
const canvas: HTMLCanvasElement = ('canvas')
const ctx = ('2d') as CanvasRenderingContext2D
=
=
(el, 0, 0, ,)
();
return ('image/png')
}
})
}
自定义全局函数和变量
由于Vue3 没有Prototype 属性 使用 代替 然后去定义变量和函数
在 下写入,如:
.$env = '123'
.$env2 = {
name<T>(str: T) {
return '我叫' + str
}
}
type Filter = {
name<T>(str: T): string
}
// 声明要扩充@vue/runtime-core包的声明.
// 这里扩充"ComponentCustomProperties"接口, 因为他是vue3中实例的属性的类型.
declare module 'vue' {
export interface ComponentCustomProperties {
$env: string
$env2: Filter
}
}
在setup中使用
方式一:
import { getCurrentInstance, ComponentInternalInstance } from 'vue';
const { appContext } = <ComponentInternalInstance>getCurrentInstance()
(.$env);
推荐 方式二:
import {ref,reactive,getCurrentInstance} from 'vue'
const app = getCurrentInstance()
(app?.proxy?.$('李四'))
自定义插件
插件
插件是自包含的代码,通常向 Vue 添加全局级功能。你如果是一个对象需要有install方法Vue会帮你自动注入到install 方法 你如果是function 就直接当install 方法去使用
使用插件
在使用 createApp() 初始化 Vue 应用程序后,你可以通过调用 use() 方法将插件添加到你的应用程序中。
实现一个Loading插件
components/
<template>
<div
v-if="isShow"
style="width: 100vw; height: 100vh; background-color: #666; position: fixed;top: 0;"
>
loading
</div>
</template>
<script setup lang='ts'>
import { ref } from 'vue';
const isShow = ref(false)//定位loading 的开关
const show = () => {
= true
}
const hide = () => {
= false
}
//对外暴露 当前组件的属性和方法
defineExpose({
isShow,
show,
hide
})
</script>
components/
import { createVNode, render, VNode, App } from 'vue';
import Loading from './'
export default {
install(app: App) {
//createVNode vue提供的底层方法 可以给我们组件创建一个虚拟DOM 也就是Vnode
const vnode: VNode = createVNode(Loading)
//render 把我们的Vnode 生成真实DOM 并且挂载到指定节点
render(vnode, )
// Vue 提供的全局配置 可以自定义
.$loading = {
show: () => ?.exposed?.show(),
hide: () => ?.exposed?.hide()
}
}
}
import Loading from './components/loading'
let app = createApp(App)
(Loading)
type Lod = {
show: () => void,
hide: () => void
}
//编写ts loading 声明文件放置报错 和 智能提示
declare module '@vue/runtime-core' {
export interface ComponentCustomProperties {
$loading: Lod
}
}
('#app')
使用
<template>
<div></div>
</template>
<script setup lang='ts'>
import { ref,reactive,getCurrentInstance} from 'vue'
const instance = getCurrentInstance()
instance?.proxy?.$()
setTimeout(()=>{
instance?.proxy?.$()
},5000)
// (instance)
</script>
<style>
*{
padding: 0;
margin: 0;
}
</style>
样式穿透 /deep/ 和 :deep
vue3推荐:deep
插槽选择器
A 组件定义一个插槽
<template>
<div>
我是插槽
<slot></slot>
</div>
</template>
<style scoped>
:slotted(.a) { // 修改了文件下的样式
color:red
}
</style>
在 引入
<template>
<div>
<A>
<div class="a">私人定制div</div>
</A>
</div>
</template>
<script setup>
import A from "@/components/"
</script>
全局选择器
<style lang="less" scoped>
:global(div){
color:red
}
</style>
css Style完整新特性
动态 CSS
单文件组件的
<template>
<div class="div">
小满是个弟弟
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const red = ref<string>('red')
</script>
<style lang="less" scoped>
.div{
color:v-bind(red)
}
</style>
如果是对象 v-bind 请加引号
<template>
<div class="div">
小满是个弟弟
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue"
const red = ref({
color:'pink'
})
</script>
<style lang="less" scoped>
.div {
color: v-bind('');
}
</style>
module
自定义注入名称(多个可以用数组)
你可以通过给 module attribute 一个值来自定义注入的类对象的 property 键
<template>
<div :class="[,]">
小满是个弟弟
</div>
</template>
<style module="zs">
.red {
color: red;
font-size: 20px;
}
.border{
border: 1px solid #ccc;
}
</style>
与组合式 API 一同使用
注入的类可以通过 useCssModule API 在 setup() 和
<template>
<div :class="[,]">
小满是个弟弟
</div>
</template>
<script setup lang="ts">
import { useCssModule } from 'vue'
const css = useCssModule('zs')
</script>
<style module="zs">
.red {
color: red;
font-size: 20px;
}
.border{
border: 1px solid #ccc;
}
</style>
使用场景一般用于TSX 和 render 函数 居多
Vue3集成Tailwind CSS
Tailwind CSS中文官网地址:/
安装学习地址:/jrQvZ
Event Loop
JS 执行机制
在我们学js 的时候都知道js 是单线程的如果是多线程的话会引发一个问题在同一时间同时操作DOM 一个增加一个删除JS就不知道到底要干嘛了,所以这个语言是单线程的但是随着HTML5到来js也支持了多线程webWorker 但是也是不允许操作DOM
单线程就意味着所有的任务都需要排队,后面的任务需要等前面的任务执行完才能执行,如果前面的任务耗时过长,后面的任务就需要一直等,一些从用户角度上不需要等待的任务就会一直等待,这个从体验角度上来讲是不可接受的,所以JS中就出现了异步的概念。
同步任务
代码从上到下按顺序执行
异步任务
1.宏任务
script(整体代码)、setTimeout、setInterval、UI交互事件、postMessage、Ajax
2.微任务
catch finally、MutaionObserver、( 环境)
运行机制
所有的同步任务都是在主进程执行的形成一个执行栈,主线程之外,还存在一个"任务队列",异步任务执行队列中先执行宏任务,然后清空当次宏任务中的所有微任务,然后进行下一个tick如此形成循环。
next
nextTick 就是创建一个异步任务,那么它自然要等到同步任务执行完成后才执行。
<template>
<div ref="xiaoman">
{{ text }}
</div>
<button @click="change">change div</button>
</template>
<script setup lang='ts'>
import { ref,nextTick } from 'vue';
const text = ref('小满开飞机')
const xiaoman = ref<HTMLElement>()
const change = async () => {
= '小满不开飞机'
(?.innerText) //小满开飞机
await nextTick();
(?.innerText) //小满不开飞机
}
</script>
unocss原子化
参考:/AkrHY
函数式编程,h函数
参考:/tD9Nc
Vue开发桌面程序Electron
ps:打包后白屏,无法解决。爆炸…
开发桌面程序
创建一个vite 项目
npm init vite@latest
安装electron
npm install electron -D
npm install vite-plugin-electron -D
根目录新建 electron /
修改 配置文件
引入electron插件配置main entry对应electron的文件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import electron from 'vite-plugin-electron'
// /config/
export default defineConfig({
plugins: [vue(), electron({
// 这里需要调整main对象,vite-plugin-electron 0.10.4中使用main会报错。
// main: {
// entry: "electron/"
// }
entry: "electron/"
})],
})
编写代码 electron /
import { app, BrowserWindow } from 'electron'
import path from 'path'
//app 控制应用程序的事件生命周期。
//BrowserWindow 创建并控制浏览器窗口。
let win: BrowserWindow | null;
//定义全局变量获取 窗口实例
const createWindow = () => {
win = new BrowserWindow({
//
webPreferences: {
devTools: true,
contextIsolation: false,
nodeIntegration: true
//允许html页面上的javascipt代码访问nodejs 环境api代码的能力(与node集成的意思)
}
})
if () {
((__dirname, "../"));
} else {
//VITE_DEV_SERVER_HOST 如果是undefined 换成 VITE_DEV_SERVER_HOSTNAME
(`${['VITE_DEV_SERVER_URL']}`);
}
}
//isPackage 不好使换下面的
// if(.NODE_ENV != 'development'){
// ((__dirname, "../"));
// }else{
//(`${['VITE_DEV_SERVER_URL']}`);}`)
// }
//在Electron完成初始化时被触发
().then(createWindow)
配置package json 增加main 字段 type 去掉
{
"name": "my_vite",
"private": true,
"version": "0.0.0",
"main": "dist-electron/",
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build && electron-builder",
"preview": "vite preview"
},
"dependencies": {
"cross-env": "^7.0.3",
"vue": "^3.2.45"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.0.0",
"electron": "^22.0.0",
"electron-builder": "^23.6.0",
"typescript": "^4.9.3",
"vite": "^4.0.0",
"vite-plugin-electron": "^0.10.4",
"vue-tsc": "^1.0.11"
},
"build": {
"appId": "",
"productName": "electron",
"asar": true,
"copyright": "Copyright © 2022 electron",
"directories": {
"output": "release/"
},
"files": [
"dist-electron"
],
"mac": {
"artifactName": "${productName}_${version}.${ext}",
"target": [
"dmg"
]
},
"win": {
"target": [
{
"target": "nsis",
"arch": [
"x64"
]
}
],
"artifactName": "${productName}_${version}.${ext}"
},
"nsis": {
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": true,
"deleteAppDataOnUninstall": false
},
"publish": [
{
"provider": "generic",
"url": "http://127.0.0.1:8080"
}
],
"releaseInfo": {
"releaseNotes": "版本更新的具体内容"
}
}
}
npm run dev
打包Electron
需要安装electron-builder
npm install electron-builder -D
package json 配置 build 修改npm run build命令
"build": "vue-tsc --noEmit && vite build && electron-builder",
"build": {
"appId": "",
"productName": "electron",
"asar": true,
"copyright": "Copyright © 2022 electron",
"directories": {
"output": "release/"
},
"files": [
"dist"
],
"mac": {
"artifactName": "${productName}_${version}.${ext}",
"target": [
"dmg"
]
},
"win": {
"target": [
{
"target": "nsis",
"arch": [
"x64"
]
}
],
"artifactName": "${productName}_${version}.${ext}"
},
"nsis": {
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": true,
"deleteAppDataOnUninstall": false
},
"publish": [
{
"provider": "generic",
"url": "http://127.0.0.1:8080"
}
],
"releaseInfo": {
"releaseNotes": "版本更新的具体内容"
}
}
npm run build
Vue响应性语法糖
注:将来可能有用
参考:/EP0wW
环境变量
- 查看项目的环境变量
// 查看环境变量
();
/**
* BASE_URL: "/" 路由应用前缀
*
* DEV: true // 是否开发环境
*
* MODE: "development" // 当前运行环境
*
* PROD: false // 生否生产环境
*
* SSR: false // 是否服务端渲染
*/
- 配置开发环境:
在根目录下创建.文件
注意:.env后可以是任意名称,只是通常情况下都叫development
// 在.里写入
VITE_HTTP=
// 在的dev里添加:
// --mode 配置开发环境的文件名
// 现在已经默认读取了
"scripts": {
"dev": "vite --mode development",
"build": "vue-tsc && vite build",
"preview": "vite preview"
},
- 配置生产环境:
在根目录下创建.文件
注意:.env后可以是任意名称,只是通常情况下都叫production
// 在.里写入
VITE_HTTP=
// 默认读取production。不需要配置
- 查看打包文件
打开dist文件夹:
安装:npm i http-serve -g
输入:http-server -p 9002
- 在vite中获取环境变量
// 引入
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
// /config/
export default ({ mode }: any) => {
(loadEnv(mode, ));
return defineConfig({
plugins: [vue()],
})
}
webpack 构建 Vue3项目
为什么要手写webpack 不用cli (脑子有病)并不是 其实是为了加深我们对webpack 的了解方便以后灵活运用webpack 的技术
参考:/Alf9Q
Vue3 性能优化
性能优化
我们可以使用谷歌浏览器自带的DevTools 进行性能分析 LightHouse
参数介绍
选项代表意思:
FCP (First Contentful Paint):首次内容绘制的时间,浏览器第一次绘制DOM相关的内容,也是用户第一次看到页面内容的时间。
Speed Index: 页面各个可见部分的显示平均时间,当我们的页面上存在轮播图或者需要从后端获取内容加载时,这个数据会被影响到。
LCP (Largest Contentful Paint):最大内容绘制时间,页面最大的元素绘制完成的时间。
TTI(Time to Interactive):从页面开始渲染到用户可以与页面进行交互的时间,内容必须渲染完毕,交互元素绑定的事件已经注册完成。
TBT(Total Blocking Time):记录了首次内容绘制到用户可交互之间的时间,这段时间内,主进程被阻塞,会阻碍用户的交互,页面点击无反应。 CLS(Cumulative Layout Shift):计算布局偏移值得分,会比较两次渲染帧的内容偏移情况,可能导致用户想点击A按钮,但下一帧中,A按钮被挤到旁边,导致用户实际点击了B按钮。
代码分析
由于我们使用的是vite vite打包是基于rollup 的我们可以使用 rollup 的插件
npm install rollup-plugin-visualizer
配置 记得设置open 不然无效
import { visualizer } from 'rollup-plugin-visualizer';
plugins: [vue(), vueJsx(),visualizer({
open:true
})],
然后进行npm run build打包
Vite 配置优化
build:{
chunkSizeWarningLimit:2000,
cssCodeSplit:true, //css 拆分
sourcemap:false, //不生成sourcemap
minify:false, //是否禁用最小化混淆,esbuild打包速度最快,terser打包体积最小。
assetsInlineLimit:5000 //小于该值 图片将打包成Base64
},
PWA离线存储技术
npm install vite-plugin-pwa -D
修改
import { VitePWA } from 'vite-plugin-pwa'
plugins: [vue(),VitePWA(
workbox:{
cacheId:"XIaoman",//缓存名称
runtimeCaching:[
{
urlPattern:/.*\.js.*/, //缓存文件
handler:"StaleWhileRevalidate", //重新验证时失效
options:{
cacheName:"XiaoMan-js", //缓存js,名称
expiration:{
maxEntries:30, //缓存文件数量 LRU算法
maxAgeSeconds:30 * 24 * 60 * 60 //缓存有效期
}
}
}
]
},
)],
PWA 技术的出现就是让web网页无限接近于Native 应用
可以添加到主屏幕,利用manifest实现
可以实现离线缓存,利用service worker实现
可以发送通知,利用service worker实现
进行 npm run build 打包会生成
其他性能优化
- 图片懒加载
import lazyPlugin from 'vue3-lazy'
<img v-lazy="" >
- 虚拟列表
当后台一次返回大量列表数据时,可以使用虚拟列表。
其实际原理是,在可视化区域展示Dom
element-plus已集成 - 多线程 使用 new Worker 创建
worker脚本与主进程的脚本必须遵守同源限制。他们所在的路径协议、域名、端口号三者需要相同
const myWorker1 = new Worker("./");
都使用postMessage发送消息
(arrayBuffer, [arrayBuffer]);
都使用onmessage接收消息
= function (e) {
// xxx这里是worker脚本的内容
};
关闭
();
VueUse 库已经集成了 webWorker
- 防抖节流
同样VueUse 也是集成了
Vue3 Web Components
Web Components 提供了基于原生支持的、对视图层的封装能力,可以让单个组件相关的 javaScript、css、html模板运行在以html标签为界限的局部环境中,不会影响到全局,组件间也不会相互影响 。 再简单来说:就是提供了我们自定义标签的能力,并且提供了标签内完整的生命周期 。
参考:/qq1195566313/article/details/127328300