前置

从创建一个简单浏览器导航首页项目展开,该篇随笔包含以下内容的简单上手

  • vite
  • vue3
  • vuex4
  • vue-router next

预览效果有助于理清这些内容,限于篇幅,不容易展开叙述。由于项目逻辑简单,只使用了少量 API,我只是写这个小项目过把手瘾,所以对应标题 上手。如果您只是想学习 vue 周边的 API,那么,这篇文章将给您带来有限的知识。

初始化项目

使用 vite 初始化 vue3 项目。什么是 vite?Vite 是一个 Web 构建工具。开发过程中通过浏览器 ES Module 导入为您的代码提供服务,生成环境与 Rollup 捆绑在一起进行打包。

特性:

  • 闪电般快速的冷服务器启
  • 动即时热模块更换(HMR)
  • 真正的按需编译

vite 截至今天支持的功能:

  • Bare Module Resolving
  • Hot Module Replacement
  • TypeScript
  • CSS / JSON Importing
  • Asset URL Handling
  • PostCSS
  • CSS Modules
  • CSS Pre-processors
  • JSX
  • Web Assembly
  • Inline Web Workers
  • Custom Blocks
  • Config File
  • HTTPS/2
  • Dev Server Proxy
  • Production Build
  • Modes and Environment Variables
npm init vite-app aweshome
npm install
npm run dev
npm run build

最终生成的目录结构与使用 vue-cli 相似:

│  .npmignore
│ a.txt
│ index.html
│ package.json
├─public
│ favicon.ico
└─src
│ App.vue
│ index.css
│ main.js
├─assets
│ logo.png
└─components
HelloWorld.vue

可以在项目根目录下创建 vite.config.js 配置 Vite:

module.exports = {
// 导入别名
// 这些条目可以是精确的请求->请求映射*(精确,无通配符语法)
// 也可以是请求路径-> fs目录映射。 *使用目录映射时
// 键**必须以斜杠开头和结尾**
alias: {
// 'react': '@pika/react',
// 'react-dom': '@pika/react-dom'
// '/@foo/': path.resolve(__dirname, 'some-special-dir'),
},
// 配置Dep优化行为
optimizeDeps: {
// exclude: ['dep-a', 'dep-b'],
},
// 转换Vue自定义块的功能。
vueCustomBlockTransforms: {
// i18n: src => `export default Comp => { ... }`,
},
// 为开发服务器配置自定义代理规则。
proxy: {
// proxy: {
// '/foo': 'http://localhost:4567/foo',
// '/api': {
// target: 'http://jsonplaceholder.typicode.com',
// changeOrigin: true,
// rewrite: path => path.replace(/^\/api/, ''),
// },
// },
},
// ...
}

更多配置可以参考Github

另外,现在可以使用 vitepress 代替原来的 vuepress 构建文档或博客。

vue-router next

npm i vue-router@next

src/router/index.js

import {createRouter, createWebHistory} from 'vue-router'
import Home from '../components/home/Home.vue'
import Cards from '../components/cards/Cards.vue' const router = createRouter({
history: createWebHistory(),
routes: [
// route -> routes
{
path: '/',
name: 'home',
component: Home,
},
{
path: '/cards',
name: 'cards',
component: Cards,
},
],
}) export default router

vue router next 还添加了动态路由,解决规则冲突的问题。做过权限管理应该深有体会。更多配置可以参考 Github

vuex4

使用与 vuex3 相同的 API。

安装

npm i vuex@next

src/constants 下存放了静态数据,它们都是如下形式:

export const vue = [
{
title: 'vue',
desc: 'Vue 是用于构建用户界面的渐进式的框架',
link: 'https://cn.vuejs.org/v2/guide/',
img: import('../assets/images/vue.png'), // require -> import
},
{
title: 'vue Router',
desc: 'Vue Router 是 Vue.js 官方的路由管理器。',
link: 'https://router.vuejs.org/zh/',
img: import('../assets/images/vue.png'),
},
// ...
]

src/store/index.js

import {createStore} from 'vuex'

