vue

Vue介绍

介绍Vue

img1

Vue是一套用于构建用户界面的渐进式框架。与其他大型框架不同的是,Vue被设计为可以自底向上逐层应用。Vue的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。

MVVM(Model-View-ViewModel)架构

  1. View:视图层(UI用户界面)
  2. ViewModel:业务逻辑层(一切js可视为业务逻辑)
  3. Model:数据层(存储数据及对数据的处理如增删改查)

View是作为视图层,简单来说可以把它理解为HTML页面;Model 是作为模型层,它是负责处理业务逻辑以及和服务器端进行交互的;ViewModel 是作为视图模型层,也就是 Vue 框架所起到的作用了,主要是作为 View 层和 Model 层之间的通信桥梁。

为什么学习Vue

  1. Vue是目前前端最火的框架之一
  2. Vue是目前企业技术栈中要求的知识点
  3. Vue可以提升开发体验
  4. Vue学习难度较低

环境搭建

安装nvm/nodejs

window下安装并使用nvm(含卸载node、卸载nvm、全局安装npm)_window安装nvm-CSDN博客

在vsCode中安装插件

搜索volar

如果使用vue2需要将vetur

Volar为vue文件提供代码高亮和语法支持。

vsCode汉化

  1. 打开VSCode——输入快捷键“Ctrl+shift+p”,打开命令行输入框,在输入框中输入“configure display language”。
  2. 选择“Install additional languages...”。
  3. 在出现的扩展插件列表中找到“Chinese(Simplified)Language Pack for Visual Studio Code”——选择“Install”。
  4. 下载完成后,右下角会出现“重启”提醒——点击“Restart”重启。

配置代码片段

VSCode 如何配置用户代码片段_vscode配置代码片段-CSDN博客

设置>用户代码片段

{
	"Print to console": {
		"prefix": "vue3",
		"body": [
			"<template>",
			" <div></div>",
			"</template>",
			"<script setup>",
			"import {ref,reactive} from 'vue'",
			"",
			"</script>",
			"<style scoped>",
			"",
			"</style>",
			
		],
		"description": "Log output to console"
	}
}

构建前端项目

vite

npm create vite@latest vueDemo
cd vueDemo
npm install  //安装依赖
npm run dev  //运行

vue 脚手架

npm install @vue/cli -g
vue create <project>

通过命令配置镜像

切换为淘宝镜像命令(安装一些package容易报错)

npm config set registry https://registry.npm.taobao.org

查看当前使用的镜像地址命令

npm config get registry

如果返回 https://registry.npm.taobao.org,说明镜像配置成功。

切换回原镜像(安装一些package不容易报错)

npm config set registry https://registry.npmjs.org

cnpm 安装

npm install -g cnpm --registry=https://registry.npm.taobao.org

Vue模板语法&指令

模板插值语法

在script声明一个变量可以直接在template使用,用法为{{name}}

方法一:
<script>
  export default{
    setup(){
      const name='小明'
      return {
        name
      }
    }
  }
</script>

<template>
  <div>{{name}}</div>
</template>
方法二(推荐):
<script setup>
const name='小明'
</script>

<template>
  <div>{{name}}</div>
</template>

<style scoped>

</style>


在{{}}中支持运算,三元算法

指令

内置指令 | Vue.js (vuejs.org)

v-开头的都是vue的指令

v-text用来显示文本

v-html用来展示富文本

v-if用来控制元素的显示隐藏(切换真假DOM)

v-else-if 表示v-ifelse if块,可以链式调用

v-else v-if条件收尾语句

v-show 用来控制元素的显示隐藏(display none block CSS切换)

v-on 简写@用来给元素添加事件

v-bind简写:用来绑定元素的属性Attr

v-model 双向绑定

v-for 用来遍历元素

v-on修饰符 冒泡案例

v-once性能优化只渲染一次

v-pre 跳过该元素及其所有子元素的编译

v-memo 性能优化会有缓存

Ref

只有被ref包裹的变量才是响应式的。

ref() 接收参数,并将其包裹在一个带有 .value 属性的 ref 对象中返回

const count = ref(0)

console.log(count) // { value: 0 }
console.log(count.value) // 0

count.value++
console.log(count.value) // 1

Reactive

reactive是另一种声明响应式状态的方式

ref的区别

  • ref 支持所有的类型,reactive 支持引用类型(Array,Object,Map,Set
  • ref 取值赋值都需要加上 .value ,reactive是不需要.value
  • reactiveproxy代理的对象,不能直接赋值,否则会破坏响应式对象的
const list = reactive([])
const list2 = reactive({ arr: [] })
function add () {
  setTimeout(() => {
    // 方法一:数组可以使用push加解构
    let res = ["arr1", "arr2", "arr3"]
    list.push(...res)
    //方法二:添加一个对象,把数组作为一个属性去解决
    list2.arr = res
  },2000)
}

computed用法

计算属性就是当依赖的属性的值发生变化的时候,才会触发他的更改,如果依赖的值,不发生变化的时候,使用的是缓存中的属性值。

let firstName = ref('张')
let lastName = ref('三')
//1.选项式写法:支持一个对象传入get函数以及set函数自定义操作
const name1 = computed({
  get () {
    return firstName.value + '-' + lastName.value
  },
  set (newVal) {
    console.log(newVal)
    // console.log(newVal.split('-'))
    console.log(lastName.value)
    lastName.value = newVal.split('-')[1]
    firstName.value = newVal.split('-')[0]

  }
})

//2.函数式写法:只能支持一个getter函数不允许修改值的
const name2 = computed(() => {
  return firstName.value + '-' + lastName.value
})

function changeName () {
  name1.value = '小-满'

}

watch侦听器

计算属性允许我们声明性地计算衍生值。然而在有些情况下,我们需要在状态变化时执行一些“副作用”:例如更改 DOM,或是根据异步操作的结果去修改另一处的状态。

在组合式 API 中,我们可以使用 watch 函数在每次响应式状态发生变化时触发回调函数

const msg = ref('dddd')
watch(msg, (newVal, oldVal) => {
  console.log(newVal, oldVal)
}, {
  deep: true,//深度监听
  immediate: true,//立即执行一次,默认false
  flush: 'pre',//pre:组件更新之前调用,sync:同步执行,post:组件更新之后执行
})

watchEffect侦听器

立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。

如果用到message 就只会监听message 就是用到几个监听几个 而且是非惰性 会默认调用一次

let message = ref<string>('')
let message2 = ref<string>('')
 watchEffect(() => {
    console.log('message', message.value);
    console.log('message2', message2.value);
})

认识组件

组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。在实际应用中,组件常常被组织成层层嵌套的树状结构:

生命周期

简单来说就是一个组件从创建 到 销毁的 过程 成为生命周期。

Vue3组合式API 是没有 beforeCreate 和 created 这两个生命周期的

选项式 API Hook inside setup
beforeCreate Not needed*
created Not needed*
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured
renderTracked onRenderTracked
renderTriggered onRenderTriggered
activated onActivated
deactivated onDeactivated

父子组件传参

父组件通过v-bind绑定一个数据,然后子组件通过defineProps接受传过来的值。

代码:

给子组件传递字符串不需要v-bind,传递变量需要,

给子组件传值

<template>
 <div>父组件</div>
 <hr>
 <sonVue title="传入的值1"></sonVue>
 <sonVue :title="data_val"></sonVue>
</template>
<script setup>
import {ref} from 'vue'
import sonVue from './son.vue';
const data_val='传入的值2'
</script>
<template>
  <div>子组件</div>
  <hr>
  {{title}}
</template>
<script setup>
import { ref } from 'vue'
const props = defineProps({
  title: {
    type: String,
    default: '默认值'
  }
})
console.log(props.title)
</script>

子组件给父组件传值:defineEmits

<template>
  <div>子组件</div>
  <hr>
  <button @click="changeClick">给父组件传值</button>
</template>
<script setup>
import { ref, reactive } from 'vue'
const emit = defineEmits(['subBack'])
function changeClick () {
  emit('subBack', '传回去的值')
}
</script>
<template>
  <div>父组件</div>
  <hr>
  <sonVue @subBack="getValFun"></sonVue>

</template>
<script setup>
import { ref, reactive } from 'vue'
import sonVue from './son.vue';
const data_val = '传入的值2'
function getValFun (val) {
  console.log(val)
}
</script>

给父组件传递属性或函数:defineExpose

template>
  <div>子组件</div>
  <hr>
  <button @click="changeClick">给父组件传值</button>
</template>
<script setup>
import { ref, reactive } from 'vue'
const emit = defineEmits(['subBack'])
function changeClick () {
  emit('subBack', '传回去的值')
}
defineExpose({
  name: '小满',
  open: () => console.log(1)
})
</script>
<template>
  <div>父组件</div>
  <hr>
  <sonVue @subBack="getValFun" ref="sonFun"></sonVue>

</template>
<script setup>
import { ref, reactive } from 'vue'
import sonVue from './son.vue';
const data_val = '传入的值2'
function getValFun (val) {
  console.log(val)
  console.log(sonFun.value.name)
  sonFun.value.open()
}
const sonFun = ref() //接收组件实例
</script>

局部组件

一个页面里由很多部分组成,就可以将其拆分为组件,在页面中引用

全局组件

一个组件使用频次较高,可以注册为全局组件。

main.js中注册,然后全局使用

import { createApp } from 'vue'
import router from './router'
import App from './App.vue'
import CardVue1 from './components/Card1.vue'
const app = createApp(App)
app.component('CardVue1', CardVue1)
app.use(router).mount('#app')

递归组件

原理跟我们写js递归是一样的 自己调用自己 通过一个条件来结束递归 否则导致内存泄漏。

<template>
  <div v-for="item in data" class="tree">
    <input type="checkbox" v-model="item.checked">
    <span>{{item.name}}</span>
    <!-- 在组件中引用自身,不需要重新使用import引入 -->
    <!-- <Tree :data="item?.children" v-if="item?.children?.length"></Tree> -->
    <xiaoman :data="item?.children" v-if="item?.children?.length"></xiaoman>

  </div>
</template>
<script setup>
import { ref, reactive } from 'vue'
const props = defineProps({
  data: Array
})
</script>
<script>
//如果需要更改组件名,使用下面的方法
export default {
  name: 'xiaoman'
}
</script>
<style scoped>
.tree {
  padding-left: 10px;
}
</style>

注释:可选链运算符?.)允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。?. 运算符的功能类似于 . 链式运算符,不同之处在于,在引用为空 (nullish ) (null 或者 undefined) 的情况下不会引起错误,该表达式短路返回值是 undefined。与函数调用一起使用时,如果给定的函数不存在,则返回 undefined

