寒假学的时候属于是有点过于敷衍了,现在update一下😮💨
最近是跟ts一起学的,所以里面会有ts相关的内容(无伤大雅无伤大雅
内容
1. 自定义hooks
1.1 介绍及使用
hook本质上是一个函数
作用是将setup
函数中使用的Composition API
(ref、reactive、computed、watch、生命周期)进行了封装
一般使用:
- 将可复用功能抽离为外部js文件
- 函数名文件名以
use
开头,形如useXXX
- 引用时,在setup中解构出自定义hooks的变量和方法
如:const { name } = useXXX()
在A组件中想要实现一个小功能:鼠标触摸页面的时候,能够拿到鼠标的坐标并显示在页面上
<!-- A.vue -->
<script setup lang="ts">
import { reactive, onMounted, onBeforeUnmount } from "vue";
// 数据
let point = reactive({
x: 0,
y: 0,
})
// 方法
const savePoint = (e: MouseEvent) => {
point.x = e.pageX;
point.y = e.pageY;
}
// 生命周期钩子
onMounted(() => {
window.addEventListener("mousemove", savePoint);
})
onBeforeUnmount(() => {
window.removeEventListener("mousemove", savePoint);
})
</script>
如果其他组件也想使用这个功能,我们可以把关于这个功能的数据、方法、生命周期钩子都提出去封装起来
新建一个文件夹hooks
,新建一个文件usePoint.ts
// usePoint.ts
import {reactive,onMounted,onBeforeUnmount,toRefs} from 'vue';
export default function () {
// 数据
let point = reactive({
x: 0,
y: 0
})
// 方法
const savePoint = (e: MouseEvent) => {
point.x = e.pageX;
point.y = e.pageY;
}
// 生命周期钩子
onMounted(() => {
window.addEventListener('mousemove',savePoint)
})
onBeforeUnmount(() => {
window.removeEventListener('mousemove',savePoint)
})
// 这里用toRefs,方便引入时解构
return toRefs(point);
}
修改A组件中的代码
<script setup lang="ts">
import usePoint from "../hooks/usePoint";
const { x, y } = usePoint();
</script>
<template>
<h1>{{ x }}, {{ y }}</h1>
</template>
1.2 VueUse
VueUse
是vue3的一个hook库,帮我们封装了一些常用的功能
还有很多,可以去官网看:
https://vueuse.org/guide/
安装:
npm i @vueuse/core
使用:
跟我们自己封装的hook使用方法是一样的
这里也是使用了一个VueUse中获取鼠标坐标的hook
<script setup lang="ts">
import { useMouse } from '@vueuse/core'
const { x, y } = useMouse()
</script>
<template>
<h1>{{ x }}, {{ y }}</h1>
</template>
1.3 对比vue2Mixin
Minxin
的不足:
1.mixin
容易发生冲突,每个mixin
的property
都被合并到同一个组件中,难以追溯其到底来自哪个mixin
export default {
mixins: [a,b,c,d,e], // 一个组件中可以混入各种功能的mixin
mounted() {
console.log(this.name); // 这个name来自哪个mixin?
}
}
但在vue3自定义hooks
中:const { name } = useX()
, 就很清晰可以知道来自哪个hook
,便于维护管理
2. 无法给mixin
传递参数来改变逻辑
可以通过调用mixin
内部方法来传递参数,却无法直接给Mixin
传递参数
export default {
mixins: [addMixin], // 一个组件中可以混入各种功能的mixin
mounted() {
this.add(num1, num2) //调用addMixin内部的add方法
}
}
vue3自定义hooks
可以灵活传递任何参数来改变它的逻辑
形象的讲法:vue3自定义hooks是组件下的函数作用域,vue2mixin是组件下的全局作用域,全局作用域有时是不可控的
2. globalProperties
在vue2.x中我们定义全局变量和函数的时候是把他们加在Vue.prototype
上
vue3.x中要用app.config.globalProperties
代替
// vue2.x
Vue.prototype.$api = () => {}
// vue3.x
const app = createApp({})
app.config.globalProperties.$api = () => {}
使用:
<script setup lang="ts">
import { getCurrentInstance } from 'vue'
const instance = getCurrentInstance();
instance?.appContext.config.globalProperties.$api.xxx
// 也可以这样用:instance?.proxy?.$api.xxx
</script>
⚠️:getCurrentInstance()
必须在setup
或生命周期钩子
中调用,在其他地方调用会为null
⚠️:如果是在模板中用,双括号中可以直接访问挂载在全局的属性,不用getCurrentInstance
了
3. filters(vue3已移除)
(过滤器filters在vue3中已经被移除了,下面介绍一下vue3中如何替代过滤器)
在vue2.x中,过滤器一般用于一些常见的文本格式化,可以放在双花括号内或者v-bind表达式中
简单复习一下:(其实也没什么必要只是我之前还真没用过这个😢
<template>
<!-- account经accountInUSD过滤器过滤 -->
<!-- 相当于 {{ accountInUSD(account) }} -->
<p>{{ account | accountInUSD }}</p>
</template>
<script>
export default {
props: {
account: {
type: Number
}
},
filters: {
accountInUSD(value) {
return '$' + value
}
}
}
</script>
3.1 局部过滤器
官方推荐局部过滤器可以用方法调用
或者计算属性
来替换
计算属性怎么写这里就略过了,如何传值看我这篇博客:
vue 计算属性传参数
3.2 全局过滤器
那如果是全局过滤器,用计算属性或方法调用就不太方便了,就可以通过👆说过的全局属性globalProperties
让所有组件能使用到
// main.ts
const app = createApp(App)
interface IFilter {
accountInUSD: (value: number) => string
}
// 不声明用的时候ts会报错,声明之后在模板中用$filters会有提示
declare module 'vue' {
export interface ComponentCustomProperties {
$filters: IFilter
}
}
// 这个$filters对象里可以定义多个函数
app.config.globalProperties.$filters = {
accountInUSD(value: number): string {
return '$' + value
}
}
然后在模板上用这个$filters
对象:
<template>
<p>{{ $filters.accountInUSD(account) }}</p>
</template>
4. 自定义插件
4.1 简单了解一下
如果插件是一个对象
,必须提供install()
方法
如果插件是一个函数
,它会被作为install()
方法
install()
方法调用时,会将Vue作为参数传入
使用插件:app.use(xxx)
4.2 loading案例
案例:实现一个loading插件
1.components
文件夹下新建一个loading
文件夹,下面再建index.vue
、index.ts
2./loading/index.vue
<!-- index.vue -->
<script setup lang="ts">
import { ref } from "vue";
const isShow = ref<boolean>(false);
const show = () => {
isShow.value = true;
};
const hide = () => {
isShow.value = false;
};
// 这里注意属性和方法一定要暴露出去
defineExpose({
isShow,
show,
hide,
});
</script>
<template>
<div v-if="isShow" class="loading">
<div class="loading-content">Loading...</div>
</div>
</template>
<style lang="less" scoped>
.loading {
position: fixed;
background-color: #000;
display: flex;
justify-content: center;
align-items: center;
&-content {
font-size: 30px;
color: #fff;
}
}
</style>
3./loading/index.ts
import { App, createVNode, VNode, render } from 'vue'
import Loading from './index.vue'
// 这里我们将参数定为一个对象,这个对象必须有install方法,参数是vue
export default {
install (app: App) {
// 给组件创建一个虚拟dom
const vnode:VNode = createVNode(Loading)
// 用render函数转成真实dom,第二个参数是挂载在哪个节点
render(vnode, document.body)
// show()和hide()挂载在全局
app.config.globalProperties.$loading = {
show: vnode.component?.exposed?.show,
hide: vnode.component?.exposed?.hide
}
}
}
4.main.ts
中使用Loading插件,并写$loading
的声明
import { createApp } from 'vue'
import App from './App.vue'
import Loading from './components/loading'
const app = createApp(App)
app.use(Loading)
declare module 'vue' {
export interface ComponentCustomProperties {
$loading: {
show: () => void,
hide: () => void
}
}
}
5.在某个组件中使用
<script setup lang="ts">
import { getCurrentInstance } from "vue";
const instance = getCurrentInstance();
const showLoading = () => {
instance?.appContext.config.globalProperties.$loading.show();
};
const hideLoading = () => {
instance?.appContext.config.globalProperties.$loading.hide();
}
</script>
<template>
<button @click="showLoading">show loading</button>
<button @click="hideLoading">hide loading</button>
</template>
5. 样式穿透:deep()
主要用于修改常用的组件库,有些组件库的样式是需要我们改的,但是用了scoped之后常常发现样式加不上去
举个例子:
<script setup lang="ts">
import { ref } from "vue";
const input = ref("");
</script>
<template>
<el-input v-model="input" placeholder="Please input" class="my-input" />
</template>
<style lang="less" scoped>
.my-input {
.el-input__inner {
background-color: red;
}
}
</style>
上面这一段样式,没有生效
但是我们的这个样式实际上是加上了的,是因为scoped会在css语句末尾给我们加上属性选择器[data-v-xxx],但是这个.el-input__inner
上面并没有这个属性选择器,这个属性选择器在它的父级.my-input
身上
在vue2中,解决方法:用/deep/
修改属性选择器的位置
<style lang="less" scoped>
.my-input {
/deep/ .el-input__inner {
background-color: red;
}
}
</style>
这样把属性选择器换到.my-input
后面,而.my-input
后面是有属性选择器的,就可以生效了
在vue3中推荐使用:deep()
:
<style lang="less" scoped>
.my-input {
:deep(.el-input__inner) {
background-color: red;
}
}
</style>
6. :slotted()、:global()、:v-bind()
6.1 插槽选择器:slotted()
举例:
子组件A中定义一个插槽,在父组件App中引入
<!-- A.vue -->
<template>
<slot></slot>
</template>
<style lang="less" scoped>
h1 {
color: red;
}
</style>
<!-- App.vue -->
<template>
<A>
<h1>AAAA</h1>
</A>
</template>
看下效果:
会发现在A组件中设置的样式未生效
(其实还是scoped的锅
解决方法:在A组件中使用插槽选择器
:slotted(h1) {
color: red;
}
6.2 全局选择器:global()
之前我们想要加入全局样式,通常都是新建一个不加scoped的style标签
<!-- App.vue -->
<style>
h1 {
color: red
}
</style>
<style lang="less" scoped>
</style>
现在可以用更优雅的解决方式:全局选择器:global()
<!-- App.vue -->
<style lang="less" scoped>
:global(h1) {
color: red
}
</style>
6.3 动态css v-bind()
<script lang="ts" setup>
import { ref } from 'vue'
const red = ref<string>('red')
</script>
<template>
<div class="box">xxxxxx</div>
</template>
<style lang="less" scoped>
.box {
color:v-bind(red)
}
</style>
如果是对象,要加引号:
<script lang="ts" setup>
import { ref } from 'vue'
const red = ref({
color: 'red'
})
</script>
<template>
<div class="box">xxxxxx</div>
</template>
<style lang="less" scoped>
.box {
color:v-bind('red.color')
}
</style>
7. nextTick
更改了一些数据后,需要等dom更新完再执行的操作放在await nextTick
后面,(不要忘记包裹在async函数里
<script setup lang='ts'>
import { ref,nextTick } from 'vue';
const text = ref<string>('poem')
const box = ref<HTMLElement>()
const change = async () => {
// 更改了一些数据
text.value = 'change poem'
// dom还未更新就会执行这句话
console.log(box.value?.innerText) // poem
await nextTick();
// 现在dom更新了
console.log(box.value?.innerText) // change poem
}
</script>
<template>
<div ref="box">
{{ text }}
</div>
<button @click="change">change box</button>
</template>
8. 移动端适配解决方案
postcss-pxtorem
+amfe-flexable
postcss-px-to-viewport
8.1 postcss-pxtorem+amfe-flexable
amfe-flexable
:
能根据设备的宽高来设置html的font-size,将1rem设置为设备宽度➗10
并且页面宽度变化时会自动重新计算html的font-size
postcss-pxtorem
:
将对应的像素单位转换为rem
安装:
npm i postcss-pxtorem -D
npm i amfe-flexible -D
在main.ts
中引入amfe-flexible
import 'amfe-flexible'
在vite.config.ts
中配置postcss-pxtorem
:
import { defineConfig } from 'vite'
import postCssPxToRem from 'postcss-pxtorem'
export default defineConfig({
...
css: {
postcss: {
plugins: [
postCssPxToRem({
rootValue: 37.5, // 1rem的大小
propList: [*] // 需要转换的属性,全部转换
})
]
}
}
})
8.2 postcss-px-to-viewport
postcss-px-to-viewport
会将对应的像素单位自动转换为视口单位
安装:
npm i postcss-px-to-viewport -D
在vite.config.ts
中配置postcss-pxtorem
:
import { defineConfig } from 'vite'
import postCssPxToViewport from 'postcss-pxtorem'
export default defineConfig({
...
css: {
postcss: {
plugins: [
postCssPxToViewport({
unitToConvert: 'px', // 要转化的单位
viewportWidth: 750, // 设计稿的宽度
})
]
}
}
})
参考博文:
https://juejin.cn/post/7083401842733875208
https://xiaoman.blog.csdn.net/article/details/125237755
https://blog.csdn.net/weixin_43554584/article/details/121345084?utm_source=app&app_version=5.0.0