vue介绍

282 阅读13分钟

前言

vue上手比较简单,并且对写小程序也有很大帮助,因此很多人会以 vue 入门前端这个行业,相比较 react、nextjs,其上手更加简单、迅速,也是一个渐进式开发框架,深得大家喜爱

这篇文件简单介绍下 vue,从创建项目到基础(实际上前面介绍的 uni-app 就是用的 vue,只不过 ui 组件不一样,环境不同罢了)

vue 参考地址

Vite 参考地址

案例demo

配置 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 同时使用,vue2v-for优先级高,vue3v-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)

只需要设置 stylelang 属性即可,需要支持 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

shallowReactivereactive 的优化措施,但只有对象的第一层会响应反馈到视图,子对象则不会响应到视图上,能提升一些性能,更需要注意别该出 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

至于 watchwatchEffect 直接去看文档吧,感觉用的场景略少,就不多介绍了 -- 这里

简介一下 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 参考这里

<!-- 非活跃的组件将会被缓存! -->
<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 事件监听器。最常见的例子就是 classstyle 和 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 等内容,就不放到这篇文章了