Vue3.2+Pinia+VueRouter+Vite+TS环境搭建及知识点学习

一.环境搭建

1.搭建vite项目
npm init vite@latest my-project
cd my-project
npm install
npm run dev
2.安装vue-router
npm install vue-router@next --save

在src目录下创建router文件夹,在文件夹下创建index.ts

import { createRouter,Router, createWebHistory, RouteRecordRaw } from 'vue-router';
const history = createWebHistory()
const routes: Array<RouteRecordRaw> = [
  {
    path: '/',
    name: '/index',
    component: () => import('../views/Index.vue'),
    meta:{
        title:'首页'
    }
  },
];
const router: Router = createRouter({
   history:createWebHistory('/'),
  routes,
})
export default router

在main.ts中引入router

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

将App.vue中内容替换

<router-view />
3.安装Sass
npm install --save-dev sass
就这么一句安装就可以使用了,用vuecli的时候,还要安装sass-loader、node-sass等,但是vite只需要安装sass就可以了
4.安装pinia
npm install pinia@next

数据持久化,可使用插件pinia-plugin-persist
npm install pinia-plugin-persist -S

import {createPinia} from "pinia"
//需要注意的是从pinia中结构出来的createPinia是一个函数,挂载需要先调用执行
import piniaPersist from 'pinia-plugin-persist'
const store =createPinia()
store.use(piniaPersist)

app.use(createPinia())
// index.js

import {defineStore} from "pinia"
export const useAppStore = defineStore("app",{
    state:()=>{
        return{
            count:1,
            number:1
        }
    },
    getters:{
        bdCount(){
            return  this.count*2
        }
    },
    actions:{
        increment(num){
            this.count=Math.round(100 * Math.random()*num)
        }
    },
    persist: {
        enabled: true,
        //通过修改配置项,改成localStorage
        strategies: [
            {
                storage: localStorage,
                //paths: ["count"], //可以通过paths字段指定需要缓存的数据,如不指定paths,则默认是缓存所有
            },
        ],
    }
})

pinia使用


<template>
  <NavbarHeader :msg="msg" />
  我是index里面的值:{{count}} <br /><br />
  我是getter里面的值:{{bdCount}}<br />
  <button @click="change1">修改store.name</button><br />

  <button @click="reset">reset</button><br />
  <button @click="moreEdit">批量修改state数据</button><br />
  <button @click="fatherAction">父组件里面调用action</button><br />
</template>
<script setup lang="ts">
import NavbarHeader from '@/components/index/NavbarHeader.vue'
import { ref, reactive } from 'vue'

import { storeToRefs } from 'pinia'
import { useAppStore } from '@/store/index.js'
const store = useAppStore()

const { count } = storeToRefs(store)
const { bdCount } = storeToRefs(store)

let change1=()=>{store.count++}
let reset=()=>{store.$reset()}
let moreEdit=()=>{
  store.$patch({
    count:99,
    number:99
  })
}
let fatherAction=()=>{store.increment(2)}

let msg=ref('大家好才是真的好')
</script>
<style lang="scss" scoped>

</style>
5.安装axios
  1. 安装axios,然后main.ts文件挂载axios

npm install axios

import Axios from 'axios'
createApp( App ).config.globalProperties.Axios = Axios
  1. 创建utils目录并封装request.ts

import axios, {AxiosInstance, AxiosRequestConfig} from 'axios'
import { $serverUrl } from "../config"
class HttpRequest {
    private readonly baseUrl: string;
    constructor() {
        this.baseUrl = $serverUrl
    }
    getInsideConfig() {
        const config = {
            baseURL: this.baseUrl,
            headers: {
                //
            }
        }
        return config
    }

// 请求拦截
    interceptors(instance: AxiosInstance, url: string | number | undefined) {
        instance.interceptors.request.use(config => {
            // 添加全局的loading..
            // 请求头携带token
            return config
        }, (error: any) => {
            return Promise.reject(error)
        })
        //响应拦截
        instance.interceptors.response.use(res => {
            //返回数据
            const {data} = res
            return data
        }, (error: any) => {
            console.log('error==>',error)
            return Promise.reject(error)
        })
    }

    request(options: AxiosRequestConfig) {
        const instance = axios.create()
        options = Object.assign(this.getInsideConfig(), options)
        this.interceptors(instance, options.url)
        return instance(options)
    }
}

const http = new HttpRequest()
export default http
  1. 创建apis目录编写请求文件

import http from '../utils/request'

