第1个
动态引入组件
在配置路由时,写成 component: () => import('@/views/login/LoginPage.vue') 函数形式,是为了实现 路由懒加载(Route Lazy Loading) 。
这是前端性能优化的核心手段之一。如果对比直接导入的写法,你能更直观地理解它的作用:
一、 两种写法的对比
1. 传统写法:直接导入(静态导入)
JavaScript
import LoginPage from '@/views/login/LoginPage.vue' // 在文件最顶部引入
const routes = [
{ path: '/login', component: LoginPage } // 直接把组件赋值给 component
]
-
工作原理:当用户访问网站首页(哪怕根本没点击登录页)时,浏览器在一开始就会把所有的页面代码全部下载下来。
2. 函数形式写法:动态导入(路由懒加载)
JavaScript
const routes = [
// component 变成了一个返回 Promise 的函数
{ path: '/login', component: () => import('@/views/login/LoginPage.vue') }
]
-
工作原理:利用 ES6 的
import()动态导入函数。当项目打包时,打包工具(如 Vite 或 Webpack)会把LoginPage.vue单独拆分成一个小 JS 文件。只有当用户真正点击或访问/login这个网址时,浏览器才会去临时下载对应的 JS 代码。
二、 为什么要用函数形式?(核心优势)
1. 极大地提升首屏加载速度(解决“白屏”问题)
如果你的项目有 50 个页面,如果全部采用传统静态导入:
-
打包后会生成一个巨型
main.js文件(包含 50 个页面的所有代码)。 -
用户第一次打开首页时,必须等这个巨大文件彻底下载完才能看到画面,页面会出现漫长的白屏。
而采用函数形式(懒加载):
-
打包工具会把 50 个页面拆分成 50 个“小包”。
-
用户打开首页时,只下载首页用到的代码(可能只有几百 KB),瞬间就能打开页面。
2. 按需加载,节省流量
用户可能进了你的网站只逛了首页,根本没去访问管理后台或设置页面。采用函数形式,那些他没去过的页面代码就永远不会下载,既省用户的手机流量,也节省服务器的带宽。
三、 它的底层原理是什么?
在 JavaScript 中,() => import('...') 是一个箭头函数。
Vue Router 内部在路由切换时,会检查 component 属性:
-
如果它发现
component传的是一个普通组件(对象),它就直接渲染。 -
如果它发现
component传的是一个函数,Vue Router 会在网址匹配成功的这一刻,才去执行这个函数。 -
函数执行
import(...)后,会发下一个网络 HTTP 请求去拉取这个页面组件代码,下载成功后把它渲染到<router-view>中。
💡 开发建议
-
首页 / 登录页:因为是用户一定会访问的页面,有时也可以直接
import引入。 -
其他绝大多数业务页面(如:用户中心、设置页、详情页等) :一律强烈建议写成
() => import(...)的函数形式!
动态导入原理
当你在 Vue Router 中写下 component: () => import('@/views/login/LoginPage.vue') 时,构建工具(Vite 或 Webpack)在编译阶段会识别出这里的 import() 函数,并自动触发 代码分割(Code Splitting) 技术。
它们的具体工作原理和分包机制如下:
一、 静态导入 vs 动态导入在编译时的差异
1. 静态导入 (import Login from '...')
在打包时,打包工具会将所有通过静态 import 引入的代码沿着依赖树递归合并,最终打进同一个主 Bundle JS 文件中(如 index-a1b2c3.js)。
2. 动态导入 (() => import('...'))
import('...') 是 ES6 规范的原生语法,具有语法特殊性:
-
当打包工具(Vite 底层的 Rollup 或 Webpack)在扫描代码 AST(抽象语法树)时,一旦遇到
import('...'),就会将其认定为一个隐式的分割点(Split Point) 。 -
编译器会自动切断主文件的依赖链,把
import()路径所指向的模块及其独有的子依赖,单独打包提取成一个新的独立文件( Chunk File,如LoginPage-x8y9z0.js)。
二、 Vite 与 Webpack 的具体分包机制
虽然最终目的相同,但 Vite(基于 Rollup)和 Webpack 在实现细节上略有不同:
1. Vite / Rollup 的分包逻辑
-
自动识别与生成:Rollup 原生对 ES Module 支持极佳。每检测到一个
import(),它就会为该模块自动生成一个新的 Chunk。 -
自定义文件名:如果不做特殊配置,Vite 会根据组件的文件名或 Hash 生成文件名。如果需要自定义打包后的文件名,可以使用 Rollup 的
manualChunks配置,或者依赖 Rollup 的智能分包:如果两个懒加载路由共同引用了一个公共组件,Rollup 会自动将该公共组件抽离成第三个共享 Chunk,避免重复打包。
2. Webpack 的分包逻辑
在 Webpack 中,这个功能依赖其内置的 SplitChunksPlugin:
-
当遇到
import()时,Webpack 会将其解析为一个异步 Promise 节点。 -
魔术注释(Magic Comments) :Webpack 允许你在
import()函数内部写注释来控制打包行为,例如自定义文件名:JavaScript
component: () => import( /* webpackChunkName: "login-group" */ '@/views/login/LoginPage.vue' )加上这行注释后,打包出来的文件就会叫
login-group.js。如果有多个路由配置了相同的webpackChunkName,Webpack 会自动将它们合并打包到同一个 Chunk 文件中(即路由分组打包)。
三、 运行时(浏览器端)是如何加载这个分包的?
打包完成后,原有的 () => import('...') 代码会被编译转换成一段网络加载逻辑:
-
路由未匹配时:
浏览器只加载了核心的
main.js,其他路由的 Chunk 文件(如LoginPage.js)躺在服务器上,根本不会占用用户的下载流量和内存。 -
用户点击链接(匹配到
/login)时:-
Vue Router 执行
component函数,内部调用被编译后的import()函数。 -
该函数在浏览器端会动态向 DOM 中插入一个
<script src="/assets/LoginPage-x8y9z0.js"></script>标签(或通过fetch异步获取)。 -
脚本下载并解析完成后,Promise 状态变为 resolved,返回组件模块。
-
Vue Router 拿到组件,完成渲染并在
<router-view>中展示。
-
四、 性能补充:Prefetch 与 Preload(预加载)
既然懒加载需要临时下载文件,会不会导致用户点击时卡顿一下?
打包工具通常支持预加载机制来解决这个问题:
-
Vite:默认会自动为动态导入注入
<link rel="modulepreload">,在浏览器空闲时静默预加载这些 Chunk,等用户点击跳转时,文件已经在浏览器缓存里了,实现“秒开”。 -
Webpack:可以通过魔术注释
/* webpackPrefetch: true */,让浏览器在网络空闲(Idle)时提前把懒加载文件下载好。
第2个
页面请求情况
答案是:不一定。这完全取决于你使用的路由配置方式、是否开启了预加载,以及组件内部的代码逻辑。
我们可以把情况分为以下几种常见场景:
1. 会请求服务器的情况(首次访问懒加载路由)
如果你使用的是前面提到的 路由懒加载 写法:
JavaScript
{ path: '/login', component: () => import('@/views/login/LoginPage.vue') }
-
首次切换到该组件时:浏览器会向服务器发送 HTTP 请求。
-
请求的是什么? 请求的是该组件对应的 JS 代码文件/静态资源(例如
LoginPage.a1b2c3.js),而不是数据。 -
请求的是数据吗? 如果该组件的
onMounted或created生命周期函数里写了axios.get(...)发送接口请求,那么组件下载完成后,还会接着向后端服务器发送接口请求去拿数据。
-
2. 不会请求服务器的情况
情况 A:重复切换已下载过的路由组件
-
当你从
/home切换到/login,再切回/home时,不会重新请求组件的 JS 代码文件。因为浏览器和 Vue Router 已经把该组件的代码缓存到了本地内存中。 -
注意:如果你的组件生命周期(如
onMounted)里写了发送 API 接口请求的代码,虽然不请求 JS 文件,但依然会向后端发送 API 接口请求获取最新数据(除非你做了前端数据缓存)。
情况 B:使用的是静态导入(未配置懒加载)
如果你的路由是用普通方式导入的:
JavaScript
import LoginPage from '@/views/login/LoginPage.vue'
-
当用户第一次打开网站首页时,所有页面的 JS 代码就已经一次性全部下载完了。
-
此时在不同页面间切换,完全不需要向服务器请求任何 JS 文件,切换过程纯粹是在前端内存中替换 DOM 节点,速度极快。
情况 C:开启了 <keep-alive> 缓存组件
如果你在 Vue 中使用了 <keep-alive> 将组件包裹起来:
HTML
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" />
</keep-alive>
</router-view>
- 当组件被缓存后,再次切换回该组件时,既不会重新下载 JS 文件,也不会重新触发
onMounted里的 API 接口请求(除非写在onActivated钩子中)。
总结速查
| 场景 | 是否向服务器请求组件代码(JS文件)? | 是否向后端请求业务数据(API接口)? |
|---|---|---|
| 首次切入“懒加载路由” | 会(请求拆分出的 JS 包) | 会(如果在组件挂载时写了 Ajax/Axios) |
| 再次切入已访问过的路由 | 不会(利用浏览器本地缓存) | 会(只要写了 onMounted 发请求就会重新触发) |
| 静态导入的路由组件 | 不会(首页时已一次性下载完) | 会(由组件内部逻辑决定) |
使用 <keep-alive> 包裹的组件 | 不会 | 不会(组件状态和数据被冻结在内存中) |
第3个
element plus的span,offset
在 Element Plus(或 Element UI)的栅格系统(Layout 布局)中,:span 和 :offset 是用来控制组件宽度和位置偏移的两个核心属性。
它的底层逻辑非常简单:Element 把一整行的宽度均匀分成了 24 等份(你可以把整行想象成一个由 24 格组成的栅格)。
一、 两个属性的大白话解释
1. :span="6" (占几格 / 宽度)
-
含义:指定当前列(
<el-col>)占用 24 格中的多少格。 -
计算:。
-
效果:这个组件的宽度会占据整行宽度的 1/4 (25%) 。
2. :offset="3" (左边空几格 / 偏移量)
-
含义:指定当前列向右偏移(推开)多少格的距离(相当于在左侧加了
margin-left)。 -
计算:。
-
效果:这个组件的左边会空出 12.5% 的空白距离。
二、 组合起来看你的这行代码
HTML
<el-row>
<el-col :span="6" :offset="3" class="form">
<!-- 你的表单内容 -->
</el-col>
</el-row>
实际排版效果图解:
Plaintext
|--- 3格空白 (12.5%) ---|--- 6格表单内容 (25%) ---|---------------- 15格剩余空白 (62.5%) ----------------|
├─────────────── 整 行 宽 度(共 24 格) ────────────────────────────────────────────────────────┤
-
左边先产生 3 格的留白;
-
紧接着展示 6 格宽度的表单;
-
剩下右边还空着 15 格()。
三、 常见应用场景
-
表单居中/居左微调:比如你想让一个宽度为 12 格(半屏宽)的表单在页面中间显示,就可以设置
:span="12"并设置:offset="6"(因为左边空 6 格,中间占 12 格,右边就自动剩下 6 格,实现了居中:)。 -
两列拉开间距:
HTML
<el-row> <!-- 第一列占 8 格 --> <el-col :span="8">左边内容</el-col> <!-- 第二列占 8 格,并且往右推 4 格,这样两列中间就有了 4 格的空隙 --> <el-col :span="8" :offset="4">右边内容</el-col> </el-row>
总结:
-
span控制自己有多宽(最大 24)。 -
offset控制左边留白多宽(往右推)。
user-select 是 CSS 中的一个样式属性,用来控制用户是否能够在网页上选中文本或元素内容(即用鼠标拖动划选、或者按住双击高亮文本的操作)。
use-select css
一、 常见属性值及效果
CSS
/* 语法示例 */
.element {
user-select: none;
}
| 属性值 | 效果说明 | 常见使用场景 |
|---|---|---|
none | 禁止选中。用户无法用鼠标拖拽划选该元素内的任何文本。 | 按钮、菜单、图标、游戏画布、防止用户轻易复制内容。 |
text | 允许选中文本(默认行为之一)。用户可以正常划选文本。 | 文章段落、新闻正文、博客内容。 |
all | 一键全选。只要用户点击该元素的任意位置,整个元素内的文本就会被一次性全部选中。 | 代码块、长链接、卡密/邀请码框、方便用户一键复制。 |
auto | 默认值/自动。根据浏览器的默认行为决定(通常文本可选中,按钮等不可选中)。 | 普通网页元素。 |
二、 实际应用场景示例
1. 防止按钮文字被意外选中(提升 UI 体验)
当你频繁快速点击一个自定义按钮(比如 + / - 数量按钮,或者连点游戏)时,如果不加 user-select: none,文字很容易被蓝色高亮划选,非常影响视觉体验:
CSS
/* 让按钮看起来更像原生 App,多次点击不会选中文字 */
.custom-button {
user-select: none;
cursor: pointer;
}
2. 方便用户复制内容(一键选中)
如果你页面上有一个优惠码或者代码片段,希望用户点一下就能全部高亮方便复制:
CSS
/* 点击一下就会全选 "VIP666" */
.promo-code {
user-select: all;
}
三、 兼容性与补充说明
-
兼容性写法:
在一些较老的浏览器(如旧版 Safari 或 Edge)中,可能需要加上浏览器前缀:
CSS
.no-select { -webkit-user-select: none; /* Safari / Chrome 旧版本 */ -moz-user-select: none; /* Firefox 旧版本 */ -ms-user-select: none; /* IE / Edge 旧版本 */ user-select: none; /* 标准语法 */ } -
不能防真正的“防偷 / 防复制” :
设置
user-select: none只是限制了前端页面的 UI 交互(鼠标划选)。如果是懂技术的用户,依然可以通过浏览器的 F12 开发者工具直接提取 DOM 里的文字,或者通过禁用 CSS 来绕过。
第4个
表单校验
表单校验规则的本质,就是按顺序列出一组“安检标准” 。只有用户输入的内容依次通过了所有的标准,输入框下方才不会报错。
以下为您逐块拆解每个配置的含义,以及那个看起来最复杂的 validator(自定义校验器):
一、 先看通用属性的大白话解释
每个规则对象 { ... } 里面都有几个常用属性:
-
required: true:非空校验。必须填写,如果不填或删光了,就会报错。 -
message:报错提示信息。当校验不通过时,输入框下方显示的红色小字。 -
trigger: 'blur':触发时机。blur表示失去焦点时触发(即用户鼠标点击别处、离开当前输入框的瞬间进行检查)。 -
min/max:长度限制。规定最少和最多输入的字符数。 -
pattern:正则表达式。用来检查格式是否符合特定规则(如:必须是手机号、密码不能带空格等)。
二、 逐条拆解你的校验代码
1. 用户名 (username)
JavaScript
username: [
// 规则 ①:不能留空,否则提示“请输入用户名”
{ required: true, message: '请输入用户名', trigger: 'blur' },
// 规则 ②:字数必须在 5 到 10 个字符之间,否则提示“用户名必须是5-10位的字符”
{ min: 5, max: 10, message: '用户名必须是5-10位的字符', trigger: 'blur' }
]
2. 密码 (password)
JavaScript
password: [
// 规则 ①:不能留空
{ required: true, message: '请输入密码', trigger: 'blur' },
// 规则 ②:必须符合正则表达式 /^\S{6,15}$/
{
pattern: /^\S{6,15}$/,
message: '密码必须是6-15位的非空字符',
trigger: 'blur'
}
]
💡 正则表达式
/^\S{6,15}$/解读:
\S:匹配非空白字符(即密码中间绝对不能包含空格、换行或 Tab)。
{6,15}:长度要求为 6 到 15 位。
3. 确认密码 (repassword) —— 最核心的逻辑
JavaScript
repassword: [
// 规则 ①:不能留空
{ required: true, message: '请再次输入密码', trigger: 'blur' },
// 规则 ②:格式也必须是 6-15 位非空字符
{
pattern: /^\S{6,15}$/,
message: '密码必须是6-15的非空字符',
trigger: 'blur'
},
// 规则 ③:自定义校验器(解决“两次输入的密码是否一致”问题)
{
validator: (rule, value, callback) => {
// value: 用户在【确认密码】框里填写的字符串
// formModel.value.password: 用户在【密码】框里填写的字符串
if (value !== formModel.value.password) {
// 如果两次输入的不一样,抛出错误,提示红色小字
callback(new Error('两次输入密码不一致!'))
} else {
// 如果两次输入的一样,直接调用 callback(),代表校验“通过!”
callback()
}
},
trigger: 'blur'
}
]
三、 重点解读:validator(自定义校验函数)
像“字数限制”、“正则匹配”这种简单的需求,Element 给我们提供了 min/max 或 pattern。
但如果遇到需要两个字段相互对比的复杂逻辑(比如:确认密码必须等于密码),自带的规则就不够用了。这时就需要用到 validator 自定义校验。
它接收三个固定参数:
-
rule:当前的校验规则配置信息(一般用不上)。 -
value:当前输入框里用户填写的值(即确认密码框里的值)。 -
callback:通报结果的“裁判”函数(非常关键!):-
校验失败:必须调用
callback(new Error('报错信息'))。 -
校验成功:必须无参调用
callback()。
-
💡 绝大多数初学者会踩的坑:
在写 validator 时,无论成功还是失败,都必须保证 callback() 一定会被执行!如果漏掉了 else { callback() },表单提交时就会卡住无法完成最终的 validate() 验证。