开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天, 点击查看活动详情
引言
前端是一个发展的非常迅速的领域,如今的前端已经越来越卷,难度也随之上升,从很久之前的有手就行的“切图仔”,到如今的前端工程化。
越来越多的技术涌出。
html5,css3,css4,Less,Sass,stylus,ES6~ES12,babel,webpack,vite,vue2,vue3,React,TypeScript,jq,ajax,axios,VueX,Router......很多很多的技术栈,随着nodejs的推出,成功让js脱离浏览器的桎梏,让前端可以去做后端的事情,去操作数据库,去实现一些请求。
今天我带大家简单学习一下vite+vue3+ts实现一个超简单的记账本,旨在初步入门
vite,vue3,vuex和ts。
因为我们这个项目没做后端,所以数据无法持久性的存储,之后会搭一个后端。
vite与webpack
前端是一个日新月异,发展的非常非常快的领域,他的技术栈更迭速度非常快,需要前端工程师不断地去学习新技术,不能懈怠。
时过境迁,从 webpack、Rollup 到 Parcel
工具不断变迁,今天我们来介绍一下当下非常非常非常流行的打包工具----vite。
传统的webpack的不足之处
在讲解vite的优势之前我们先看一下传统的技术栈:webpack的构建的过程。
webpack在build的时候,遇到一些所需的依赖,会跳转到依赖处去分析理解依赖的内容,再回来继续往后构建,而如今前端工程化后,组件间的依赖往往非常复杂,繁琐,webpack在不断的跳转的去构建所额外需要的依赖的时候就会花费大量的时间,导致打包时间也会成倍的增加。
而现在很多项目往往都有复杂的依赖关系,这也导致其越来越慢。
vite的优势
原因一
webpack出现的比较早,那时候浏览器并不支持模块化开发,并不支持ES Modules的标准,那时候的浏览器采用的加载顺序就是从上往下的同步加载。
所以webpack需要把所有的文件都遍历完成,理清依赖关系,定义每个模块的局部变量和全局变量的覆盖顺序,打包成完整的文件后才能交给浏览器去进行加载。
而ES Modules是一种将JavaScript程序拆分为可按需导入的单独模块的机制。
这是vite之所以这么快的第一个原因。
原因二
vite在启动的时候分为 冷启动 和 热更新。
冷启动的快
我们先来说他在冷启动下的快。
Vite在冷启动的时候,将代码分为依赖和源码两部分,源码部分使用ESModules或者CommonJS拆分到很多个小的模块中,
对于依赖部分,Vite使用Esbuild对依赖进行预构建。
Esbuild使用Go语言开发,相对于JavaScript,Go语言是一种编译型语言,在编译阶段就已经将源码转译为机器码。所以比js要快很多很多。
既然底层是大名鼎鼎的Go语言,所以ES Build相比单线程异步的js,他的运行速度和运行效率是要比js快很多的。
Rollup和webpack都没有使用多线程的能力,而ES Build不同,它使用了多线程的优势。
所以也导致了Esbuild构建模块的速度比webpack快到10-100倍。
对于源码部分
Vite省略了webpack遍历打包的时间,这部分工作交给浏览器,基本没有打包的时间,Vite只是在浏览器发送对模块的请求时,拦截请求,对源码进行转换后提供给浏览器,实现了源码的动态导入。
热更新的快
vite在热更新上,不需要对所有的改动都完全重构打包,vite只会对失活的模块进行热重载,而不影响页面的其他部分。
无论项目的规模多大,Vite的热更新也是在原生的ESM上进行的,只会加载当前使用到的模块。
vite的缺陷
Vite的生态与webpack相差甚远,在plugin等方面无法媲美诞生时间久远的webpack。
Vite的开发环境很惊艳,但是因为一些原因,生产环境还是使用Rollup进行构建的,还是需要打包的。
初始化一个项目
既然vite这么香的,那么我们就开始我们的vite+vue3+vuex+ts实现一个超简单的记账本之旅啦。
打开我们的cmd
输入npm init vite@latest
接下来为我们的项目起名
然后选择我们要使用的框架,这里我们选择Vue作为开发的框架。
然后选择语言,这里我选择Ts,当然不熟悉ts的小伙伴也可以选择js作为开发语言。
然后回车。
咦?
瞬间完成了????
vite快也不能这么快吧。
好吧
原来是我们没有下载依赖。
接下来我们cd到项目中去。
然后npm install
下载相关依赖。
静待一会,还是相比webpack肉眼可见的快的完成了项目的创建。
接下来我们使用
npm run dev
来启动项目。
挖趣
这么快????
接下来我们进入http://localhost:5173/
看到如下内容,完成了demo的创建。
项目的编写
接下来我们正是进入项目的编写。
项目结构如上。
我们先删除自动生成的组件HelloWorld.vue,再清空App.vue中的全部内容。
小准备
我们准备一下要用到的依赖
npm i vue-router
npm install element-plus --save
npm install echarts --save
npm install less less-loader --save-dev
main.ts
我们首先修改一下main.ts文件
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from '../router';
import store from '../store'
import menu from './components/menu.vue';
import 'element-plus/dist/index.css'
import * as ECharts from 'echarts'
import ElementPlus from 'element-plus'
const app = createApp(App);
app.use(router);
app.use(store);
app.use(ElementPlus);
app.config.globalProperties.$ECharts = ECharts;
app.component('Menu',menu);
app.mount('#app');
我们抛弃了Vue2的写法,采取了Composition Api的组合式语法。
content1.vue
我们来编写第一个组件。
router.ts
我们创建一个router.js文件。
内容如下:
import { createRouter , createWebHashHistory } from "vue-router";
import demo1 from './src/components/demo1.vue'
import demo2 from './src/components/demo2.vue'
interface Routers {
path:string;
component:object
}
const routes:Array<Routers> = [{
path:"/demo1",
component:demo1
},{
path:"/demo2",
component:demo2
}
]
const router = createRouter({
history:createWebHashHistory(),
routes
})
export default router;
Menu.vue
接下来我们来编写Menu.vue的内容
<template>
<div class="Menu">
<router-link to="/demo1" class="Menu_child">demo1</router-link>
<router-link to="/demo2" class="Menu_child">demo2</router-link>
</div>
<router-view></router-view>
</template>
<style scoped lang="less">
@width : 100%;
@height:10%;
.Menu {
width: @width;
height: @height;
position: absolute;
bottom: 0%;
right: 0;
left: 0;
display: flex;
font-size: 24px;
justify-content: center;
}
.Menu .Menu_child {
margin-right: 0;
flex-grow: 1;
}
</style>
demo2.vue
<template>
<header>
<h1 @click="initCharts">简单展示一下</h1>
</header>
<main id="main" class="dark">
<!-- <div id="content">
{{fishMoney}},{{VegetablesMoney}},{{meatMoney}}
</div> -->
</main>
</template>
<style scoped>
#main {
display: flex;
justify-content: center;
width: 600px;
height: 540px;
}
#content {
width: 360px;
height: 360px;
}
</style>
<script setup lang="ts">
import echarts from "echarts";
import { onMounted } from "vue";
import { ref ,Ref , computed } from "vue";
import { useStore } from 'vuex';
const store = useStore();
let fishMoney:Ref<number> = computed(()=>store.state.fishMoney);
let VegetablesMoney:Ref<number> = computed(()=>store.state.VegetablesMoney);
let meatMoney:Ref<number> = computed(()=>store.state.meatMoney);
interface Data {
value:number;
name:string;
}
interface Series {
type:string;
data:Array<Data>;
roseType:string
}
interface Option {
series:Series;
}
function initCharts(){
let myChart = echarts.init(document.getElementById('main') as HTMLElement);
let data:Array<Data> = [
{
value:fishMoney.value,
name:"卖鱼的钱"
},
{
value:VegetablesMoney.value,
name:"卖菜的钱"
},
{
value:meatMoney.value,
name:"卖肉的钱"
}
]
let series:Array<Series> = [
{
type: 'pie',
data,
roseType: 'area'
}
]
let option = {
series
};
myChart.setOption(option);
}
</script>
demo1.vue
<template>
<header>
<h1>记账</h1>
</header>
<main class="dark">
<div class="content">
卖鱼的收入:<el-input-number v-model="f" @input="fishMoneyStatus"/>
卖菜的收入:<el-input-number v-model="v" @input="VegetablesMoneyStatus"/>
卖肉的收入:<el-input-number v-model="m" @input="meatMoneystatus"></el-input-number>
</div>
<el-button type="primary">帮助</el-button>
</main>
</template>
<style scoped>
.content {
display: flex;
justify-content: center;
}
</style>
<script setup lang="ts">
import { ref ,Ref , computed } from "vue";
import { useStore } from 'vuex';
const store = useStore();
let f:Ref<number> = ref(0);
let v:Ref<number> = ref(0);
let m:Ref<number> = ref(0);
let fishMoney:Ref<number> = computed(()=>store.state.fishMoney);
let VegetablesMoney:Ref<number> = computed(()=>store.state.VegetablesMoney);
let meatMoney:Ref<number> = computed(()=>store.state.meatMoney);
// f = fishMoney;
// v = VegetablesMoney;
// m = meatMoney;
function fishMoneyStatus() {
store.commit('fishMoneyStatus',f);
console.log(f);
console.log(computed(()=>store.state.fishMoney).value);
}
function VegetablesMoneyStatus() {
store.commit('VegetablesMoneyStatus',v);
}
function meatMoneystatus() {
store.commit('meatMoneystatus',m);
}
</script>
最后看一下Menu.vue文件,这里引入了路由的使用的菜单
<template>
<div class="Menu">
<router-link to="/demo1" class="Menu_child">demo1</router-link>
<router-link to="/demo2" class="Menu_child">demo2</router-link>
</div>
<router-view></router-view>
</template>
<style scoped lang="less">
@width : 100%;
@height:10%;
.Menu {
width: @width;
height: @height;
position: absolute;
bottom: 0%;
right: 0;
left: 0;
display: flex;
font-size: 24px;
justify-content: center;
}
.Menu .Menu_child {
margin-right: 0;
flex-grow: 1;
}
</style>
最后新建一个文件夹store 建立一个index.ts文件
内容如下:
import { createStore } from 'vuex';
const store = createStore({
state(){
return {
fishMoney:0,
VegetablesMoney:0,
meatMoney:0
}
},
mutations:{
fishMoneyStatus (state,f):void {
state.fishMoney = f;
},
VegetablesMoneyStatus(state,v):void {
state.VegetablesMoney = v;
},
meatMoneystatus(state,m):void {
state.meatMoney = m;
}
}
})
export default store;