Vue3+Vite+Pinia

Vite是一个新型前端构建工具,提供开箱即用的配置和高度可扩展性。Vue3则引入了CompositionAPI,简化组件逻辑。文章介绍了如何搭建Vite项目,解决配置问题,以及Vue3中的响应式数据、生命周期钩子、路由、组件通信和状态管理。还提到了Vue2与Vue3的区别,如数据拦截从`Object.defineProperty`到`newProxy`的改变。
摘要由CSDN通过智能技术生成

一、Vite

中文官网:Vite中文网

 Vite是一种新型前端构建工具,能够显著提升前端开发体验

Vite 意在提供开箱即用的配置,同时它的 插件 API 和 JavaScript API 带来了高度的可扩展性,并有完整的类型支持。

搭建Vite项目相关命令:

使用 NPM:

$ npm create vite@latest

使用 Yarn:

$ yarn create vite

使用 PNPM:

$ pnpm create vite

要构建一个 Vite + Vue 项目,运行:

# npm 6.x
npm create vite@latest my-vue-app --template vue

# npm 7+, extra double-dash is needed:
npm create vite@latest my-vue-app -- --template vue

# yarn
yarn create vite my-vue-app --template vue

# pnpm
pnpm create vite my-vue-app --template vue

 Vite解决@问题

修改项目中的vite.config.js文件

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from "path"

export default defineConfig({
  plugins: [vue()],
  // 配置根路径
  resolve: {
    // ↓路径别名,主要是这部分
    alias: {
      "@": resolve(__dirname, "./src")
    }
  }
})

错误提示:找不到模块 “path” 或其相应的类型声明 或者 找不到名称 “__dirname”

对node进行类型声明
yarn add @types/node -D
npm i @types/node --D

二、Vue3

Vue是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。

Vue 的组件可以按两种不同的风格书写:选项式 API 和组合式 API。 

选项式 API (Options API)

使用选项式 API,我们可以用包含多个选项的对象来描述组件的逻辑,例如 datamethods 和 mounted。选项所定义的属性都会暴露在函数内部的 this 上,它会指向当前的组件实例。

组合式 API (Composition API)

通过组合式 API,我们可以使用导入的 API 函数来描述组件逻辑。在单文件组件中,组合式 API 通常会与 <script setup> 搭配使用。这个 setup attribute 是一个标识,告诉 Vue 需要在编译时进行一些处理,让我们可以更简洁地使用组合式 API。比如,<script setup> 中的导入和顶层变量/函数都能够在模板中直接使用。

创建一个 Vue 应用

> npm init vue@latest

这一指令将会安装并执行 create-vue,它是 Vue 官方的项目脚手架工具

当你准备将应用发布到生产环境时,请运行:

> npm run build

此命令会在 ./dist 文件夹中为你的应用创建一个生产环境的构建版本。 

setup

1、定义数据

死数据,不可以修改之类的,但是可以展示视图层:

let str = ''

 响应式数据:ref(),在使用的时候需要:x.value,可以更改值

let str = ref('1')
const btn = () =>{
    str.value = '2'
}

响应式数据:reactive(),在使用的时候不需要.value,可以直接使用,但是reactive只能写对象和数组(只能写引入类型)

const str = reactive([1,2,3])
const btn = () => {
    str[2] = 4
}

 2、setup语法糖插件

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

下载安装:

npm i unplugin-auto-import -D

 在vite.config.js中配置

//引入
import AutoImport from 'unplugin-auto-import/vite'

//在plugins里使用
plugins: [
    vue(),
    AutoImport({
        imports:['vue', 'vue-router']
    })
]

vue2和vue3数据拦截不同的点

vue2 => Object.defineProperty

vue3 => new Proxy

toRefs

解构 ==》 响应式数据

let obj = reactive({
    name:'张三',
    age:19
})
let {name, age} = toRefs( obj )

computed

接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value 暴露 getter 函数的返回值。它也可以接受一个带有 get 和 set 函数的对象来创建一个可写的 ref 对象。

//只读
let changeStr = computed(() => {
    return str.value
})
//可写的
let changeStr2 = computed({
    get(){
        return str.value
    }
    set(val){
        str.value = val
    }
})

