今天使用vue3的命令创建了个新项目
代码请参考
https://gitee.com/hellow-world-lzc/lzc-vue-three
npm init vue@latest
命令行提示在create-vue,ok
提示是否,ts,jsx,router,pinia…可以按照个人需要选择
创建完成后,
cd your-app-name
npm i
npm run dev
运行这些命令即可完成项目本地编译启动
我这里继续安装了些可能会用到的框架,插件,库
npm install element-plus --save
npm install moment --save
npm i --save lodash-es
npm install echarts --save
npm i axios vue-axios --save
npm install --save sass
将初始代码中的无用部分先清理掉
在views下新创建一个first-page页面,
简单修改路由
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: () => import('@/views/first-page/index.vue')
}
]
})
export default router
给首页index.vue来点简单的布局
页面头部
左侧菜单
内容主体
代码运行
先将axios简单封装下,方便在demo中调用
axios.ts
import axios from 'axios'
const initAxios = axios.create({
withCredentials: true,
timeout: 10000 //数据响应过期时间
})
//请求拦截器
initAxios.interceptors.request.use(
(config) => {
//在发送之前做点什么
return config
},
(error) => {
//对请求错误做点什么
return error
}
)
//响应拦截器
initAxios.interceptors.response.use((response) => {
return response
})
//导出
export default () => initAxios
不同请求方式 method.ts
import Axios from './axios'
const instance = Axios()
export default {
get(url: string, params: any, headers: any) {
return instance.get(url, { params, headers })
},
post(url: string, params: any, headers: any) {
return instance.post(url, params, { headers })
},
put(url: string, params: any, headers: any) {
return instance.put(url, params, { headers })
},
delete(url: string, params: any, headers: any) {
return instance.delete(url, { params, headers })
}
}
注册接口api request.ts
import service from './method'
//声明一个基础接口变量
let baseURL: string
//配置开发环境
if (process.env.NODE_ENV === 'development') {
baseURL = 'http://192.168.0.100:5050'
}
// 配置生产环境
if (process.env.NODE_ENV === 'production') {
baseURL = 'http://192.168.0.100:5050'
}
//设置请求头(如果请求头统一的话可以在axios文件设置,则无须从这里传过去)
const headers = {
// Accept: "application/json;charset=UTF-8",
'Content-Type': 'application/json'
}
//根据自身需求
const _service = {
getList(data: any) {
const url = baseURL + '/api/list'
return service.get(url, data, headers)
},
getDetail(data: any) {
const url = baseURL + '/api/detail'
return service.get(url, data, headers)
},
createItem(data: any) {
const url = baseURL + '/api/add'
return service.put(url, data, headers)
},
updateItem(data: any) {
const url = baseURL + '/api/update'
return service.post(url, data, headers)
},
deleteItem(data: any) {
const url = baseURL + '/api/delete'
return service.delete(url, data, headers)
}
}
//导出
export default _service
在index.vue中简单引入使用下request
template:
内容主体
{{ msg }}
typescript
import _service from '@/service/request'
import { onMounted, ref } from 'vue'
let msg = ref('')
const getList = () => {
msg.value = '请求中...'
_service
.getDetail({
userName: 'helloworld',
password: 'helloworld'
})
.then((res) => {
msg.value = 'res: ' + res
console.log(msg.value)
})
.catch((err) => {
msg.value = 'err: ' + err
console.log(msg.value)
})
}
onMounted(() => {
getList()
})
这里为了方便使用,将element-plus 全局引入
// main.ts
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(ElementPlus)
app.mount('#app')
在index.vue添加个按钮组件,绑定事件,启动运行
将打包成功的dist/index.html使用live server打开(vscode 扩展:live server)
页面空白,提示路径不对,404
在vite.config.js文件添加base:‘./’
重新build,将dist/index.html open by live server
文件路径是没问题了
不过页面依然是空白的,
稍微百度了下,这里是因为我们新建的项目里,路由模式默认了historry模式导致,
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
name: 'home',
component: () => import('@/views/first-page/index.vue')
}
]
})
export default router
打包成功,页面展示内容和本地启动完全一致。
那么,在上面这一阶段:我们已经搭建好:vue3 + ts + vite + scss + element-plus + vouter + axios.并且在本地启动调试&打包构建启动验证完毕。
接下来,我们继续尝试vue组件化开发的内容:组件通信
在index.vue下创建两个子组件,方便一会儿尝试父子,子父,兄弟,祖孙通信
// 父组件 index.vue
从a组件来的信息:{{ msgFromA }}
import childA from './child-a.vue'
import childB from './child-b.vue'
let msgFromA = ref('')
const handleA = (val: string) => {
msgFromA.value = val
}
// child-a.vue porps,emit实现父子传值,子父通信
child-a
来自父组件的数据:{{ msg }}
通知父组件
上面已经实现了父子组件的传值通信
接下来我们尝试下兄弟组件通信
在vue2版本中,我们是用事件总线来实现兄弟组件通信。
vue3版本中,已经移除了$on,$emit。我们可以使用官方推荐的mitt来实现
安装mitt
npm install --save mitt
封装下eventBus
import mitt from 'mitt'
const eventBus = mitt()
export default eventBus
b组件发起事件
import eventBus from '@/utils/eventBus.ts'
let val = ref('')
const clickEvent = () => {
eventBus.emit('bToA', val.value)
}
a组件注册事件
import eventBus from '@/utils/eventBus.ts'
let msgFromB = ref('')
onMounted(() => {
eventBus.on('bToA', (val: string) => {
msgFromB.value = val
})
})
到这里,我们最基本的props,emit,eventBus都已经实现.
记录下ts的相关提示信息
根据提示,是因为两个参数的类型不明确。
这里调整下eventBus.ts的代码,带上类型
import mitt, { Emitter } from 'mitt'
type Events = {
[propName: string]: any
}
const eventBus: Emitter = mitt()
export default eventBus
改为定义type即可
import mitt, { type Emitter } from 'mitt'
type Events = {
[propName: string]: any
}
const eventBus: Emitter = mitt()
export default eventBus
那么进入下一个环节,关于vue的computed和watch相关学习
import { computed, onMounted, ref } from 'vue'
let computedVal = computed(() => {
return val.value.toUpperCase()
})
computed也可以支持get,set操作,
computed也支持传参,
这里就不演示了
关于数据监听,有watch,watchEffect两种
watch是指定监听的数据
watchEffect是自动跟踪回调的响应式依赖
import { computed, onMounted, ref, watch, watchEffect } from 'vue'
let val = ref('')
let modelVal = ref('')
watch(val, async () => {
const a = await new Promise((resolve) => {
resolve(Math.ceil(Math.random() * 100 + 100))
})
console.log(a + val.value)
})
watchEffect(() => {
const b = val.value + modelVal.value
console.log(b)
})
基本的组件开发内容,基本到这里结束。
下面可以尝试下其他内容,
比如:透传,依赖注入,插槽
比如:国际化
国际化实现还穿插了状态管理,本地缓存
接着昨天的内容继续,
今天研究了下关于国际化的内容
如果只是处理非UI组件的内容,昨天的代码已经足够了。
https://gitee.com/hellow-world-lzc/lzc-vue-three/commit/c383fb76c1a8d8821ca93331125c5d7dcb04932c
但是UI组件的内容并没有一起切换。
今天参考了element plus + vue-i18n的内容,重写了下国际化的内容。
首先安装vue-i18n
npm install --save @types/vue-i18n
这里说下为什么安装@types
当我们用 npm 等包管理工具安装第三方包的时候,有些包并不是 TypeScript 编写的,自然也不会导出 TypeScript 声明文件。这种情况下,如果我们在 TypeScript 项目中引入了这种包,则会编译报错(没有设置 allowJS)。举个例子,当我们通过npm install jquery --save 安装 jquery 包并引用的时候,TypeScript 会报错。
vue-i18n同理
安装好之后,创建下lang相关内容文件,引用vue-i18n
// src/lang/i18n.ts
import zh from './zh.json'
import en from './en.json'
import { createI18n } from 'vue-i18n'
const i18n = createI18n({
legacy: false, // 没有该参数可能会报错
globalInjection: true, // 全局注入$t函数
locale: 'zh',
messages: {
zh: {
...zh
},
en: {
...en
}
}
})
export default i18n
// zh.json
{
"firstPage": {
"header": "页面头部",
"menu": "页面左侧菜单",
"main": "页面主体内容"
},
"greeting": {
"hello": "你好"
}
}
在main.ts中引入i18n
import i18n from './lang/vue-i18n'
const app = createApp(App)
app.use(i18n)
接下来是改造我们的路由入口文件app.vue
// 参考element plus说明,使用configProvider组件包裹好路由
// ts
import zhCn from 'element-plus/lib/locale/lang/zh-cn'
import en from 'element-plus/lib/locale/lang/en'
import { useLangStore } from '@/stores/index'
const lang = useLangStore()
interface localesType {
[zh: string]: Object
}
const locales = reactive({
zh: zhCn,
en: en
})
const locale = computed(() => {
// 不添加localesType这个接口,ts会报错
return locales[lang.language]
})
考虑到本地保存和全局通信,使用pinia和localStorage
创建stores相关文件
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
export const useLangStore = defineStore(
'lang',
() => {
const i18n = useI18n()
let language = ref(localStorage.getItem('language') || 'zh')
function setLanguage(lang: string) {
language.value = lang
localStorage.setItem('language', lang)
i18n.locale.value = lang
}
return { language, setLanguage }
},
{
persist: true
}
)
看看效果
这是切换到英文状态下的结果。
接下来,我们刷新页面,看看是否缓存到了
ok,基本功能已经完成。
遗留问题一:ts报错,
这里vue-i18n安装的版本:
查看了下vue-i18n的官网
https://vue-i18n.intlify.dev/guide/advanced/typescript.html
ok,uninstall下@types/vue-i18n
npm uninstall @types/vue-i18n
npm i --save vue-i18n@latest
遗留问题二:提示考虑到tree-shaking,需要做一些相应的配置
在vite.config.ts添加一行即可解决
'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js',
routes: [
{
path: '/',
redirect: '/index'
},
{
path: '/index',
name: 'home',
component: () => import('@/views/first-page/first-index.vue'),
children: [
{
path: 'content-management',
name: 'content-management',
component: () => import('@/views/content-management/content-index.vue')
}
]
},
{
path: '/login',
name: 'login',
component: () => import('@/views/login/login-index.vue')
},
{
path: '/menu-management',
name: 'menu-management',
component: () => import('@/views/menu-management/menu-index.vue')
}
]
第二步:分包策略
1)浏览器的缓存策略:
访问网站时向服务器获取静态资源并缓存起来,如css、js等
下次再访问时,如果之前保存的 “静态资源” 名字没有改变,则不会重新请求
2)vite打包文件生成策略:
打包时只要代码内容变了,就会生成hash字符完全不同的新文件
3)vite分包策略:
我们的业务代码时常改变,而依赖不会变化
所以把依赖分开打包,以避免多次重新请求资源
可以看到content-index.js明显减小,新加了我们配置的四个分包js
第三步:gzip压缩
把打包后的静态资源压缩成 gzip格式
服务器响应资源文件时发送 gzip格式文件
浏览器拿到 gzip文件 后再解压使用
文件比较小不建议用 gzip ,因为浏览器解压时间可能大于请求原来资源的时间
通过配置 vite 插件 “vite-plugin-compression” 实现:
第四步:使用cdn加速
我们项目的所有依赖以及文件在我们进行打包以后会放到我们的服务器上面去
我们把第三方模块写成 cdn 的形式
保证我们自己代码的一个小体积,降低我们自己服务器的传输压力
第三方代码通过 cdn 向最近最优的服务器请求过来
// npm i --save vite-plugin-cdn-import
import { Plugin as importToCDN } from 'vite-plugin-cdn-import'
这次就没有关于echarts的包了,
用live server打开index.html
还可以做下去除console.log,debugger的操作
明天的任务是学习下vue3里面新增的几个组件