export function getFileList() {
  return http.request({
    url: '/lencon/pc/index/school_type',
    method: 'post'
  })
}
export function saveSysEventType (parameter:any) {
  return http.request({
    url: '/lencon/pc/index/titleImg',
    method: 'post',
    data: parameter,
    headers: {
      'Content-Type': 'application/json;charset=UTF-8'
    }
  })
} 
  1. 页面请求编写

<script setup lang='ts'>
import { getFileList, saveSysEventType} from '../apis/index'
import { ref, isRef, reactive,onMounted } from 'vue' 
interface IndexData{
  dirct:any
  imgUrl:string
}
const data = reactive<IndexData>({
   dirct:[],
   imgUrl:''
})
onMounted(() => {
   dirct()
   dircts() 
});
const dirct = ()=>{
  getFileList().then((res:any) => {
    data.dirct = res.data
  })
}
const dircts = ()=>{
  let datas={id:9}
  saveSysEventType(datas).then((res:any) => {
    data.imgUrl = res.res
  })
} 
</script>
6.如果 vue3 报错提示 找不到模块“./XXX.vue”或其相应的类型声明
在src根目录下创建一个后缀为env.d.ts的文件

declare module '*.vue' {
    import type { DefineComponent } from 'vue'
    // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
    const component: DefineComponent<{}, {}, any>
    export default component
}

declare module '@/store/index.js'

完整vite.config.ts

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
//npm install @types/node --save-dev
import path from 'path' 
//vite打包性能优化之开启Gzip压缩
import viteCompression from "vite-plugin-compression"; //npm i vite-plugin-compression -D 
export default defineConfig({
  server:{
    host: '0.0.0.0',
    port: 3000,
    strictPort: false, // 若3000端口被占用,是否直接结束项目
    https: false, // 是否开启 https
    open: true, // 是否自动在浏览器打开
    proxy: {
      "/api": {
        target: "http://pro.dangjian.link",
        changeOrigin: true,
        secure: false, // 如果是https接口,需要配置这个参数
        // ws: true, // websocket是否支持
        rewrite: (path) => path.replace(/^\/api/, ""),
      },
    }
  },
  build: {
    target: "es2020", //指定es版本,浏览器的兼容性
    outDir: "dist",
    assetsDir: "assets", // 指定静态资源存放路径
    // cssCodeSplit: true, // css代码拆分,禁用则所有样式保存在一个css里面
    sourcemap: false, // 是否构建source map 文件
    // manifest: true, // 是否在outDir中生成 manifest.json
    rollupOptions: {
      // input: '/path/to/main.ts' // 覆盖默认的 .html 入口
    },
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true,
      },
    }
  },
  resolve: { 
    // 配置项目路径别名,在开发时不需要写完整的路径名称,需要安装 @types/node,执行命令npm i -D @types/node --save-dev即可
    alias: {
      "@": path.resolve(__dirname, "src"),
      "@assets": path.resolve(__dirname, "src/assets"),
      "@components": path.resolve(__dirname, "src/components"),
    },
  },
  optimizeDeps: {
    include: [],
  },
  plugins: [
    vue(),
    viteCompression({
      verbose: true,
      disable: false,
      threshold: 10240,
      algorithm: "gzip",
      ext: ".gz",
    })
  ]
})
7.使用Animate.css和hover.css
npm install animate.css --save
import 'animate.css';

<div class="animated swing">Example</div>

Animate.css官网
https://animate.style/
npm i hover.css --save 
import 'hover.css'

<div class="hvr-bounce-to-left">Example</div>

hover.css官网
http://ianlunn.github.io/Hover/
8.封装全局方法
main.ts 

const checkImg = (value:string) => {
    if (!value) return ''
    if(value.substring(0,4)!='http'){
      return $serverUrl+value;
    }else{
      return value;
    }
}
app.config.globalProperties.$checkImg = checkImg

组件内使用
import { getCurrentInstance } from 'vue'
const { $checkImg } = currentInstance.appContext.config.globalProperties

<img :src="$checkImg(item.news_headpic)" alt="">
9.1安装element-plus(全局引入)
npm install element-plus --save


html 引入
<link rel="stylesheet" href="//unpkg.com/element-plus/dist/index.css" />
<!-- Import Vue 3 -->
<script src="//unpkg.com/vue@3"></script>
<!-- Import component library -->
<script src="//unpkg.com/element-plus"></script>

import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)

安装icon

npm install @element-plus/icons-vue

import * as ElementPlusIconsVue from '@element-plus/icons-vue'