watch() 

 侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。

 第一个参数是侦听器的。这个来源可以是以下几种:

  • 一个函数,返回一个值
  • 一个 ref
  • 一个响应式对象
  • ...或是由以上类型的值组成的数组

第二个参数是在发生变化时要调用的回调函数。这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数。

第三个可选的参数是一个对象,支持以下这些选项:

  • immediate:在侦听器创建时立即触发回调。第一次调用时旧值是 undefined
  • deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。
  • flush:调整回调函数的刷新时机。
  • onTrack / onTrigger:调试侦听器的依赖。
//监听一个数据
watch(str, (newVal,oldVal) =>{
 console.log(newVal,oldVal)
})

//同时监听多个数据,并在初始化的时候就监听一次
watch([str,num], (newVal,oldVal) =>{
 console.log(newVal,oldVal)
},{
    immediate:true
})

//监听对象某一个key,并且深度监听
watch( ()=>obj.m, (newVal,oldVal) =>{
 console.log(newVal,oldVal)
},{
    deep:true
}

//监听路由
let router = useRouter()
watch(()=> router.currentRoute.value, (newVal)=>{
    console.log(newVal.value)
},{
    immediate:true
})

watchEffect()

立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。

const count = ref(0)
watchEffect(() => console.log(count.value))
// -> 输出 0
count.value++
// -> 输出 1

生命周期钩子

Vue2------------------vue3
beforeCreate     -> setup()
created              -> setup()
beforeMount     -> onBeforeMount ——组件挂载到节点上之前执行的函数 
mounted            -> onMounted——组件挂载完成后执行的函数
beforeUpdate    -> onBeforeUpdate——组件更新之前执行的函数
updated             -> onUpdated——组件更新完成之后执行的函数
beforeDestroy  -> onBeforeUnmount——组件死亡(卸载)之前执行的函数
destroyed         -> onUnmounted——组件完全死亡(卸载)后执行的函数
activated           -> onActivated——被包含在 <keep-alive> 中的组件 会多出两个生命周期钩                                                          子函数 被激活时执行 
deactivated       -> onDeactivated——从A组件 切换 到 B 组件 A组件消失时执行
errorCaptured  -> onErrorCaptured——当前捕获一个子孙组件的异常时激活钩子函数

onBeforeUpdate(()=>{
    console.log('修改前');
})
onUpdated(()=>{
    console.log('修改后');
})
onBeforeUnmount(()=>{
    console.log('销毁前');
})
onUnmounted(()=>{
    console.log('销毁后');
})

 路由

 下载安装:

npm i vue-router -S

1、tag属性去除了

<router-link to="/about">跳转</router-link>

2、写法问题

let router = useRouter()  //等同于vue2中的this.$router
let route = useRoute()    //等同于vue2中的this.$route
const goAbout = ()=>{
    router.push('/')
}

3、导航守卫

 全局路由守卫(3个)

1、router.beforeEach((to, from, next) => {}) 全局前置守卫,路由跳转前触发

2、router.beforeResolve((to, from, next) => {})全局解析守卫,在所有组件内守卫和异步路由组件被解析之后触发

3、router.afterEach((to, from) =>{})全局后置守卫,路由跳转完成后触发

路由独享守卫

beforeEnter(to, from,next) 路由对象单个路由配置,单个路由进入前触发

组件路由守卫(3个)

1、beforeRouteEnter(to, from, next) 在组建生命周期beforeCreate阶段触发,Vue3中不可以使用beforeRouteEnter路由守卫

2、beforeRouteUpdate(to, from, next)当前路由改变时触发

3、beforeRouteLeave(to, from, next)导航离开该组件的对应路由时触发

// router/index.js 页面
router.beforeEach((to, from, next) => {
    console.log(to, from);
    next()
});

组件通讯

父传子

//父组件
<template>
    <Son :msg="str2"></Son>
</template>
<script setup>
import Son from "@/components/son.vue";
    let str2 = ref('今天天气不错')
</script>

//子组件
<template>
    <div>
        我是子组件-----{{msg}}
    </div>
</template>
<script setup>
import { defineProps } from 'vue'
    const props = defineProps({
        msg:{
            type:String,
            default:'2222222'
        }
    })
</script>

 子传父

//子组件
<template>
    这是子组件----{{num}}
    <button @click="changeNum">点击</button>
</template>
<script setup>
    let num = ref(999)
    const emit = defineEmits(['fn'])
    const changeNum = () =>{
        emit('fn',num)
    }
</script>

//父组件
<template>      
    <Son @fn='changeAbout'></Son>
</template>
<script setup>
import Son from "@/components/son.vue";
    const changeAbout = (n) =>{
        console.log(n.value);
    }
</script>

父子组件双向数据v-model

//父组件
<template>
    <Son v-model:num="num"></Son>
</template>
<script setup>
import Son from "@/components/son.vue";
    let num = ref(3)
</script>

//子组件
<template>
    <div>
        双向数据------{{num}}
        <button @click="btn">子按钮</button>
    </div>
</template>
<script setup>
    const emit = defineEmits(['update:num'])
    const props = defineProps({
        num:{
            type:Number,
            default:100
        }
    })
    const btn = ()=>{
        emit('update:num',200)
    }
</script>

兄弟组件之间的传值

1、下载安装

npm i mitt -S

2、新建plugins/Bus.js文件

//Bus.js
import mitt from 'mitt'
const emitter = mitt()
export default emitter

//C组件
<template>
    C组件
    <button @click="btn">C按钮</button>
</template>
<script setup>
    import emitter from '../plugins/Bus'
    let strC = ref('这个是C组件')
    const btn = ()=>{
        emitter.emit('fn',strC)
    }
</script>

//D组件
<template>
    <div>
        D组件----{{strD}}
    </div>
</template>
<script setup>
import { onBeforeMount } from 'vue'
import emitter from '../plugins/Bus'
    let strD = ref('')
    onBeforeMount(()=>{
        emitter.on('fn',e=>{
            strD.value = e.value
        })
    })
</script>

 插槽

匿名插槽、具名插槽、作用域插槽、动态插槽名

 匿名插槽

//父组件
<Son>
    这个是XXXX数据
</Son>

//子组件
<div>
    <slot></slot>
</div>

具名插槽

//父组件
<Son>
    <template v-slot:yyy>  //这里可以简写<template #yyy>
        我是yyy数据
    </template>
</Son>

//子组件
<div>
    <slot name="yyy"></slot>
</div>

作用域插槽

让子组件在渲染时将一部分数据提供给插槽。

//父组件
<Son>
    <template v-slot='{data}'>  //这里可以简写成<template #default='{data}'>
        {{data}}
    </template>
</Son>

//子组件
<template>
    <div v-for="item in list" :key="item.id">
        <slot :data=item></slot>
    </div>
</template>
<script setup>
    let list = ref([
        {id:1,name:'张三'},
        {id:2,name:'李四'},
        {id:3,name:'王五'},
    ])
</script>

动态插槽名

//父组件
<template>
    <Son>
        <template v-slot:[xxx]>
            这个是XXXX数据
        </template>
    </Son>
</template>
<script setup>
    let xxx = ref('xxx')
</script>

Teleport传送

<Teleport> 是一个内置组件,它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。

<Teleport to="#modals">
  <div>A</div>
</Teleport>

//渲染结果为
<div id="modals">
  <div>A</div>
</div>

 <Teleport> 接收一个 to prop 来指定传送的目标。to 的值可以是一个 CSS 选择器字符串,也可以是一个 DOM 元素对象。

 动态组件

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

异步组件

在大型项目中,我们可能需要拆分应用为更小的块,并仅在需要时再从服务器加载相关组件。Vue 提供了 defineAsyncComponent,提升性能。

 vueuse:https://vueuse.org/core/useIntersectionObserver/#useintersectionobserver

下载安装:npm i @vueuse/core -S

使用场景1

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

<template>
    <A></A>
    <B></B>
    <div ref="target">
        <D v-if="targetIsVisible"></D>
    </div>
</template>
<script setup>
    import { useIntersectionObserver } from '@vueuse/core'
    import A from '../components/A.vue'
    import B from '../components/B.vue'
    const D = defineAsyncComponent(() =>
        import('../components/D.vue')
    )
    const target = ref(null)
    const targetIsVisible = ref(false)

    const { stop } = useIntersectionObserver(
      target,
      ([{ isIntersecting }]) => {
        targetIsVisible.value = isIntersecting
      },
    )
</script>

使用场景2

打包分包处理:打包完成后,异步组件有单独的js文件,是从主体js分包出来的

<template>
    <Suspense>
        <template #default>
            <A></A>
        </template>
        <template #fallback>
            Loading...
        </template>
    </Suspense>
</template>
<script setup>
    const A = defineAsyncComponent(() =>
        import('../components/A.vue')
    )
</script>

Mixin混入

Mixin 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个 mixin 对象可以包含任意组件选项。当组件使用 mixin 对象时,所有 mixin 对象的选项将被“混合”进入该组件本身的选项。

在src目录下创建mixins/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
    }
}