import {vue, react, wechat, across, compileBuild} from '../constants/docs'
import {frontEndTools, OfficeTools} from '../constants/tools'
import {tools, docs, community} from '../constants/asideData'
import {blogs} from '../constants/community' const store = createStore({
state: {
asideData: [],
mainData: [],
},
mutations: {
setAsideData(state, key) {
const asideActions = {
'2': tools,
'3': docs,
'4': community,
}
state.asideData = asideActions[key]
},
setMainData(state, menuItemText) {
const actions = new Map([
['前端工具', frontEndTools],
['办公工具', OfficeTools],
['vue', vue],
['react', react],
['微信开发', wechat],
['跨端框架', across],
['编译构建', compileBuild],
['博客', blogs],
])
state.mainData = actions.get(menuItemText)
},
},
actions: {},
modules: {},
}) export default store

main.js

结合上文的 vuex vue-router 可以看出,vue3 核心插件的 api 都做了简化。

import './index.css'
import {createApp} from 'vue'
import store from './store'
import App from './App.vue'
import router from './router' const app = createApp(App) app.use(store)
app.use(router)
app.mount('#app')

sass

npm i sass

package.json > dependencies

{
"dependencies": {
"vue": "^3.0.0-beta.15",
"vue-router": "^4.0.0-alpha.13",
"vuex": "^4.0.0-beta.2"
},
"devDependencies": {
"@vue/compiler-sfc": "^3.0.0-beta.15",
"sass": "^1.26.8",
"vite": "^1.0.0-beta.1"
}
}

components

这个小项目本质上可以只有一个页面 .vue 构成,我将它拆分,便于阅读。