const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}
9.2按需导入elementPlus
  1. 按需导入需要再继续安装两个插件

npm install -D unplugin-vue-components unplugin-auto-import
  1. 在项目的vite.config.ts中引入并配置plugins

// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default {
  plugins: [
    // ...
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
}
9.3按需导入elementPlus使用icons。且按需引入icnon
  1. 正常使用icon

import * as Icons from "@element-plus/icons-vue";
Object.keys(Icons).forEach((key) => {app.component(key, Icons[key as keyof typeof Icons]);});
 <el-icon><component is="Loading"></component></el-icon>
  1. 按需引入icon

安装插件


npm install -D unplugin-auto-import unplugin-icons

main可以去掉全局引入


// import * as Icons from "@element-plus/icons-vue";
 
const app = createApp(App)
// Object.keys(Icons).forEach((key) => {app.component(key, Icons[key as keyof typeof Icons]);});

vite.config.ts中修改


import Icons from 'unplugin-icons/vite'
import IconsResolver from 'unplugin-icons/resolver'
 
plugins: [
    vue(),
    AutoImport({
      // Auto import functions from Vue, e.g. ref, reactive, toRef...
      // 自动导入 Vue 相关函数,如:ref, reactive, toRef 等
      imports: ['vue'],
      // Auto import functions from Element Plus, e.g. ElMessage, ElMessageBox... (with style)
      // 自动导入 Element Plus 相关函数,如:ElMessage, ElMessageBox... (带样式)
      resolvers: [
        ElementPlusResolver(),
        // Auto import icon components
        // 自动导入图标组件
        IconsResolver({
          prefix: 'Icon',
        }),
      ],
      dts: path.resolve(path.resolve(__dirname,'src'), 'auto-imports.d.ts'),
    }),
    Components({
      resolvers: [
        // Auto register icon components
        // 自动注册图标组件
        IconsResolver({
          enabledCollections: ['ep'],
        }),
        // Auto register Element Plus components
        // 自动导入 Element Plus 组件
        ElementPlusResolver(),
      ],
 
      dts: path.resolve(path.resolve(__dirname,'src'), 'components.d.ts'),
    }),
    Icons({
      autoInstall: true,
    }),
    // AutoImport({
    //   resolvers: [ElementPlusResolver()],
    // }),
    // Components({
    //   resolvers: [ElementPlusResolver()],
    // }),
    vueSetupExtend (),
  ]

页面使用