在B、C组件中使用,这里只展示其中一个的代码

<template>
    -----{{num}}---
    <button @click="favBtn">
        {{fav? '收藏中...' : '收藏'}}
    </button>
</template>
<script setup>
    import mixin from '../mixins/mixin.js'
    let {num,fav,favBtn} = mixin()
</script>

在选项式API中使用

export const fnaBtn = {
    data() {
        return {
            num:10
        }
    },
    methods: {
        favBtnAdd(params) {
            this.num += params
        }
    },
}
<template>
    <div>
        -----{{num}}-----
        <button @click="favBtnAdd(2)">获取</button>
    </div>
</template>
<script>
import {fnaBtn} from '@/mixins/mixin'
    export default {
        mixins:[fnaBtn]
    }
</script>

 Provide / Inject 依赖注入

1、provide() 接受两个参数:第一个参数是要注入的 key,可以是一个字符串或者一个 symbol,第二个参数是要注入的值。

2、inject()第一个参数是注入的 key。Vue 会遍历父组件链,通过匹配 key 来确定所提供的值。如果父组件链上多个组件对同一个 key 提供了值,那么离得更近的组件将会“覆盖”链上更远的组件所提供的值。如果没有能通过 key 匹配到值,inject() 将返回 undefined,除非提供了一个默认值。第二个参数是可选的,即在没有匹配到 key 时使用的默认值。

第二个参数也可以是一个工厂函数,用来返回某些创建起来比较复杂的值。在这种情况下,你必须将 true 作为第三个参数传入,表明这个函数将作为工厂函数使用,而非值本身。

组合式 API:依赖注入 | Vue.js

//提供
provide('count', count)

// 注入响应式的值
const count = inject('count')

Vuex

下载安装: 

# npm
npm install vuex@next --save

# yarn
yarn add vuex@next --save

 创建/src/store/index.js文件

import { createStore } from 'vuex'
import user from './modules/user'
export default createStore({
  state: { //存放数据的地方
  },
  getters:{//类似于组件的计算属性
  },
  mutations: {//状态更新的唯一方式:提交 mutation,store.commit('方法名')
  },
  actions: {//异步操作,通过dispatch 调用action 中定义的sum_action 方法
  },
  modules: {//模块儿化管理
    user
  }
})

在main.js文件中引入

import store from './store/index'
createApp(App).use(router).use(store).mount('#app')

 在组件中使用时

import { useStore } from 'vuex' // 引入useStore 方法
const store = useStore()  // 该方法用于返回store 实例
console.log(store.state.user.userInfo)  // store 实例对象
let numStore = computed(()=> store.state.numStore)
const btn = () =>{
    store.commit('changeNum',2)
    store.dispatch('changeBtn')
}

持久化存储vuex-persistedstate

下载安装:npm install vuex-persistedstate  --save

 在store/index.js中引用

import persistedstate from 'vuex-persistedstate';

在modules下面添加plugins配置