动态组件

动态组件 就是:让多个组件使用同一个挂载点,并动态切换,这就是动态组件。

在挂载点使用component标签,然后使用v-bind:is=”组件”

插槽slot

插槽就是子组件中的提供给父组件使用的一个占位符,用 表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的标签。

匿名插槽

子组件

 <div>
      <slot></slot>
  </div>

父组件

 <indexSlotVue>
  
      <template v-slot>
        <div>
          我是插槽呀!
        </div>
      </template>
    </indexSlotVue>

具名插槽

具名插槽其实就是给插槽取个名字。一个子组件可以放多个插槽,而且可以放在不同的地方,而父组件填充内容时,可以根据这个名字把内容填充到对应插槽中。

子组件

 <div class="content">
    <header>
      <slot name="header"></slot>
    </header>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>

父组件

 <indexSlotVue>
      <template v-slot:header>
        <div>
          顶部插槽
        </div>
      </template>
      <template v-slot:footer>
        <div>
          底部插槽
        </div>
      </template>
    </indexSlotVue>

插槽简写

<indexSlotVue>
      <template #header>
        <div>
          顶部插槽
        </div>
      </template>
      <template #footer>
        <div>
          底部插槽
        </div>
      </template>
    </indexSlotVue>

作用域插槽

在子组件动态绑定参数 派发给父组件的slot去使用

 <div v-for="item in data">
 	<slot :data="item"></slot>
 </div>
 
 const data = reactive([
  { name: 'slot1', value: 1 },
  { name: 'slot2', value: 2 },
  { name: 'slot3', value: 3 },
])
<indexSlotVue>
      <template v-slot="{data,index}">
        <div>
          {{data}}-{{data.name}}-{{data.value}}-{{index}}
        </div>
      </template>
    </indexSlotVue>

动态插槽

插槽可以是一个变量名