<el-icon><i-ep-circle-check-filled /></el-icon> 
10.安装 Ant Design of Vue(Ant Design Vue — An enterprise-class UI components based on Ant Design and Vue.js
npm i --save ant-design-vue

import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';

const app = createApp(App);

app.use(Antd).mount('#app');

如果你使用的 Vite,你可以使用 unplugin-vue-components 来进行按需加载。但是此插件无法处理非组件模块,如 message,这种组件需要手动加载:

import { message } from 'ant-design-vue';
import 'ant-design-vue/es/message/style/css'; //vite只能用 ant-design-vue/es 而非 ant-design-vue/lib
10.1安装Swiper@6使用
npm install swiper@6 --save
import SwiperCore, {Autoplay,Pagination} from 'swiper';
import {Swiper,SwiperSlide} from 'swiper/vue';
import 'swiper/swiper.min.css';
import 'swiper/components/pagination/pagination.scss';
SwiperCore.use([Autoplay,Pagination]);

export default {
        components: {
            Vue3SeamlessScroll,Swiper, SwiperSlide
        },
        setup () {
            const store = useStore()
            let swiper_options = reactive({
                autoplay:{
                    delay:8000,
                    disableOnInteraction: true
                },
                loop:true,
                speed:1000,    
                pagination:{
                    clickable: true
                }
            })
            const data = reactive<Table>({
                tableData:[],
                page:1,
                totalPages:1,
                deptId: computed(() => {
                    return store.getters.deptId
                }),
                async getData () {
                    let { deptId } = data
                    let res = await  Axios.post("/lencon/bigData/learning/problem", { deptId,page:data.page,pageSize:5 })
                    if ( res.data.code == 1 ) {
                        data.tableData = res.data.res
                        data.totalPages= res.data.totalPages
                    }
                },
            })
            const onSwiper = (swiper:any) => {
                // if(data.page==data.totalPages){
                //     data.page=1
                // }else{
                //     data.page+=1
                // }
                // data.getData()
            };
            const onSlideChange = () => {
                if(data.page==data.totalPages){
                    data.page=1
                }else{
                    data.page+=1
                }
                data.getData()
            };
 
            watch(() => data.deptId, () => {
                data.getData()
            })
            // 初始化表格
            onMounted(() => {
                data.getData()
            })
     return {
       ...toRefs( data ),
        swiper_options,onSwiper,onSlideChange
   }
 }
10.2 Vue3SeamlessScroll无缝滚动
npm install vue3-seamless-scroll --save

import vue3SeamlessScroll from 'vue3-seamless-scroll';
const app = createApp(App);
app.use(vue3SeamlessScroll)

vue3-seamless-scroll - npm

11.集成echarts
npm i echarts --save

使用 可以采用这位大佬的笔记 懒得写了  哈哈
https://blog.csdn.net/sunddy_x/article/details/124432548

补充 vite 引入文件

{ iconClass: new URL('@/assets/images/index/index_right_2.png', import.meta.url).href}
12. vite-plugin-vue-setup-extend此插件解决了:使用setup语法糖的时候没办法直接为组件定义name(页面缓存需要name属性)
npm i vite-plugin-vue-setup-extend -D

在vite.config.ts配置

import vueSetupExtend from 'vite-plugin-vue-setup-extend'
plugins: [
    vueSetupExtend ()
    ]
<script lang="ts" setup name="自定义name">

</script>

完整的main.ts

import { createApp } from 'vue'

import App from './App.vue'
import router from './router/index'
import Login from '@/components/common/Login.vue'

import ElementPlus from 'element-plus'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import 'element-plus/dist/index.css'
import 'animate.css'
import 'hover.css'
import {createPinia} from "pinia"
import piniaPersist from 'pinia-plugin-persist'
const store =createPinia()
store.use(piniaPersist)

import Axios from 'axios'
import { $serverUrl } from "./config"
const checkImg = (value:string) => {
    if (!value) return ''
    if(value.substring(0,4)!='http'){
      return $serverUrl+value;
    }else{
      return value;
    }
}

const app = createApp(App)
app.config.globalProperties.$serverUrl = $serverUrl 
app.config.globalProperties.Axios = Axios
app.config.globalProperties.$checkImg = checkImg
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}

app.component('Login',Login).use(ElementPlus).use(router).use(store).mount('#app')
13.引入插件时候报错:无法找到模块“pinia”的声明文件等。

可能是TS升级到5.x带来的规范性问题

把tsconfig.json配置项moduleResolution:"bundler"改为 moduleResolution:"node"就可以了

("moduleResolution": "node" 表示 模块化查找的时候按照nodejs方式进行查找。"moduleResolution": "bundler" 表示 打包工具的模块解析策略来查找。 TS升级到5.x,默认"moduleResolution": "bundler" )

二、组件通讯

  1. 父子向子组件传值

父组件
<navbar-header ref="navbarheader" :jsonData="data.dirct" 
      @editclick="editclick" @emitclick="emitclick" @removeclick="removeclick">
  </navbar-header>
子组件
defineProps<{
   jsonData:any
}>();
  1. 子组件向父组件传值

子组件
const emit = defineEmits(['addclick','removeclick','editclick'])
const addFather = ()=>{
emit('addclick', {name: "干部培训666",type:22} )
}
const removeFather = (e:String,index:number)=>{
emit('removeclick', {id:e,type:index} )
}
const editFather = (e:object,index:number)=>{
emit('editclick', {...e,index:index} )
}

父组件
const addclick = (obj:object)=>{
  data.dirct.push(obj)
}
const removeclick = (e:any)=>{
  data.dirct.splice(e.type,1)
}
const editclick = (e:any)=>{
  data.dirct[e.index].name='干部培训a:'+e.type
}
  1. 父组件获取子组件内的值及修改子组件的值

父组件

我是子组件内的值:{{data.sondata}}
<el-button type="info" mini @click="editSonData">获取子组件值</el-button>

let navbarheader = ref()
const getSonData= ()=>{
  data.sondata=navbarheader.value.sonName
}
const editSonData= ()=>{
  navbarheader.value.editSon(5)
}

子组件(切记一定要defineExpose出来)
const counts = ref(0)
const editSon = (e:number)=>{
  counts.value+=e
}
const sonName = ref("JackSon");
defineExpose({
sonName,
editSon
})

完整代码

父组件
<template>
  <img :src="data.imgUrl" draggable="false" />
  <br />
  <navbar-header ref="navbarheader" :jsonData="data.dirct" 
      @editclick="editclick" @addclick="addclick" @removeclick="removeclick">
  </navbar-header>
  <hr />
  我是子组件内的值:{{data.sondata}}
  <el-button type="info" mini @click="editSonData">获取子组件值</el-button>
</template>

<script setup lang='ts'>
import { getFileList, saveSysEventType} from '../apis/index'
import { ref, isRef, reactive,onMounted } from 'vue'
import NavbarHeader from '@/components/index/NavbarHeader.vue'
interface IndexData{
  dirct:any
  imgUrl:string
  sondata:any
}
const data = reactive<IndexData>({
   dirct:[],
   imgUrl:'',
   sondata:{},
})
onMounted(() => {
   dirct()
   dircts()
   getSonData()
});
let navbarheader = ref()
const getSonData= ()=>{
  data.sondata=navbarheader.value.sonName
}
const editSonData= ()=>{
  navbarheader.value.editSon(5)
}
const dirct = ()=>{
  getFileList().then((res:any) => {
    data.dirct = res.data
  })
}
const dircts = ()=>{
  let datas={id:9}
  saveSysEventType(datas).then((res:any) => {
    data.imgUrl = res.res
  })
}
const addclick = (obj:object)=>{
  data.dirct.push(obj)
}
const removeclick = (e:any)=>{
  data.dirct.splice(e.type,1)
}
const editclick = (e:any)=>{
  data.dirct[e.index].name='干部培训a:'+e.type
}
</script>
<style lang='scss' scoped>

</style>
子组件
<template>
    <div class="card">
      <button type="button" @click="counts++">我是父组件改变的值:{{ counts }}</button>
    </div> 
    <div class="item" v-for="(item ,index) in jsonData" :key="index">
      {{item.name}}
      <el-button type="success" @click="editFather(item,index)">修改父组件的值</el-button>
       <el-button type="danger" @click="removeFather(item.name,index)">删除父组件的值</el-button>
    </div><br />
    <el-button type="primary" @click="addFather">增加父组件的值</el-button><br />
    
    <br />
  </template>
  <script setup lang="ts">
  import { ref,isRef } from 'vue'   
  defineProps<{
     jsonData:any
  }>(); 
  const counts = ref(0)

  const emit = defineEmits(['addclick','removeclick','editclick'])
  const addFather = ()=>{
    emit('addclick', {name: "干部培训666",type:22} )
  }
  const removeFather = (e:String,index:number)=>{
    emit('removeclick', {id:e,type:index} )
  }
  const editFather = (e:object,index:number)=>{
    emit('editclick', {...e,index:index} )
  }
  const editSon = (e:number)=>{
      counts.value+=e
  }
  const sonName = ref("JackSon");
  defineExpose({
    sonName,
    editSon
  })
  </script>
  
  <style scoped>
  .read-the-docs {
    color: #888;
  }
  .item{
    margin: 10px;
  }
  </style>
  
  1. 补充(兄弟组件传值)

1.安装mitt
npm install --save mitt

2.创建一个js文件 比如 utils/bus.ts

import mitt from 'mitt'
const emiiter = mitt()
export default emiiter

3.组件中使用
组件A传值
import emitter from '../../utils/bus'
emitter.emit('handleEvent', ref({show:true}))

组件B收值
import { ref, reactive, onMounted, computed, watch, onBeforeMount, onUnmounted } from 'vue'  
import emitter from '../../utils/bus'

onBeforeMount(() => {
    // 开启监听,监听到handleEvent事件后,执行handleEvent函数
    emitter.on('handleEvent',handleEvent) 
})

const handleEvent = (Aname:any)=>{
   navData.NeedLogin = Aname.value.show
}

onUnmounted(() => {
    emitter.off('handleEvent',handleEvent)//  取消handleEvent事件的handelEventFn函数监听
    emitter.off('handleEvent')//  取消handleEvent事件的全部处理函数的监听
})
  1. 多层组件传值(Provide / Inject)

import { provide, ref } from'vue'

let flag = ref<number>(1)
provide('flag', flag)

子组件接收
import { inject, Ref, ref } from 'vue'
const flag = inject<Ref<number>>('flag', ref(1))

修改值
const change = () => {
    flag.value = 2
}

三、计算属性

购物车案例

<template>
  <el-table :data="tableData" style="width: 100%">
    <el-table-column prop="name" label="名字" width="180" />
    <el-table-column label="数量">
      <template #default="scope">
        <el-input-number v-model="scope.row.num" :min="1" :max="10" @change="handleChange" />
      </template>
    </el-table-column>
    <el-table-column prop="price" label="价格(元)" />
    <el-table-column label="操作">
       <template #default="scope">
        <el-button size="small" @click="handleEdit(scope.$index, scope.row)">Edit</el-button>
        <el-button size="small" type="danger" @click="handleDelete(scope.$index, scope.row)">Delete</el-button>
       </template>
    </el-table-column>
  </el-table>
  <div>合计:{{total}}</div> <br />
  <div>合计:{{token}}</div><br />
  <el-button type="primary" plain @click="setToken">pinia设置token</el-button><br />
  <el-button type="danger" plain @click="remToken">pinia清除token</el-button>
</template>

<script setup lang='ts'>
import { ref, reactive, computed } from 'vue'
import { ElMessage } from 'element-plus'
import { storeToRefs } from 'pinia'
import { useAppStore } from '@/store/index.js'
import { saveSysEventType} from '../apis/index'
const store = useAppStore()
interface User {
  name: string
  num: number
  price: number
}
const tableData = reactive<User[]>([
  {name: "袜子",num: 1,price: 10},
  {name: "鞋",num: 1,price: 100},
  {name: "裙子",num: 1,price: 200},
  {name: "裤子",num: 1,price: 150},
])
const handleEdit = (index: number, row: User) => {
  ElMessage({
    message: '你点击了修改按钮: '+row.name,
    type: 'success',
  })
}
const handleDelete = (index: number, row: User) => {
  tableData.splice(index,1)
}

let total = ref<number>(0)
total = computed<number>(() => {
   return tableData.reduce((prev, next) => {
      return prev + (next.num * next.price)
   }, 0)
})
const handleChange = (value: number) => {
  
}
const token=computed(()=>{
   return storeToRefs(store).token.value
})
const setToken= ()=>{
  store.setToken(20)
  dircts()
}
const remToken = ()=>{
  store.setToken(0)
}
const dircts = ()=>{
  let datas={id:9}
  saveSysEventType(datas).then((res:any) => {
     console.log(res)
  })
}
</script>
<style lang='scss' scoped>

</style>

四、监听属性watch和watchEffect

watch

watch(token,(newVal,oldVal)=>{
  console.log('新的值----', newVal);
  console.log('旧的值----', oldVal);
},{
  immediate:true,
  deep:true
})
watch([total,token],(newVal,oldVal)=>{
  console.log('新的值----', newVal);
  console.log('旧的值----', oldVal);
},{
  immediate:true
})

immediate的值为true时表示非惰性的立即执行的(默认情况下是false)
deep深层次触发(此处设置deep无意义)
  1. 监视所定义的响应式数据和多个响应式数据

  1. 监听reactive对象

1.若监视的是reactive定义的响应式数据,则无法正确获得oldValue
2.若监视的是reactive定义的响应式数据,则watch会强制开启深度监视
let person = reactive({
    name:'张三',
    age:'18',
    job:{
        j1:{
            salary:20
        }
    }
})
watch(()=>person.name,(newValue,oldValue)=>{
    console.log('person的job改变了',newValue,oldValue)
})
 watch(()=>person.age,(newValue,oldValue)=>{
    console.log('person的job改变了',newValue,oldValue)
})
watch(()=>person.job,(newValue,oldValue)=>{
    console.log('person的job改变了',newValue,oldValue)
})
//从上边我们发现改变name,age都会触发监听,但是改变job不会
//这是因为name和age属性的值只是一个简单的基本类型数据,
//而job属性的值是一个对象,比较深,想要监视到,就要开启深度监视,程序如下:
watch(()=>person.job,(newValue,oldValue)=>{
    console.log('person的job改变了',newValue,oldValue)
},{deep:true})//此时job改变,会被监视到,此处的deep配置生效
//需要和情况三进行区分,此处watch监视的是reactive所定义的对象中的某个属性,而情况三watch监视的是reactive所定义的对象

//情况五:监视reactive所定义的响应式数据中的某些属性,写成数组的形式
watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{
    console.log('person的name或age改变了',newValue,oldValue)
})

