前置

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

  • 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. K-means聚类分析

    一.原理 先确定簇的个数,K 假设每个簇都有一个中心点 centroid 将每个样本点划分到距离它最近的中心点所属的簇中 选择K个点做为初始的中心点 while() { 将所有点分配个K个中心点形成K ...

  2. SpringBoot返回html页面

    一般Controller返回数据或页面,今天谈一下返回页面的场景. 一.不使用template 1. controller中定义对应的访问路由及返回的页面(使用Controller,不要使用RestC ...

  3. 数据库之 MySQL --- 数据处理 之 表操作、CRUD(六)

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一. 表操作 创建数据库CREATE DATABASE demo-- 删除数据库DROP DATABAS ...

  4. Java实现 LeetCode 400 第N个数字

    400. 第N个数字 在无限的整数序列 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, -中找到第 n 个数字. 注意: n 是正数且在32为整形范围内 ( n < 231 ...

  5. Java实现 LeetCode 372 超级次方

    372. 超级次方 你的任务是计算 ab 对 1337 取模,a 是一个正整数,b 是一个非常大的正整数且会以数组形式给出. 示例 1: 输入: a = 2, b = [3] 输出: 8 示例 2: ...

  6. 关于C#委托三种调用的分享

    一.同步调用 1.同步调用会按照代码顺序来执行2.同步调用会阻塞线程,如果是要调用一项繁重的工作(如大量IO操作),可能会让程序停顿很长时间,造成糟糕的用户体验,这时候异步调用就很有必要了. 举个栗子 ...

  7. MMDVM中继板测试软件MMDVMCal

    运行方法: 只支持windows 64位系统 32位下载:https://share.weiyun.com/52uHAO5 64位下载:https://share.weiyun.com/5IgdqvL ...

  8. 06.Django-用户认证

    用户认证 Django 内置一个 auth 模块,帮助用户实现注册.登录.注销以及修改密码等功能,帮助开发者省去了很多功夫 用于认证的数据表 auth_user User是auth模块中维护用户信息的 ...

  9. GatewayWorker与ThinkPHP等框架结合

    使用GatewayWorker时开发者最关心的是如何与现有mvc框架(ThinkPHP Yii laravel等)整合,以下是官方推荐的整合方式. 见示意图: ## 总体原则 现有mvc框架项目与Ga ...

  10. Nice Jquery Validator 【从 jQuery Validation 迁移】

    1. 初始化表单验证 .validate VS .validator jquery-validation : $("#myform").validate(options) nice ...