前言
vue上手比较简单,并且对写小程序也有很大帮助,因此很多人会以 vue 入门前端这个行业,相比较 react、nextjs,其上手更加简单、迅速,也是一个渐进式开发框架,深得大家喜爱
这篇文件简单介绍下 vue,从创建项目到基础(实际上前面介绍的 uni-app 就是用的 vue,只不过 ui 组件不一样,环境不同罢了)
配置 vite-vue
vite 创建一个 vue-ts 项目(也可以创建react项目),参考这里
//直接创建vue项目,根据提示安装
pnpm create vue@latest
//或者直接去vite网站创建
//ts
pnpm create vite my-vue-app --template vue-ts
//js
pnpm create vite my-vue-app --template vue
vscode配置
搜索 vue,前面的都插件应用,没啥问题哈,可以自行看说明,有很多代码块提示,很方便
vue-router
加入 vue-router,创建完毕后默认就有,感觉不对,可以重新安装
yarn add vue-router@4
pnpm add vue-router@4
tailwindcss
安装配置 tailwindcss,参考 这里,也可以参考我的前面一篇 文章
由于 vue 项目编写本身 css 就可以隔离,因此基本不存在起名问题,某些情况下略显多余,但是也确实好用,能够提升不少效率
pnpm add tailwindcss @tailwindcss/postcss postcss -D
配置,首先在根目录创建 postcss.config.mjs,然后导入 tailwindcss 到 css 入口文件
//先创建到根目录 postcss.config.mjs
export default {
plugins: {
'@tailwindcss/postcss': {},
},
}
//css 入口文件顶部(base.css就行) 导入 tailwindcss
@import "tailwindcss";
main.css中的可以根据需要删除改进
vscode 搜索插件 tailwindcss 第一个就是了,安装后就有提示了
sass
pnpm add sass -D
vite.config.ts plugins 同级别设置 css
css: {
preprocessorOptions: {
scss: {
// additionalData: `@import "./src/styles/global.scss";
},
},
},
autoprefixer
安装
pnpm add autoprefixer postcss -D
配置
// postcss.config.mjs 下面加入 autoprefixer
export default {
plugins: {
'@tailwindcss/postcss': {},
autoprefixer: {},
},
}
//package.json中加入(放到 version 下面就行)
"browserslist": [
"last 2 versions",
"not dead",
">= 0.2%"
],
移除生产环境log
导入 @rollup/plugin-terser 库
pnpm add @rollup/plugin-terser -D
在 vite.config.ts 中配置
import terser from '@rollup/plugin-terser'
plugins: [
...
terser({
compress: {
// 移除 console.log
drop_console: true,
// 移除 debugger
drop_debugger: true,
},
}),
],
Element-plus(unplugin-auto-import自动导入)
Element-plus 也是 vue 中比较火爆的一个UI组件库,使用也非常简单,且支持按需导入 unplugin-auto-import就是自动引入依赖的,放到这里了
//导入 element-plus
pnpm install element-plus
//按需加载
npm install -D unplugin-vue-components unplugin-auto-import
pnpm install -D unplugin-vue-components unplugin-auto-import
调整我们的配置项
//vite.config.ts
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
// ...
plugins: [
// ...
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
})
//main.ts
import ElementPlus from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
//完整引入,如果是按需,则不需要
app.use(ElementPlus, {
locale: zhCn, //设置中文
})
//按需导入的话,最外层包上这个
<el-config-provider :locale="zhCn">
<RouterView />
</el-config-provider>
import zhCn from 'element-plus/es/locale/lang/zh-cn'
如果您的终端提示 legacy JS API Deprecation Warning, 您可以配置以下代码在 vite.config.ts 中(我这里没有,也贴出来)
css: {
preprocessorOptions: {
scss: { api: 'modern-compiler' },
}
}
此外 unplugin-auto-import 会帮我们自动在根目录下生成一个 auto-imports.d.ts 文件,我们可以在 tsconfig.app.json 中 include 一下
"include": ["env.d.ts", "src/**/*", "src/**/*.vue", "auto-imports.d.ts"],
eslint组件单个名字的错误
eslint 默认会让我们的单个单词组件提示 vue/multi-word-component-names 错误,意思是我们单个单词可能会和系统组件有冲突,尽量避免,可使用两个单词解决
例如: Login.vue 组件报错,改成 UserLogin.vue 就没事了
也可以在 eslint.config.ts(mjs) 中,最下面添加下面的内容(当然是比较推荐两个单词了,提示的没错)
export default defineConfigWithVueTs(
{
name: 'app/files-to-lint',
files: ['**/*.{ts,mts,tsx,vue}'],
//rules写到这里会被后面的配置给覆盖,因此不生效
},
{
name: 'app/files-to-ignore',
ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'],
},
pluginVue.configs['flat/essential'],
vueTsConfigs.recommended,
skipFormatting,
//就是这里加上rules就行了
{
rules: {
'vue/multi-word-component-names': 'off',
},
},
)
vue介绍
vue与uni-app
我之前写的 uni-app 就是 vue 语法,可以说只是基础ui组件库不太一样,可以参考这个 uni-app介绍,基本就是拿来就用,这也是为什么很多写 vue 的,写小程序移动端时优先选 uni-app 的原因(尽管 uni-app 无法匹配 flutter、原生等)
生命周期
vue前端组件和小程序组件生命周期生命周期不同,在开发 uniapp 的时候需要注意,生命周期参考这里
下面使用默认的模式介绍,后面是 setup 模式了,看下就就知道 setup 算是简化后的版本了,通过一些钩子函数,能够更好的编写(有一点 react 的函数式组件了意思了,但是还是不一样哈)
<script>
export default {
data() {
return {
name: 'marshal',
age: 40,
}
},
//vue前端生命周期方法
mounted() {
// 安装完毕后调用,一般逻辑在这里,调用一次
console.log('mounted')
},
onUnmounted() {
//卸载函数
}
//app小程序端生命周期方法
onLoad() {
// 渲染之后调用,一般逻辑在这里,调用一次
console.log('onLoad')
},
//小程序使用
onShow() {
//在 onLoad之后调用,或者 keep-alive 缓存被激活时调用
//比 activated 初次进入多走了一步,这是需要注意的地方
console.log('onShow')
},
onUnload() {
// 卸载之后调用,移除监听等操作在这里
console.log('onUnload')
},
methods: {
}
}
</script>
//setup 模式
<script setup>
import {
onMounted, //vue的 mounted 的钩子,改了名字,实际上推荐 uni-app的组件生命周期
} from 'vue'
import { onLoad, onShow, onUnload} from '@dcloudio/uni-app'
onMounted(() => {
})
onUnMounted(() => {
})
onLoad((options) => {
})
onShow(() => {
})
onUnload(() => {
})
</script>
ps:后面就直接少 setup 模式下的了案例了,默认的也很找到对应的
绑定参数、绑定事件
静态绑定 直接传入内容即可,无论是字符串还是数字等
<img src="@/static/c1.png"></image>
动态绑定参数 只需要在属性前面加入 : 即可完成绑定,外部属性变化会传递到组件内部,以完成更新,即::[attributeName]
绑定事件只需要加入 @ 即可,内部的事件会反馈给外面指定方法,即算绑定成功,即:@[eventName],事件相关的参考这里,例如:事件修饰、按键修饰,能够以链式的方式处理
如下所示
<template>
<view class="container">
<dy-double-button :name="data.name" @valueChanged="onComponentChanged" />
<view @click="onUpdateName">点击我从外部更新desc</view>
<view @click="count++">也支持一些函数表达式</view>
<view @click="test('啦啦啦')">也支持函数传参调用等</view>
<view @click.stop="count++">阻止事件向下传递</view>
</view>
</template>
<script>
import { reactive } from 'vue'
const data = reactive({
name: '啦啦啦',
count: 1
})
const test = (params) => {
}
</script>
样式绑定
样式绑定的时候并不是传递字符串,而是一个对象,因此需要使用单括号{}包起来
静态和动态绑定({}), style 直接设置属性,传递样式对象或者样式对象数组
动态绑定的 class 表示的是是否应用某一个class,style 则是直接应用样式
<div
class="staticClassName"
:class="{backgroundRed: isRed'}" //这样是不是不用强搭style了 🤣
></div>
绑定 data 数据
<div
:class="classObject" //也可以绑定到js对象中去
></div>
const classObject = reactive({ active: true, 'text-danger': false })
绑定集合,使用[]
<div
:class="[classObject1, classObject]" //也可以绑定到js对象中去
></div>
支持一个 css 属性设置多个兼容性参数
display: ['-webkit-box', '-ms-flexbox', 'flex']
v-model简化input绑定
即使有参数绑定,但是类似于 viewModel 相关的双向绑定功能也有需求场景,在 input 上,默认不仅仅需要传递 value 还需要 input 回调更新,很麻烦,如果不需要处理输入,可以直接使用 v-model,直接双向绑定参数,自动更新
<input
:value="text"
@input="event => text = event.target.value">
<input v-model="text">
资源路径
资源路径的引用主要以 绝对路径、相对路径的方式来引入
//绝对路径,通过根目录下的某个文件夹开始直接引入
<img src="@/static/c1.png"></image>
//相对于当前文件位置引入文件(用的最舒服的就是组件同级目录下,引用自己的图片、代码逻辑文件等)
<img src="../../static/c2.png"></image>
如果想动态直接导入没办法写死路径,可以直接导入的方式切换,或者直接使用 new Url 方式转化字符串处理
//除了上面的
import shaderString from './shader.glsl?raw'
//需要导入字符串的形式
const url = new URL(@/assets/react-logo.png', import.meta.url).href
//可以提取
const getImageUrl = (name: string) => {
return new URL(`@/assets/${name}`, import.meta.url).href
}
显示字符参数
与上面不同的是,显示字符不需要绑定之类的,只需要直接填充即可,如果需要显示或者嵌入某个字符串,可以使用双大括号方式引入变量,即: {{ 变量 }},这个跟微信小程序一样的
<text>总年龄:{{totolAge}}</text>
<text>{{totolAge}}</text>
v-if、v-else-if、v-else
主要是一个判断语句
<view v-if="status===1">我成功了</view>
<view v-else-if="status===2">我失败了</view>
<view v-else>我都没参加</view>
注意:不建议跟 v-for 同时使用,vue2 中 v-for优先级高,vue3 中 v-if优先级高
v-show
v-show 功能类似于 v-if,但是没有 else,其是更改 css 上的属性来实现显隐,因此不能用于 temlate,所以平时不太推荐
<view v-show="status===1">我显示了</view>
v-for
常见的遍历语句,可以生成 list,另外如果需要提高性能,需要设置:key,可以加入唯一值,也可以直接使用索引,能避免更新时频繁创建 item
//因为直接遍历出来了名字,所以不需要微信的重命名那套了,更加简洁易懂
<view v-for="(item, index) in listData" :key="index">
<text>第{{index + 1}}个元素是:{{item}}</text>
</view>
css支持配置(scss、less)
只需要设置 style 的 lang 属性即可,需要支持 scss 就设置成 scss,需要支持 less 就设置成 less 即可,当然也可以设置成 css, 非常方便,这里就不多介绍了,需要学习前两个的可以搜一下
<!-- 设置 lang="scss" 就支持 scss 的内容了 -->
<style lang="scss">
//设置单个页面背景
page {
width: 100%;
height: 100%;
}
</style>
data、template、style
data 就是我们的 state 状态机,我们可以通过修改 data 的参数来更新内容, template 就是xml组件,style 就是 css,支持 less、scss
通过 this.data属性名 可以修改 data 内容 和显示内容
<template>
<view class="container">
<text class="large-size theme-color">{{啦啦啦}}</text>
</view>
</template>
<script>
export default {
data() {
return {
name: '啦啦啦'
};
},
methods: {
onUpdate() {
this.name = '哈哈' //通过 this.name 可以修改内容和现实
},
}
}
</script>
<style>
.container {
width: 100%;
height: 100%;
}
</style>
常用的基础api
我们只要拼出来,就会自动导入该钩子(uni-app 的生命周期函数目前不会,要手动导入,也许以后会),如下所示
import {
ref,
reactive,
shallowReactive,
computed,
} from 'vue'
响应式 api 文档,可以参考 这里
下面只介绍比较常用的
ref
可以是这样对象的
const sTitle = ref('单个标题')
//也可以是这样对象的,内部更新也会即使响应到视图上
const title = ref({
title: '默认标题',
sub: {
subTitle: '子标题'
}
}) //这个也是
更改值的时候这样,需要 .value 形式更改,子对象改变,也会响应到视图上
sTitle.value = '初始化单个标题'
title.value.title = '初始化标题'
title.value.sub.subTitle = '初始化子标题'
使用时,则不需要 .value,会自动解包
<text>{{sTitle}} --- {{title.title}} -- {{title.sub.subTitle}}</text>
可以看到,其用起来 .value 是有点啰嗦的,后面的 reactive 将会深得你喜爱
reactive
创建对象,用它可以直接代替原来的 data了,用起来也很舒服,并且可以分割成好几个对象,能对子对象解包,以便于修改时能应用到视图上
//使用上方便了
const data = reactive({
name: '哈哈',
age: 10,
stu: {
name: '一班',
number: 30,
},
})
更改内容,看着也简洁了,且子对象修改仍然可以反馈到视图上,和非响应式的 data 一样
data.name = 'mm'
data.age = 20
data.stu.name = '二班'
data.stu.number = 33
视图上就需要额外添加一个我们声明的 reactive 对象字段了,这里命名为 data,多个对象甚至可以使用多个reactive
<text>名字:{{data.name}} ----- 年龄:{{data.age}}</text>
<text>班级:{{data.stu.name}} ------ 人数:{{data.stu.number}}</text>
shallowReactive
shallowReactive 为 reactive 的优化措施,但只有对象的第一层会响应反馈到视图,子对象则不会响应到视图上,能提升一些性能,更需要注意别该出 bug 了
ps:如果不存在性能瓶颈,就别瞎倒腾了,另外 ref 等也有 shallow 系列
//使用上方便了
const data = shallowReactive({
name: '哈哈',
age: 10,
stu: {
name: '一班',
number: 30,
},
})
同时修改对象和子对象,会发现子对象修改无法渲染出来,响应效率高了,是付出了些代价的,可以根据情况进行优化
data.name = 'mm'
data.age = 20
data.stu.name = '二班'
data.stu.number = 33
computed
这个 computed 计算属性,使用方式和默认的一样,只不过是利用了钩子函数罢了,如下所示
//相当于直接返回一个get方法
const averageAge = computed(() => (data.age + shadowData.age) / 2)
//带有set方法,修改该计算属性时,会根基自己的规则应用到指定对象上
const totolAge = computed({
get() {
return data.age + shadowData.age
},
set(newVal) {
let half = newVal / 2
data.age = half
shadowData.age = newVal - half
},
})
watch、watchEffect
至于 watch、watchEffect 直接去看文档吧,感觉用的场景略少,就不多介绍了 -- 这里
简介一下 watch 使用
//第一个为观察属性,第二个为回调
watch(() => data.name, (val, preVal) => {
})
//第三个参数可以设置立即执行
watch(() => data.name, (val, preVal) => {
}, { immediate: true })
watch(id, (newId, oldId, onCleanup) => {
//可在onCleanup函数的第三个参数,使用清理逻辑
onCleanup(() => {
// 清理逻辑
})
})
相比较 watch 明确依赖 watchEffect 使用更为简单,其能够自动捕获内部使用的参数,当改变时会自动回调,缺点是依赖不够明确,但一般确实无需了解那么多,因此很受欢迎
watchEffect((onCleanup) => {
// ...
onCleanup(() => {
// 清理逻辑
})
})
组件
创建组件的方式不多讲了,右键创建即可,应用和基础开发一样,只不过新增一个组件之间交互使用的 props、emit罢了
这里面会用到 defineProps、defineEmits、reactive
defineProps为声明属性 props的,为只读,方便接收参数,但不可以给默认值,通过 props.属性名 调用
defineEmits为定义向外反馈通道的,会返回一个参数,通过返回的向外反馈
reactive 这个前面介绍过,是为了配合 props的,当我们需要更改 props 的某个参数时,可以将 props 的默认参数传递给我们需要的参数,然后内部使用即可,如果是依赖关系,那么最好使用 computed
<script setup>
import {
reactive,
} from "vue";
//也是只读的,defineProps 不需要从 vue 中引入
const props = defineProps({
name: String,
age: Number,
desc: String,
})
//定义一个向外反馈的 emit 函数指针,可以通过调用该函数直接反馈给外部
const emit = defineEmits(['onChanged'])
//这个可以用于在内部更新
const data = reactive({
cName: props.name,
cAge: props.age,
})
function onUpdateNameAge() {
//更改名字依旧
data.cName = '新名字'
data.cAge = 30
//调用函数反馈给外部即可
emit('onChanged', '更新了desc内容')
}
</script>
视图也比较简洁,可以使用 props 也可以使用 data,根据自己要用的参数使用即可
<view @click="onUpdateNameAge">名字:{{data.cName}} 年龄:{{data.cAge}}</view>
<view class="normal-size black">备注:{{props.desc}}</view>
动态组件
有时候需要展示多个页面,为了方便切换,我们可能要使用 v-if、v-else-if,实际上没必要,直接使用 component 传入组件集合动态处理就行了
//tabs就是我们注册的组件,可以放到集合中,根据索引切换页面
<!-- currentTab 改变时组件也改变 -->
<component :is="tabs[currentTable]"></component>
动态组件 + KeepAlive保活
上面的动态组件存在一个问题,就是组件每次切换都会销毁重建,因此页面状态无法保存,因此有了 KeepAlive 组件(<keep-alive>)
<!-- 非活跃的组件将会被缓存! -->
<KeepAlive>
<component :is="activeComponent" />
</KeepAlive>
这样就可以避免页面销毁状态丢失了
也可以通过 include(包含)、exclude(排除) 定制页面缓存问题,不同的状态可以缓存,也可以不缓存,毕竟有些页面是需要卸载好一些
可以使用 ,、正则、数组方式处理
<!-- 以英文逗号分隔的字符串 -->
<KeepAlive include="a,b">
<component :is="view" />
</KeepAlive>
<!-- 正则表达式 (需使用 `v-bind`) -->
<KeepAlive :include="/a|b/">
<component :is="view" />
</KeepAlive>
<!-- 数组 (需使用 `v-bind`) -->
<KeepAlive :include="['a', 'b']">
<component :is="view" />
</KeepAlive>
例如:使用path缓存路由,也可以使用路由的 meta 配置 keepAlive 参数,方便动态过滤配置个别页面,如果是自动生成路由,接口控制菜单,则可以通过设置菜单的类似参数来配置
<template>
<keep-alive :include="routePath">
<router-view></router-view>
</keep-alive>
</template>
<script setup>
const route = useRoute()
const routePath = computed(() => route.path) //route.matched 遍历 meta参数
</script>
注册组件
全局注册,注册后,全局都可以使用
app
.component('ComponentA', ComponentA)
.component('ComponentB', ComponentB)
.component('ComponentC', ComponentC)
局部注册,按需导入,直接在局部组件 import 就行了,缺点每次需要导入,优点就是导入关系明确,更容易移植
import ComponentA from './ComponentA.vue'
v-model 动态绑定
v-model 也是组件中比较常用的功能了,参考地址
Vue 3.4 开始,推荐的实现方式是使用 defineModel() 更方便了
const model = defineModel()
//默认自定义场景这样,model 值改变时,会自动通知到外部
<button @click="model.value++">Increment</button>
//并且可以用于 input 这种,这样input的页可以传递给外部了
<input v-model="model" />
Vue 3.0 默认使用如下所示,也就是我们常见的 modelValue、update:modelValue
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>
<template>
<input
:value="props.modelValue"
@input="emit('update:modelValue', $event.target.value)"
/>
</template>
此外还支持多个 v-model 声明式时只需要多一个名字,使用时多一个:即可
<UserName
v-model:first-name="first"
v-model:last-name="last"
/>
<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>
<template>
<input type="text" v-model="firstName" />
<input type="text" v-model="lastName" />
</template>
组件透传
“透传 attribute”指的是传递给一个组件,却没有被该组件声明为 props、emits 的 attribute 或者 v-on 事件监听器。最常见的例子就是 class、style 和 id。
当一个组件以单个元素为根作渲染时,透传的 attribute 会自动被添加到根元素上
为什么会这样,如果开发的时候打开调试器就了解了,实际上 vue 3 的组件外部是一个 template 相当于什么都没有,对其设置 class 相对于也没效果,vue 则自动将该效果应用到根节点
真多这个组件透传,如果使用过微信小程序就知道他有多好了,微信小程序相对于外部还放了一个节点,甚至会对布局有影响,对比之下就了解到 vue 的这个组件透传更好用了(当然不是说微信小程序没办法处理哈😂)
//component
<div>我是组件的根组件<div>
//此时里面组件字体红色的
<componet style="color: red" />
//相当于
<div style="color: red">我是组件的根组件<div>
插槽
插槽也就是一个占位的功能组件,可以将我们的某个指定内容替换,这里的插槽就是代表我们的子组件,封装组件时,有时需要传递自定义内容到组件,并放置到组件的某个位置,因此需要用到插槽,更多插槽参考这里
本来只想像微信小程序默认插槽那样使用,实际vue的其他插槽很好使用,这里除了默认使用,还介绍了插槽默认值、具名插槽、条件插槽、插槽作用域、具名插槽作用域(延伸到插槽列表)
插槽默认使用
//组件 Component
<div>
<div>这里是标题和头部信息</div>
......
<slot></slot> <!-- 插槽,将传递的子节点,将途欢slot节点 放置到这里-->
</div>
//外部使用组件,并传递子UI
<Component>
<div>外面自定义内容,让封装组件能够更加通用</div>
<Component>
插槽默认值
当组件没有传递子内容,也就插槽内容时,可以使用slot默认值,传递了,则替换整个slot插槽节点
//组件 Component
<div>
<div>这里是标题和头部信息</div>
......
<slot>
我是插槽默认值
</slot>
</div>
//外部使用组件,并没使用插槽,则会显示插槽默认内容
<Component>
<Component>
ps:本来觉得到这里就可以了,后面发现还有不少人喜欢使用插槽后面的一些功能,这里也多介绍一些比较实用的插槽
具名插槽
这也是比较常用的插槽,也相当于插槽的显示使用方式,有些组件需要自定义好几处,需要使用几处插槽,也就是说具名插槽是多插槽解决方案(并且是vue给我们提供的,我们不需要自己编写了)
组件内 slot 通过 name 给插槽设置具体名字(不设置的就是默认的 default,我们一般都不写),外部通过 v-slot:name、#name 来使用
//component 我们的组件有着三个场景
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
//外部使用具体插槽
<component>
//v-slot 方式
<template v-slot:header>我是头部</template>
// #方式,比较简洁,也是常用方式
<template #foot >我是底部插槽</template>
//此时 default 就是默认插槽
<template #default>我是默认插槽</template>
//隐式插槽,也就是我们常用的
我是隐式插槽,相当于 default,不用使用具名
</component>
条件插槽
这也是比较好用的一个插槽功能,其一般也是基于具名插槽的基础上(默认插槽实际上也是有 default的),可以通过外部是否传递插槽,对插槽做出更具的的包装,让外部传递内容更简单
$slots.name 的方式作为判断条件
//component 我们的组件有着三个场景
<div class="container">
//这里希望有 header时,给传递的内容添加一个外部盒子,让其更合适,没有时也更简洁
//同时能减少组件外部使用者传递重复内容
<div v-if="$slots.header" class="card-header">
<slot name="header" />
</div>
</div>
作用域插槽
这也是比较好用的一个插槽了,要是我们前面写的小程序的那个瀑布流,要是使用到这个作用域插槽,那使用起来就简单多了
<!-- <component> 的模板 -->
<div>
//给外部提供两个参数,会合并到一个对象中
<slot :text="message" :count="1"></slot>
//也可以使用 v-bind直接绑定一个对象,不会再合并
//<slot v-bind="item"></slot>
</div>
//外部直接使用内部 solt 传递过来的参数
<component v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</component>
ps:有人会觉得和具名插槽冲突了吧,实则不然,那个是 v-slot: 这个是 v-slot= 不一样的哈🤣
具名作用域插槽
有了作用域插槽,必然有具名作用域插槽
<!-- <component> 的模板 -->
<div>
//给外部提供两个参数
<slot name="header" :text="greetingMessage" :count="1"></slot>
</div>
//外部直接使用内部 solt 传递过来的参数,这一步简写是不是更简单呢
<component #header="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</component>
//可能有人觉得会这么写,vue直接就简化了🤣
<component #header v-slot="slotProps">
延伸到列表:前面说的瀑布流的改进,也就是可以直接列表方式展开 slot了,我们直接简写一些,不多说了
<!-- <component> 的模板 -->
<ul>
<li v-for="item in items">
//可以使用 v-bind 绑定一个对象,也可以直接展开属性的方式到声明对象
<slot name="item" v-bind="item"></slot>
</li>
</ul>
<component #header="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</component>
依赖注入 provide、inject
在根组件、父组件注入一些数据,在子组件中读取,跟其他的什么 Context、Provider 类似,一般用于保存全局、公共信息
//注入,例如:app.vue
provide('config', {
language: 'En',
theme: 'dark'
})
//取出 demo.vue
const config = inject('config')
console.log(config)
异步组件
异步组件也算是大项目页面过多的一个页面性能优化手段,可以参考这里
使用 defineAsyncComponent 异步导入组件
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)
最后得到的 AsyncComp 是一个外层包装过的组件,仅在页面需要它渲染时才会调用加载内部实际组件的函数。它会将接收到的 props 和插槽传给内部组件,所以你可以使用这个异步的包装组件无缝地替换原始组件,同时实现延迟加载
也可以与普通组件一样,异步组件可以使用 app.component() 全局注册:
app.component('MyComponent', defineAsyncComponent(() =>
import('./components/MyComponent.vue')
))
既然是渲染到页面时加载,那么避免不了有时会加载失败(刚开始也会失败,一般看到问题会刷新页面),页面失败可以有页面失败的处理方式,包括设置替换组件等
const AsyncComp = defineAsyncComponent({
// 加载函数
loader: () => import('./Foo.vue'),
// 加载异步组件时使用的组件
loadingComponent: LoadingComponent,
// 展示加载组件前的延迟时间,默认为 200ms
delay: 200,
// 加载失败后展示的组件
errorComponent: ErrorComponent,
// 如果提供了一个 timeout 时间限制,并超时了
// 也会显示这里配置的报错组件,默认值是:Infinity
timeout: 3000
})
Transition动画
v-if、v-show 切换很直接,不带有动画,有时候我们希望切换时有动画,那么就可以套上 <Transition> 并设置 css,即可
//js
<Transition>
<p v-if="show">hello</p>
</Transition>
//css
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
有时候一个页面有多个效果,可以使用 name 区分样式,默认的可以立即为 name 为 v
//js
<Transition name="fade">
<p v-if="show">hello</p>
</Transition>
//css
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
状态管理
直接使用 reactive 即可,利用响应式的特性,哪里需要导入到哪里,使用上有点像 react 中的 轻量级状态库 zustand
// store.js
import { reactive } from 'vue'
const store = reactive({
count: 1,
})
export default store
<!-- Component.vue -->
<script setup>
import { store } from './store.js'
</script>
<div class="cursor-pointer" @click="store.count++">点击改变count{{ store.count }}</div>
也可以使用 Pinia 等状态库,一般来说,上面的足够用了,大项目需要更多要求,Pinia 也可以上
ps:过分地依赖使用状态管理库,只会让项目更难维护😂
最后
就介绍到这里吧,后面还会介绍 vue-router 等内容,就不放到这篇文章了