App.vue
<template>
<Header />
<main>
<router-view></router-view>
</main>
<Footer />
</template> <script>
import Header from './components/Header.vue'
import Footer from './components/Footer.vue' export default {
name: 'app',
components: {
Header,
Footer,
},
}
</script> <style>
main {
flex: 1;
}
</style>
components/cards/Aside.vue
<template>
<aside>
<ul>
<li :index="item.index" v-for="item in this.$store.state.asideData" :key="item.index" ref="menuItem" @click="handleSelect(item.value)">
<i class="fas fa-home"></i>
<span>{{ item.value }}</span>
</li>
</ul>
</aside>
</template> <script>
import store from '../../store' export default {
setup(props, context) {
return {
handleSelect(value) {
store.commit('setMainData', value)
},
}
},
}
</script> <style lang="scss">
aside {
flex: 1;
background-color: rgb(238, 238, 238);
height: 100%;
li {
display: flex;
align-items: center;
height: 56px;
line-height: 56px;
font-size: 14px;
color: #303133;
padding: 0 1.4rem;
list-style: none;
cursor: pointer;
transition: border-color 0.3s, background-color 0.3s, color 0.3s;
white-space: nowrap;
&:hover {
background-color: rgb(224, 224, 224);
}
}
} @media screen and (max-width: 768px) {
aside {
display: none;
&.active {
display: block;
}
}
}
</style>
components/cards/Cards.vue
<template>
<div id="card-outer">
<Aside />
<section></section>
</div>
</template> <script>
import Aside from './Aside.vue'
import router from '../../router' export default {
components: {
Aside,
},
}
</script> <style lang="scss">
#card-outer {
display: flex;
align-content: stretch;
height: 100%;
& > section {
flex: 8;
}
} .main-card {
margin: 10px 0;
cursor: pointer;
.main-card-content {
display: flex;
align-items: center;
img {
width: 30px;
height: 30px;
margin-right: 10px;
}
.main-card-content-info {
width: 90%;
h3 {
font-size: 14px;
}
p {
font-size: 12px;
color: #888ea2;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
width: 100%;
line-height: 1.8;
}
}
span {
margin-left: 10px;
text-decoration: none;
&:nth-of-type(1) {
font-size: 18px;
font-weight: 700;
color: #ffa502;
white-space: nowrap;
}
&:nth-of-type(2) {
font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
</style>
components/home/Home.vue
<template>
<section id="search">
<div class="search-sources" style="margin-bottom: 10px;">
<span size="mini" type="primary" v-for="(item, index) in source" @click="changeSource(item.name)" :key="index" :style="`background:${item.color};border-color:${item.color}`"
>{{ item.name }}
</span>
</div>
<div class="searchbox" :class="searchbarStyle.className">
<input :placeholder="searchbarStyle.placeholder" v-model="searchValue" clearable v-on:keyup.enter="submit" />
<button @click="submit" slot="append" icon="el-icon-search">
<i class="fas fa-search"></i>
</button>
</div>
</section>
</template> <script>
export default {
data: () => ({
baseUrl: 'https://www.baidu.com/s?ie=UTF-8&wd=',
searchValue: '',
searchbarStyle: {
className: 'baidu',
placeholder: '百度一下,你就知道',
},
source: [
{
name: '百度',
color: '#2932E1',
},
{
name: '搜狗',
color: '#FF6F17',
},
{
name: 'Bing',
color: '#0c8484',
},
{
name: 'Google',
color: '#4285F4',
},
{
name: 'NPM',
color: '#EA4335',
},
],
}),
methods: { // 可以在 vue3 中使用 options API
changeSource(name) {
const actions = new Map([
[
'百度',
() => {
this.baseUrl = 'https://www.baidu.com/s?ie=UTF-8&wd='
this.searchbarStyle = {
className: 'baidu',
placeholder: '百度一下,你就知道',
}
},
],
[
'Bing',
() => {
this.baseUrl = 'https://cn.bing.com/search?FORM=BESBTB&q='
this.searchbarStyle = {
className: 'bing',
placeholder: '必应搜索',
}
},
],
[
'搜狗',
() => {
this.baseUrl = 'https://www.sogou.com/web?query='
this.searchbarStyle = {
className: 'sougou',
placeholder: '搜狗搜索',
}
},
],
[
'Google',
() => {
this.baseUrl = 'https://www.google.com/search?q='
this.searchbarStyle = {
className: 'google',
placeholder: 'Google Search',
}
},
],
[
'NPM',
() => {
this.baseUrl = 'https://www.npmjs.com/search?q='
this.searchbarStyle = {
className: 'npm',
placeholder: 'Search Packages',
}
},
],
])
actions.get(name)()
},
submit() {
const url = this.baseUrl + this.searchValue
window.open(url)
},
},
}
</script> <style lang="scss">
#search {
display: flex;
flex-direction: column;
justify-content: center;
align-content: stretch;
margin: 0 auto;
height: 40vh;
width: 40%;
& > div {
display: flex;
}
} .search-sources {
span {
margin-right: 0.5rem;
padding: 0.4rem 0.6rem;
color: #fff;
font-size: 14px;
line-height: 14px;
border-radius: 2px;
&:hover {
filter: contrast(80%);
transition: 0.3s;
}
}
} .searchbox {
padding-left: 1rem;
height: 2.6rem;
border-radius: 6px;
background-color: #fff;
border: 1px #ccc solid;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); input {
flex: 7;
border: none;
font-size: 1rem;
} button {
flex: 1;
i {
margin-right: 0;
}
}
} $sources-color: (
baidu: #2932e1,
bing: #0c8484,
sougou: #ff6f17,
google: #4285f4,
npm: #ea4335,
); $source-list: baidu bing sougou google npm; @each $source in $source-list {
.#{$source} {
&:hover {
border-color: map-get($sources-color, $source);
box-shadow: 0 2px 4px map-get($sources-color, $source);
transition: all 0.5s;
}
input {
&:hover {
border-color: map-get($sources-color, $source);
}
}
}
} @media screen and (max-width: 768px) {
#search {
width: 90%;
}
}
</style>
components/Header.vue
<template>
<header>
<ul class="nav">
<li @click="handleSelect('home')">
<i class="fas fa-home"></i>
<span>首页</span>
</li>
<li @click="handleSelect('tools')">
<i class="fas fa-tools"></i>
<span>工具</span>
</li>
<li @click="handleSelect('docs')">
<i class="fas fa-file-alt"></i>
<span>文档</span>
</li>
<li @click="handleSelect('community')">
<i class="fas fa-comment-dots"></i>
<span>社区</span>
</li>
</ul>
<MobileMenu />
</header>
</template> <script>
import MobileMenu from './MobileMenu.vue'
import store from '../store'
import router from '../router' export default {
components: {
MobileMenu,
}, setup() {
const handleSelect = item => {
store.commit('setAsideData', item)
if (item === 'home') {
router.replace({name: 'home'})
} else {
const actions = {
tools: ['setMainData', '前端工具'],
docs: ['setMainData', 'vue'],
community: ['setMainData', '博客'],
}
store.commit(actions[item][0], actions[item][1])
router.replace({name: 'cards'})
}
} return {
handleSelect,
}
},
}
</script> <style lang="scss">
header {
display: flex;
height: 60px;
align-content: stretch;
padding: 0 9.5rem;
} .nav {
display: flex;
align-items: center;
align-content: stretch;
li {
padding: 0.5rem 0.75rem;
&:hover {
background-color: #f3f1f1;
& span {
color: #3273dc;
}
}
}
} @media screen and (max-width: 768px) {
header {
padding: 0;
}
}
</style>
components/MobileMenu.vue
<template>
<section id="mobile-menu">
<div id="navbarBurger" class="navbar-burger burger" data-target="navMenuMore" :class="{active}" @click="sideToggle">
<span></span>
<span></span>
<span></span>
</div>
</section>
</template> <script>
export default {
data: () => ({
active: false,
}),
methods: {
sideToggle() {
this.active = !this.active
const classList = document.querySelectorAll('aside')[0].classList
this.active ? classList.add('active') : classList.remove('active')
},
},
}
</script> <style lang="scss">
#mobile-menu {
display: none;
position: absolute;
right: 0;
top: 0;
z-index: 999999;
} @media screen and (max-width: 768px) {
#mobile-menu {
display: block;
.navbar-burger {
position: relative;
color: #835656;
cursor: pointer;
height: 60px;
width: 60px;
margin-left: auto;
span {
background-color: #333;
display: block;
height: 1px;
left: calc(50% - 8px);
position: absolute;
transform-origin: center;
transition-duration: 86ms;
transition-property: background-color, opacity, transform;
transition-timing-function: ease-out;
width: 16px;
&:nth-child(1) {
top: calc(50% - 6px);
}
&:nth-child(2) {
top: calc(50% - 1px);
}
&:nth-child(3) {
top: calc(50% + 4px);
}
}
&.active {
span {
&:nth-child(1) {
transform: translateY(5px) rotate(45deg);
}
&:nth-child(2) {
opacity: 0;
}
&:nth-child(3) {
transform: translateY(-5px) rotate(-45deg);
}
}
}
}
}
}
</style>