import { createStore } from 'vuex'
import persistedstate from 'vuex-persistedstate';
import user from './modules/user'
export default createStore({
  state: { //存放数据的地方
  },
  getters:{//类似于组件的计算属性
  },
  mutations: {//状态更新的唯一方式:提交 mutation,store.commit('方法名')
  },
  actions: {//异步操作,通过dispatch 调用action 中定义的sum_action 方法
  },
  modules: {//模块儿化管理
    user
  },
  plugins: [persistedstate({
    key: 'per-vuex',//浏览器中的名字
    paths:['user'] //需要存储起来的参数模块,不写存储的就是指当前文件内的参数
  })]
})

Pinia

 大致总结:

1、支持选项式API和组合式API写法

2、pinia没有mutations,只有:state、getters、actions

3、pinia分模块不需要modules

4、TypeScript支持更好

5、自动化代码拆分

6、pinia体积更小(性能更好)

7、pinia可以直接修改数据

Pinia🍍 官网👉:Pinia | The intuitive store for Vue.js 

 下载安装

npm install pinia
// or
yarn add pinia

在main.js中引入

import { createPinia } from 'pinia'
createApp(App).use(router).use(createPinia()).mount('#app')

在store/index.js中引入

import { defineStore } from "pinia";
export const useStore = defineStore('storeId', {
    state: () => {
        return {
            counter:0,
        }
    },
    getters: {//有缓存机制,几乎和vuex是一样的
    },
    actions: {//几乎也没有什么变化
        upNum(val) {
            this.counter += val
        }
    }
})

在组件中使用

import {useStore} from '../store/index'
const store = useStore();
console.log(store.counter);

修改数据

import {useStore} from '../store/index'
import {storeToRefs} from 'pinia'
const store = useStore();
let {counter} = storeToRefs(store)
const changBtn = ()=>{
    counter.value +=1
}

批量更新数据store.$patch()

import {useStore} from '../store/index'
import {storeToRefs} from 'pinia'
const store = useStore();
let {counter} = storeToRefs(store)
const changBtn = ()=>{
    store.$patch((state)=>{
        state.counter ++
    })
}

使用actions中提供的方法

const btnAdd = () =>{
    store.upNum(200)
}

模块儿化直接在store里创建模块儿化js文件,如user.js、shop.js等,shop.js文件内容在下面

在组件中引入使用,例如

import {shop} from '../store/shop'
import {storeToRefs} from 'pinia'
const shopStore = shop()
const {shopList} = storeToRefs(shopStore)

持久化存储

下载安装:

npm i pinia-plugin-persist --save

修改main.js文件

import store from './store/index'
createApp(App).use(router).use(store).mount('#app')

在store/index.js中

import { createPinia } from 'pinia'
import piniaPluginPersist from 'pinia-plugin-persist'

const store = createPinia()
store.use(piniaPluginPersist)

export default store

在store/shop.js中配置

import { defineStore } from "pinia";
export const shop = defineStore('shopId', {
    state: () => {
        return {
            shopList: [
                { id: 1, name: '苹果' },
                { id: 2, name: '橘子' },
            ]
        }
    },
    getters: {
    },
    actions: {
    },
    persist: {//开启数据缓存
        enabled: true,
        strategies: [
            {
                key: 'my-shop',
                storage: localStorage,
                paths:['shopList'] //默认是全部需要持久化,也可以通过该属性指定
            }
        ]
    }
})

设置代理

在vite.config.js中添加

//设置代理解决跨域问题
  server: {
    proxy: {
      '/api':'所代理的地址'
    }
  }

在src目录下创建utils/request.js文件

import axios from 'axios'
//创建axios对象
const service = axios.create();
//请求拦截器
service.interceptors.request.use(config => {
    return config;
}, error => {
    Promise.reject(error)
});

//响应拦截
service.interceptors.response.use(response => {
    //判断code码
    return response.data
}, error => {
    return Promise.reject(error)
});
export default service;

在src目录下创建api文件夹,里面添加需要添加的API接口,例如

import request from '../utils/request'
export function getSliders() {
    return request({url:'/api/slider/getSliders'})
}

在组件中使用

import {getSliders} from '../api/slider'
    let list = ref([]);
    onBeforeMount(()=>{
        getSliders().then(res=>{
            list.value = res.data.list
            for (let index = 0; index < list.value.length; index++) {
                if(index == 0){
                    list.value.splice(0,1)
                }
            }
        })
    })
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值