<indexSlotVue>
      <template #[name]>
        <div>
          顶部插槽
        </div>
      </template>
     
    </indexSlotVue>
const name=ref('header')

异步组件

在大型应用中,我们可能需要将应用分割成小一些的代码块 并且减少主包的体积,这时候就可以使用异步组件。

使用defineAsyncComponent引入

const indexSlotVue = defineAsyncComponent(() => import('../components/Slot/indexSlot.vue'))

Teleport传送组件

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

to是挂载的DOM节点,disabled是否挂载,true不允许挂载。

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

keep-alive缓存组件

有时候我们不希望组件被重新渲染影响使用体验;或者处于性能考虑,避免多次重复渲染降低性能。。而是希望组件可以缓存下来,维持当前的状态。这时候就需要用到keep-alive组件。

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

  • 初次进入时:onMounted>onActivated
  • 退出后触发deactivated
  • 再次进入;只会触发onActivated
  • 事件挂载的方法等,只执行一次的放在onMounted中;组件每次进去执行的方法放在onActived中。

兄弟组件传值&Bus

借助父组件传值

父组件

<template>
  <div>
    <AVue @on-click="getFlag"></AVue>
    <BVue :flag="Flag"></BVue>
  </div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import AVue from './A.vue';
import BVue from './B.vue';
const Flag = ref(false)
function getFlag (flag) {
  Flag.value = flag
}
</script>

子组件A

<template>
  <div class="A_brother">
    <button @click="changeClick">传递flag的值</button>
  </div>
</template>
<script setup>
import { ref, reactive } from 'vue'
const emit = defineEmits(['on-click'])
let flag = false
function changeClick () {
  flag = !flag
  emit("on-click", flag)
}
</script>

子组件B

<template>
  <div class="B_brother">
    {{flag}}
  </div>
</template>
<script setup>
import { ref, reactive } from 'vue'
const props = defineProps({
  flag: Boolean
})
</script>

Bus

安装mitt

npm i mitt -S
局部使用

*建议局部使用

bus.js

import mitt from 'mitt'

const bus = mitt()
export default bus

子组件A

<template>
  <div class="A_brother">
    <button @click="changeClick">传递flag的值</button>
  </div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import bus from '../../Bus';
// const emit = defineEmits(['on-click'])
let flag = false
function changeClick () {
  flag = !flag
  // emit("on-click", flag)
  bus.emit('on-click', flag)
}

子组件B

<template>
  <div class="B_brother">
    {{flag}}
  </div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import bus from '../../Bus';
// const props = defineProps({
//   flag: Boolean
// })
const flag = ref(false)
onMounted(() => {
  bus.on('on-click', (val) => {
    flag.value = val
  })
})
全局使用

在main.js中添加

import { createApp } from 'vue'
import App from './App.vue'

// 1. 引入 mitt,是一个函数
import mitt from 'mitt'
// 2. 调用 mitt
const Mit = mitt()

// 3. TypeScript注册
// 由于必须要拓展 ComponentCustomProperties 类型才能获得类型提示
declare module "vue" {
    export interface ComponentCustomProperties {
        $Bus: typeof Mit
    }
}

const app = createApp(App)

// 4. vue3 挂载全局 API
app.config.globalProperties.$Bus = Mit

app.mount('#app')

scss安装

Sass官网

安装scss

npm install sass sass-loader --save-dev