最后

一套流程下来,vite 给我的感觉就是“快”。对于 vue 周边, API 都是做了一些简化,如果你对 esm 有些了解,将更有利于组织项目,可读性相比 vue2.x 也更高。也有一些问题,限于篇幅,本文没有探讨。做项目还是上 vue2.x 及其周边。另外,我没找到 vue3 组件库。

⚡ vue3 全家桶体验的更多相关文章

  1. 助你上手Vue3全家桶之Vue3教程

    目录 前言 1,setup 1.1,返回值 1.2,注意点 1.3,语法 1.4,setup的参数 2,ref 创建响应式数据 3,reactive 创建响应式数据 4,computed 计算属性 5 ...

  2. 助你上手Vue3全家桶之Vue-Router4教程

    目录 1,前言 1,Router 2.1,跳转 2.2,打开新页面 3,Route 4,守卫 4.1,onBeforeRouteLeave 4.2,onBeforeRouteUpdate 4.3,路由 ...

  3. 助你上手Vue3全家桶之VueX4教程

    目录 1,前言 2,State 2.1,直接使用 2.2,结合computed 3,Getter 3.1,直接使用 3.2,结合computed 4,Mutation 4.1,直接使用 4.2,结合c ...

  4. 基于 vite 创建 vue3 全家桶项目(vite + vue3 + tsx + pinia)

    vite 最近非常火,它是 vue 作者尤大神发布前端构建工具,底层基于 Rollup,无论是启动速度还是热加载速度都非常快.vite 随 vue3 正式版一起发布,刚开始的时候与 vue 绑定在一起 ...

  5. vue3 vite2 封装 SVG 图标组件 - 基于 vite 创建 vue3 全家桶项目续篇

    在<基于 vite 创建 vue3 全家桶>一文整合了 Element Plus,并将 Element Plus 中提供的图标进行全局注册,这样可以很方便的延续 Element UI 的风 ...

  6. 开箱即用 yyg-cli(脚手架工具):快速创建 vue3 组件库和vue3 全家桶项目

    1 yyg-cli 是什么 yyg-cli 是优雅哥开发的快速创建 vue3 项目的脚手架.在 npm 上发布了两个月,11月1日进行了大升级,发布 1.1.0 版本:支持创建 vue3 全家桶项目和 ...

  7. Vue3全家桶升级指南一composition API

    1.setup() vue3中的composition API中最重要的就是setup方法了,相当于组件的入口,所有的composition API都必须放到setup()中的使用. setup是在组 ...

  8. Vue3全家桶升级指南二ref、toRef、toRefs的区别

    ref是对原始数据的拷贝,当修改ref数据时,模板中的视图会发生改变,但是原始数据并不会改变. toRef是对原始数据的引用,修改toRef数据时,原始数据也会发生改变,但是视图并不会更新. 在vue ...

  9. Vue3 全家桶,从 0 到 1 实战项目,新手有福了

    前端发展百花放,一技未熟百技出.未知何处去下手,关注小编胜百书. 我是前端人,专注分享前端内容! 本篇文章主要是,使用 vite 创建一个vue3 项目,实践 vie-router4 vuex4 结合 ...