watchEffect立即执行,没有惰性

watchEffect函数内部所指定的回调中用到的数据只要发生变化,就会重新执行回调

watchEffect(()=>{
  console.log('新的值----', token.value);
})

对比

是否有惰性

参数

获得值

watch

有惰性,数值再次改变后执行监听函数

可以侦听多个数据的变化

参数可以拿到当前值和原始值

watchEffect

立即执行没有惰性

不需要传递侦听内容,自动感知代码依赖

不需要传递到很多参数,不能获取原始值

五、Vue3生命周期

onBeforeMount()
在组件DOM实际渲染安装之前调用。在这一步中,根元素还不存在。
onMounted()
在组件的第一次渲染后调用,该元素现在可用,允许直接DOM访问
onBeforeUpdate()
数据更新时调用,发生在虚拟 DOM 打补丁之前。
onUpdated()
DOM更新后,updated的方法即会调用。
onBeforeUnmount()
在卸载组件实例之前调用。在这个阶段,实例仍然是完全正常的。
onUnmounted()
卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。

六、全局组件,递归组件,动态组件

  1. 全局组件的使用

  1. 递归组件的使用

父组件
<template>
  <tree-data :data="data" @edit-father="editFather"></tree-data>
</template>

<script setup lang='ts'>
import TreeData from '@/components/common/TreeData.vue'
import { ref, reactive } from 'vue'
type treeData = {
  name: string,
  icon?: string,
  children?: treeData[] | []
}
const data = reactive<treeData[]>([
  {
    name: '陕西省',
    children: [
      {
        name: '西安市',
        children: [
          {
            name: '长安区',
            children: [
              {
                name: '郭杜镇'
              },
              {
                name: '大学城'
              },
            ]
          },
          {
            name: '雁塔区'
          },
          {
            name: '高新区',
            children: [
              {
                name: '科技园'
              },
            ]
          }
        ]
      },
    ]
  },
  {
    name: '四川省',
    children: [
      {
        name: '成都市',
        children: [
          {
            name: '高新区'
          }
        ]
      },
    ]
  },
  {
    name: '北京市',
    children: []
  }
])
const editFather = (item:treeData)=>{
    console.log(item);
}
</script>
<style lang='scss' scoped>

