Vue3 +Vite + Pinia + 实战项目

360 阅读8分钟

01 Vue3介绍

1. Vue是什么

一套用于构建用户界面渐进式JavaScript框架。

2. Vue的特点
  1. 采用组件化模式,提高代码复用率、且让代码更好维护。
  2. 声明式编码,让开发人员无需直接操作DOM,提高开发效率。
  3. 使用虚拟DOM+Diff算法,尽量复用DOM节点。

02 为什么用Vite

1. 什么是Vite

Vite :新一代前端构建工具。

2. vue-cli vue2.x | vue3.x

vue create demo:基于的构建工具webpack。
webpack:更新缓慢。修改一个页面保存后会自动加载所有页面。
基于打包器启动时,重建整个包的效率很低。

3. 为什么用Vite
  • 可以大大的提升我们的开发效率。
  • 真正的按需编译,不再等待整个应用编译完成。(修改一个页面保存后只加载此页面)

03 基于Vite搭建Vue3项目

官方文档:v3.cn.vuejs.org/guide/insta…
vite官网:vitejs.cn

创建项目:npm init @vitejs/app <项目名称>
安装步骤: image.png

image.png 启动项目: image.png 第一步 安装Router:npm install vue-router@4 -S
第二步 创建目录和文件

  • 在项目根目录,新建router目录
  • 在router目录中新建index.js文件

第三步 填充router/index.js的内容
第四步 在main.js挂载

import { createApp } from 'vue'
import App from './App.vue'
import router from "./router";

createApp(App).use(router).mount('#app')

04 Vite解决@问题

1. 写法
  • vue2:在template必须要有父元素。
  • vue3:在Vite构建的vue3项目中,无需父元素。
2. Vite解决@问题

过去写Vue项目的时候,@代表src目录,例如引入文件import About from '@/views/About.vue'
那么在Vite构建的Vue3项目中需要先配置一下

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
const path = require('path');