随机推荐

  1. 2019-ICLR-DARTS: Differentiable Architecture Search-论文阅读

    DARTS 2019-ICLR-DARTS Differentiable Architecture Search Hanxiao Liu.Karen Simonyan.Yiming Yang GitH ...

  2. Java实现 LeetCode 824 山羊拉丁文(暴力)

    824. 山羊拉丁文 给定一个由空格分割单词的句子 S.每个单词只包含大写或小写字母. 我们要将句子转换为 "Goat Latin"(一种类似于 猪拉丁文 - Pig Latin ...

  3. Java实现 LeetCode 37 解数独

    37. 解数独 编写一个程序,通过已填充的空格来解决数独问题. 一个数独的解法需遵循如下规则: 数字 1-9 在每一行只能出现一次. 数字 1-9 在每一列只能出现一次. 数字 1-9 在每一个以粗实 ...

  4. Java实现 LeetCode 240 搜索二维矩阵 II

    public static boolean searchMatrix(int[][] matrix, int target) { if(matrix.length == 0) return false ...

  5. Java 虚拟机是如何判定两个 Java 类是相同的?

    Java 虚拟机是如何判定两个 Java 类是相同的? 答:Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样.只有两者都相同的情况,才认为两个类是相同的.即便是同样的字节代码 ...

  6. java实现奇怪的比赛

    ** 奇怪的比赛** 某电视台举办了低碳生活大奖赛.题目的计分规则相当奇怪: 每位选手需要回答10个问题(其编号为1到10),越后面越有难度.答对的,当前分数翻倍:答错了则扣掉与题号相同的分数(选手必 ...

  7. java实现第六届蓝桥杯星系炸弹

    星系炸弹 题目描述 在X星系的广袤空间中漂浮着许多X星人造"炸弹",用来作为宇宙中的路标. 每个炸弹都可以设定多少天之后爆炸. 比如:阿尔法炸弹2015年1月1日放置,定时为15天 ...

  8. 使用macaca抓页面元素,执行命令后报安装失败处理Error: Command failed: ……pm install -r "/data/local/tmp/com.macaca.android.testing"

    最近换了小米手机做自动化测试,执行命令的时候报安装失败错误,错误如下 解决:设置小米允许USB安装就好了 pm install -r "/data/local/tmp/com.macaca. ...

  9. 练习C++的vector语法-约瑟夫问题

    //测试vector,约瑟夫问题 #include <iostream> #include <vector> using namespace std; int main() { ...

  10. EasyARM-iMX257如何配置出低速率CAN

    EasyARM-iMX257如何配置出低速率CAN 在EasyARM-iMX257 Linux开发指南 V1.02.01"5.6 socket CAN编程socket CAN编程" ...