</style>
子组件
<template>
<div>
    <div v-for="(item, index) in data" :key="index">
        <div style="margin-left: 20px;cursor: pointer;" @click.stop="handleTreeItem(item)">
        {{ item.name }}
         <TreeData @edit-father="handleTreeItem" v-if="item?.children?.length" :data="item.children"></TreeData>
        </div>
    </div>
</div>
</template>

<script setup lang='ts'>
defineProps<{
    data:any
}>()
type treeData = {
  name: string;
  icon?: string;
  children?: treeData[] | [];
};
const emit = defineEmits(['edit-father'])
const handleTreeItem=(item:treeData)=>{
   emit('edit-father',item)
}
</script>
<style lang='scss' scoped>

</style>
  1. 动态组件

<template>
    <ul class="tab_list">
        <li :class="active==index?'active':''" @click="changeComponent(item,index)" v-for="(item, index) in data" :key="index">
            {{ item.name }}
        </li>
    </ul>
  <component :is="current.comName"></component>
</template>

<script setup lang='ts'>
import { ref, reactive,markRaw } from 'vue'
import ComponentsOne from '@/components/Dynamic/ComponentsOne.vue'
import ComponentsTwo from '@/components/Dynamic/ComponentsTwo.vue'
import ComponentsThree from '@/components/Dynamic/ComponentsThree.vue'

