1. Vue.js框架核心语法解析
作为当前最流行的前端框架之一,Vue.js以其渐进式设计和响应式数据绑定机制赢得了大量开发者的青睐。我在多个企业级项目中采用Vue.js作为技术栈,发现其核心语法设计既考虑了入门友好度,又为复杂应用提供了足够的扩展性。
1.1 基础模板语法
Vue的模板语法可能是开发者最先接触的部分。不同于React的JSX,Vue采用了更接近原生HTML的模板写法:
html复制<div id="app">
{{ message }} <!-- 文本插值 -->
<span v-bind:title="dynamicTitle">悬停提示</span> <!-- 属性绑定 -->
<button v-on:click="reverseMessage">反转消息</button> <!-- 事件绑定 -->
</div>
这种设计让传统Web开发者更容易上手,但要注意几个关键细节:
- 插值表达式中的JavaScript环境受限,只能包含单个表达式
- v-bind和v-on指令可以简写为
:和@符号 - 避免在模板中编写复杂逻辑,应该抽离到methods或computed中
提示:在大型项目中,我会配置ESLint的vue/valid-template-root规则,防止出现多个根元素的常见错误。
1.2 响应式系统原理
Vue的响应式系统是其核心特性。通过Object.defineProperty(Vue 2)或Proxy(Vue 3)实现数据劫持:
javascript复制// Vue 2响应式实现简例
function defineReactive(obj, key) {
let value = obj[key]
const dep = new Dep() // 依赖收集器
Object.defineProperty(obj, key, {
get() {
dep.depend() // 收集当前正在计算的watcher
return value
},
set(newVal) {
value = newVal
dep.notify() // 通知所有依赖进行更新
}
})
}
实际开发中需要注意:
- 对象新增属性需要用Vue.set()方法
- 数组变异方法如push/pop等已被Vue重写
- 大数据量的响应式处理要考虑性能优化
1.3 组件化开发模式
Vue的组件系统是其工程化的基石。一个典型的单文件组件(SFC)结构:
vue复制<template>
<div class="example">{{ msg }}</div>
</template>
<script>
export default {
props: ['initialMsg'], // 声明props
data() {
return {
msg: this.initialMsg
}
},
mounted() {
console.log('组件挂载完成')
}
}
</script>
<style scoped>
.example {
color: red;
}
</style>
在大型项目中,我总结出以下最佳实践:
- 组件命名采用PascalCase风格
- props定义尽量详细,指定类型和默认值
- 避免在子组件中直接修改props
- 作用域样式(scoped)能有效避免CSS污染
2. Vue Router深度解析
2.1 路由基础配置
现代前端应用几乎都是SPA(单页应用),Vue Router是官方推荐的路由解决方案。基本配置如下:
javascript复制import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home,
meta: { requiresAuth: true } // 路由元信息
},
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue') // 路由懒加载
}
]
const router = new VueRouter({
mode: 'history', // 去除hash模式中的#号
base: process.env.BASE_URL,
routes
})
export default router
实际项目中的经验技巧:
- 路由模块应该拆分到单独的文件中管理
- 动态导入(懒加载)能显著提升首屏性能
- 路由元信息(meta)非常适合权限控制场景
2.2 导航守卫实战
导航守卫是路由系统的核心功能,允许在路由切换时插入控制逻辑:
javascript复制router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!store.getters.isLoggedIn) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
next()
}
})
常见应用场景包括:
- 页面访问权限控制
- 路由切换时的数据预取
- 页面访问统计埋点
- 表单未保存时的离开确认
注意:守卫中必须调用next(),否则路由会卡住。我曾遇到过因为忘记调用next导致页面白屏的bug。
2.3 动态路由高级用法
对于大型后台管理系统,动态路由能根据权限动态生成菜单:
javascript复制// 从API获取用户权限路由配置
const asyncRoutes = await getUserRoutes()
// 动态添加路由
router.addRoutes([
...asyncRoutes,
{ path: '*', redirect: '/404' } // 404页面处理
])
配合vuex可以构建完整的权限控制方案:
- 用户登录后获取权限路由配置
- 过滤出有权限的路由表
- 调用addRoutes动态注册
- 将路由信息存入vuex供侧边栏菜单使用
3. Vuex状态管理详解
3.1 核心概念解析
Vuex是Vue的官方状态管理库,适用于中大型应用的状态管理。其核心架构如下:
javascript复制const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
},
getters: {
doubleCount: state => state.count * 2
}
})
各模块职责明确:
- state:单一状态树
- mutations:同步修改状态(必须同步)
- actions:处理异步操作,提交mutation
- getters:计算派生状态
3.2 模块化实践
随着应用复杂度上升,应该将store分割成模块:
javascript复制const userModule = {
namespaced: true,
state: () => ({ profile: null }),
mutations: {
SET_PROFILE(state, payload) {
state.profile = payload
}
},
actions: {
async fetchUser({ commit }, userId) {
const res = await api.getUser(userId)
commit('SET_PROFILE', res.data)
}
}
}
const store = new Vuex.Store({
modules: {
user: userModule,
products: productsModule
}
})
模块化开发建议:
- 为每个业务领域创建独立模块
- 启用namespaced避免命名冲突
- 在组件中使用mapXXX辅助函数简化调用
- 大型项目可以考虑按文件拆分(state.js, actions.js等)
3.3 持久化与插件开发
Vuex的状态在页面刷新后会丢失,需要持久化方案:
javascript复制// 简易的localStorage持久化插件
const persistPlugin = store => {
// 初始化时读取
if (localStorage.getItem('vuex')) {
store.replaceState(
JSON.parse(localStorage.getItem('vuex'))
)
}
// 订阅mutation变化
store.subscribe((mutation, state) => {
localStorage.setItem('vuex', JSON.stringify(state))
})
}
const store = new Vuex.Store({
// ...
plugins: [persistPlugin]
})
对于复杂场景,推荐使用vuex-persistedstate库,它支持:
- 自定义存储引擎(localStorage/sessionStorage/cookie)
- 路径白名单/黑名单控制
- 数据加密
- 多tab同步
4. Vue CLI工程化实践
4.1 项目脚手架与配置
Vue CLI是官方标准化的项目脚手架工具,创建项目:
bash复制vue create my-project
关键特性包括:
- 基于webpack的预配置构建系统
- 可配置的GUI界面(vue ui)
- 丰富的官方插件生态(Router, Vuex, Babel等)
- 现代化模式(ES modules, 现代浏览器支持)
自定义配置可以通过vue.config.js实现:
javascript复制module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true
}
}
},
chainWebpack: config => {
config.module
.rule('svg')
.exclude.add(resolve('src/icons'))
.end()
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(resolve('src/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
}
}
4.2 性能优化策略
生产环境构建的优化手段:
- 代码分割与懒加载:
javascript复制const UserDetails = () => import('./views/UserDetails.vue')
- 开启Gzip压缩(需要服务器配合):
javascript复制const CompressionPlugin = require('compression-webpack-plugin')
module.exports = {
configureWebpack: {
plugins: [new CompressionPlugin()]
}
}
- 使用CDN引入外部库:
javascript复制module.exports = {
configureWebpack: {
externals: {
vue: 'Vue',
'vue-router': 'VueRouter',
vuex: 'Vuex'
}
}
}
- 开启PWA支持(通过@vue/cli-plugin-pwa)
4.3 自定义插件开发
Vue CLI的插件系统允许扩展项目功能。典型插件结构:
code复制vue-cli-plugin-myplugin/
├── generator.js # 生成器
├── prompts.js # 交互提示
├── index.js # 服务钩子
└── package.json
开发过的一个实际插件示例:
javascript复制// generator.js
module.exports = (api, options) => {
api.extendPackage({
dependencies: {
'axios': '^0.21.1'
}
})
api.injectImports(api.entryFile, `import axios from 'axios'`)
api.render('./template')
}
插件可以:
- 添加新命令到vue-cli-service
- 修改webpack配置
- 注入环境变量
- 添加新的UI界面
5. 常见问题与解决方案
5.1 响应式数据问题
问题1:数组变化未触发视图更新
原因:Vue不能检测到以下数组变动:
- 直接通过索引设置项:vm.items[index] = newValue
- 修改数组长度:vm.items.length = newLength
解决方案:
javascript复制// 使用Vue.set或数组变异方法
Vue.set(vm.items, index, newValue)
vm.items.splice(index, 1, newValue)
问题2:对象新增属性不响应
解决方案:
javascript复制Vue.set(vm.someObject, 'newProp', 123)
// 或使用Object.assign
vm.someObject = Object.assign({}, vm.someObject, {
newProp: 123
})
5.2 性能优化技巧
- v-for优化:
html复制<!-- 总是使用key -->
<li v-for="item in items" :key="item.id">
{{ item.text }}
</li>
<!-- 避免v-if和v-for一起使用 -->
<!-- 不好 -->
<ul>
<li v-for="user in users" v-if="user.isActive" :key="user.id">
{{ user.name }}
</li>
</ul>
<!-- 好 -->
<ul>
<template v-for="user in activeUsers">
<li :key="user.id">
{{ user.name }}
</li>
</template>
</ul>
- 计算属性缓存:
javascript复制computed: {
// 计算属性基于它们的响应式依赖进行缓存
reversedMessage() {
return this.message.split('').reverse().join('')
}
}
- 函数式组件:对于无状态组件可以标记为functional提升性能
vue复制<template functional>
<div class="cell">
{{ props.value }}
</div>
</template>
5.3 调试技巧
- Vue DevTools安装与使用:
- Chrome商店安装Vue.js devtools
- 生产环境需要额外配置:
javascript复制Vue.config.devtools = true
- 错误追踪:
javascript复制Vue.config.errorHandler = function (err, vm, info) {
// 处理错误
// 上报错误到监控系统
}
- 性能分析:
bash复制vue-cli-service build --report
会生成report.html可视化分析包大小
6. 进阶实战技巧
6.1 自定义指令开发
除了内置指令(v-model, v-show等),Vue允许注册自定义指令:
javascript复制// 全局注册
Vue.directive('focus', {
inserted: function (el) {
el.focus()
}
})
// 局部注册
directives: {
focus: {
inserted: function (el) {
el.focus()
}
}
}
实际项目中的典型应用:
- 输入框自动聚焦
- 权限控制指令
- 防抖/节流指令
- 拖拽指令
6.2 渲染函数与JSX
对于复杂动态组件,可以使用渲染函数:
javascript复制Vue.component('anchored-heading', {
render: function (createElement) {
return createElement(
'h' + this.level, // 标签名
this.$slots.default // 子节点数组
)
},
props: {
level: {
type: Number,
required: true
}
}
})
在Vue中也可以使用JSX:
jsx复制render() {
return (
<div class="container">
<h1>{this.title}</h1>
{this.showButton && (
<button onClick={this.handleClick}>Click</button>
)}
</div>
)
}
6.3 组合式API实践
Vue 3的组合式API也可以在Vue 2中使用@vue/composition-api插件:
javascript复制import { ref, computed, onMounted } from '@vue/composition-api'
export default {
setup() {
const count = ref(0)
const double = computed(() => count.value * 2)
function increment() {
count.value++
}
onMounted(() => {
console.log('组件挂载')
})
return {
count,
double,
increment
}
}
}
组合式API的优势:
- 更好的逻辑复用与组合
- 更灵活的代码组织
- 更好的TypeScript支持
- 更小的生产包体积
6.4 TypeScript集成
Vue 3对TypeScript的支持非常完善,Vue 2项目也可以通过以下配置获得良好支持:
- 安装依赖:
bash复制npm install @vue/cli-plugin-typescript --save-dev
- 配置tsconfig.json:
json复制{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"moduleResolution": "node",
"experimentalDecorators": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}
- 编写类型化的Vue组件:
typescript复制import { Component, Vue, Prop } from 'vue-property-decorator'
@Component
export default class HelloWorld extends Vue {
@Prop({ type: String, required: true }) readonly msg!: string
count = 0
get doubleCount(): number {
return this.count * 2
}
increment(): void {
this.count++
}
}
7. 测试策略与实施
7.1 单元测试配置
Vue CLI已内置Jest测试支持,典型测试示例:
javascript复制import { shallowMount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'
describe('HelloWorld.vue', () => {
it('renders props.msg when passed', () => {
const msg = 'new message'
const wrapper = shallowMount(HelloWorld, {
propsData: { msg }
})
expect(wrapper.text()).toMatch(msg)
})
it('increments count when button is clicked', async () => {
const wrapper = shallowMount(HelloWorld)
await wrapper.find('button').trigger('click')
expect(wrapper.vm.count).toBe(1)
})
})
测试工具链:
- Jest:测试运行器
- @vue/test-utils:官方测试工具库
- vue-jest:处理SFC文件转换
- babel-jest:ES6+语法转换
7.2 E2E测试方案
对于端到端测试,可以使用Cypress:
- 安装:
bash复制vue add @vue/e2e-cypress
- 编写测试:
javascript复制describe('My First Test', () => {
it('Visits the app root url', () => {
cy.visit('/')
cy.contains('h1', 'Welcome to Your Vue.js App')
})
it('Tests user login flow', () => {
cy.visit('/login')
cy.get('#username').type('testuser')
cy.get('#password').type('123456')
cy.get('form').submit()
cy.url().should('include', '/dashboard')
})
})
7.3 测试覆盖率与持续集成
配置jest测试覆盖率:
javascript复制// jest.config.js
module.exports = {
collectCoverage: true,
collectCoverageFrom: [
'src/**/*.{js,vue}',
'!src/main.js',
'!src/router/index.js',
'!**/node_modules/**'
],
coverageReporters: ['html', 'text-summary']
}
集成到CI流程(GitHub Actions示例):
yaml复制name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
- run: npm ci
- run: npm run test:unit
- run: npm run test:e2e
8. 项目架构最佳实践
8.1 大型项目目录结构
经过多个企业级项目验证的目录结构:
code复制src/
├── api/ # API请求封装
├── assets/ # 静态资源
├── components/ # 公共组件
│ ├── base/ # 基础UI组件
│ └── business/ # 业务组件
├── composables/ # 组合式函数(Vue3)
├── directives/ # 自定义指令
├── filters/ # 全局过滤器
├── layouts/ # 布局组件
├── mixins/ # 混入对象
├── plugins/ # Vue插件
├── router/ # 路由配置
│ ├── index.js
│ ├── routes.js
│ └── guard.js # 导航守卫
├── store/ # Vuex状态管理
│ ├── index.js
│ ├── actions.js
│ ├── mutations.js
│ └── modules/ # Vuex模块
├── styles/ # 全局样式
├── utils/ # 工具函数
├── views/ # 页面组件
└── main.js # 应用入口
8.2 样式管理方案
推荐使用SCSS预处理器和BEM命名规范:
scss复制// variables.scss
$primary-color: #1890ff;
$border-radius: 4px;
// mixins.scss
@mixin ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// component样式示例
.user-card {
&__header {
padding: 16px;
border-bottom: 1px solid #f0f0f0;
&-title {
font-size: 16px;
@include ellipsis;
}
}
&__body {
padding: 16px;
}
}
全局样式处理技巧:
- 在vue.config.js中配置全局变量注入
- 使用CSS Modules避免样式冲突
- 第三方UI库样式覆盖使用深度选择器
::v-deep
8.3 代码规范与提交约定
- ESLint配置:
javascript复制module.exports = {
root: true,
env: {
node: true
},
extends: [
'plugin:vue/essential',
'eslint:recommended',
'@vue/typescript/recommended'
],
rules: {
'vue/multi-word-component-names': 'off',
'vue/attribute-hyphenation': ['error', 'always'],
'vue/html-indent': ['error', 2],
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
}
- Git提交规范(配合husky和commitlint):
code复制<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
常用type:
- feat:新功能
- fix:bug修复
- docs:文档变更
- style:代码格式
- refactor:重构
- test:测试相关
- chore:构建过程或辅助工具变更
9. 微前端集成方案
9.1 qiankun微前端实践
在大型系统中集成Vue微应用的配置示例:
javascript复制// 主应用配置
import { registerMicroApps, start } from 'qiankun'
registerMicroApps([
{
name: 'vue-app',
entry: '//localhost:7101',
container: '#subapp-container',
activeRule: '/vue',
props: {
basePath: '/vue'
}
}
])
start()
子应用改造要点:
- 导出生命周期钩子:
javascript复制// main.js
let instance = null
function render(props = {}) {
const { container } = props
instance = new Vue({
router,
store,
render: h => h(App)
}).$mount(container ? container.querySelector('#app') : '#app')
}
// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
render()
}
export async function bootstrap() {
console.log('[vue] vue app bootstraped')
}
export async function mount(props) {
console.log('[vue] props from main framework', props)
render(props)
}
export async function unmount() {
instance.$destroy()
instance.$el.innerHTML = ''
instance = null
}
- 修改webpack配置:
javascript复制// vue.config.js
module.exports = {
devServer: {
port: 7101,
headers: {
'Access-Control-Allow-Origin': '*'
}
},
configureWebpack: {
output: {
library: `vue-app`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_vue-app`
}
}
}
9.2 样式隔离方案
微前端中的样式隔离策略:
- Shadow DOM(qiankun支持)
- CSS命名空间约定
- 动态样式表加载/卸载
- postcss插件添加前缀
推荐实践:
javascript复制// 启动qiankun时开启沙箱
start({
sandbox: {
strictStyleIsolation: true // 使用Shadow DOM
}
})
9.3 应用间通信
微应用间通信方案:
- 基于props的父子通信
- 全局状态管理(如redux)
- 自定义事件总线
- localStorage/sessionStorage
qiankun提供的initGlobalState方法:
javascript复制// 主应用初始化state
import { initGlobalState } from 'qiankun'
const actions = initGlobalState(state)
actions.onGlobalStateChange((state, prev) => {
console.log('全局状态变更:', prev, state)
})
// 子应用获取全局actions
export function mount(props) {
props.onGlobalStateChange((state, prev) => {
// 响应全局状态变化
})
props.setGlobalState(state)
}
10. 安全防护实践
10.1 常见前端安全漏洞
- XSS防护:
javascript复制// 使用DOMPurify净化HTML
import DOMPurify from 'dompurify'
Vue.directive('safe-html', {
inserted: (el, binding) => {
el.innerHTML = DOMPurify.sanitize(binding.value)
}
})
- CSRF防护:
javascript复制// axios配置
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = 'X-CSRFToken'
- CSP配置:
html复制<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' 'unsafe-inline' cdn.example.com;
style-src 'self' 'unsafe-inline'">
10.2 敏感数据处理
- 数据脱敏:
javascript复制// 工具函数
export function desensitize(str, start = 3, end = 4) {
if (!str || typeof str !== 'string') return ''
if (str.length <= start + end) return str
const head = str.substr(0, start)
const tail = str.substr(-end)
return `${head}****${tail}`
}
- 加密存储:
javascript复制// 使用crypto-js进行前端加密
import CryptoJS from 'crypto-js'
const SECRET_KEY = 'your-secret-key'
export function encrypt(data) {
return CryptoJS.AES.encrypt(
JSON.stringify(data),
SECRET_KEY
).toString()
}
export function decrypt(ciphertext) {
const bytes = CryptoJS.AES.decrypt(ciphertext, SECRET_KEY)
return JSON.parse(bytes.toString(CryptoJS.enc.Utf8))
}
10.3 生产环境加固
- 禁用devtools:
javascript复制// main.js
if (process.env.NODE_ENV === 'production') {
Vue.config.devtools = false
Vue.config.performance = false
}
- 代码混淆:
javascript复制// vue.config.js
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
configureWebpack: {
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true
},
output: {
comments: false
}
}
})
]
}
}
}
- API防护:
javascript复制// axios拦截器示例
axios.interceptors.request.use(config => {
if (process.env.NODE_ENV === 'production') {
config.headers['X-Requested-With'] = 'XMLHttpRequest'
config.headers['X-Request-ID'] = generateUUID()
}
return config
})
11. 性能监控与分析
11.1 前端性能指标采集
核心Web指标(Core Web Vitals)监控:
javascript复制// 使用web-vitals库
import { getCLS, getFID, getLCP } from 'web-vitals'
function sendToAnalytics(metric) {
const body = JSON.stringify(metric)
navigator.sendBeacon('/analytics', body)
}
getCLS(sendToAnalytics)
getFID(sendToAnalytics)
getLCP(sendToAnalytics)
自定义性能监控:
javascript复制// 页面加载时间统计
window.addEventListener('load', () => {
const timing = performance.timing
const loadTime = timing.loadEventEnd - timing.navigationStart
// 发送到监控系统
trackPerformance('page_load', loadTime)
})
11.2 错误监控实现
全局错误捕获:
javascript复制// Vue错误处理
Vue.config.errorHandler = (err, vm, info) => {
trackError({
type: 'vue_error',
error: err.stack,
component: vm.$options.name,
info
})
}
// 全局错误监听
window.addEventListener('error', event => {
trackError({
type: 'window_error',
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno
})
})
// 未处理的Promise rejection
window.addEventListener('unhandledrejection', event => {
trackError({
type: 'promise_error',
reason: event.reason?.stack || event.reason
})
})
11.3 用户行为追踪
基于路由的页面访问统计:
javascript复制router.afterEach((to, from) => {
trackPageView(to.fullPath)
})
自定义事件追踪:
javascript复制// 指令方式
Vue.directive('track', {
inserted(el, binding) {
el.addEventListener('click', () => {
trackEvent(binding.value)
})
}
})
// 使用示例
<button v-track="'button_click'">点击我</button>
12. 移动端适配方案
12.1 响应式布局实现
- viewport配置:
html复制<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
- rem适配方案:
javascript复制// 设置根字体大小
function setRem() {
const docEl = document.documentElement
const width = docEl.clientWidth || window.innerWidth
const rem = width / 7.5 // 设计稿750px时,1rem=100px
docEl.style.fontSize = `${rem}px`
}
window.addEventListener('resize', setRem)
setRem()
- vw/vh方案:
scss复制// postcss配置
module.exports = {
plugins: {
'postcss-px-to-viewport': {
viewportWidth: 750,
unitPrecision: 3,
viewportUnit: 'vw',
selectorBlackList: ['.ignore'],
minPixelValue: 1,
mediaQuery: false
}
}
}
12.2 移动端组件库选型
- Vant(推荐):
bash复制npm install vant -S
按需引入配置:
javascript复制// babel.config.js
module.exports = {
plugins: [
['import', {
libraryName: 'vant',
libraryDirectory: 'es',
style: true
}, 'vant']
]
}
- NutUI(京东风格):
javascript复制import NutUI from '@nutui/nutui'
import '@nutui/nutui/dist/nutui.css'
Vue.use(NutUI)
- Mint UI(老牌移动端库):
javascript复制import MintUI from 'mint-ui'
import 'mint-ui/lib/style.css'
Vue.use(MintUI)
12.3 移动端专属问题处理
- 点击延迟解决:
bash复制npm install fastclick -S
javascript复制import FastClick from 'fastclick'
FastClick.attach(document.body)
- 1px边框问题:
scss复制.border-1px {
position: relative;
&::after {
content: "";
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 1px;
background: #eee;
transform: scaleY(0.5);
transform-origin: 0 0;
}
}
- iOS橡皮筋效果禁用:
javascript复制document.body.addEventListener('touchmove', (e) => {
if (e._isScroller) return
e.preventDefault()
}, { passive: false })
13. 服务端渲染(SSR)实践
13.1 Nuxt.js基础配置
Nuxt.js是Vue的SSR框架,创建项目:
bash复制npx create-nuxt-app my-ssr-app
核心目录结构:
code复制├── assets/ # 未编译资源
├── components/ # Vue组件
├── layouts/ # 布局
├── middleware/ # 中间件
├── pages/ # 路由页面
├── plugins/ # 插件
├── static/ # 静态文件
├── store/ # Vuex
└── nuxt.config.js # 配置文件
13.2 数据预取策略
- asyncData方法:
javascript复制export default {
async asyncData({ params }) {
const post = await axios.get(`https://api.example.com/posts/${params.id}`)
return { post }
}
}
- Vuex集成:
javascript复制// store/index.js
export const actions = {
async fetchPosts({ commit }) {
const posts = await axios.get('https://api.example.com/posts')
commit('SET_POSTS', posts)
}
}
// 页面中使用
export default {
async asyncData({ store }) {
await store.dispatch('fetchPosts')
}
}
13.3 性能优化技巧
- 组件级缓存:
javascript复制// nuxt.config.js
module.exports = {
render: {
bundleRenderer: {
cache: require('lru-cache')({
max: 1000,
maxAge: 1000 * 60 * 15
})
}
}
}
// 组件中添加serverCacheKey
export default {
name: 'FeaturedPosts',
serverCacheKey: props => props.posts.length
}
- CDN静态资源:
javascript复制// nuxt.config.js
export default {
build: {
publicPath: 'https://cdn.example.com/nuxt/'
}
}
- 开启HTTP/2推送:
javascript复制// nuxt.config.js
export default {
render: {
http2: {
push: true,
pushAssets