export default defineConfig({
  plugins: [vue()],
  resolve: {
    // 配置路径别名
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
});
3. 引入vue文件要写.vue的扩展名,引入.js文件不用写扩展名。

05 Vue3代码写法风格

Vue3做了向下兼容,也支持Vue2的写法(有些不行)
Vue2.x写法==>叫做选项式API
Vue3的setup写法==>叫做组合式API

06 setup定义数据和插件安装

1. setup内部写法: 定义数据
  • 死数据:不可以修改之类的,但是可以展示视图层
    let str = '1';
  • 响应式数据:ref
    在使用的时候需要用value获取值:x.value
  • 响应式数据:reactive
    在使用的时候直接获取对象值,不用ref一样不需要用x.value
    reactive 只能写对象或者数组
2 Vue2 和 Vue3数据拦截不同的点

Vue2.x ==> Object.defineProperty
对象中的属性是对象,层级比较多时;获取不到底层的值,所以需要$set()方法

Vue3.x ==> new Proxy

3. setup语法糖插件安装

解决import { ref , reactive ... } 引入的问题

npm安装淘宝镜像 npm install -g cnpm --registry=http://registry.npmmirror.com
下载安装npm i unplugin-auto-import -D

//引入插件
import AutoImport from 'unplugin-auto-import/vite';
export default defineConfig({
    // 引入插件
    plugins: [
        vue(),
        AutoImport({
            imports:['vue','vue-router']
        })
    ],
});

07 toRefs和computed

1. toRefs() 解构响应式数据
let obj = reactive({
    name:'张三',
    age:20
})
let { name,age } = toRefs( obj );

获取name的值:name.value

2. computed计算属性

let changeStr = computed(()=>{ return str.value; })
计算属性默认是只读的。不允许修改。
表单输入框使用时,需要用到“可写”的属性。可以用下面的写法:

let changeStr = computed({
    get(){
        return str.value;
    },
    set( val ){
        str.value = val;
    }
})

08 watch

1. 监听某一个数据
watch( str , (newVal,oldVal)=>{
    console.log( newVal,oldVal);
})
2. 同时监听多个数据「一起监听」
watch( [ str , num ] , (newVal,oldVal)=>{
    console.log( newVal,oldVal);
})
3. 初始化监听
watch( num , (newVal,oldVal)=>{
    console.log( newVal,oldVal);
},{
    immediate:true
})
4. 监听对象
watch( obj , (newVal)=>{
    console.log( newVal )
},{
    immediate:true,
})
5. 监听对象某一个key,并且深度监听
watch( ()=>obj.m , (newVal,oldVal)=>{
    console.log( newVal,oldVal )
},{
    immediate:true,
    deep:true
})
6. 立即执行监听函数
watchEffect(()=>{
    console.log( str.value )
})
7. 监听路由
let router = useRouter();

watch( ()=>router.currentRoute.value,( newVal )=>{
    console.log( newVal );
},{
    immediate:true
})

09 生命周期钩子

1. 选项式 API

beforeCreate ...

2. setup 组合式API

注意:没有beforeCreate和created
其他生命周期要使用前面加"on" 例如:onMounted
onBeforeMount() // 请求接口
onMounted() // 获取DOM
onBeforeUpdate() // 修改前
onUpdated() //修改后
onBeforeUnmount() //销毁前

参考链接:v3.cn.vuejs.org/guide/compo…

10 路由

vue-router : router.vuejs.org/zh/api/

1. tag属性去除了

<router-link to='/about' tag='div'>跳转到关于我们</router-link>

2. 写法问题

let router = useRouter(); ===> this.$router
let route = useRoute(); ===> this.$route

3. 导航守卫
  • 全局路由守卫
    • beforeEach(to, from, next) 全局前置守卫,路由跳转前触发
    • beforeResolve(to, from, next) 全局解析守卫 在所有组件内守卫和异步路由组件被解析之后触发
    • afterEach(to, from) 全局后置守卫,路由跳转完成后触发
  • 路由独享守卫
    • beforeEnter(to,from,next) 路由对象单个路由配置 ,单个路由进入前触发
  • 组件路由守卫
    • beforeRouteEnter(to,from,next) 在组件生命周期beforeCreate阶段触发
    • beforeRouteUpdadte(to,from,next) 当前路由改变时触发
    • beforeRouteLeave(to,from,next) 导航离开该组件的对应路由时触发

11 组件-父传子

<template>
    <div>
        <List :msg='msg'></List>
    </div>
</template>
<script setup>
import List from '../components/List.vue'
let msg = ref('这是父传过去的数据');
</script>
<template>
    <div> 
        这是子组件 ==> {{ msg }}
    </div>
</template>

<script setup>
defineProps({
    msg:{
        type:String,
        default:'1111'
    }
})
</script>

12 组件-子传父

<template>
    <div> 
        这是子组件 ==> {{ num }}
        <button @click='changeNum'>按钮</button>
    </div>
</template>

<script setup lang='ts'>
let num = ref(200);

const emit = defineEmits<{
	(e: 'fn', id: number): void
}>()

const changeNum = ()=>{
	emit('fn',num)
}	
</script>
<template>
    <div>
        <List @fn='changeHome'></List>
    </div>
</template>

<script setup>
import List from '../components/List.vue'
const changeHome = (n)=>{
    console.log( n.value );
}
</script>

13 v-model传值

父:

<List v-model:num='num'></List>
<script setup>
    import List from '../components/List.vue'
    let num = ref(1);
</script>

子:

const props = defineProps({
    num:{
        type:Number,
        default:100
    }
})
const emit = defineEmits(['update:num'])
const btn = ()=>{
    emit('update:num',200);
}

14 组件-兄弟组件传值

1. 下载安装

npm install mitt -S

2. plugins/Bus.js

import mitt from 'mitt'; const emitter = mitt() export default emitter;

3. A组件

emitter.emit('fn',str);

4. B组件

emitter.on('fn',e=>{ s.value = e.value; })

15 插槽

1. 匿名插槽
父:
<A>
	这是xxxxx数据
	这是yyyyy数据
</A>
子:
<header>
	<div>头部</div>
	<slot></slot>
</header>
<footer>
	<div>底部</div>
	<slot></slot>
</footer>
2. 具名插槽

父:

<A>
	<template v-slot:xxx>
		这是xxxxx数据
	</template>

	<template v-slot:yyy>
		这是yyyyy数据
	</template>
</A>

简写:<template #xxx>
子:

<header>
	<div>头部</div>
	<slot name='xxx'></slot>
	<slot name='yyy'></slot>
</header>

<footer>
	<div>底部</div>
	<slot name='xxx'></slot>
</footer>
3. 作用域插槽

父:

<template v-slot='{data}'>
	{{ data.name }} --> {{ data.age }}
</template>

简写:<template #default='{data}'>
子:

<div v-for='item in list' :key='item.id'>
	<slot :data='item'></slot>
</div>
4. 动态插槽:说了就是通过数据进行切换

父:

<template #[xxx]>
	这是xxxxx数据
</template>
<script setup>
	let xxx = ref('xxx');
</script>

16 Teleport 传送

<teleport to='#container'></teleport>
<teleport to='.main'></teleport>
<teleport to='body'></teleport>
必须传送到有这个dom的内容【顺序】

17 动态组件

<component :is="动态去切换组件"></component>

18 异步组件

提升性能

vueuse : vueuse.org/core/useint…

1. 使用场景1

组件按需引入:当用户访问到了组件再去加载该组件

<template>
	<div ref='target'>
		<C v-if='targetIsVisible'></C>
	</div>
</template>

<script setup>
import { useIntersectionObserver } from '@vueuse/core'

const C = defineAsyncComponent(() =>
	import('../components/C.vue')
)

const target = ref(null);
const targetIsVisible = ref(false);

const { stop } = useIntersectionObserver(
	target,
	([{ isIntersecting }]) => {
	if( isIntersecting ) {
		targetIsVisible.value = isIntersecting
	}
	},
)
</script>
2. 使用场景2
<Suspense>
	<template #default>
		<A></A>
	</template>
	<template #fallback>
		加载中...
	</template>
</Suspense>
<script setup>
const A = defineAsyncComponent(() =>
	import('../components/A.vue')
)
</script>
3.打包分包处理

npm run build打包完成后,异步组件有单独的js文件,是从主体js分包出来的

A.c7d21c1a.js
C.91709cb2.js

19 Mixin

混入是什么?来分发 Vue 组件中的可复用功能。

1. setup写法 Vue3

mixin.js:

import { ref } from 'vue'
export default function(){
    let num = ref(1);
    let fav = ref(false);
    let favBtn = ()=>{
        num.value += 1;
        fav.value = true;
        setTimeout(()=>{
                fav.value = false;
        },2000)
    }
    return {
        num,
        fav,
        favBtn
    }
}

组件:

<template>
    <div>
        <h1>A组件</h1>
        {{ num }}
        <button @click='favBtn'>
            {{ fav ? '收藏中...' : '收藏' }}
        </button>
    </div>
</template>
<script setup>
import mixin from '../mixins/mixin.js'
let { num , fav , favBtn } = mixin();
</script>
2. 选项式api写法 Vue2

mixin.js:

export const fav = {
    data () {
        return { num:10 }
    },
    methods:{
        favBtn( params ){
            this.num += params;
        }
    }
}

组件:

<template>
    <div>
        <h1>A组件</h1>
        {{ num }}
        <button @click='favBtn(1)'>按钮</button>
    </div>
</template>
<script type="text/javascript">
import { fav } from '../mixins/mixin.js'
export default{
    data () {
        return {
            str:'你好'
        }
    },
    mixins:[fav]
}
</script>

20 Provide和Inject

Provide和Inject ==> 依赖注入
提供:

<script setup>
    provide('changeNum', num );
</script>

注入:

<template>
    <div>
        <h1>B组件</h1>
        {{ bNum }}
    </div>
</template>
<script setup>
    const bNum = inject('changeNum');
</script>

21 Vuex

  1. state:
    let num = computed( ()=> store.state.num );
  2. getters:
    let total = computed( ()=> store.getters.total );
  3. mutations:
    store.commit('xxx')
  4. actions:
    store.dispatch( 'xxx' )
  5. modules:
    和之前的版本使用一样
  6. Vuex持久化存储【插件】
  • npm i vuex-persistedstate -S

  • import persistedState from 'vuex-persistedstate'

export default createStore({
    modules: {
    user
    },
    plugins:[persistedState({
    key:'xiaoluxian',
    paths:['user']
    })]
});

22 Pinia简单使用

1. Vuex和pinia的区别

参考网址: github.com/vuejs/rfcs/…

  1. pinia没有mutations,只有:state、getters、actions
  2. pinia分模块不需要modules(之前vuex分模块需要modules)
  3. pinia体积更小(性能更好)
  4. pinia可以直接修改state数据
2. pinia使用

官方网址:pinia.vuejs.org/

具体使用:xuexiluxian.cn/blog/detail…

3. pinia持久化存储

参考链接:xuexiluxian.cn/blog/detail…

23 Pinia分模块

24 Pinia持久化存储

25 设置代理

API参考链接:staging-cn.vuejs.org/api/#onmoun…

参考文章:xuexiluxian.cn/blog/detail…

  1. 设置代理
    文件:vite.config.js
import { defineConfig } from 'vite' 
import vue from '@vitejs/plugin-vue' 
import AutoImport from 'unplugin-auto-import/vite' 
export default defineConfig({ 
    plugins: [ 
        vue(), 
        AutoImport({ 
            imports:['vue','vue-router']//自动导入vue和vue-router相关函数 
        }) 
    ], 
    server:{ 
        proxy:{ 
            '/api':'http://testapi.xuexiluxian.cn'
        } 
    }
})
  1. axios二次封装
    文件:新建utils/request.js
import axios from 'axios'; 
//1. 创建axios对象 
const service = axios.create(); 
//2. 请求拦截器 
service.interceptors.request.use(config => {
    return config; 
}, error => { 
    Promise.reject(error); 
}); 
//3. 响应拦截器 
service.interceptors.response.use(response => { 
    //判断code码 
    return response.data; 
},error => { 
    return Promise.reject(error); 
}); 
export default service;
  1. API解耦合
    文件:api/new.js
import request from '../utils/request' 
export function mostNew( data ){ 
    return request({ 
        url:'/api/course/mostNew', 
        method:"post", 
        data 
    })
}

26 项目搭建

使用的技术栈: vite + vue3 + pinia

做的项目:www.xuexiluxian.cn/

1. 创建项目

npm init @vitejs/app saas

2. 安装router
  1. npm install vue-router@4 -S
  2. 在src目录新建router/index.js
import { createRouter, createWebHistory } from "vue-router";
import Home from "../views/Home.vue";

const routes = [
    {
    path: "/",
    name: "Home",
    component: Home,
    },
    {
    path: "/about",
    name: "About",
    component: () =>
        import(/* webpackChunkName: "about" */ "../views/About.vue"),
    },
];

const router = createRouter({
    history: createWebHistory(),
    routes,
});

export default router;	
3. unplugin-auto-import插件 和 @代表src目录
  1. 安装setup语法糖插件 npm i unplugin-auto-import -D
  2. 在vite.config.js中进行配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue';
//引入插件
import AutoImport from 'unplugin-auto-import/vite';

const path = require('path');
export default defineConfig({
    plugins: [
        vue(),
        //配置插件
        AutoImport({
            imports:['vue','vue-router']
        })
    ],
    resolve: {
        // 配置路径别名
        alias: {
            '@': path.resolve(__dirname, './src'),
        },
    },
});
4. element-plus

网址:element-plus.org/zh-CN/guide…

  1. 下载 npm install element-plus --save
    npm install -D unplugin-vue-components unplugin-auto-import
  2. 配置修改vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue';
//引入插件
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

const path = require('path');
export default defineConfig({
    plugins: [
        vue(),
        //配置插件
        AutoImport({
            resolvers: [ElementPlusResolver()],
            imports:['vue','vue-router']
        }),
        Components({
            resolvers: [ElementPlusResolver()],
        }),
    ],
    resolve: {
        // 配置路径别名
        alias: {
            '@': path.resolve(__dirname, './src'),
        },
    },
});

27 首页-头部布局

28 关于项目布局的说明

29 首页-banner布局

30 首页-新上好课布局

31 首页-底部布局

32 axios二次封装和轮播图数据

33 新上好课数据渲染

34 首页菜单分类数据渲染

35 课程页-布局

36 课程页-数据渲染

37 课程页-课程分类筛选课程

38 课程页-更多条件筛选

39 课程页-分页

40 课程页-布局样式调整

41 课程详情页-样式布局

42 课程详情页-布局完成

43 课程详情页-数据渲染

44 头部跳转-监听路由

45 登录-账号密码登录

46 登录-短信登录

47 登录成功以后

48 登录后-显示用户信息

49 退出登录

50 课程页-下载资料

51 课程页-进入课程播放页

52 课程页-渲染右侧课程数据

53 课程页-课程视频播放(防盗链)

54 课程页-播放视频记录

55 购物车-导航守卫

56 购物车-数据渲染和单选、全选

57 购物车-删除购物车数据

58 加入购物车

59 进入确认订单页

60 确认订单页-数据渲染

61 支付宝支付

62 微信支付

63 支付成功