一、Vue语法 (vue2、vue3)
01.生命周期
02.组件注册
Vue2
方法1:全局注册。
// main.js
import HmButton from './components/HmButton.vue'
Vue.component('HmButton', HmButton)
方法2:局部注册。
// 组件内部
export default {
components: {
HelloWorld: () => import('@/components/HelloWorld')
}
};
Vue3
只需导入组件,无需手动注册。
03.computed
Vue2
写法1:简写。
// 任务:根据数据numA、numB, 计算出numC的值。
export default {
data() {
return {
numA: 10,
numB: 20
}
},
// 计算属性
computed: {
numC() {
return this.numA + this.numB
}
}
}
写法2:getter、setter写法。
export default {
// 计算属性
computed: {
total: {
get() {
...
},
set(val) {
...
}
}
}
}
Vue3
写法1:简写。
// (1)导入。
import { ref, computed } from "vue";
// (2)定义计算属性:computed(回调函数)
const count = ref([1, 2, 3, 4, 5, 6, 7, 8, 9])
const computed_count = computed(()=> count.value.filter(item => item > 2) })
写法2:getter、setter写法。
// (1)导入
import { ref, computed } from "vue";
// (2)定义计算属性:computed(对象),且该对象包含get()和set()方法。
const computed_count = computed({
get: () => count.value + 1,
set: (val) => count.value = val - 1
})
04.watch
Vue2
情况1:监听一个简单数据类型。
// 任务:监听简单数据类型myData的改变。
export default {
data() {
myData: '猪妹'
},
// 定义监听器:watch: { 函数 }。 (函数名与数据名称一致)
watch: {
myData(newValue, oldValue) {
console.log(newValue, oldValue)
}
}
}
情况2:监听多个简单数据类型。
// 任务:监听简单数据类型myData的改变。
export default {
data() {
myData: '猪妹',
myName: '张益达'
},
// 定义监听器:watch: { 函数1, 函数2, ... }。 (函数名与数据名称一致)
watch: {
myData(newValue, oldValue) {
console.log(newValue, oldValue)
},
myName(newValue, oldValue) {
console.log(newValue, oldValue)
}
}
}
情况3:监听对象的一个属性。
export default {
data() {
obj: {
word: '',
lang: ''
}
},
// 定义watch监听器:watch{ 函数是1, 函数2, ... } (函数名为[对象.数据])
watch: {
'obj.word'(newValue, oldValue){
console.log(newValue, oldValue)
},
'obj.lang'(newValue, oldValue){
console.log(newValue, oldValue)
}
}
}
情况4:监听整个对象。
export default {
data() {
obj: {
word: '',
lang: ''
}
},
// 定义watch监听器: watch: { 键值对 }
watch: {
obj: {
deep: true, // 监听整个对象
immediate: true, // 页面打开后自动执行一次该对象或属性的监听。
handler(obj) {
...
}
}
}
}
Vue3
情况1:监听一个简单数据类型。
// 任务:监听数据count的变化。
// (1) 准备数据count
const count = ref(1)
// (2) 定义watch监听器:watch(被监听的数据, 回调函数)
watch(count, (newValue, oldValue) => {
...
})
情况2:监听多个简单数据类型。
// 任务:同时监听数据count、name的变化。
// (1) 准备数据count、name
const count = ref(1);
const name = ref('思密达')
// (2) 定义watch监听器:watch([数据1,数据2,...], 回调函数)
watch([count, name], ([newCount, newName],[oldCount, oldName]) => {
...
})
情况3:监听整个对象。
// 任务:监听对象obj的改变。
// (1) 准备数据obj
const obj = ref({
age: 18,
name: '张益达'
})
// (2) 定义watch监听器: watch(对象, 回调函数, { immediate:true, deep: true })
watch(obj, (newValue, oldValue) =>
{
console.log(newValue, oldValue)
},
{
immediate: true, // 表示页面一打开就执行一次watch
deep: true // 深度监视(默认是浅层监视,只能监视简单数据类型)
})
情况4:监听对象中的一个属性。
// 任务: 监听对象obj中的age属性的改变。
// (1) 准备数据obj
const obj = ref({
age: 18,
name: '张益达',
})
// (2) 定义watch监听器:watch(回调函数, 回调函数)
watch(
() => obj.value.age,
(newValue, oldValue) => { ... }
)
05.this.$nextTick
原理:onMounted()是同步方法,而this.$nextTick()是异步方法,会在mounted后执行。
Vue2
vue源码:
class Vue {
// 1.构造器
constructor(options) {
// (1) 获取data
this.$data = options.data
// (2) 修改生命周期created()的this指向, 并调用该函数
options.created.bind(this)()
// (3) 获取DOM元素
this.$el = document.querySelector(options.el)
// (4) 修改生命周期mounted()的this指向, 并调用该函数
options.mounted.bind(this)()
}
// 2.提供$nextTick函数
$nextTick(callback) {
return Promise.resolve().then(() => {
callback()
})
}
}
实操:
created() {
// (1) 显示输入框
this.isShowEdit = true
// (2) 让输入框获取焦点: 等待DOM渲染完成后,才开始执行该行代码。
this.$nextTick(() => this.$refs.inp.focus() )
}
Vue3
<script setup>
// 导入nextTick函数
import { nextTick, ref } from 'vue'
// 准备数据
let flag = ref(true)
// Mounted生命周期
onMounted(() => {
// 延迟到DOM渲染后,才执行该代码
nextTick(() => { flag.vlaue = false })
})
</script>
06.自定义指定
Vue2
先学习四个钩子函数:
(1) bind() // 当前指令被绑定到元素时调用
(2) inserted() // 表示当前指令所绑定的元素,被渲染到页面时调用。
(3) updated() // 被绑定元素的DOM更新后调用。
(4) componentUpdated() // 当binding.value的值改变时被调用。
情况1:全局注册。
// main.js中注册。
Vue.directive('loading', {
bind(el, binding) {
binding.value ? el.classList.add('loading') : el.classList.remove('loading')
},
updated(el, binding) {
binding.value ? el.classList.add('loading') : el.classList.remove('loading')
}
})
情况2:局部注册。
// 组件内部注册。
directives: {
'loading': {
bind(el, binding) {
binding.value ? el.classList.add('loading') : el.classList.remove('loading')
},
updated(el, binding) {
binding.value ? el.classList.add('loading') : el.classList.remove('loading')
}
}
}
使用方法如下:
Vue3
先学习几个钩子函数(没有学习成本,跟组件生命周期一致)
情况1:全局注册。
const app = createApp(App)
app.directive('指令名称', {
// 钩子函数
})
07.自定义插件
Vue2
Vue3
调用:由于vue3没有this,不能直接调用this.$showLoginDialog()
08.自定义组件(手动)
Vue2
步骤1:手动获取组件根DOM节点
// 功能:根据传入的组件comp,组件需要的属性props,就可以构建出组件的DOM节点。
function getComponentRootDom (comp, props) {
const vm = new Vue({
render: (h) => h(comp, { props })
})
vm.$mount()
return vm.$el
}
// 使用示例:<Icon :type='home'>
import Icon from '@/components/Icon'
const dom = getComponentRootDom(Icon, { type: 'home' })
console.log(dom)
步骤2: 挂载到vue原型
import Vue from 'vue'
const vm = new Vue({...})
// 方式1:在某个实例上扩展属性、方法。(只影响一个vue实例)
vm.$sayHello() = function(){}
// 方式2:在vue的原型上扩展属性、方法。(影响到每一个vue实例)
Vue.prototype.$sayHello() = function(){}
Vue3
09.插槽
Vue2
情况1:默认插槽。
// 定制MyDialog组件
<div class="container">
<slot></slot> // 用slot占位,默认name:default。
</div>
// 使用方法
import MyDialog from '@/components/MyDialog'
<MyDialog>
<div>我是用来填充插槽的</div>
</MyDialog>
情况2:具名插槽。(v-slot:插槽名称,简写为:#插槽名称)
情况3:作用域插槽。
// (1) 给slot标签,以添加属性的方式传值。
<slot :id="item.id" msg="测试文本"></slot>
// (2) 将来这些参数会被统一收集到一个对象中:{id: 3, msg: '测试文本'}
// (3) 在template中,通过"#插槽名=obj"来接收,默认插槽名称为default。
<MyTable>
<template #default="obj">
<button @click="delete(obj.id)">删除</button>
</template>
</MyTable>
Vue3
跟vue2一致。
10.获取组件DOM实例
Vue2
情况1:获取普通dom节点
// (1) 找到目标标签,添加ref属性
<div ref="chartRef">我是渲染图表的容器</div>
// (2) 在mounted()中,获取DOM。(因为要等页面渲染出来才能获取的到)
mounted() {
console.log(this.$refs.chartRef);
}
情况2:获取组件的dom节点
// (1) 找到目标组件,添加ref属性
<BaseForm ref='chartRef'></BaseForm>
// (2) 在mounted()中,获取DOM。(因为要等页面渲染出来才能获取的到)
mounted(){
console.log(this.$refs.chartRef);
}
情况3:获取父、子组件实例
Vue3
情况1:获取DOM节点实例
// 任务:获取h1这个DOM节点。
<template>
<h1 ref='h1Ref'>我是h1标签</h1>
</template>
<script setup>
// 调用ref函数,根据ref标识得到ref对象
const h1Ref = ref(null)
</script>
情况2:获取组件实例,使用方式如下:
A组件
<template>
<h1 ref='h1Ref'>我是h1标签</h1>
</template>
<script setup>
// 2.准备数据testMessage
const testMessage = ref('我真的很帅')
// 3. 将数据testMessage暴露出去
defineExpose({
testMessage
})
</script>
B组件
<template>
<A ref='ARef'>我是h1标签</A>
</template>
<script setup>
import A from '@/components/A.vue'
// 1.如果B组件想调用A组件内提供的testMessage数据,
// 需要在A组件中通过defineExpose({})暴露后才能被访问。
const ARef = ref(null)
console.log(ARef.testMessage.value)
</script>
11.指令修饰符
第一类:v-model修饰符
(1).lazy(输入框聚焦、失焦时触发)
(2).number(尝试将输入框的value值转为number类型)
(3).trim(去除输入框的value值首尾空格)
第二类:事件修饰符
(1).stop(阻止事件冒泡)
(2).prevent(防止触发默认事件)
(3).capture(将默认触发方式改为捕获触发)
(4).self(只有当事件发生在目标元素自身时才会触发)
(5).once(事件只触发一次)
第三类:键盘修饰符
(1).enter(回车键触发)
(2).tab(tab键触发)
(3).delete(通过该元素需要配合v-if使用,是指DOM节点显示、隐藏时触发)
(4).esc(esc键触发)
12.防抖、节流
Vue2
Vue3
防抖的实现
// vue3实现防抖: 在utils目录下,新建tools.ts文件。这里用到vue3提供的customRef函数。
import {customRef} from 'vue'
export const debounceRef = (value:string, delay:number) => {
let timer:any = null
return customRef((track, trigger) => {
return {
get() {
track() // 依赖收集
return value
},
set(newValue) {
clearTimeout(timer)
timer = setTimeout(() => {
value = newValue
trigger() //派发更新
}, delay)
}
}
})
}
// 使用方法:
<input type="text" v-model="text">
import { debounceRef, throttle } from '@/utils/tool.ts'
let text = debounceRef('1233', 3000)
节流的实现
// 节流的实现:在utils目录下,新建tools.ts文件。这里用到vue3提供的customRef函数。
export const throttle = (func:Function, delay:number) => {
let flag = false
return function() {
if(flag) return
flag = true
func.apply(this, arguments)
setTimeout(() => {
flag = false
}, delay);
}
}
// 使用方法:
<button @click="btn">按钮</button>
const btn = throttle(() => {
console.log(1)
}, 10000)
13.路由跳转
Vue2
方法1:声明式导航。
// 第一种:根据路由路径path跳转。
(1) 动态路由:
首先去路由模块中,配置{ path:'/home/:参数名?'}
<router-link :to="/home/黑狗程序员">User</router-link>
(2) 查询参数:
<router-link :to="/home?username=思密达&age=18">User</router-link>
<router-link :to="{ path: '/home', query:{ username:'思密达', age: 18 }">
// 第二种:根据路由名称name跳转。
(1) 动态路由:
<router-link :to="{ name: 'user', params: { id: 123 } }">
(2) 查询参数:
<router-link :to="{ name: 'user', query: { id: 123, username: 'john' } }">
方法2:编程式导航。
// 方式1:根据path路径跳转。
(1)动态路由:
this.$router.push('/home/黑狗程序员')
(2)查询参数:
this.$router.push('/home?username=思密达&age=18')
this.$router.push({ path: '/home', query{ username:'思密达', age: 18 } })
// 方式2:根据name命名路由跳转。
(1)动态路由:
this.$router.push({ path: '/home', params:{username: '思密达'} })
(2)查询参数:
this.$router.push({ path: '/home', query: { username: '思密达', age: 18 } })
方法3:点击返回上一页。
Vue3
14.keep-alive
(1) 找到不需要被销毁的组件,然后用<keep-alive>标签将该组件进行包裹起来。
(2) 如果用<keep-alive>标签来包裹一级路由, 则一级路由所匹配的组件都会被缓存。
<keep-alive>
<router-view></router-view>
</keep-alive>
(3) 对于不需要缓存的组件,比如是详细页面的组件,就可以使用如下方法进行排除掉。
其实keep-alive标签,有三个属性是可以设置的,最常用的include。
// 组件名怎么看的话,优先看export default{ name:组件名},如果没有配置name的话,就是这个组件的文件名称。
// 属性前面记得加上冒号,才能识别组件名,从而识别组件。
1):include="['组件名1','组件名2',...]" ,表示只缓存数组中有写的组件。 // 数组中的都缓存,其他都不缓存。
2):exclude="['组件名1','组件名2',...]",表示出现在数组内的组件,都不会被缓存。 // 数组中的都不缓存,其他的都缓存。
3) max:值,表示最多可以缓存多少组件实例。
// 1. 被缓存的组件, 会多出两个生命周期
(1) actived: 激活时, 表示组件被看到时触发.
(2) deactived: 失活时, 离开该组件后触发.
// 2. 像created(), mounted(), destory()都不会被调用。
15.动态组件(Vue3)
<template>
<ul>
<li v-for="(item, index) in tabList" :key="item.name" @click="changeCom(index)">
{{ item.name }}
</li>
</ul>
<!-- 动态组件 -->
<keep-alive>
<component :is="currentComponent.com"></component>
</keep-alive>
</template>
<script setup>
import { ref, markRaw } from "vue"
// 导入组件
import componentA from '@/components/componentA.vue'
import componentB from '@/components/componentB.vue'
import componentC from '@/components/componentC.vue'
// 准备数据,markRaw只是为了让它不要浪费响应式,我们不需要使用响应式。
let tabList = ref([
{ name: 'A小伙子', com: markRaw(componentA) },
{ name: 'B小伙子', com: markRaw(componentB) },
{ name: 'C小伙子', com: markRaw(componentC) }
])
// 选中当前组件
const currentComponent = ref({
com: componentA
})
// 改变组件
const changeCom = (index) => {
currentComponent.value.com = tabList.value[index].com
console.log(currentComponent.value.com, index);
}
</script>
16.
·······························
二、Vue模块配置
01.路由模块
Vue2
步骤1:安装router。
'npm i router@3'
步骤2:配置路由模块。(src => router => index.js)
import VueRouter from 'vue-router'
import Vue from 'vue'
Vue.use(VueRouter)
const router = new VueRouter({
mode: "hash", // 一共有两种路由模式,分别是hash(默认,原理是a链接锚点跳转)、history(需要后端配置访问规则,原理未知)。
routes: [
{ path: '/login', component: Login },
{ path: '/',
component: Layout,
redirect: '/home', // redirect重定向,遇到路径'/',就重定向到'/home'。(避免初次页面打开时空白)
children: [
{ path: '/home', component: Home },
{ path: '/category', component: Category },
{ path: '/cart', component: Cart },
{ path: '/user', component: User }
]
},
{ path: '/search', component: Search },
{ path: '/searchlist', component: SearchList },
{ path: '/prodetail', component: ProDetail },
{ path: '/pay', component: Pay },
{ path: '/myorder', component: MyOrder }
{ path: '*', component: NoFind} // *表示任意路径都可以匹配。(当上面都匹配不到时,这里就匹配一个404页面给用户)
]
})
export default router
步骤3:挂载到vue实例。(main.js)
// main.js
import router from '@/router/index.js'
new vue({
render: h => h(app),
router // 注册一下即可
}).$mount('#app');
步骤4:配置路由守卫。
情况1:全局路由守卫。
// 1.配置前置路由守卫
// 其中to、from是路由对象,next方法用于拦截,next()表示放行,next(路径)表示拦截到指定路径。
const authUrls = ['/pay', '/myorder']
router.beforeEach((to, from, next) => {
if (!authUrls.includes(to.path)) {
next()
return
}
//如果是权限页面,需要判断token,如果存在就放行,如果不存在就跳到登录页面
const token = store.getters.token
token ? next() : next('/login')
})
// 2.配置后置路由守卫
router.afterEach(() => {
nProgress.done()
})
情况2:独享路由守卫。
Vue3
步骤1:安装路由模块。
'npm i vue-router@4'
步骤2:配置路由模块。(src => router => index.js)
import { createRouter, createWebHistory } from 'vue-router'
// (1) 创建路由实例对象
// hash模式:createWebHashHistory(基地址)。
// history模式:createWebHistory(基地址)。
const router = createRouter({
// 【import.meta.env.BASE_URL】是vite.config.js中的环境变量。
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
redirect: '/home',
},
{
path: '/home',
component: () => import('@/views/home.vue')
},
{
path: '/about',
component: () => import('@/views/about.vue')
}
]
})
// (2) 导出路由实例
export default router
步骤3:挂载到vue实例(main.js)。
// 导入路由模块
import router from '@/router/index.js'
// 注册
app.use(router)
步骤4:配置路由守卫。
情况1:全局路由守卫(默认全部拦截,不过可以用数组来指定哪些不需要拦截的。)
// 1. 配置前置路由守卫
/* (1)to:到哪里去,完整路由信息对象(路径,参数) 获取路径:to.path
(2)from:从哪里来,完整路由信息对象(路径,参数)
(3)next():默认直接放行,放行到to要去的路径。
next(路径):进行拦截,拦截到next里面配置的路径。
*/
const authUrls = ['/pay', '/myorder']
router.beforeEach((to, from, next) => {
if (!authUrls.includes(to.path)) {
next()
return
}
// 如果是权限页面,需要判断token,如果存在就放行,如果不存在就跳到登录页面
const token = store.getters.token
token ? next() : next('/login')
})
// 2. 后置路由守卫
router.afterEach(() => {
nProgress.done()
})
情况2:独享路由守卫(给单个路由配置路由守卫)
02.axios模块
Vue2
步骤1:安装axios。
'npm i axios'
步骤2:配置axios模块。(src => utils => request.js)
// 1.创建axios实例
const instance = axios.create({
baseURL: 'https://study.duyiedu.com/',
timeout: 2000
})
// 2.添加请求拦截器
instance.interceptors.request.use(function (config) {
const token = localStorage.getItem('token');
if(token){
config.headers.authorization = token;
}
return config; // 返回处理后的配置
})
// 3.添加响应拦截器
instance.interceptors.response.use(function (resp) {
// 2xx 范围内的状态码都会触发该函数。
return resp.data.data;
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
alert(error.message); // 弹出错误消息
})
export default instance
步骤3:配置环境变量。
// 1. 在哪配置环境变量
在项目根目录下,配置两个文件(这两个文件与src目录同级):
(1) .env.development // 开发环境会被运行
(2) .env.production // 生产环境会被运行
// 2.怎么定义环境变量?
必须以'VUE_APP_'开头,来声明环境变量。 例如:声明一个baseURL: 'VUE_APP_BASE_URL = https://www.baidu.com'
// 3.怎么读取环境变量?
(1) 访问环境变量:'process.env.VUE_APP_变量名'。
(2) 判断是否开发环境:'process.env.NODE_ENV'。// production、development
步骤4:解决跨域(开发阶段)。
// 在vue.config.js中配置,因为代理只在开发阶段有效!
module.exports = {
// 配置代理服务器
devServer: {
proxy: {
'/commercial_api': {
target: 'https://www.zhihu.com', // 要转发的目的服务器
changeOrigin: true // 是否改变源地址
}
}
}
}
// 二次封装的基地址写环境变量。
const instance = axios.create({
baseURL: process.env.NODE_ENV === 'production' ? process.env.VUE_APP_BASE_URL : '',
timeout: 2000
})
步骤5:封装所有的api。
import request from '@/utils/request.js'
// get请求的写法
export const getShopListApi = (data, config={}) => {
request.get('https://www.baidu.com', {
params: data,
...config
})
}
// post请求的写法
export const changeListApi = (data, config={}) => {
request.post('https://www.baidu.com', data, config)
}
Vue3
步骤1:安装axios。
'npm i axios'
步骤2:配置axios模块。(src => utils => request.js)
import axios from 'axios'
// 1.创建axios实例
const instance = axios.create({
baseURL: '',
timeout: 10000
})
// 2.添加请求拦截器
instance.interceptors.request.use(function (config) {
const token = localStorage.getItem('token');
if(token){
config.headers.authorization = token;
}
return config; // 返回处理后的配置
})
// 3.添加响应拦截器
instance.interceptors.response.use(function (resp) {
return resp.data;
}, function (error) {
alert(error.message); // 弹出错误消息
})
// 4.统一get、post的传参方式
const http = {
get(url:any, params:any, config:any) {
return new Promise((resolve, reject) => {
instance.get(url, {params, ...config}).then((res) => {
resolve(res)
}).catch((error) => {
reject(error)
})
})
},
post(url:any, params:any, config:any) {
return new Promise((resolve, reject) => {
instance.post(url, params, config).then((res) => {
resolve(res)
}).catch((error) => {
reject(error)
})
})
}
}
// 导出
export default http
步骤3:配置环境变量。
// 1. 在哪配置环境变量
在项目根目录下,配置两个文件(这两个文件与src目录同级):
(1) .env.development // 开发环境会被运行
(2) .env.production // 生产环境会被运行
// 2.怎么定义环境变量?
必须以'VUE_APP_'开头,来声明环境变量。 例如:声明一个baseURL: 'VUE_APP_BASE_URL = https://www.baidu.com'
// 3.怎么读取环境变量?
(1) 访问环境变量:'process.env.VUE_APP_变量名'。
(2) 判断是否开发环境:'process.env.NODE_ENV'。// production、development
步骤4:解决跨域(开发阶段)。
import { fileURLToPath, URL } from 'node:url'
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'
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
'@stores': fileURLToPath(new URL('./src/stores', import.meta.url)),
'@views': fileURLToPath(new URL('./src/views', import.meta.url))
}
},
// 代理
server: {
proxy: {
'/api': {
target: 'http://39.100.86.70:8088',
rewrite: path => path.replace(/^\/api/, '')
}
}
},
})
步骤5:封装所有的api。
import request from '@/utils/request.js'
// get请求的写法
export const getShopListApi = (data, config={}) => {
request.get('https://www.baidu.com', {
params: data,
...config
})
}
// post请求的写法
export const changeListApi = (data, config={}) => {
request.post('https://www.baidu.com', data, config)
}
03.vuex模块
步骤1:安装vuex。
'npm i vuex@3'
步骤2:配置vuex核心模块。(src => store => index.js)
import Vue from 'vue'
import Vuex from 'vuex'
// (1)将Vuex在Vue中进行注册。
Vue.use(Vuex)
// (2)创建vuex的store仓库,store包含了state、actions、mutations、modules、getters五个部分。
const store = new Vuex.Store({
// (2.1)定义state。
state: {
username: '思密达',
score: 100,
age: 18
},
// (3)定义actions。
// actions中的方法:第一个参数是上下文参数context,一般表示store仓库,后面的才是形参。
// actions是不能直接操作state的,它只能context.commit('方法名', 参数)提交给mutaions处理。
actions: {
changeUsername (context, n) {
setTimeout(() => {
context.commit('changeName', n)
}, 2000)
}
},
// (4)定义mutations。
// mutations中的方法,第一个参数是state,后面的才是形参。
mutations: {
addOne (state, n) {
state.score += n
},
changeName (state, n) {
state.username = n
}
},
// (5)定义modules。
modules: {
user
}
})
export default store
步骤3:挂载到vue实例。(main.js)
// main.js文件
import store from '@/store/index.js'
new Vue({
render: h => h(App),
store
}).$mount('#app')
步骤4:配置分模块。(可选)
前提:需要开启namespace命名空间
export default {
// (1)开启命名空间。
namespaced: true,
// (2)分模块数据。
state: {
token: 'ewsmdkawln',
userInfo: {},
array: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
},
mutations: {
setToken (state, token) {
state.token = token
},
changeArray (state) {
state.array = ['金', '刚', '经']
}
},
actions: {
setToken (context, token) {
context.commit('setToken', token)
}
},
getters: {
filterArray (state) {
return state.array.filter(item => item > 5)
}
}
}
步骤5:调用主模块的数据、方法。
// 1.获取state
(1)手写:
1)模板中:{{ $store.state.xxx }}
2)组件JS中:this.$store.state.xxx
3)JS文件中:store.state.xxx
(2)映射:
computed: {
/* 原理:本质还是计算属性来的。
computed: {
count() {
return this.$store.state.count
}
} */
...mapState(['username', 'score'])
}
// 2.调用mutations(只能传递一个参数,想传多个可以用对象)
(1)手写:this.$store.commit('add', n)
(2)映射:
methods: {
/* 原理:本质还是普通方法,此时要确保触发的函数名 === mutaitons中的函数名
addOne (n) {
this.$store.commit('add', n)
} */
...mapMutations(['addOne']),
}
// 3.调用actions(只能传递一个参数,想传多个可以用对象)
(1)手写:this.$store.dispatch('changeUsername', n)
(2)映射:
methods: {
/* 原理:本质还是普通方法,此时要确保触发的函数名 == actions中的函数名
addOne (n) {
this.$store.dispatch('changeUsername', n)
} */
...mapActions(['changeUsername'])
}
步骤6:调用分模块的数据、方法。
// 1.获取state
(1)手写:
1)模板中:{{ $store.state.模块名称.xxx }}
2)组件中:this.$store.state.模块名称.xxx
3)JS模块中:store.state.模块名称.xxx
(2)映射:
computed: {
/* 原理:本质还是计算属性来的。
computed: {
xxx() {
return this.$store.state.模块名称.xxx
}
} */
...mapState('模块名称', ['xxx1', 'xxx2'])
}
// 2.调用mutations(只能传递一个参数)
(1)手写:this.$store.commit('add', n)
(2)映射:
methods: {
/* 原理:本质还是普通方法,此时要确保触发的函数名 == mutaitons中的函数名
addOne (n) {
this.$store.commit('模块名称/add', n)
} */
...mapMutations('模块名称', ['addOne']),
}
// 3.调用actions(只能传递一个参数)
(1)手写:this.$store.dispatch('changeUsername', n)
(2)映射:
methods: {
/* 原理:本质还是普通方法,此时要确保触发的函数名 == actions中的函数名
addOne (n) {
this.$store.dispatch('模块名称/changeUsername', n)
} */
...mapActions('模块名称', ['changeUsername'])
}
// 4.使用getters
(1)手写:{{ $store.getters['user/filterArray'] }} // 逆天设计
(2)映射:
computed: {
/* 原理:本质还是计算属性来的。
computed: {
filterArray() {
return this.$store.state.模块名称.filterArray
}
} */
...mapGetters('模块名称', ['filterArray'])
}
步骤7:持久化数据。
安装插件:npm i --save vuex-persistedstate
import Vuex from 'vuex'
import Vue from 'vue'
// 导入持久化函数
import createPersistedState from 'vuex-persistedstate'
Vue.use(Vuex)
const store = new Vuex.Store({
state () {
return {
num: 10
}
},
mutations: {
changeNum (state, value) {
state.num += 1
}
},
// 持久化配置
plugins: [
createPersistedState({
key: 'simida',
// render中写要存哪些东西,如果都要存,那么render函数可以不写。
render (state) {
return {
...state
}
}
})
]
})
export default store
04.pinia模块
了解pinia的优势。
// Pinia的优势
1. 去掉了mutaitons,只需写在actions即可。
去掉了modules,每一个store都是一个独立的模块。
2. 提供组合式API写法:普通数据就是state,普通函数就是actions,computed就是getters,确实有点牛逼。
3. 可以配合TypeScript,提供可靠的类型推荐。
4. pinia可以直接修改state,可以不通过mutations来修改。
步骤1:安装pinia。
'npm i pinia'
步骤2:创建pinia实例。(src => store => index.js)
// 导入createPinia函数,创建出pinia实例
import { createPinia } from 'pinia';
// 导入pinia持久化插件
import persist from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(persist)
export default pinia
步骤3:挂载到vue实例。
import App from './App.vue'
import { createApp } from 'vue'
// 导入pinia实例
import pinia from '@/store/index.js'
const app = createApp(App)
// 把pinia实例挂载到vue
app.use(pinia)
步骤4:配置pinia模块。(src => store => user.js)
import { defineStore } from 'pinia';
import persist from 'pinia-plugin-persistedstate' // pinia持久化插件。
// 1. 组合式API写法。 优点:普通数据就是state,普通函数就是actions,computed就是getters,确实有点牛逼。
export const useUserStore = defineStore('user', () => {
// (1) 维护数据count
let count = ref(0)
const setCount = (value) => count.value += value
const getCount = () => count.value
const doubleCount = computed(() => count.value * 2)
// (2) 维护数据token
const token = ref('123abc456bdc')
const setToken = (value) => token.value += value
const getToken = () => token.value
// (3) 记得最后要导出这些state、actions、getters。
return {
count, doubleCount, setCount, getCount,
token, setToken, getToken
}
},
{
// 2. 开启pinia持久化插件
// 写法1:使用默认配置。
persist: true
// 写法2:自定义配置。
persist: {
// 默认是localStorage
storage: sessionStorage,
// 默认持久化全部,可指定好哪些state、getters、actions要被持久化
paths: ['count', 'token', 'setToken', 'getCount'],
// 默认是以仓库的唯一标识'user'作为键名,值就是全部被持久化的state、getters、actions。
key: 'hm-user'
}
})
步骤5:pinia持久化插件安装。
'npm i pinia-plugin-persistedstate --save-dev'
步骤6:使用pinia。
<script setup>
// 导入store的counter模块
import { useUserStore } from '@/store/counter'
// (1) 不要对state、getters进行解构,会丢失响应式。
const userStore = useUserStore()
// (2) 如果真的需要解构,需要特殊处理后才能恢复响应式:需要使用storeToRefs()函数包一下。
const { count } = storeToRefs(userStore)
// (3) 如果解构store中的函数,则不用任何处理,因为函数不需要响应式。
const { addCount, minusCount } = userStore
</script>
<template>
<div>
<div>我是爷爷</div>
<span>{{ userStore.count }} - {{ userStore.msg }} - {{ userStore.double }}</span>
<button @click="userStore.addCount">+</button>
</div>
</template>
05.组件库
1.element-plus
步骤1:安装组件库
'npm i element-plus --save'
步骤2:导入组件库。
全部导入:
import { createApp } from 'vue'
import App from './App.vue'
// 导入Element-Plus的组件和样式
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
const app = createApp(App)
app.use(ElementPlus).mount('#app')
按需导入:
1)安装自动引入工具
'npm install -D unplugin-vue-components unplugin-auto-import'
2)去vite.config.js中配置自动引入工具。
// 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()],
}),
],
})
3)可能并没有完全搞定,可能还需要在main.js导入组件库的css样式。
import 'element-plus/dist/index.css'
06.组件封装
1.消息提示组件
步骤1:目录结构。
步骤2:index.js。
import { createApp, ref, watch } from 'vue'
import ToastComponent from './index.vue'
// 消息队列
const messageArr = ref([])
// 弹出消息提示框
const showMessage = (options) => {
// 动态生成一个消息提示框
const app = createApp(ToastComponent, options)
const EL = document.createDocumentFragment() // 虚拟节点
const VM = app.mount(EL) // 真实的节点
// 从组件接收一些东西进来用
const { setIsShowMessage, setNewTop, top, height } = VM
// 然后插入到body后面
document.body.appendChild(EL)
// 加入到消息队列。
messageArr.value.push(VM)
// 监视messageArr,刚调用方法时要执行一次,为了为弹框设置一次高度。
watch(messageArr, () => {
// 动态计算当前消息提示框的位置。当前VM的位置= (当前位置的index + 1) * top + 当前位置index * height
const currentIndex = messageArr.value.findIndex(item => item === VM)
const newTop = (currentIndex + 1) * top + currentIndex * height
setNewTop(newTop)
}, { immediate: true })
// 几秒后,消息提示框消失、从消息队列中移除、删除DOM节点
setTimeout(() => {
setIsShowMessage(false)
messageArr.value = messageArr.value.filter(item => item !== VM)
VM.$el.addEventListener('transitionend', () => {
app.unmount()
})
}, options.duration);
}
export default showMessage
步骤3:index.vue。
<script setup>
// 导入
import { ref, toRefs, onBeforeMount, onMounted, computed } from 'vue'
// 外界拿数据
const props = defineProps({
type: {
type: String,
default: 'info'
},
content: {
type: String,
default: '这是一个消息提示内容'
}
})
// 定义映射数据
const types = {
success: 'success',
error: 'error',
warn: 'warn',
info: 'info'
}
const resultType = computed(() => {
console.log(types[props.type]);
return types[props.type]
})
// 1.控制显示(默认不显示)
const isShowMessage = ref(false)
let timer = null
const setIsShowMessage = (flag) => {
isShowMessage.value = flag
}
// 2.设置默认值
let top = ref(20) // 默认高度
const height = 40 // 弹框原始高度
// 设置top
const setNewTop = (newTop) => {
top.value = newTop
}
// 导出
defineExpose({
setIsShowMessage,
setNewTop,
height,
top: 20
})
</script>
<template>
<Transition name="slide-fade">
<div v-show="isShowMessage" class='message-container' :class="[resultType]"
:style="{top: top + 'px'}">
{{ content }}
</div>
</Transition>
</template>
<style lang='scss' scoped>
.message-container {
position: fixed;
z-index: 9999;
left: 50%;
transform: translate(-50%);
width: 360px;
height: 40px;
background-color: #edf2fc;
border-radius: 15px;
text-align: center;
line-height: 40px;
color: #91949a;
transition: all 0.3s;
// 不同样式
&.success {
color: #67c23a;
background-color: #f0f9eb;
}
&.error {
color: #f56c6c;
background-color: #fef3f3;
}
&.warn {
color: #e6a9a2;
background-color: #fdf6ec;
}
&.info {
color: #91949a;
background-color: #edf2fc;
}
}
.slide-fade-enter-active {
transition: all 0.3s ease-out;
}
.slide-fade-leave-active {
transition: all 0.3s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-fade-enter-from,
.slide-fade-leave-to {
transform: translateX(-50%) translateY(-20px);
opacity: 0;
}
</style>
步骤4:使用方法如下。
// 步骤1:导入核心函数showMessage
import showMessage from '@/components/Message/index.vue'
// 步骤2:绑定好点击事件
const show = () => {
showMessage({
type: 'success', // 类型可以有:success、error、info、warn
content: '这是一条提示消息',
duration: 3000
})
}
2.滑动验证组件
步骤1:目录结构如下。
步骤2:index.vue。
<template>
<div class="slider-container" :style="{ width: w + 'px' }">
<div class="slider-canvas" :style="{ width: w + 'px', height: h + 'px' }">
<!-- 大图 -->
<canvas :width="w" :height="h" ref="canvas" />
<!-- 小图 -->
<canvas class="slider-block" :width="l" :height="smallHeight" ref="block" />
<!-- 结果遮罩层 -->
<div
:class="`result-mask ${resultMask.class}`"
:style="{ height: `${resultMask.height}px` }"
/>
<div class="btn" @click="refresh"></div>
</div>
<!-- 拖动的滑块内容 -->
<div class="slider-square">
<div class="box">
<div
class="slider-square-icon"
@mousedown="sliderDown"
:style="{ left: slideInfo.sliderLeft }"
></div>
<span>{{ slideInfo.sliderText }}</span>
</div>
</div>
</div>
</template>
<script>
import { ref, reactive, onMounted, watch, onDeactivated } from 'vue'
export default {
props: {
w: {
// 大图的宽度
type: Number,
default: 320,
},
h: {
// 大图的高度
type: Number,
default: 160,
},
l: {
// 小图的宽度/高度(正方形的宽度,实际宽度需要加上圆弧的宽度)
type: Number,
default: 40,
},
sliderText: {
// 滑块文字
type: String,
default: "拖动滑块向右>>",
},
bigImg: {
// 大图源
type: String,
default: "",
},
smImg: {
// 小图图源
type: String,
default: "",
},
r: {
// 小图的拼图半径
type: Number,
default: 0,
},
accuracy: {
// 拖动误差范围
type: Number,
default: 5,
},
posY: {
// Y坐标
type: Number,
default: 0,
},
posX: {
// X坐标
type: Number,
default: 0,
},
smallHeight: {
// 小图的宽度/高度(正方形的宽度,实际宽度需要加上圆弧的宽度)
type: Number,
default: 40,
}
},
setup (props, { emit }) {
const canvas = ref(null); // 大图ref
const canvasCtx = ref(null); // 大图canvas绘制容器
const block = ref(null); // 小图ref
const blockCtx = ref(null); // 小图canvas绘制容器
const { w, h, l, r, sliderText, posY, posX, accuracy, smallHeight } = props;
const slideInfo = reactive({
sliderLeft: 0, // 可拖动滑块的left
block_x: 0, // 拼图最终要滑动的位置x
block_y: 0, // 拼图位置的y
sw: l, // 拼图的实际宽度 小图宽度 + 2*半径 + 3(border)
sliderText, // 滑块提示文字
bigImg: "",
smImg: "",
});
const origin = reactive({
// 鼠标按下初始位置
orginX: 0,
originY: 0,
});
const resultMask = reactive({
// 结果提示背景色
class: "",
height: 0,
});
// 初始化canvas dom
const initDom = () => {
canvasCtx.value = canvas.value.getContext("2d");
blockCtx.value = block.value.getContext("2d");
};
// 初始化图片
const initImg = () => {
const bigImg = createImg(() => {
canvasCtx.value.drawImage(bigImg, 0, 0, w, h);
}, props.bigImg);
const smImg = createImg(() => {
blockCtx.value.drawImage(smImg, 0, 0, l, smallHeight);
}, props.smImg);
};
const createImg = (onload, url) => {
const img = document.createElement("img");
img.crossOrigin = "Anonymous";
img.onload = onload;
img.onerror = () => {
img.src = url;
};
img.src = url;
return img;
};
onMounted(() => {
initDom();
initImg();
bindEvents();
});
const slider = ref(false);
// 鼠标按下
const sliderDown = (e) => {
slider.value = true;
slideInfo.sliderText = "";
origin.orginX = e.pageX;
origin.originY = e.pageY;
};
// 鼠标移动
const sliderMove = (e) => {
if (slider.value) {
const moveX = e.pageX - origin.orginX;
const moveY = e.pageY - origin.originY;
if (moveX < 0 || moveX + l > w) return false;
slideInfo.sliderLeft = moveX + "px";
let blockLeft = ((w - slideInfo.sw) / (w - 40)) * moveX;
block.value.style.left = blockLeft + "px";
}
};
// 鼠标松开
const sliderUp = (e) => {
if (!slider.value) return false;
slider.value = false;
resultMask.height = h / 2;
const left = parseInt(block.value.style.left);
emit("sliderJudge", left);
};
// 将鼠标移动,鼠标松开事件绑定在dom上,如果鼠标移出滑块范围,还可以继续移动
const bindEvents = () => {
document.addEventListener("mousemove", sliderMove);
document.addEventListener("mouseup", sliderUp);
};
// 调用接口获取图片,异步,监听图片地址变动
watch(
() => props.bigImg,
(newVal) => {
reset(); // 重置图片状态
initImg(); // 重新加载图片
}
);
//刷新图片
const refresh = () => {
emit("refresh");
}
// 重置/刷新图片
const reset = () => {
canvasCtx.value.clearRect(0, 0, w, h);
blockCtx.value.clearRect(0, 0, l, smallHeight);
slideInfo.sliderText = sliderText;
resultMask.height = 0;
resultMask.class = "";
slideInfo.sliderLeft = 0;
block.value.style.left = 0;
};
// 组建销毁时,移除鼠标事件
onDeactivated(() => {
document.removeEventListener("mousemove", sliderMove);
document.removeEventListener("mouseup", sliderUp);
});
return {
canvas,
canvasCtx,
blockCtx,
block,
slideInfo,
resultMask,
refresh,
sliderDown,
sliderMove,
sliderUp,
reset,
initDom,
initImg
};
}
}
</script>
<style lang="scss" scoped>
.slider-container {
.slider-canvas {
position: relative;
.slider-block {
position: absolute;
top: 0;
left: 0;
}
.btn {
position: absolute;
right: 0;
top: 0;
width: 34px;
height: 34px;
cursor: pointer;
background-image: url('../../assets/images/light.png');
background-position: 34px 35px;
z-index: 999;
}
@keyframes result-show {
0% {
opacity: 0;
}
100% {
opacity: 0.7;
}
}
.result-mask {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
opacity: 0.7;
&.success {
background-color: #52ccba;
animation: result-show 1s;
}
&.fail {
background-color: #f57a7a;
animation: result-show 1s;
}
}
}
}
.slider-square {
background-color: #52a352;
height: 40px;
text-align: center;
line-height: 40px;
border: 1px solid #ddd;
position: relative;
margin-top: 12px;
.slider-square-icon {
position: absolute;
top: 0;
left: 0;
height: 39px;
width: 39px;
background-color: #ee9016;
box-shadow: blanchedalmond 0px 0px 1px 2px;
cursor: pointer;
&:hover {
background-color: rgb(224, 147, 30);
}
}
span {
color: #fff;
}
}
</style>
步骤3:组件使用方法。
// 步骤1:导入组件
import SlideSplit from '@/components/SlideSplit/index.vue'
// 步骤2:template声明组件
<template>
<SlideSplit v-if="isShowSlideSplit" :w="slideInfo.bigWidth" :h="slideInfo.bigHeight"
:l="slideInfo.smallWidth" :bigImg="slideInfo.bigImageBase64" :smImg="slideInfo.smallImageBase64"
:r="slideInfo.smallWidth" :accuracy="slideInfo.accuracy" :smallHeight="slideInfo.smallHeight"
@refresh="handlerRequest" @sliderJudge="onJudge"></SlideSplit>
</template>
// 步骤3:js中准备数据
const slideInfo = ref({
bigWidth: 0,
bigHeight: 0,
bigImageBase64: '',
smallImageBase64: '',
smallWidth: 0,
smallHeight: 0,
accuracy: 8,
bigImage: null, // 为了ts类型不报错,下面属性均无效。
smallImage: null,
posX: null,
posY: null
})
3.虚拟滚动组件(等高)
方法1:手写。
组件源码:
// 组件源码virtualScroll.vue(每一项,是插槽+作用域插槽定义的,将来插入自定义组件即可。)
<script setup>
// 导入
import { ref, toRefs, onBeforeMount, onMounted, computed } from 'vue'
// 1.外部传入的数据
const props = defineProps({
// 总数据
items: {
type: Array,
default: () => []
},
// 每一项数据的高度
itemHeight: {
type: Number,
default: 60
},
// items数组中,哪一个属性作为v-for中的key值
keyField: {
type: String,
default: 'id'
}
})
// 2.长列表的高度
const totalHeight = computed(() => {
return props.items.length * props.itemHeight
})
// 3.渲染池:根据原始数据,构造出要渲染的10条数据
const pool = ref([]) // 要渲染的数据
const containerDOM = ref(null) // DOM节点
// 前面多显示10个,用缓存换取性能
let preBuff = 5
let nextBuff = 5
const setPool = () => {
// 获取container滚动的高度
const scrollTop = containerDOM.value.scrollTop
// 获取容器的高度
const containerHeight = containerDOM.value.clientHeight
// 计算开始、结束的位置
let startIndex = Math.floor(scrollTop / props.itemHeight)
let endIndex = Math.ceil((scrollTop + containerHeight) / props.itemHeight)
// 前面多显示10个,用缓存换取性能
startIndex -= preBuff
if (startIndex < 0) startIndex = 0
endIndex += nextBuff
// 计算起始位置的偏移量
let startPos = startIndex * props.itemHeight
pool.value = props.items.slice(startIndex, endIndex).map((item, i) => (
{
item: item,
position: startPos + i * props.itemHeight
}
))
}
onMounted(() => {
setPool()
})
</script>
<template>
<div class='virtualScroll-container' ref="containerDOM" @scroll="setPool">
<div class="virtualScroll-wrapper" :style="{height: totalHeight+'px'}">
<div class="virtualScroll-item"
:style="{height: props.itemHeight+'px', transform: `translateY(${poolItem.position}px)`}"
v-for="poolItem in pool" :key="poolItem.item[keyField]">
<slot :item="poolItem.item"></slot>
</div>
</div>
</div>
</template>
<style lang='scss' scoped>
// 最外层容器
.virtualScroll-container {
overflow: auto;
width: 100%;
height: 100%;
scrollbar-width: none; // 在火狐中,隐藏滚动条样式
-ms-overflow-style: none; // 在ie10+、Edge中隐藏滚动条样式
// 在webkit内核,隐藏滚动条样式
&::-webkit-scrollbar {
display: none;
}
// 长列表容器
.virtualScroll-wrapper {
position: relative;
// 列表的每一项
.virtualScroll-item {
position: absolute;
left: 0;
top: 0;
width: 100%;
border-bottom: 1px solid black;
}
}
}
</style>
测试组件:
// 测试组件test.vue
<script setup>
import { ref, toRefs, onBeforeMount, onMounted } from 'vue'
// 导入组件
import virtualScroll from './index.vue'
import listItem from './listItem.vue'
// 1.准备一万条模拟数据
const items = []
for (let i = 0; i < 10000; i++) {
items.push({
id: i,
count: `我是第${i}条数据`
})
}
// 2.准备数据的key值
const keyField = 'id'
</script>
<template>
<div class='test-container'>
<virtualScroll :items="items" :itemHeight="100" :keyField="keyField" v-slot="{item}">
<listItem :item="item"></listItem>
</virtualScroll>
</div>
</template>
<style lang='scss' scoped>
// 最外层容器
.test-container {
width: 500px;
height: 500px;
border: 1px solid #000;
margin: 100px auto;
}
</style>
方法2:使用插件。
// 安装
npm install --save vue-virtual-scroller@next
// 导入(记得要导入css样式)
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
// 组件使用如下:
<template>
<RecycleScroller
class="scroller"
:items="list"
:item-size="32"
key-field="id"
v-slot="{ item }"
>
<div class="user">
{{ item.name }}
</div>
</RecycleScroller>
</template>
<script>
export default {
props: {
list: Array,
},
}
</script>
<style scoped>
.scroller {
height: 100%;
}
.user {
height: 32%;
padding: 0 12px;
display: flex;
align-items: center;
}
</style>
·······························
三、Vue项目起步
01.搭建note环境
步骤1:安装nvm插件。(安装前需要卸载原来的note.js)
网盘地址:'https://pan.baidu.com/s/1uoxlk8CVNHV2KTCwIGbQMQ?pwd=yi5m'
提取码:'yi5m'
步骤2:修改nvm源。(打开nvm配置文件,配置下面两条)
'nvm npm_mirror https://npmmirror.com/mirrors/npm/'
'nvm node_mirror https://npmmirror.com/mirrors/node/'
步骤3:配置npm源。
// 将npm源设置为淘宝镜像
'npm config set registry https://registry.npmmirror.com'
// 获取当前npm源
'npm config get registry'
步骤4:nvm的命令大全。
// 查看当前安装和使用的 node 版本
nvm list
// 安装某个 node 版本
nvm install v版本号
// 切换 node 版本
nvm use v版本号
// 设置默认版本
nvm alias v12.22.12
02.创建vue项目
Vue2
步骤1:安装vue-cli脚手架。
// 安装
'npm i -g @vue/cli@5.0.8'
// 检测
'vue --version'
步骤2:创建vue2项目。
// 创建项目
'vue create 项目名称'
// 启动项目
'yarn serve' 或者 'npm run serve'
Vue3
步骤1:安装vue-cli脚手架。
// 安装
'npm i -g @vue/cli@5.0.8'
// 检测
'vue --version'
步骤2:创建vue3项目。
// 步骤1:确保电脑安装了node.js
`node --version` // 查看node.js的版本
// 步骤2:创建vue3项目
`npm init vue@latest` // 1.初始化vue3项目结构
`项目名称` // 2.紧接着输入项目的名称
`npm install` // 3.最后根据提示执行命令
// 步骤3:启动项目
`npm run dev`
03.配置源码路径(Vue3)
步骤:在vite.config.js文件中,配置alias选项
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'),
}
}
})
04.解决require is not defined
在vite环境下,就可能出现require is not defined的报错
步骤1:安装插件
yarn add -D vite-plugin-require-transform
or
npm i vite-plugin-require-transform --save-dev
步骤2:去vite.config.js中配置,配置plugins
import { defineConfig } from 'vite'
import requireTransform from 'vite-plugin-require-transform';
plugins: [
requireTransform({
fileRegex: /.js$|.vue$/
})
],
步骤3:如果还是解决不了,就去vite.config.js中修改如下配置
// 不用require('path')引入,而是使用import导入
import path from 'path'
05.配置环境变量
// 1. 在哪配置环境变量
在项目根目录下,配置两个文件(这两个文件与src目录同级):
(1) .env.development // 开发环境会被运行
(2) .env.production // 生产环境会被运行
// 2.怎么定义环境变量?
必须以'VUE_APP_'开头,来声明环境变量。
例如:声明一个baseURL: 'VUE_APP_BASE_URL = https://www.baidu.com'
// 3.怎么读取环境变量?
(1) 访问环境变量:'process.env.VUE_APP_变量名'。
(2) 判断是否开发环境:'process.env.NODE_ENV'。// production、development
06.解决跨域
方法1:配置代理解决跨域(仅开发阶段)。
// 代理解决跨域原理
前端请求到node代理的服务器,通过node代理服务器来向后端发起请求,
就可以成功的发起请求并获取数据。因为这是服务器与服务器之间的通信,不存在同源策略的影响,
同源策略只在浏览器中起作用。
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// 导入自动import插件
import AutoImport from 'unplugin-auto-import/vite'
export default defineConfig({
// 插件
plugins: [
vue(),
AutoImport({
imports: ['vue', 'vue-router']
})
],
// 代理
server: {
proxy: {
'/api': 'http://testapi.xuexiluxian.cn'
}
},
// 配置@符号
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
方法2:解决生产阶段跨域问题(需要后端解决好跨域问题)
// 根据环境不同,从而请求不同的基地址。
(1)获取环境变量:process.env.VUE_APP_变量名
(2)获取环境:process.env.NODE_ENV
// 代码演示
const instance = axios.create({
baseURL: process.env.NODE_ENV === 'production' ? process.env.VUE_APP_BASE_URL : '',
timeout: 2000
})
07.按需加载路由
不要一次性加载全部路由,用到才加载,方法如下: