shallowRef
是 Vue 3 中新引入的响应式数据类型之一,它与 ref 类型非常相似,但是有一些不同点。
不同的是,shallowRef
只会对其包装的对象进行浅层次的响应式处理,即如果这个对象的子属性发生改变,那么这个改变不会被响应到视图中。
<template>
<div>{{ Man }}</div>
<button @click="change">修改</button>
</template>
<script setup lang="ts">
import { shallowRef } from "vue";
const Man = shallowRef({ name: "小4" });
const change = () => {
Man.value.name = "小5";
console.log(Man);
};
</script>
<style lang="scss" scoped></style>
需要注意的是,如果响应函数里有Ref,则shallowRef也会变成深响应式,浅响应式shallowRef
会被深响应式ref
影响。
shallowRef
的应用场景一般是在某些需要保持数据响应式特性的情况下使用,但同时也要避免做出过多的响应式开销。比如:
可以强制更新浅响应式的数据
具体来说,triggerRef 接收一个 ref 对象,并立即触发它的 setter 函数来更新该 ref 的值。这条更新操作会自动触发组件重新渲染,从而达到实时更新视图的效果。
let message: Ref<Obj> = shallowRef({
name: "小5"
})
const changeMsg = () => {
message.value.name = '大5'
// 浅响应变深响应
triggerRef(message)
}
triggerRef
的用途比较灵活,可以在多种场景下使用。例如:
ref
对象设置监听器,在变化时使用triggerRef
强制更新。ref
对象中,在异步任务完成后使用triggerRef
更新数据。需要注意的是,triggerRef
虽然可以强制触发视图更新,但是不建议频繁使用。过度使用会导致性能问题和代码可读性下降。通常情况下,Vue 的响应式系统会自动检测数据变化并触发更新,只有在必要的情况下才考虑手动触发。
深响应式ref
底层会自动调用triggerRef
,因此要实现浅响应式,不能把深响应式放在一块。
customRef
用于创造一个自定义的ref
类型。与ref
及其变体如 shallowRef
不同,customRef
允许我们自己决定如何处理设置(ref.value)并得到(ref.value)读取操作。
具体地说,customRef
接收一个工厂函数 (track
, trigger
),该函数需要返回一个对象,该对象包含了 get
和 set
两个方法。这两个方法的作用分别对于 ref.value
的读取和更新操作,可以自行定义它们的实现逻辑。同时,还需要注意在读取或者更新值时手动调用 track
和 trigger
函数,以便进行依赖追踪和触发更新:
import { customRef } from 'vue'
const myCustomRef = customRef((track, trigger) => {
let value = 0
return {
get() {
// 依赖追踪
track()
return value
},
set(newValue) {
// 更新值
value = newValue
// 触发组件重新渲染
trigger()
}
}
})
customRef
的应用场景比较灵活,通常用于以下情况:
customRef
来自定义 ref 的行为。customRef
来替代 ref
和 reactive
等原有的响应式 API。需要注意的是,customRef 是一个相对高级的 API,在开发中建议慎用。同时在对其使用时,也需要关注性能、代码可读性及其使用场景等因素。
reactive定义的对象是不能直接赋值,否则会破坏响应式对象
import { reactive } from "vue";
let list = reactive<string[]>([]);
const add = () => {
setTimeout(() => {
let res = ["EDG", "RNG", "JDG"];
console.log(list);
list.push(...res); // 正确的修改reactive对象方法
// list = res; // reactive proxy 不能直接赋值,否则会破坏响应式对象
console.log(list);
}, 2000);
};
把属性变成只读的
const info = ref('xiao5')
const newInfo = readonly(info)
newInfo.value = '555555' // 会报错,提示newInfo是只读属性
和shallowRef类似。
toRef
用于创建一个针对源对象的响应式引用。它将一个对象的属性转换为单独的 ref,以便更容易地进行响应式处理。
具体来说,toRef 接收一个源对象和一个属性键名作为参数,并返回一个 ref 对象。这个 ref 对象会自动跟踪源对象中对应属性的变化,并在变化时更新视图。
下面是一个使用 toRef 的例子:
import { toRef } from 'vue'
const myObj = {
name: "vue",
version: "3.2"
}
const myRef = toRef(myObj, "version")
上述代码中,我们创建了一个名为 myObj
的对象,并通过 toRef
将其中的 version
属性转换为一个 ref
对象。这样,当我们修改 myObj.version
的值时,myRef.value
的值也会随之更新。
有了 toRef
,我们就可以方便地将源对象中特定的属性进行响应式处理,而不需要将整个源对象都转化为响应式对象或者手动为每个属性创建 ref。
需要注意的是,**toRef 返回的 ref 对象本质上只是一个引用,而非复制。**因此如果在程序中同时存在多个对同一个 ref 对象的引用,那么它们之间的关系和更新状态是相互影响的。
toRefs
可以帮我们批量创建ref
对象,主要是方便我们解构reactive
import {reactive, toRefs} from 'vue'
const obj = reactive({
foo: 1,
bar: 1
})
let {foo, bar} = toRefs(obj)
foo.value++;
console.log(foo.value, bar.value); // 2 1
将响应式对象转换成普通对象
计算属性就是当该计算属性所依赖的属性的值发生变化的时候,才会触发该计算属性的更改。
import { computed, reactive, ref } from 'vue'
let price = ref(0)//$0
let m = computed<string>(()=>{
return `$` + price.value
})
price.value = 500
注意:函数形式只能get,不能set
<template>
<div>{{ mul }}</div>
<div @click="mul = 100">click</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
let price = ref<number | string>(1) //$0
let mul = computed({
get: () => {
return price.value
},
set: (value) => {
price.value = 'set' + value
}
})
</script>
对象形式能get,也能set
<template>
<div>
<input type="text" placeholder="搜索" v-model="keyword"/>
<div style="margin-top: 20px">
<table border width="600" cellpadding="0" cellspacing="0">
<thead>
<tr>
<th>物品名称</th>
<th>物品单价</th>
<th>物品数量</th>
<th>物品总价</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="item in filterData" :key="item.name">
<td>{{ item.name }}</td>
<td>{{ item.price }}</td>
<td>
<button @click="item.num > 0 ? item.num-- : item.num">-</button
>{{ item.num }} <button @click="item.num++">+</button>
</td>
<td>{{ item.num * item.price }}</td>
<td><button @click="delItem(item)">删除</button></td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5" align="right">总价:{{ sumPrice }}</td>
</tr>
</tfoot>
</table>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from "vue";
interface Data {
name: string;
price: number;
num: number;
}
let keyword = ref<string>('')
// 计算属性实现搜索功能
let filterData = computed(() => {
return data.filter(item => {
return item.name.includes(keyword.value)
})
})
let data = reactive<Data[]>([
{
name: "小5的绿毛",
price: 500,
num: 1,
},
{
name: "小5的红衣",
price: 5000,
num: 2,
},
{
name: "小5的黑袜",
price: 500000,
num: 1,
},
]);
// 计算属性总价
let sumPrice = computed<number>(() => {
return data.reduce((preValue:number, item:Data) => {
return preValue + item.num * item.price;
}, 0);
});
const delItem = (item: Data) => {
const index:number = data.findIndex((item1) => item1 == item);
data.splice(index, 1);
};
</script>
<style lang="scss" scoped></style>
与Vue2.x中watch配置功能一致
两个小“坑”:
//情况一:监视ref定义的响应式数据
watch(sum,(newValue,oldValue)=>{
console.log('sum变化了',newValue,oldValue)
},{immediate:true})
//情况二:监视多个ref定义的响应式数据
watch([sum,msg],(newValue,oldValue)=>{
console.log('sum或msg变化了',newValue,oldValue)
})
/* 情况三:监视reactive定义的响应式数据
若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
若watch监视的是reactive定义的响应式数据,则强制开启了深度监视
*/
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
},{immediate:true,deep:false}) //此处的deep配置不再奏效
//情况四:监视reactive定义的响应式数据中的某个属性
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true})
//情况五:监视reactive定义的响应式数据中的某些属性
watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true})
//特殊情况
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
// watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调
watchEffect(() => {
const x1 = sum.value
const x2 = person.age
console.log('watchEffect配置的回调执行了')
})
高级侦听器补充
使用setup
语法糖模式是没有beforeCreate
和created
这两个周期的。
所以生命周期的一般顺序是:
Setup => Mounted => Updated => unMounted
组件A
<template>
<h2>hello!!</h2>
<!-- 测试Mounted前后 -->
<h2>测试Mounted前后</h2>
<div ref="div">{{ divContent }}</div>
<!-- 测试Updated前后 -->
<h2>测试Updated前后</h2>
<button @click="update">update</button>
</template>
<script setup lang="ts">
import {
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onMounted,
onUnmounted,
onUpdated,
ref,
} from "vue";
let num = ref(0);
let divContent = ref<string>("content");
let div = ref<HTMLDivElement>();
const update = () => {
num.value++;
divContent.value = "我被改啦";
};
console.log("setup");
onBeforeMount(() => {
console.log("创建之前===>", div.value);
});
onMounted(() => {
console.log("创建完成===>", div.value);
});
onBeforeUpdate(() => {
console.log("更新之前===>", div.value?.innerText);
});
onUpdated(() => {
console.log("更新完成===>", div.value?.innerText);
});
onBeforeUnmount(() => {
console.log("卸载之前===>");
});
onUnmounted(() => {
console.log("卸载完成===>");
});
</script>
<style lang="scss" scoped></style>
App.vue
<template>
<A v-if="flag"></A>
<hr />
<!-- 测试UnMounted前后 -->
<h2>测试UnMounted前后</h2>
<button @click="change">unmounted</button>
</template>
<script setup lang="ts">
import A from "./components/A.vue";
import { ref } from "vue";
let flag = ref<Boolean>(true);
const change = () => {
flag.value = !flag.value;
};
</script>
<style lang="scss" scoped></style>
setup
语法糖模式是没有beforeCreate
和created
这两个周期的。onBeforeMount
是读不到dom
的,onMounted
可以读取到dom
的。onBeforeUpdated
获取的是更新之前的dom
,onUpdated
获取的是更新之后的dom
。v-if
可以测试unMouned
SCSS(Sass CSS)是CSS的一种较新的形式。它是基于 CSS3 的一个把 CSS 的语法进行了扩展的版本,因此,所有标准的 CSS 都是合法的 SCSS 代码。在 SCSS 中可以使用变量、嵌套、混入、函数等高级功能,极大地提高了 CSS 的编写效率。下面是 SCSS 的一些常用语法:
$primary-color: #2196f3;
nav {
ul {
margin: 0;
padding: 0;
list-style: none;
}
li { display: inline-block; }
a {
display: block;
padding: 6px 12px;
text-decoration: none;
}
}
@mixin square($size: 50px, $color: red) {
width: $size;
height: $size;
background-color: $color;
}
.box {
@include square(100px, blue);
}
.message {
border: 1px solid #ccc;
padding: 10px;
}
.success {
@extend .message;
border-color: green;
}
@function double($n) {
@return $n * 2;
}
以上是 SCSS 的一些常用语法,当然还有很多其他的功能和特性,需要根据具体的需要进行学习和了解。
BEM(Block Element Modifier)是一种前端开发中常用的 CSS 类命名方式,它可以帮助我们编写出易于维护和扩展的代码。
BEM 的基本思想是将页面分解为多个块(Block),每个块包含多个元素(Element),而一个元素可以是另一个块的子块。块和元素分别由名称和描述符组成,这些名称和描述符之间使用双下划线和双短横线来分隔,例如:
/* 定义一个名为 list 的块 */
.list { … }
/* 定义一个名为 item 的元素 */
.list__item { … }
/* 定义一个名为 icon 的修饰符 */
.list__item--icon { … }
其中,list
是块的名称,item
是元素的名称,icon
是修饰符的名称。通过这种方式定义类名,可以使得 HTML 结构和 CSS 样式更加清晰、易于理解和扩展。
具体来说,BEM 将页面中的每个模块都视为独立的“块”,在 CSS 中以 .block
的形式进行命名,例如 .header
、.menu
、.button
等。然后再将一个块内部的各个元素视为该块的组成部分,以 .block__element
的形式进行命名,例如 .header__logo
、.menu__item
、.button__icon
等。需要注意的是,一个元素只能属于一个块,并且必须包含在该块的范围内。
如果需要对某个元素进行样式修饰,则可以使用修饰符(Modifier),以 .block__element--modifier
的形式进行命名,例如.button__icon--small
、.menu-item--active
等。修饰符可以对一个或多个元素进行样式修改,以实现不同状态或风格的变化。
Element UI
就采用了 BEM(Block Element Modifier)的命名方式来定义组件样式类。
在 Element UI
中,每个组件都被视为一个块(Block),并以 el-
作为前缀进行命名,例如 el-button
、el-input
等。每个组件包含多个元素(Element),并以 __
进行分隔,例如 el-button__icon
、el-input__prefix
等。同时,也可以为一个元素添加修饰符(Modifier),以 -
进行分隔,例如 el-button--primary
、el-input--disabled
等。
具体来说,通过 BEM 的命名方式,可以使 Element UI 的组件结构更加清晰,易于理解和修改,同时也方便了其他开发人员对组件样式的继承和覆盖。在使用 Element UI 组件时,我们只需要在 HTML 元素中指定相应的 CSS 类名即可,例如:
<el-button class="el-button--primary">确定el-button>
上述代码中,我们为 el-button 组件添加了一个 el-button--primary
的修饰符,以实现按钮的主题色为蓝色。
总之,在使用 Element UI
组件时,建议按照 BEM 命名方式规范命名 CSS 类名,这样既能保证代码风格的统一性,也能提高代码的可读性和可维护性。
案例:配置全局BEM全局方式
配置文件bem.scss
$block-sel: "-" !default; // B
$element-sel: "__" !default; // E
$modifier-sel: "--" !default; // M
$namespace: 'xw' !default;
@mixin bfc {
height: 100%;
overflow: hidden;
}
// .xw-block {
// content
// }
// 混入
@mixin b($block) {
$B: $namespace + $block-sel + $block; // 变量
.#{$B} { // 插值语法#{name}
@content; // @content相当于一个占位符,会把自定义样式里的内容替换进来
}
}
@mixin flex {
display: flex;
}
// .xw-block__inner {
// content
// }
@mixin e($element) {
$selector: &; // & 父选择器引用符,此处使用定义变量$selector,来获取父级的类名,如xw-block
@at-root { // 控制指令,用于将样式规则返回到其顶级作用域中,使用嵌套规则时非常有用,可避免样式规则嵌套过深,此处跳出嵌套
#{$selector + $element-sel + $element} {
@content;
}
}
}
@mixin m($modifier) {
$selector: &;
@at-root {
#{$selector + $modifier-sel + $modifier} {
@content
}
}
}
修改vite.config.ts
文件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "./src/bem.scss";`
}
}
}
})
测试BEM配置
App.vue
block
element
modify
局部组件也就是常规的在components
文件夹中的定义组件,然后在页面导入并引用组件。
例如组件使用频率非常高,如table、input、button等这些组件,几乎每个页面都在使用,这类组件就可以封装成全局组件。
我们也可以在components
文件夹中自定义频繁使用的组件,并将自定义组件注册成全局组件。
将注册成全局组件的方法都是一样的,看如下案例:
我们在这封装一个Card组件,想在任何地方去使用
标题
副标题
{{ content }}
在main.ts
中注册全局组件
import { createApp } from 'vue'
import App from './App.vue'
// 导入组件
import CardVue from './components/Card.vue'
export const app = createApp(App)
// 注册全局组件
app.component('Card', CardVue)
app.mount('#app')
在其他文件中使用全局组件
递归组件的原理和我们写js递归一样,自己调用自己,然后通过一个条件来结束递归,否则导致内存泄漏。
这里我们写一个简易的递归树案例
Tree.vue
{{ item.name }}
// 自己调用自己,并且使用v-if和?扩展运算符来终止递归
在其他页面使用递归组件
什么是动态组件:
让多个组件使用同一个挂载点,并动态切换,这就是动态组件。
用法如下:
引入组件
import A from './A.vue'
import B from './B.vue'
通过is切换A、B组件
使用场景:tab切换
下面来看案例:
定义三个子组件,分别是B、C、D。
{{ item.name }}
// 此处调用动态组件
效果展示:
注意事项:
1.在Vue2 的时候is 是通过组件名称切换的 在Vue3 setup 是通过组件实例切换的
2.如果你把组件实例放到Reactive Vue会给你一个警告runtime-core.esm-bundler.js:38 [Vue warn]: Vue received a Component which was made a reactive object. This can lead to unnecessary performance overhead, and should be avoided by marking the component with markRaw
or using shallowRef
instead of ref
.
Component that was made reactive:
这是因为reactive 会进行proxy 代理 而我们组件代理之后毫无用处,为了节省性能开销,推荐我们使用shallowRef 或者 markRaw 跳过proxy 代理。
异步组件是什么?
异步组件是一种在 Vue.js 应用程序中,延迟加载组件的机制。当应用程序需要加载某个组件时,使用异步组件可以将该组件从应用程序的主要代码中分离出来,以实现按需加载和提高应用程序的初始性能。
传统的情况下,所有组件都是同步加载的,这意味着当用户打开应用程序时,所有组件都会立即被加载并注册到 Vue.js 实例中。随着应用程序变得更加庞大,这些组件的数量和大小也会增加,导致应用程序加载速度变慢,甚至影响用户体验。使用异步组件机制,可以将某些不常用的组件或者较为耗时的组件单独分离出来,延迟加载,从而提高应用程序性能。
在 Vue 2 中,可以通过 require.ensure
或者 Webpack 的 import()
函数来实现异步组件。而在 Vue 3 中,则提供了新的 API:defineAsyncComponent
来实现异步组件的定义。
异步组件的应用场景
当应用程序比较庞大时,需要优化应用程序的加载速度和时间。在这种情况下,将组件拆分成多个小模块,并使用异步加载以降低应用程序的初始负载就非常重要了。
如骨架屏的实现
除了优化应用程序的启动性能之外,还可以通过按需加载组件来减小应用程序的总体大小。例如,在一个包含多个不同页面的单页应用程序中,可能只有某些页面才需要特定的组件或功能。如果将所有组件都打包到应用程序中,则会增加整体应用程序的。
顶层await
在 Vue 3 中,await 可以被用于异步组件的定义中。在组件定义对象中,可以使用 load 函数来定义加载组件的异步操作,例如:
const AsyncComponent = {
name: 'AsyncComponent',
async load() {
const component = await import('./MyComponent.vue');
return component.default;
},
render() {
return h('div', [this.Component ? h(this.Component) : null])
}
};
在上面的代码中,load 函数是一个 async 函数,它通过 import 语句异步地加载了我们想要的组件,并返回该组件的默认导出值。
在 render 函数中,我们使用 this.Component 来渲染异步加载的组件。当异步组件还没有加载完成时,this.Component 的值为 null,所以这里使用了条件判断来避免出现“渲染组件未定义”的错误。
总之,Vue 3 中的 await 可以被用于异步组件的定义中,帮助我们更方便地实现组件的异步加载。
中可以使用顶层 await。结果代码会被编译成
async setup()
。
父组件引用子组件 通过defineAsyncComponent加载异步配合import 函数模式便可以分包
结果如下:
这时,我们就可以使用teleport
来实现将子组件Dialog
渲染到指定DOM节点body
。
代码如下:
我是父组件
Vue中keep-alive的深入理解和使用
在 Vue 中,Transition 是一种动画效果的实现方式。在组件切换时,可以通过添加 Transition 的相关属性来实现过渡动画效果。
使用 Transition 可以让我们的应用程序更加生动、具有交互性。例如,在两个组件之间进行切换时,添加 Transition 效果可以使界面转换更加自然、平滑,提升用户的体验感受。
在进入/离开的过渡中,会有6个class切换。
案例:
transition动画组件有如下过渡class类名(props)
duration
属性定义过渡时间。<transition enter-from-class="" enter-active-class="" enter-to-class="" leave-from-class="" leave-active-class="" leave-to-class="" >...</transition>
<transition :duration="1000">...</transition>
<transition :duration="{ enter: 500, leave: 800 }">...</transition>
官方文档 Animate.css | A cross-browser library of CSS animations.
安装animate库:$ npm install animate.css --save
基本用法:An animated element
copy此类名,调用时加上类名animate__animated
动画效果就能生效。
结合transition动画组件自定义库使用的案例:
是 Vue.js 内置的一个高级动画组件,它可以帮助我们实现在添加或删除元素时,自动应用过渡动画。通常与
一起使用,当数据改变时,
会自动检测哪些元素被添加或删除,然后应用相应的过渡动画。
在使用上与
组件类似,但它能够对一组元素进行过渡效果的处理。常见的应用场景包括列表渲染,在增加或删除列表项时,以平滑动画效果来显示添加和删除的元素,从而增强用户体验。
下面看一个简单的案例:
{{ item }}
组件还有一个特殊之处。除了进入和离开,它还可以为定位的改变添加动画。
只需要为
组件的move-class
属性绑定自定义的过渡类别就行,我们通过以下案例来体会移动过渡。
{{ item.number }}
结果展示:
mitt
是一个小巧的 JavaScript 事件总线库,可以用于前端项目中轻松地实现自定义事件的管理和监听。
它是按照发布-订阅模式设计的,可以在任何地方使用,例如 React、Vue、Angular 等前端框架中。相比于 Vue.js 或 React 内置的事件系统,mitt
更为灵活,不需要依赖特定的框架或库。
以下是一个简单的示例:
import mitt from 'mitt'
// 创建一个事件总线
const eventBus = mitt()
// 监听事件
eventBus.on('event1', data => {
console.log('event1', data)
})
// 触发事件
eventBus.emit('event1', 'hello world')
// 移除事件监听器
eventBus.off('event1', listener)
下面我们使用mitt
实现全局配置事件总线:
配置入口文件main.ts
import { createApp } from 'vue'
import App from './App.vue'
// import CardVue from './components/Card.vue'
import mitt from 'mitt'
const Mit = mitt()
export const app = createApp(App)
// 注册全局组件
// app.component('Card', CardVue)
// 这段代码利用了ts module declaration的特性,声明了一个名为vue的模块
// 并通过ComponentCustomProperties接口扩展了Vue.js组件实例类型。
// 其中ComponentCustomProperties是一个内置的Vue.js接口,可用于向组件实例添加自定义属性。
declare module 'vue' {
export interface ComponentCustomProperties {
// 通过$Bus: typeof Mit 的语法,我们将$Bus属性的类型指定为Mit类型的的类型本身,
// 也就是自定义事件总线库的类型,这样对于使用TS的项目,就可以在组件中
// 直接访问到$Bus属性,并且享受到类型检查的好处。
$Bus: typeof Mit
}
}
// 使用mitt注册全局事件Bus
app.config.globalProperties.$Bus = Mit
app.mount('#app')
父组件App.vue
通过兄弟组件中的B组件向C组件派发事件,C组件触发事件
B.vue
我是B
C.vue
C
参考博客:学习Vue3 第二十五章(TSX)
TSX 是一种在 TypeScript 中编写 React 组件的语法,相比于传统的 JSX 语法,它能提供更好的类型检查和编译时错误检查。TSX 其实就是将 JSX 和 TypeScript 结合起来,所以可以在 TSX 中使用 JSX 的所有语法,同时也可以使用 TypeScript 的类型注解来为组件和组件 Props 声明类型。通常,TSX 的文件扩展名为 “.tsx”。
安装tsx插件:npm install @vitejs/plugin-vue-jsx -D
修改vite.config.ts
配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// 导入tsx插件
import vueJsx from '@vitejs/plugin-vue-jsx'
export default defineConfig({
// 启动tsx插件
plugins: [vue(),vueJsx()],
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "./src/bem.scss";`
}
}
}
})
在 TSX 中,需要遵循以下规范:
.tsx
为后缀。v-bind
,使用 {}
来代替,例如 value = { this.props.name }
。if
语句,可以使用三目运算符代替,例如 { condition ? true : false }
。for
循环,可以使用 Array.map()
函数来代替,例如 { this.props.items.map(item => - {item}
) }
。v-show
。ref
在template
自动解包.value
,但tsx不会,要手动加.value
。案例代码:
App.tsx
// 写法一
// export default function () {
// return (小5)
// }
// 写法二:optionsAPI
// import { defineComponent } from "vue"
// export default defineComponent({
// data() {
// return {
// age: 23
// }
// },
// render() {
// return ({this.age})
// }
// })
// 写法三:setup
import { defineComponent, ref } from "vue";
// 组件A
const A = (_:any, { slots }) => (
<>
<div>{slots.default ? slots.default() : '默认值'}</div>
</>
)
interface Props {
name?: string
}
export default defineComponent({
props: {
name: String
},
emits: ['on-click'],
setup(props: Props, {emit}) {
const flag = ref(false)
const data = [
{
name: '小5——1'
},
{
name: '小5——2'
},
{
name: '小5——3'
}
]
const fn = (value: any) => {
console.log('触发了', value);
// 派发事件
emit('on-click', value)
}
const slot = {
default: () => (<div>小5default slots</div>)
}
const modelValue = ref()
return () => (
<>
<h5>测试父组件向子组件传递数据</h5>
<div>props:{props?.name}</div>
<hr />
<h5>测试map代替v-for</h5>
{
data.map(item => {
return (<div>{item.name}</div>)
})
}
<hr />
<h5>测试三元表达式代替v-if</h5>
<div>{flag.value ? <div>true</div> : <div>false</div>}</div>
<hr />
<h5>测试事件派发和监控</h5>
<button onClick={() => fn('尼赣嘛')}>按钮</button>
<hr />
<h5>测试插槽</h5>
<A v-slots={slot}></A>
<hr />
<h5>测试v-model</h5>
<input type="text" v-model={modelValue.value} />
<div>{modelValue.value}</div>
</>
)
}
})
App.vue
在 Vue 3 中,使用 v-model
与 Vue 2 中的用法略有不同,主要是因为 Vue 3 中有了新的 setup()
函数和 reactive()
函数,这些函数使得我们可以更方便地编写组件逻辑。
在 Vue 3 中,v-model
可以使用 modelValue
和 update:modelValue
两个属性来实现双向数据绑定。
案例:使用双向绑定v-model
实现点击切换开关控制子组件的显示与隐藏,并可以通过关闭按钮隐藏子组件。
父组件App.vue
<template>
<div>
<h3>我是父组件</h3>
<div>isShow:{{ isShow }}</div>
<button @click="isShow = !isShow">切换</button>
<A v-model="isShow"></A>
</div>
</template>
<script setup lang="ts">
import A from "./components/A.vue";
import { ref } from "vue";
const isShow = ref<boolean>(true);
</script>
子组件A.vue
:
<template>
<div class="main" v-if="modelValue">
<h3>我是子组件</h3>
<div>content</div>
<button class="btn" @click="close">关闭</button>
</div>
</template>
<script setup lang="ts">
// 接收父组件v-model传来的值,并用modelValue代替
defineProps<{
modelValue: boolean;
}>();
// 定义更新modelValue的emit,实现双向绑定
const emit = defineEmits(["update:modelValue"]);
// 通过事件触发emit
const close = () => {
emit("update:modelValue", false);
};
</script>
<style lang="scss" scoped>
.main {
position: relative;
left: 200px;
width: 200px;
height: 200px;
border: 1px solid #ccc;
}
.btn {
position: absolute;
top: 0;
right: 0;
}
</style>
案例:继上一个案例,实现多个值的双向绑定。
父组件App.vue
<template>
<div>
<h3>我是父组件</h3>
<div>isShow:{{ isShow }}</div>
<div>text:{{ text }}</div>
<button @click="isShow = !isShow">切换</button>
<!-- 传多个v-model -->
<A v-model="isShow" v-model:textVal="text"></A>
</div>
</template>
<script setup lang="ts">
import A from "./components/A.vue";
import { ref } from "vue";
const isShow = ref<boolean>(true);
const text = ref<string>("小5");
</script>
子组件A.vue
:
<template>
<div class="main" v-if="modelValue">
<h3>我是子组件</h3>
<div>content</div>
<button class="btn" @click="close">关闭</button>
<div>内容:<input type="text" :value="textVal" @input="change" /></div>
</div>
</template>
<script setup lang="ts">
// 同时接收父组件v-model传来的多个值
defineProps<{
modelValue: boolean;
textVal: string;
}>();
// 定义更新多个值的emit
const emit = defineEmits(["update:modelValue", "update:textVal"]);
// 通过事件触发emit
const close = () => {
emit("update:modelValue", false);
};
const change = (e: Event) => {
const target = e.target as HTMLInputElement;
emit("update:textVal", target.value);
};
</script>
在Vue 3中,你可以使用自定义修饰符来改变v-model指令的行为。自定义修饰符应该用.符号来表示,例如.number和.trim。
// 同时接收父组件v-model传来的多个值
defineProps<{
modelValue: boolean;
textVal: string;
textValModifiers?: { // 值+Modidiers
default: () => {
}
}>();
在Vue中,自定义指令(Directive)是一种非常有用的功能,可以将对DOM元素的操作封装到可复用的指令中。指令可以用于添加样式、绑定事件、自定义触发器等方面,它们提供了一种简单而强大的方式来扩展HTML。
Vue中有v-if
、v-for
、v-show
、v-model
等等一系列方便快捷的指令,下面我们来了解一下vue中的提供的自定义指令。
Vue2指令的钩子函数:bind、inserted、update、componentUpdated、unbind。
第一个参数:el
,表示当前绑定的DOM元素
第二个参数:binding
,binding
是一个Object,其他包含以下属性。
v-my-direactive:foo
中,arg
为foo
。created
和mounted
,这俩将作为函数存在dir
中。v-my-directive.bar
中,修饰符对象为{bar: true
。beforeUpdate
和updated
中可用,无论值是否有更改都可用。v-my-directive="1 + 1"
,该值为2。App.vue
<template>
<button @click="show = !show">开关</button>
<div
v-if="show"
v-move-directive:foo.bar="{ background: 'green' }"
style="width: 100px; height: 100px; background-color: #ccc"
></div>
</template>
<script setup lang="ts">
import { Directive, DirectiveBinding, ref } from "vue";
let show = ref(true);
const vMoveDirective: Directive = {
created() {},
mounted(el: any, dir: any) {
console.log("el:", el);
console.log("dir:", dir);
// 通过自定义指令传来的value改变样式
el.style.background = dir.value.background;
},
};
</script>
<template>
<div class="btns">
<!-- 通过自定义指令根据按钮的权限控制按钮的显示与隐藏 -->
<button v-has-show="'shop:create'">创建</button>
<button v-has-show="'shop:edit'">编辑</button>
<button v-has-show="'shop:delete'">删除</button>
</div>
</template>
<script setup lang="ts">
// import { ref, reactive } from 'vue'
import type { Directive } from "vue";
// 本地存储用户id
localStorage.setItem("userId", "xiao5-per");
// mock后台返回的权限数据
const permission = [
"xiao5-per:shop:edit",
"xiao5-per:shop:create",
// "xiao5-per:shop:delete",
];
// 获取本地存储的用户id
const userId = localStorage.getItem("userId") as string;
// 自定义指令
const vHasShow: Directive<HTMLElement, string> = (el, binding) => {
// 如果权限不存在则隐藏按钮
if (!permission.includes(userId + ":" + binding.value)) {
el.style.display = "none";
}
};
</script>
<style lang="scss" scoped>
.btns {
button {
margin: 10px;
}
}
</style>
由于mock的后台返回的数据没有删除按钮的权限,因此,删除按钮被隐藏,结果展示如下:
<template>
<div v-move class="box">
<div class="header"></div>
<div>内容</div>
</div>
</template>
<script setup lang="ts">
import { Directive, DirectiveBinding } from "vue";
const vMove: Directive<any, void> = (
el: HTMLElement,
binding: DirectiveBinding
) => {
// 获取header元素
let moveElement: HTMLDivElement = el.firstElementChild as HTMLDivElement;
// console.log(moveElement);
// 鼠标按下时的回调
const mouseDown = (e: MouseEvent) => {
let X = e.clientX - el.offsetLeft;
let Y = e.clientY - el.offsetTop;
// 鼠标移动的回调
const mouseMove = (e: MouseEvent) => {
// console.log(e);
// 更改el的位置
el.style.left = e.clientX - X + "px";
el.style.top = e.clientY - Y + "px";
};
// 鼠标按下时开始监听鼠标移动并触发其回调
document.addEventListener("mousemove", mouseMove);
document.addEventListener("mouseup", () => {
// 鼠标抬起时终止监听鼠标移动并终止回调
document.removeEventListener("mousemove", mouseMove);
});
};
// 监听鼠标按下的情况
moveElement.addEventListener("mousedown", mouseDown);
};
</script>
<style lang="scss" scoped>
.box {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 200px;
height: 200px;
border: 1px solid #ccc;
.header {
height: 20px;
background: black;
cursor: move;
}
}
</style>
<template>
<div>
<img
v-lazy="item"
width="360"
height="500"
v-for="item in arr"
:key="item"
alt=""
/>
</div>
</template>
<script setup lang="ts">
// import { ref, reactive } from 'vue'
// vite自带了读取文件的方法,如下
// glob 是懒加载模式
// let modules = {
// 'xxxxx': () => import('xxxxx')
// }
// globEager 是静态加载模式
// import xxxxx from 'xxxxx'
import { Directive } from "vue";
// 加了eager参数后就等同于globEager
let imageList: Record<string, { default: string }> = import.meta.glob(
"./assets/images/*.*",
{ eager: true }
);
// console.log(imageList);
let arr = Object.values(imageList).map((item) => item.default);
// console.log(arr);
let vLazy: Directive<HTMLImageElement, string> = async (el, binding) => {
const def = await import("./assets/vue.svg");
el.src = def.default;
// console.log(el);
// IntersectionObserver 是一种现代的异步浏览器 API,可以用于监视目标元素
// (通常是指网页中的某个 DOM 元素)与其祖先元素或顶级文档视窗之间的交叉状态。
// 这个 API 通过异步方式,提供了对所监视元素的交差信息的回调和事件。
const observer = new IntersectionObserver((entries) => {
// console.log(entries[0]);
// console.log("value:", binding.value);
if (entries[0].intersectionRatio > 0) {
el.src = binding.value;
console.log("懒加载一张");
// 取消监听
observer.unobserve(el);
}
});
observer.observe(el);
};
</script>
<style lang="scss" scoped></style>
结果展示:
知识点补充:
IntersectionObserver
是一种现代的异步浏览器 API,可以用于监视目标元素(通常是指网页中的某个 DOM 元素)与其祖先元素或顶级文档视窗之间的交叉状态。这个 API 通过异步方式,提供了对所监视元素的交差信息的回调和事件。
通过这个 API,网页开发人员可以更好地进行性能优化,动态加载和卸载内容,从而为用户提供更快、更流畅和更可靠的体验。它尤其适用于对于那些已经在节流、分块加载、预加载等方面进行了优化操作并仍然有性能瓶颈的页面。
基本用法:
const observer = new IntersectionObserver((entries) => {
// console.log(entries[0]);
// console.log("value:", binding.value);
if (entries[0].intersectionRatio > 0) {
el.src = binding.value;
console.log("懒加载一张");
// 取消监听
observer.unobserve(el);
}
});
observer.observe(el)
IntersectionObserver
中回调函数的参数entries
所返回的对象的每个属性的含义如下:
intersectionRect
占boundingClientRect
的比例,即目标元素在可见区域展现的面积占目标元素的面积的比例,完全可见时为1,完全不可见时小于等于0。Vue2
Vue.prototype.$http = () => {}
Vue3
const app = createApp(App)
app.config.globalProperties.$http = () => {}
vue3取消了filters,这里我们自己全局配置一个filters。
main.ts
import { createApp } from 'vue'
import App from './App.vue'
export const app = createApp(App)
// vue3取消了filters,这里我们自己全局配置一个filters
app.config.globalProperties.$filters = {
format<T extends any>(str: T): string {
return `${str}`
}
}
// 定义一个全局属性
app.config.globalProperties.$env = 'dev'
type Filter = {
format<T>(str: T): string
}
// 配置全局事件/属性的声明代码[格式都是一致的]
declare module 'vue' {
// ComponentCustomProperties是一个内置的Vue.js接口,可用于向组件实例添加自定义属性。
export interface ComponentCustomProperties {
$filters: Filter,
$env: string
}
}
app.mount('#app')
上述代码实现了定义一个全局函数filters
和全局属性env
。并且使用内置apiComponentCustomProperties
实现全局事件/属性的声明。
下面我们通过以下代码尝试使用全局函数/属性。
App.vue
<template>
<h5>template使用全局属性/函数</h5>
<div>{{ $filters.format("尼赣嘛") }}</div>
<div>{{ $env }}</div>
</template>
<script setup lang="ts">
import { getCurrentInstance } from "vue";
const instance = getCurrentInstance();
// setup语法使用全局属性/函数
console.log(instance?.proxy?.$filters.format("尼赣嘛"));
console.log(instance?.proxy?.$env)
</script>
<style lang="scss" scoped></style>
Vue 3 的插件系统与 Vue 2 类似,它允许开发者对 Vue 核心添加全局功能或自定义组件,并可以在不同的组件中共享这些公用的逻辑和状态。在 Vue 3 中,推荐使用新的 app.use()
API 来安装插件。
插件的vue文件
components/Loading/index.vue
<template>
<!-- 插件内的html -->
<div v-if="isShow" class="loading">
<div class="loading-content">Loading</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
// 插件内的函数
const isShow = ref(false); // 定位loading1的开关
// 插件内的方法
const show = () => {
isShow.value = true;
};
const hide = () => {
isShow.value = false;
};
// 对外暴露 当前组件的属性和方法
defineExpose({
isShow,
show,
hide,
});
</script>
<style lang="scss" scoped>
.loading {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.8);
display: flex;
justify-content: center;
align-items: center;
&-content {
font-size: 30px;
color: #fff;
}
}
</style>
配置一个插件对象
components/Loading/index.ts
import { createVNode, render, VNode, App } from "vue";
// 引入插件的vue文件
import Loading from './index.vue'
// 暴露插件对象
export default {
install(app: App) {
// createVNode:vue底层方法,可以给组件创建一个DOM 也就是Vnode
const vnode: VNode = createVNode(Loading)
// render把我们的Vnode渲染成真实的DOM 并挂载到指定节点
render(vnode, document.body) // 第二个参数是挂载点
console.log(app);
console.log(vnode);
// 可以通过vnode中的component属性的exposed获取暴露的属性和方法
console.log(vnode.component?.exposed);
// Vue提供的全局配置 可自定义
app.config.globalProperties.$loading = {
show: () => vnode.component?.exposed?.show(),
hide: () => vnode.component?.exposed?.hide()
}
// 调用全局注册的$loading
// app.config.globalProperties.$loading.show()
}
}
修改入口文件的全局配置项,注册插件
main.ts
import { createApp } from 'vue'
import App from './App.vue'
import Loading from './components/Loading'
export const app = createApp(App)
// 启用插件
app.use(Loading)
// 定义类型声明
type Loading = {
show: () => void,
hide: () => void
}
// 配置全局事件的声明代码
declare module 'vue' {
export interface ComponentCustomProperties {
// 声明loading插件
$loading: Loading
}
}
app.mount('#app')
在App.vue
中使用插件
<template>
<div></div>
</template>
<script setup lang="ts">
import { getCurrentInstance } from "vue";
const instance = getCurrentInstance();
// 调用插件的show方法展示loading页面
instance?.proxy?.$loading.show();
// 调用插件的hide方法隐藏loading页面
// instance?.proxy?.$loading.hide();
</script>
下面我们编写一个包含先前学习的知识点的简易插件
// 定义一个插件对象,该对象需要包含 install 方法
const ExamplePlugin = {
install(app) {
// 在 Vue 应用 app 上挂载 $hello 属性,值为 "Hello World!"
app.config.globalProperties.$hello = 'Hello World!';
// 定义自定义指令 v-focus,用于聚焦元素
app.directive('focus', {
mounted(el) {
el.focus();
},
});
// 定义一个全局组件 example-component,具有单个属性 message
app.component('example-component', {
props: ['message'],
template: '{{ message }}',
});
},
};
// 在 Vue 应用上安装插件
createApp(App).use(ExamplePlugin).mount('#app');
在这个示例代码中,我们首先定义了一个插件对象 ExamplePlugin
,其中包含了一个名为install
的方法,接受一个应用实例 (app)
作为唯一参数。然后在 install
方法中,我们又通过 app
对象分别实现了三个全局功能:
app.config.globalProperties
存储在任意应用实例上挂载全局属性。app.directive
注册自定义指令 focus
,用于聚焦元素。app.component
注册全局组件 example-component
,并注入属性 message
。最后,在应用启动时,我们将这个插件对象安装到应用实例之中,并调用 mount
方法挂载根组件。接着,在应用实例里就可以使用 $hello
全局属性了,也就可以在任何其他组件中直接引用 v-focus
指令和
标签了。
样式穿透主要用于修改很多vue常用的组件库(element、vant、AntDesign),组件库虽然配好了默认样式,但还是需要更改其他样式,样式穿透就派上用场了。
vue中的scoped通过在DOM结构以及CSS样式上加唯一不重复的标记:data-v-hash
的方式,以保证唯一(而这个工作是由PostCSS转译实现的),达到样式私有化、模块化的目的。
总结一下scoped的三条渲染规则:
data-v-xxx
,然后,给CSS选择器额外添加一个对应的属性选择器来选择该组件的DOM,这种做法使得样式只作用于含有该属性的DOM——组件内部DOM,从而达到了**‘样式模块化’**的效果。下面我们通过实例在检验上述的三条渲染规则:
加了scoped
没加scoped
根据上述两图成功验证了规则1:给HTML的DOM节点加一个不重复的data属性(形如:data-v-123)来表示它的唯一性。。
加了scoped
没加scoped
根据上述两图成功验证规则2: 在每句css选择器的末尾(编译后的生成的css语句)加一个当前组件的data属性选择器(如[data-v-123])来私有化样式。
根据上图成功验证规则3:如果组件内部包含其他组件,那么只会给其他组件的最外层标签加上当前组件的data属性。
样式为何需要穿透?
假设我们成功查找到输入框的样式类,并需要通过此样式类来更改输入框的背景色,如下图:
使用样式穿透后:
这里需要注意区分一下Vue2和Vue3样式穿透的写法:
Vue2写法:
/deep/ .el-input__inner {
background: red;
}
Vue3写法:
:deep(.el-input__inner) {
background: red;
}
插槽选择器使用在子组件中,通过引用父组件所调用子组件的class或id,插槽选择器的格式:
:slotted(子组件class/id) {
...
}
在之前我们想加入全局样式,通常都是新建一个style标签,不加scoped
现在有更优雅的解决方案:
<template>
<div class="div1">动态CSS</div>
<div class="div2">尼赣嘛</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
// 定义成string
const color1 = ref("red");
// 定义成对象
const color2 = ref({
color: "yellow",
});
</script>
<style lang="scss" scoped>
.div1 {
color: v-bind(color1);
}
.div2 {
// 定义成对象的写法
color: v-bind("color2.color");
}
</style>
是用来开启 CSS 模块化功能的语法。在 Vue.js 中,
标签支持添加
module
修饰符,以告诉 Vue,当前样式文件需要启用模块化。
通过使用useCssModule()
这种方式获取的样式类名,我们就可以避免全局样式冲突的问题,并且能够很容易地写出可复用的组件和模块。
总之,使用 Vue.js 中的 语法可以帮助我们大幅提升 CSS 样式的模块化程度和可维护性,是一个非常实用的特性。
JavaScript中的异步任务分为宏任务和微任务。
宏任务包括整体代码(script)、定时器(setTimeout、setInterval)、事件监听器(click等)、网络请求(Ajax等)等等。
微任务指的是在当前任务执行结束后立即执行的任务,通常是由Promise产生的回调函数、MutationObserver等等。
宏任务与微任务的执行顺序如下:
需要注意的是,微任务的执行优先级比宏任务更高,即使宏任务中间产生了微任务,也会先执行完当前的宏任务再去执行微任务队列。
下面我们通过案例来理解:
<template>
<div></div>
</template>
<script setup lang="ts">
// async await 是Promise()的语法糖 属于微任务
async function Prom() {
// 同步任务
console.log("Y");
await Promise.resolve();
// 相当于写在Promise的then里,属于微任务
console.log("X");
}
// setTimeout()是宏任务
setTimeout(() => {
console.log(1);
// Promise产生的回调是微任务
Promise.resolve().then(() => {
console.log(2);
});
}, 0);
setTimeout(() => {
console.log(3);
Promise.resolve().then(() => {
console.log(4);
});
}, 0);
Promise.resolve().then(() => {
console.log(5);
});
Promise.resolve().then(() => {
console.log(6);
});
Prom();
// 同步任务先执行
console.log(0);
</script>
首先我们应当区分哪些是同步任务、微任务、宏任务。
以上任务Prom()
内的输出Y在await
之前执行,因此这个输出属于同步任务,而Prom()
的输出X在await
之后执行,相当于写在Promise
的then
里,因此属于微任务;
Promise
的回调属于微任务,setTimeout()
属于宏任务。
根据同步任务优先异步任务,微任务优先宏任务的顺序
因此由上往下任务执行开始:
Prom()
的输出Y
、console.log(0)
会作为同步任务在主线程上按顺序直接执行Promise(输出5)
、Promise(输出6)
、Prom()
的输出X
会依次添加到微任务队列setTimeout(输出34)
、setTimeout(输出56)
会依次添加到宏任务队列setTimeout(输出34)
时,输出4
属于微任务,因此会先被添加到微任务队列后再执行,setTimeout(输出56)
同理因此输出结果为:Y 0 5 6 X 1 2 3 4
。
下面我们运行代码验证结果:
下面我们通过一个案例来体会nickTick()的妙用:
我们的需求是:在文本框输入内容,点击send按钮可以将内容发布到内容显示区,当内容显示区满时,会出现滚动条,且滚动条会自动拉至最底部。
没有使用nextTick()
的代码如下:
<template>
<div class="main">
<div class="content" ref="box">
<div class="item" v-for="item in list" :key="item.info">
{{ item.info }}
</div>
</div>
<div class="input">
<el-input type="textarea" :rows="2" v-model="input" />
</div>
<button @click="add">send</button>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
let list = ref([{ info: "张三:xxxxx" }]);
let input = ref("尼赣嘛");
// 使用ref引用获取dom元素
let box = ref<HTMLDivElement>();
const add = () => {
list.value.push({ info: input.value });
// 将此dom的拉至最低部
box.value!.scrollTop = 99999999;
};
</script>
<style lang="scss" scoped>
.main {
margin: 40px 300px;
.content {
width: 700px;
height: 400px;
border: 1px solid #ccc;
margin-bottom: 10px;
overflow: auto;
.item {
background-color: #ccc;
height: 50px;
line-height: 50px;
}
}
.input {
width: 700px;
}
}
</style>
实现效果如下:
仔细的同学可以看到:当内容填满内容显示区时,在点击发送按钮,进度条并没有拉至最底部,后续持续发送也并没有拉至最底部,每次都差一点到最底部。造成这种现象的原因是:**Vue更新DOM是异步的,而数据更新是同步,而我们这里add
方法里的将dom拉至最底部的代码是同步执行的,异步更新DOM之前同步代码已经执行完了,DOM来不及更新,因此会少一次DOM操作。**那么如何解决呢?
使用nextTick()
将更新dom的代码异步操作化即可。
nextTick() 是 Vue.js 提供的一个异步方法,用于在 DOM 更新完成后执行回调函数,通常用于在更新后获取新的 DOM 元素状态(如位置、尺寸等)或更新后触发一些回调函数。
在 Vue.js 2.x 中,nextTick()
方法是通过 Vue.$nextTick()
或 this.$nextTick()
调用,它会将传入的回调函数异步地推入到一个队列中,在下一个 DOM 更新循环之前执行。这样可以确保回调函数在更新完成后执行,并且能够获取到最新的 DOM 元素状态。
下面我们更改上面的js代码:
nextTick()的第一种写法:
<script setup lang="ts">
import { nextTick, ref } from "vue";
let list = ref([{ info: "张三:xxxxx" }]);
let input = ref("尼赣嘛");
// 使用ref引用获取dom元素
let box = ref<HTMLDivElement>();
const add = () => {
list.value.push({ info: input.value });
// 将此dom的拉至最低部
// nextTick异步渲染dom
nextTick(() => {
box.value!.scrollTop = 99999999;
});
};
</script>
nextTick()的第二种写法(推荐写法):
<script setup lang="ts">
import { nextTick, ref } from "vue";
let list = ref([{ info: "张三:xxxxx" }]);
let input = ref("尼赣嘛");
// 使用ref引用获取dom元素
let box = ref<HTMLDivElement>();
const add = async () => {
list.value.push({ info: input.value });
// 将此dom的拉至最低部
// nextTick异步渲染dom
await nextTick();
// await之后的代码都是异步的
box.value!.scrollTop = 99999999;
};
</script>
这里值得注意的是:await
之后的代码都是异步的。
效果展示:
可以看到成功实现每次更新dom将滚动条拉至最底部。
当我们操作dom的时候,发现数据读取的是上次的,这个时候就需要使用nextTick()
了。
Unocss是一个基于模板推断的CSS解决方案,它能够自动削减未使用的CSS样式,并生成可复用的样式组合。这样可以大大减少CSS文件的大小,提高页面的加载速度和用户体验。
Unocss的主要特点包括:
Unocss是实现CSS原子化的工具库。何为CSS原子化?
CSS原子化指的是将一些常用的CSS样式属性拆分为独立的原子级别,然后组合成不同的类名来实现样式的复用,通过对这些原子级别的样式进行组合和调用,可以快速地构建出各种样式效果,从而提高开发效率和代码复用性。
具体来说,CSS原子化可以拆分为以下几个方面:
font-size
、margin-top
、color
等。12px
、20px
、#333
等。p-10 bg-white text-center
。通过 CSS 原子化,可以实现样式的精细化控制,并且减少样式冲突和重复。目前已经有很多 CSS 框架或库采用了原子化的思想,比如 Tailwind CSS 和 Tachyons 等。
下面我们通过使用Unocss
库来体会原子化的优点:
安装UnoCSS:npm i -D unocss
tips:最好用于vite, webpack属于阉割版,功能很少。
配置vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// 引入
import unocss from 'unocss/vite'
export default defineConfig({
// 配置Unocss插件
plugins: [vue(), unocss({
// 按规则原子化class
rules: [
// 基础写法
['flex', { display: "flex" }],
['red', { color: 'red' }],
// 动态写法(正则匹配)
[/^w-(\d+)$/, ([, d]) => ({margin: `${Number(d) * 10}px`})]
],
// 组合式写法
shortcuts: {
cike: ['flex', 'red']
}
})],
}
})
上述配置实现了原子化的flex
、red
、w-num
类,以及组合式的cike
类。
下面我们在入口文件main.ts
中全局引入这些样式:import 'uno.css'
。
在组件中使用样式:
App.vue
另外我们还可以使用Unocss
预设,Unocss
预设是Unocss提供的一组默认设置和样式集合,用于简化Unocss的配置合使用。预设中包含了一系列已经定义好的CSS样式,可以通过引入不同的预设来实现对应的样式效果。
配置插件预设:
vite.config.ts
import {presetIcons, presetAttributify, presetUno} from 'unocss'
// 和配置rules(规则),shortcuts(组合规则)同一级配置presets
presets:[presetIcons(),presetAttributify(),presetUno()],
1.presetIcons Icon图标预设
图标集合安装:npm i -D @iconify-json/ic
首先我们去icones官网(方便浏览和使用iconify)浏览我们需要的icon,比如这里我用到了Google Material Icons图标集里面的baseline-add-circle图标。
<div class="i-ic-baseline-backspace text-3xl bg-green-500" />
2.presetAttributify 属性化模式支持
属性语义化 无须class
<div>btn</div>
<div font="black">btn</div>
3.presetUno 工具类预设
默认的 @unocss/preset-uno 预设(实验阶段)是一系列流行的原子化框架的 通用超集,包括了 Tailwind CSS,Windi CSS,Bootstrap,Tachyons 等。
例如,ml-3(Tailwind),ms-2(Bootstrap),ma4(Tachyons),mt-10px(Windi CSS)均会生效。
在tailwindcss中随便找个类名,作用即可生效
<div class="bg-slate-100">尼赣嘛</div>
Electron是一个基于Chromium和Node.js的开源框架,用于快速开发跨平台桌面应用程序。它最初由GitHub开发并称为AtomShell,后面更名为Electron。使用Electron可以轻松地将Web技术栈(HTML、CSS、JavaScript)应用到桌面应用程序的开发中,同时也可以通过Node.js的原生模块和第三方模块的支持来进行更底层的操作。
Electron在开发中具有如下优点:
如何使用vue开发桌面程序请参考:Vue开发桌面程序Electron
环境变量:主要作用就是让开发者区分不同的运行环境,来实现兼容开发和生产。
例如npm run dev
就是开发环境,npm run build
就是生产环境等等。
Vite在一个特殊的import.meta.env
对象上暴露环境变量,使用console.log(import.meta.env)
可输出并查看此对象。
{
"BASE_URL":"/", //部署时的URL前缀
"MODE":"development", //运行模式
"DEV":true," //是否在dev环境
"PROD":false, //是否是build 环境
"SSR":false //是否是SSR 服务端渲染模式
}
需要注意的一点就是这个环境变量不能使用动态赋值import.meta.env[key]
应为这些环境变量在打包的时候是会被硬编码的通过JSON.stringify
注入浏览器的。
在根目录新建env 文件 可以创建多个
另外还可以配置智能提示
在package.json
配置--mode env filename
在App.vue
中输出环境变量,npm run dev
开发者环境下启用程序会自动加载.env.development
这个文件。
。。。
在开发过程中,有时需要在代码中使用一些敏感信息或者需要保密的配置参数,如数据库连接字符串、API 密钥等。为了避免将这些信息硬编码到代码中,可以将其存储在环境变量中,并通过代码读取。
在 Node.js 中,我们可以通过 process.env 对象来读取环境变量的值。