全局使用,在vite.config.js中添加

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [vue()],
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@import '@/styles/main.scss';`
      }
    }
  }
});

less安装

npm i less less-loader

全局使用,在vite.config.js中添加

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [vue()],
  css: {
    preprocessorOptions: {
      less: {
        additionalData: `@import '@/styles/main.less';`
      }
    }
  }
});

Element Plus安装

Element官网

npm install element-plus

完整引入

import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'

const app = createApp(App)

app.use(ElementPlus)
app.mount('#app')

按需引入

按需自动引入配置完之后,在组件中可直接使用,不需要引用和注册

npm install -D unplugin-vue-components unplugin-auto-import
// vite.config.js
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()],
    }),
  ],
})
修改主题色
AutoImport({
    resolvers: [ElementPlusResolver({
    	importStyle: 'sass',//添加
    })],
}),

Components({
	resolvers: [ElementPlusResolver()],
}),
 css: {
      preprocessorOptions: {
        scss: {
          additionalData: `@use "./src/assets/scss/element.scss" as *;@use "./src/assets/scss/style.scss" as *;`
        }
      }
    },

新建一个element.scss文件

// styles/element/index.scss
/* 只需要重写你需要的即可 */
@forward 'element-plus/theme-chalk/src/common/var.scss' with ($colors: ('primary': ('base': #4B70E2,
    ),
  ),
);

// 如果只是按需导入,则可以忽略以下内容。
// 如果你想导入所有样式:
// @use "element-plus/theme-chalk/src/index.scss" as *;

Ant Design Vue 安装

npm i --save ant-design-vue@4.x

完整引入

import { createApp } from 'vue';
import Antd from 'ant-design-vue';
import App from './App';
import 'ant-design-vue/dist/reset.css';

const app = createApp(App);

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

部分引入

全局部分引入
import { createApp } from 'vue';
import { Button, message } from 'ant-design-vue';
import App from './App';

const app = createApp(App);

/* 会自动注册 Button 下的子组件, 例如 Button.Group */
app.use(Button).mount('#app');
组件部分引入(不推荐)
<template>
  <a-button>Add</a-button>
</template>
<script>
  import { Button } from 'ant-design-vue';
  const ButtonGroup = Button.Group;

  export default {
    components: {
      AButton: Button,
      AButtonGroup: ButtonGroup,
    },
  };
</script>

按需引入

ant-design-vue 默认支持基于 ES modules 的 tree shaking,直接引入 import { Button } from 'ant-design-vue'; 就会有按需加载的效果。

自动按需引入

// vite.config.js
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  // ...
  plugins: [
    // ...
    AutoImport({
      resolvers: [AntDesignVueResolver()],
    }),
    Components({
      resolvers: [AntDesignVueResolver({
      importStyle: false, // css in js
    })],
    }),
  ],
})

修改主题色

在 ConfigProvider 中配置主题

通过在 ConfigProvider 中传入 theme,可以配置主题

<template>
  <a-config-provider
    :theme="{
      token: {
        colorPrimary: '#00b96b',
      },
    }"
  >
    <a-button />
  </a-config-provider>
</template>
使用预设算法

通过修改算法可以快速生成风格迥异的主题,4.0 版本中默认提供三套预设算法,分别是默认算法 theme.defaultAlgorithm、暗色算法 theme.darkAlgorithm 和紧凑算法 theme.compactAlgorithm。你可以通过修改 ConfigProvider 中 theme 属性的 algorithm 属性来切换算法。

<template>
  <a-config-provider
    :theme="{
      algorithm: theme.darkAlgorithm,
    }"
  >
    <a-button />
  </a-config-provider>
</template>

<script setup>
  import { theme } from 'ant-design-vue';
</script>

Router

前言

Vue是单页面应用不会有那么多的HTML让我们跳转,所以需要使用路由做页面的跳转。

Vue路由允许我们通过不同的URL访问不同的内容。通过Vue可以实现多视图的单页Web应用

安装

npm install vue-router

在src下新建router文件夹,新建index.js

// import VueRouter from 'vue-router'
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
  {
    path: '/index',
    name: 'index',
    component: () => import('../views/index.vue')
  },
  {
    path: '/', // 重定向
    redirect: (_) => {
      return { path: '/index' }
    },
  },
  {
    path: '/:currentPath(.*)*', // 路由未匹配到,进入这个
    redirect: (_) => {
      return { path: '/404' }
    },
  },
]
// Vue-router新版本中,需要使用createRouter来创建路由
export default createRouter({
  // 指定路由的模式,此处使用的是web模式
  history: createWebHistory(),
  routes // short for `routes: routes`
})

在main.js中添加

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

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

路由模式

  • createWebHashHistory:创建一个 hash 模式的历史。在没有主机的 web 应用 (如 file://) 或无法通过配置服务器来处理任意 URL 的时候非常有用。路由带 #
  • createWebHistory:创建一个 HTML5 历史。对于单页应用来说这是最常见的历史。(经常使用的)
  • createMemoryHistory:用于处理服务端渲染的。SSR。

路由使用

在App.vue

<router-view />

路由跳转

html中跳转
<router-link to="/index">跳转</router-link>

<router-link :to="{name:'index'}">跳转</router-link>

<router-link :to="{path:'/index'}">跳转</router-link>
js中跳转
import {useRouter} from 'vue-router'
const router=useRouter()
function goUrl(){
	router.push({name:'index'})
	router.push({path:'/index'})
	
}
历史记录

无历史记录操作

replace:程序式地通过替换历史栈中的当前记录来导航到一个新的 URL

<router-link replace :to="{name:'index'}">跳转</router-link>
router.replace({name:'index'})

路由传参

params传参
  1. 首先需要在路由表中配置 冒号+参数(/user/:id

    // 这是动态路由 加上:/:id
    {
    path: "/routers/:id",
    name: "Routers",
    meta: { title: "动态路由" },
    component: () => import("../views/routers/routers.vue")
    },

  2. 开始跳转并传参

    goUrl() {
    	router.push({ path: "/routers/123" });
    	router.push({name:'routers',params:{id:'123'}})
    }
    
  3. 获取动态路由

    import {useRoute} from 'vue-router'
    const route=useRoute()
    let val1=route.params //{id: '123'}
    let val2=route.params.id//123
    

注意:当使用路由参数时,例如从 /user/foo 导航到 /user/bar,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。

复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch (监测变化) route 对象

watch(route, (val) => {
  console.log(val)
})
//或者
watch(() => route.params.id, (val) => {
  console.log(val)
})
query传参

这种传参方式会把你的参数以问号拼接到路由上面

  1. 不需要配置路由表

  2. 设置query并跳转

    router.push({name:'routers',query:{id:'123'}})
    
  3. 获取参数

    route.query.id
    

嵌套路由

{
    path: '/page1',
    name: 'page1',
    component: () => import('../views/page1.vue'),
    children:[{
    	path:'list',
    	name:'list',
    	component:()=>import('../views/page2.vue')
    }]
  },

命名视图

可以在同一级(同一个组件)中展示更多的路由视图,而不是嵌套显示。命名视图可以让一个组件中具有多个路由渲染出口,这对于一些特定的布局组件非常有用。命名视图的概念非常类似于“具名插槽”,并且视图的默认名称也是default。

一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用components配置

{
    path: '/page7',
    name: 'page7',
    components: {
      default: () => import('../views/page6.vue'),
      header: () => import('../views/page5.vue'),
      content: () => import('../views/page4.vue'),
    }
  },
<RouterView />
  <router-view name="header"></router-view>
  <router-view name="content"></router-view>

重定向

{
    path: '/:currentPath(.*)*', // 路由未匹配到,进入这个
   // redirect:'index',
   // redirect:{path:'/index'},
    redirect: (_) => {
      return { path: '/index' }
    },
  }

别名

{
    path: '/page1',
    name: 'page1',
    alias:['page10'],
    component: () => import('../views/page1.vue')
 }

导航守卫

全局前置守卫

router.beforeEach

router.beforeEach((to, from, next) => {
  console.log(to, form)
  next()
})

每个守卫方法接收三个参数:

  • to:Route,即将要进入的目标,路由对象;
  • fromRoute,当前导航正要离开的路由;
  • next():进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是confirmed(确认的);
  • next(false):中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from路由对应的地址。
  • next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。

可以用于权限判断

const whileList = ['/']
 
router.beforeEach((to, from, next) => {
    let token = localStorage.getItem('token')
    //白名单 有值 或者登陆过存储了token信息可以跳转 否则就去登录页面
    if (whileList.includes(to.path) || token) {
        next()
    } else {
        next({
            path:'/'
        })
    }
})
全局后置钩子

router.afterEach,不会接受 next 函数也不会改变导航本身,一般可以用来做loadingBar

router.afterEach((to, from) => {
  console.log(to,from)
})

路由元信息

通过路由记录的meta属性可以定义路由的元信息。使用路由元信息可以在路由中附加自定义的数据:

  • 权限校验标识。
  • 路由组件的过渡名称。
  • 路由组件持久化缓存(keep-alive)的相关配置。
  • 标题名称。

我们可以在导航守卫或者是路由对象中访问路由的元信息数据。

{
	path: 'index',
	name: 'index',
	component: () => import('../views/page5.vue')
	meta:{
		title:'首页',//标题
		requiresAuth:true //查看权限
	}
}

访问

router.beforeEach((to, form, next) => {
  console.log(to, form)
  console.log(to.meta.title)
  next()
})

路由过渡效果

Pinia

全局状态管理

特点:

  1. 完整的ts的支持;
  2. 足够轻量,压缩后的体积只有1kb左右
  3. 去除木塔同时,只有state,getters,actions
  4. action是支持同步和异步
  5. 代码扁平化没有模块嵌套,只有store的概念,store之间可以自由使用,每一个store都是独立的。
  6. 无需手动添加store,store一旦创建变会自动添加

安装

npm i pinia

引入

main.js

import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia).mount('#app')

初始化仓库Store

1.新建一个文件夹Store
2.新建文件
3.定义仓库Store
import {defineStore} from 'pinia'
4. defineStroe()需要一个唯一的名称,作为第一个参数传递
import { defineStore } from 'pinia'
 
export const useTestStore = defineStore("Test", {
 
})
5.定义值

state是箭头函数,返回一个对象,在对象里面定义值

import { defineStore } from 'pinia'
 
export const useTestStore = defineStore("Test", {
 	state:()=>{
 		return {
			name:'bule'
		}
 	},
 	//类似于computed 可以帮我们去修饰我们的值
     getters:{
 
     },
     //可以操作异步 和 同步提交state
     actions:{
 
     }
})

修改值

<template>
  pina:{{Test.current}}/{{Test.name}}
  <hr>
  <button @click="changeClick1()">Test1</button>
  <button @click="changeClick2()">Test2</button>
  <button @click="changeClick3()">Test3</button>
  <button @click="changeClick4()">Test4</button>
  <button @click="changeClick5()">Test5</button>
</template>
<script setup>
import { ref, reactive, toRaw } from 'vue'
import indexVue from '../components/Brother/index.vue';
import { useTestStroe } from '@/store'
const Test = useTestStroe()
function changeClick1 () {
  //直接修改
  Test.current++
}
function changeClick2 () {
  //批量修改
  Test.$patch({ current: 999,name:'测试' })
}
function changeClick3 () {
  //批量函数修改形式,可以自定义修改逻辑,推荐使用此法
  Test.$patch((state) => {
    state.current++
  })
}
function changeClick4 () {
  //通过原始对象修改整个实例,必须修改全部的值,不推荐
  Test.$state = { current: 9999, name: '123' }
}
function changeClick5 () {
  //使用actions,可以传参,此法也推荐
  Test.setCurrent()
}
</script>
//store中的actions
 actions: {
    setCurrent () {
      this.current++
    }
  }

结构store

直接结构不具有响应式,可以使用storeToRefs解决

<template>
  pinia源值:{{Test.current}}/{{Test.name}}
  <hr>
  pinia结构值:{{current}}/{{name}}
  <hr>
  <button @click="changeClick()">change</button>
</template>
<script setup>
import { ref, reactive, toRaw } from 'vue'
import indexVue from '../components/Brother/index.vue';
import { useTestStroe } from '@/store'
import { storeToRefs } from 'pinia';
const Test = useTestStroe()
//pinia 直接结构不具有响应式
// const { current, name } = Test
//使用 storeToRefs 结构具有响应式
const { current, name } = storeToRefs(Test)
function changeClick () {
  Test.current++
}

</script>

actions 使用

import { defineStore } from 'pinia'
let result = {
  name: '张三',
  age: 30
}
function Login () {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({
        name: '张三',
        age: 40
      })
    })
  })
}
export const useTestStroe = defineStore('user', {
  state: () => {
    return {
      user: {},
      name: '小满',
      current: 1
    }
  },
  //类似computed,修饰一些值
  getters: {

  },
  //类似methods,可以做同步,异步操作,提交state
  actions: {
    async setUser () {
      //同步
      this.user = result
      //异步
      const result = await Login()
      this.user = result
    }
  }
})

getters

 //类似computed,修饰一些值,有缓存的,也可以相互调用
  getters: {
    newName () {
      return `$-${this.name}-${this.getUserAge()}`
    },
    getUserAge () {
      return this.user.age
    }
  },

$reset

$reset重置store的值

function resetClick(){
	Test.$reset()
}

数据持久化

安装插件

npm i pinia-plugin-persistedstate

main.js中注册

import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const app = createApp(App)
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
app.use(router).use(pinia).mount('#app')

模块中开启持久化

export const useTestStroe = defineStore('user', {
  //若仅配置true,则全部存储
  //persist: true,
  persist:{
  	//存储的key,默认是defineStore的第一个参数
  	key:"B",
  	//存储的位置,默认localStorage
  	storage:localStorage,
  	//指定的内容
  	paths:['name']
  }
  ....省略
})

模块化引用

pinia可以定义多个defineStore,按需求引用,或将整合到一个文件中

import { useTestStroe } from './model/user.js'
import { useDepartStroe } from './model/depart.js'

export const appStore = {
  useTestStroe: useTestStroe(),
  useDepartStroe: useDepartStroe()
}

vite.config.js配置

import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import viteCompression from 'vite-plugin-compression'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// 导入对应包
import ElementPlus from 'unplugin-element-plus/vite'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig(({ command, mode }) => {
  // 环境变量
  const env = loadEnv(mode, process.cwd())
  // 生产环境判断
  const isEnvProduction = mode === 'production'
  return {
    plugins: [vue({
      script: {
        defineModel: true,
      }
    }),
    AutoImport({
      resolvers: [ElementPlusResolver({
        importStyle: 'sass',
      })],
    }),

    Components({
      resolvers: [ElementPlusResolver({
        importStyle: 'sass'
      })],
    }),

    viteCompression({
      //生成压缩包gz
      verbose: true,
      disable: false,
      threshold: 10240,
      algorithm: 'gzip',
      ext: '.gz',
    }),
    ],
    define: {
      'process.env': {}
    },
    base: '/',
    server: {//本地服务
      host: '0.0.0.0',//ip
      port: 19915,//端口号
      open: true,//是否自动在浏览器打开
      https: false,//是否开启https
      //反向代理(跨域)
      proxy: {
        '/api': {
          target: 'http://localhost:5000/',
          // target: 'http://116.63.157.19/',
          changeOrigin: true,
          rewrite: (pathStr) => pathStr.replace(new RegExp('^/api'), '')
        },
      },

    },
    css: {
      preprocessorOptions: {
        scss: {
          additionalData: `@use "./src/assets/scss/element.scss" as *;@use "./src/assets/scss/style.scss" as *;`
        }
      }
    },
    // 强制预构建插件包
    // optimizeDeps: {
    //   include: ['axios'],
    // },
    //别名
    resolve: {
      alias: {
        "@": path.resolve(__dirname, './src'),
        "@api": path.resolve(__dirname, './src/api'),
        "@assets": path.resolve(__dirname, "./src/assets"),
        "@views": path.resolve(__dirname, "./src/views"),
      }
    },
    build: {
      // 打包路径
      assetsDir: "./static",
      // 压缩
      // Terser 相对较慢,但大多数情况下构建后的文件体积更小。ESbuild 最小化混淆更快但构建后的文件相对更大。
      //启用/禁用 CSS 代码拆分
      cssCodeSplit: true,
      minify: !isEnvProduction ? 'esbuild' : 'terser',
      terserOptions: {
        compress: {
          // 生产环境去除console
          drop_console: isEnvProduction,
          drop_debugger: isEnvProduction
        },
        output: {
          // 去掉注释内容
          comments: true,
        },
      },
      // 取消计算文件大小,加快打包速度
      brotliSize: false,
      sourcemap: false,
      outDir: 'dist',
      rollupOptions: {
        input: {
          // 入口文件
          main: path.resolve(__dirname, "index.html"),
          // 其他入口
          // nested: resolve(__dirname, 'xxxx.html')
        },
        output: {
          // chunkFileNames: 'static/js/[name]-[hash].js',
          // entryFileNames: 'static/js/[name]-[hash].js',
          // assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
          manualChunks (id) { //静态资源分拆打包
            if (id.includes('node_modules')) {
              return id.toString().split('node_modules/')[1].split('/')[0].toString();
            }
          }
        },

      },
    },
  }
})

posted @ 2023-10-20 18:19  bule蓝色  阅读(657)  评论(0编辑  收藏  举报