type tabs= {
    name: string,
    comName: any
}
let active=ref<number>(0)
//建议我们使用组件跳过代理  markRaw  使用的时候我们会打印出__v_skip:true;节省性能
const data =reactive(
    [{name: 'A',comName: markRaw(ComponentsOne)},
     {name: 'b',comName: markRaw(ComponentsTwo)},
     {name: 'c',comName: markRaw(ComponentsThree)},
    ]
)
let current = reactive({
    comName: data[0].comName
})
const changeComponent = (item:tabs,index:any)=>{
    active = index
    current.comName = item.comName
}
</script>
<style lang='scss' scoped>
.tab_list{
    li{
        cursor: pointer;
    }
    .active{
        color: #f00;
    }
}
</style>

知识点补充 markRaw 和 toRaw

七、插槽

  1. 可以使用v-slot: 或者简写为# 进行插槽

<template>
   <son-slot>
        <template v-slot:header>
            <div>1</div>
        </template>
        <template v-slot>
            <div>2</div>
        </template>
        <template #footer>
            <div>3</div>
        </template>
   </son-slot>
</template>

<script setup lang='ts'>
import SonSlot from '@/components/slot/SonSlot.vue' 
</script> 
<template>
<slot name="header"></slot>
<slot></slot>
<slot name="footer"></slot> 
</template> 
  1. 作用域插槽

<template>
   <son-slot> 
        <template #defaults="{ data }">
            <div>{{ data }}</div>
        </template>

        <template #[name]>
            <div>我是动态插槽</div>
        </template>
   </son-slot>
</template>

<script setup lang='ts'>
import SonSlot from '@/components/slot/SonSlot.vue' 
</script> 
<template>
<div>
    作用域插值
    <div v-for="item in 20">
        <slot :data="item" name="defaults"></slot>
    </div>
</div> 
</template> 
  1. 动态插槽

<template>
   <son-slot>  
        <template #[name]>
            <div>我是动态插槽</div>
        </template>
   </son-slot>
</template>

<script setup lang='ts'>
import SonSlot from '@/components/slot/SonSlot.vue'
import { ref, reactive } from 'vue'
let name =ref('dongtai')
</script> 
<template> 
<slot name="dongtai"></slot>
</template> 

八、路由跳转及路由传参

  1. 路由跳转

router.ts

{
    path: '/newsDetail/:id',
    name: '/newsDetail',
    component: () => import('@/view/NewsDetail.vue'),
},

router.push({
    path: `/newsDetail/${item.news_id}`,
    query: {
        cateName: props.cateName
    }
})
  1. 路由传参

import { useRoute, useRouter } from 'vue-router'
const router = useRouter()
const route = useRoute()

console.log(route.query.cateName)
console.log(route.params.id)

九、keep-alive

开启keep-alive 生命周期的变化

初次进入时: onMounted> onActivated

退出后触发 deactivated

再次进入:

只会触发 onActivated

事件挂载的方法等,只执行一次的放在 onMounted中;组件每次进去执行的方法放在 onActivated中

十、Vue3.x中的自定义hook函数是什么?

  • 使用Vue3的组合API封装的可复用的功能函数

  • 自定义hook的作用类似于vue2中的mixin技术

  • 自定义Hook的优势: 很清楚复用功能代码的来源, 更清楚易懂.

mixins的缺点:

1.变量来源不明确(隐式传入),不利于阅读,使代码变得难以维护。
2.多个mixins的生命周期会融合到一起运行,但是同名属性、同名方法无法融合,可能会导致冲突。
3.mixins和组件可能出现多对多的关系,复杂度较高(即一个组件可以引用多个mixins,一个mixins也可以被多个组件引用)。

补充知识点

declare 是声明的关键字,通常用在编写声明文件,你可以通过 declare 去声明一个变量、声明一个函数等等,一旦声明了,TypeScript 编译器就知道它的存在,你就可以在任何地方使用了。

declare type AsideState = {
    menuList: RouteRecordRaw[];
    clientWidth: number;
};

// columnsAside
declare type ColumnsAsideState<T = any> = {
    columnsAsideList: T[];
    liIndex: number;
    liOldIndex: null | number;
    liHoverIndex: null | number;
    liOldPath: null | string;
    difference: number;
    routeSplit: string[];
};

const state = reactive<AsideState>({
    menuList: [],
    clientWidth: 0,
});

十一、Teleport将其插槽内容渲染到 DOM 中的另一个位置


<template>
    <el-button type="primary" @click="testClick">test</el-button>
    <teleport to='body' v-if="showTeleport">
        <div class="m-bg">
            <div class="m-title">
                  测试teleport
            </div>
            <div class="m-body">
                <el-button @click="closeTeleport">关闭teleport</el-button>
            </div>
        </div>
    </teleport>
  </template>
  
  <script lang="ts" setup>
  const showTeleport = ref<boolean>(false)
  const testClick = () => {
    showTeleport.value = true;
  }
  const closeTeleport = () => {
    showTeleport.value = false;
  }
</script>
<style scoped lang="scss">
.m-bg{
    width: 100%;
    height: 100%;
    background-color:rgba(5, 5, 5, 0.7);
    position: absolute;
    top: 0;
    left: 0;
    z-index: 99;
    .m-title{
        color: #fff;
    }
}
</style>

十二、vue3集成使用echarts

1.安装echarts

npm install echarts --save

2.在页面引入

import * as echarts from "echarts";

3.初始化方法

(类型“HTMLElement | null”的参数不能赋给类型“HTMLElement”的参数。 不能将类)

var myChart = echarts.init(document.getElementById('allmetting') as HTMLElement);

另外:vite.config.ts的打包优化请移步:Vite项目打包优化

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值