引言:为什么选择Belly框架?
在现代前端开发领域,Belly框架作为一个新兴但功能强大的工具,正在快速获得开发者的青睐。它结合了现代Web开发的最佳实践,提供了简洁的API设计和卓越的性能表现。本文将从零开始,全面解析Belly框架的核心概念,并通过丰富的实战案例帮助你快速掌握高效开发方法。
第一部分:Belly框架基础入门
1.1 Belly框架简介
Belly是一个轻量级但功能完整的前端框架,专为构建现代化的Web应用程序而设计。它的核心优势包括:
- 轻量级设计:核心库体积小,加载速度快
- 响应式数据绑定:自动追踪数据变化并更新视图
- 组件化架构:支持可复用的组件开发
- 优秀的性能:高效的虚拟DOM diff算法
- 友好的开发体验:清晰的API和完善的工具链
1.2 环境搭建与初始化
安装Node.js和npm
首先确保你的开发环境已安装Node.js(建议版本14.x以上)和npm。
# 检查Node.js版本
node --version
# 检查npm版本
npm --version
创建第一个Belly项目
使用Belly CLI工具快速创建项目:
# 全局安装Belly CLI
npm install -g @belly/cli
# 创建新项目
belly create my-first-belly-app
# 进入项目目录
cd my-first-belly-app
# 启动开发服务器
npm run dev
项目结构说明
创建的项目具有以下标准结构:
my-first-belly-app/
├── public/ # 静态资源
├── src/ # 源代码
│ ├── components/ # 组件
│ ├── pages/ # 页面
│ ├── utils/ # 工具函数
│ ├── app.belly # 应用入口
│ └── index.html # HTML模板
├── belly.config.js # 配置文件
├── package.json # 项目依赖
└── README.md # 说明文档
1.3 基础语法与核心概念
数据绑定
Belly使用简洁的双大括号语法进行数据绑定:
<!-- 在belly文件中 -->
<div class="container">
<h1>{{ title }}</h1>
<p>欢迎,{{ user.name }}!</p>
<p>当前计数:{{ count }}</p>
</div>
// 在脚本部分
export default {
data() {
return {
title: 'Belly应用',
user: {
name: '开发者',
id: 123
},
count: 0
}
}
}
事件处理
使用@符号进行事件绑定:
<button @click="increment">增加</button>
<button @click="decrement">减少</button>
<input @input="handleInput" placeholder="输入内容" />
export default {
data() {
return { count: 0, inputValue: '' }
},
methods: {
increment() {
this.count++;
},
decrement() {
this.count--;
},
handleInput(event) {
this.inputValue = event.target.value;
}
}
}
条件渲染
使用v-if、v-else-if、v-else指令:
<div v-if="isLoggedIn">
<h2>欢迎回来!</h2>
<p>用户:{{ username }}</p>
</div>
<div v-else-if="isLoggingIn">
<p>正在登录...</p>
</div>
<div v-else>
<button @click="login">请先登录</button>
循环渲染
使用v-for指令:
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }} - {{ item.price }}
</li>
</ul>
<!-- 带索引的循环 -->
<div v-for="(user, index) in users" :key="user.id">
<span>{{ index + 1 }}. {{ user.name }}</span>
</div>
第二部分:Belly核心概念详解
2.1 组件系统
组件定义与注册
Belly支持单文件组件(SFC)格式:
<!-- components/UserCard.belly -->
<template>
<div class="user-card">
<img :src="avatar" :alt="name" />
<h3>{{ name }}</h3>
<p>{{ bio }}</p>
<button @click="follow">{{ isFollowing ? '取消关注' : '关注' }}</button>
</div>
</template>
<script>
export default {
props: {
name: {
type: String,
required: true
},
avatar: String,
bio: String,
isFollowing: {
type: Boolean,
default: false
}
},
methods: {
follow() {
this.$emit('follow', this.name);
}
}
}
</script>
<style scoped>
.user-card {
border: 1px solid #ddd;
padding: 16px;
border-radius: 8px;
margin: 8px;
}
.user-card button {
background: #007bff;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
}
</style>
父子组件通信
父组件向子组件传递数据(Props):
<!-- Parent.belly -->
<template>
<div>
<user-card
name="Alice"
avatar="/images/alice.jpg"
bio="前端开发者"
:is-following="false"
/>
<user-card
name="Bob"
avatar="/images/bob.jpg"
bio="后端工程师"
:is-following="true"
/>
</div>
</template>
<script>
import UserCard from './components/UserCard.belly';
export default {
components: {
UserCard
}
}
</script>
子组件向父组件传递数据(Events):
<!-- Parent.belly -->
<template>
<div>
<user-card
v-for="user in users"
:key="user.id"
:name="user.name"
@follow="handleFollow"
/>
<p>已关注:{{ followedUsers.join(', ') }}</p>
</div>
</template>
<script>
export default {
data() {
return {
users: [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
],
followedUsers: []
}
},
methods: {
handleFollow(username) {
if (this.followedUsers.includes(username)) {
this.followedUsers = this.followedUsers.filter(u => u !== username);
} else {
this.followedUsers.push(username);
}
}
}
}
</script>
2.2 响应式系统
数据响应式原理
Belly使用基于Proxy的响应式系统,自动追踪数据变化:
export default {
data() {
return {
user: {
name: 'John',
age: 25,
hobbies: ['reading', 'coding']
}
}
},
mounted() {
// 直接修改对象属性,视图会自动更新
this.user.age = 26;
// 修改数组方法,视图也会更新
this.user.hobbies.push('gaming');
// 添加新属性(需要使用$set方法)
this.$set(this.user, 'email', 'john@example.com');
}
}
计算属性
计算属性用于派生状态:
export default {
data() {
return {
firstName: 'John',
lastName: 'Doe'
}
},
computed: {
// 只读计算属性
fullName() {
return `${this.firstName} ${this.lastName}`;
},
// 可读写的计算属性
name: {
get() {
return `${this.firstName} ${this.lastName}`;
},
set(value) {
const parts = value.split(' ');
this.firstName = parts[0];
this.lastName = parts[1] || '';
}
}
}
}
侦听器
侦听器用于响应数据变化执行异步操作:
export default {
data() {
return {
searchQuery: '',
searchResults: [],
loading: false
}
},
watch: {
searchQuery: {
immediate: true,
handler(newVal, oldVal) {
if (newVal.length < 2) {
this.searchResults = [];
return;
}
this.loading = true;
// 模拟API调用
setTimeout(() => {
this.searchResults = [
`${newVal} result 1`,
`${newVal} result 2`
];
this.loading = false;
}, 300);
}
}
}
}
2.3 生命周期钩子
Belly组件有完整的生命周期钩子:
export default {
// 在实例初始化之后,数据观测和事件配置之前被调用
beforeCreate() {
console.log('beforeCreate: 组件初始化');
},
// 在实例创建完成后被同步调用
created() {
console.log('created: 组件已创建,可访问data和events');
// 适合进行API调用
this.fetchData();
},
// 在挂载开始之前被调用
beforeMount() {
console.log('beforeMount: 挂载前');
},
// 挂载完成后被调用
mounted() {
console.log('mounted: DOM已挂载');
// 适合操作DOM或进行第三方库初始化
this.initThirdPartyLib();
},
// 数据更新时调用
beforeUpdate() {
console.log('beforeUpdate: 数据更新前');
},
// 由于数据更改导致的虚拟DOM重新渲染和打补丁完成后调用
updated() {
console.log('updated: DOM更新后');
},
// 实例销毁之前调用
beforeDestroy() {
console.log('beforeDestroy: 销毁前');
// 清理定时器、事件监听器等
this.cleanup();
},
// 实例销毁后调用
destroyed() {
console.log('destroyed: 组件已销毁');
}
}
第三部分:实战技巧与最佳实践
3.1 状态管理
简单状态管理
对于小型应用,可以使用Belly提供的全局状态管理:
// store.js
import { createStore } from '@belly/store';
export const store = createStore({
state: {
user: null,
theme: 'light',
notifications: []
},
mutations: {
setUser(state, user) {
state.user = user;
},
toggleTheme(state) {
state.theme = state.theme === 'light' ? 'dark' : 'light';
},
addNotification(state, notification) {
state.notifications.push(notification);
}
},
actions: {
async login({ commit }, credentials) {
try {
const user = await api.login(credentials);
commit('setUser', user);
return user;
} catch (error) {
throw error;
}
}
},
getters: {
isLoggedIn(state) {
return !!state.user;
},
themeClass(state) {
return `theme-${state.theme}`;
}
}
});
在组件中使用Store
import { store } from './store';
export default {
computed: {
user() {
return store.state.user;
},
isLoggedIn() {
return store.getters.isLoggedIn;
}
},
methods: {
async login() {
try {
await store.dispatch('login', {
username: 'user',
password: 'pass'
});
} catch (error) {
console.error('登录失败:', error);
}
},
toggleTheme() {
store.commit('toggleTheme');
}
}
}
3.2 路由管理
路由配置
// router.js
import { createRouter } from '@belly/router';
const router = createRouter({
routes: [
{
path: '/',
component: () => import('./pages/Home.belly')
},
{
path: '/about',
component: () => import('./pages/About.belly')
},
{
path: '/user/:id',
component: () => import('./pages/User.belly'),
props: true
},
{
path: '/dashboard',
component: () => import('./pages/Dashboard.belly'),
meta: { requiresAuth: true }
}
]
});
// 路由守卫
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !store.getters.isLoggedIn) {
next('/login');
} else {
next();
}
});
export default router;
在组件中使用路由
<template>
<div>
<nav>
<router-link to="/">首页</router-link>
<router-link to="/about">关于</router-link>
<button @click="goToDashboard">仪表板</button>
</nav>
<router-view></router-view>
</div>
</template>
<script>
export default {
methods: {
goToDashboard() {
this.$router.push('/dashboard');
},
goBack() {
this.$router.go(-1);
}
},
mounted() {
// 监听路由变化
this.$router.afterEach((to, from) => {
console.log(`从 ${from.path} 跳转到 ${to.path}`);
});
}
}
</script>
3.3 HTTP请求处理
封装HTTP客户端
// utils/http.js
import axios from 'axios';
const http = axios.create({
baseURL: '/api',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
});
// 请求拦截器
http.interceptors.request.use(
config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
error => {
return Promise.reject(error);
}
);
// 响应拦截器
http.interceptors.response.use(
response => {
return response.data;
},
error => {
if (error.response) {
switch (error.response.status) {
case 401:
// 未授权,跳转到登录页
window.location.href = '/login';
break;
case 403:
// 无权限
console.error('无访问权限');
break;
case 500:
// 服务器错误
console.error('服务器错误,请稍后重试');
break;
}
}
return Promise.reject(error);
}
);
export default http;
在组件中使用
import http from '../utils/http';
export default {
data() {
return {
posts: [],
loading: false
}
},
methods: {
async fetchPosts() {
this.loading = true;
try {
const response = await http.get('/posts');
this.posts = response.data;
} catch (error) {
console.error('获取文章失败:', error);
} finally {
this.loading = false;
}
},
async createPost(data) {
try {
const response = await http.post('/posts', data);
this.posts.push(response.data);
} catch (error) {
console.error('创建文章失败:', error);
}
}
},
mounted() {
this.fetchPosts();
}
}
3.4 表单处理
基础表单处理
<template>
<form @submit.prevent="handleSubmit">
<div>
<label>用户名:</label>
<input
type="text"
v-model="form.username"
@blur="validateUsername"
:class="{ error: errors.username }"
/>
<span v-if="errors.username" class="error-msg">{{ errors.username }}</span>
</div>
<div>
<label>邮箱:</label>
<input
type="email"
v-model="form.email"
@blur="validateEmail"
:class="{ error: errors.email }"
/>
<span v-if="errors.email" class="error-msg">{{ errors.email }}</span>
</div>
<div>
<label>密码:</label>
<input
type="password"
v-model="form.password"
@blur="validatePassword"
:class="{ error: errors.password }"
/>
<span v-if="errors.password" class="error-msg">{{ errors.password }}</span>
</div>
<button type="submit" :disabled="isSubmitting">提交</button>
</form>
</template>
<script>
export default {
data() {
return {
form: {
username: '',
email: '',
password: ''
},
errors: {
username: '',
email: '',
password: ''
},
isSubmitting: false
}
},
methods: {
validateUsername() {
if (!this.form.username) {
this.errors.username = '用户名不能为空';
} else if (this.form.username.length < 3) {
this.errors.username = '用户名至少3个字符';
} else {
this.errors.username = '';
}
},
validateEmail() {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!this.form.email) {
this.errors.email = '邮箱不能为空';
} else if (!emailRegex.test(this.form.email)) {
this.errors.email = '邮箱格式不正确';
} else {
this.errors.email = '';
}
},
validatePassword() {
if (!this.form.password) {
this.errors.password = '密码不能为空';
} else if (this.form.password.length < 6) {
this.errors.password = '密码至少6个字符';
} else {
this.errors.password = '';
}
},
async handleSubmit() {
// 验证所有字段
this.validateUsername();
this.validateEmail();
this.validatePassword();
// 检查是否有错误
if (this.errors.username || this.errors.email || this.errors.password) {
return;
}
this.isSubmitting = true;
try {
// 提交表单数据
const response = await this.$http.post('/register', this.form);
console.log('注册成功:', response);
// 重置表单
this.form = { username: '', email: '', password: '' };
} catch (error) {
console.error('注册失败:', error);
} finally {
this.isSubmitting = false;
}
}
}
}
</script>
<style scoped>
.error {
border-color: #ff4444;
}
.error-msg {
color: #ff4444;
font-size: 12px;
}
</style>
复杂表单处理(使用Formik-like模式)
// utils/form.js
export function useForm(initialValues, validate) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setValues(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value
}));
};
const handleBlur = (e) => {
const { name } = e.target;
setTouched(prev => ({ ...prev, [name]: true }));
if (validate) {
const validationErrors = validate(values);
setErrors(validationErrors);
}
};
const handleSubmit = async (onSubmit) => {
setIsSubmitting(true);
if (validate) {
const validationErrors = validate(values);
setErrors(validationErrors);
if (Object.keys(validationErrors).length > 0) {
setIsSubmitting(false);
return;
}
}
try {
await onSubmit(values);
} finally {
setIsSubmitting(false);
}
};
return {
values,
errors,
touched,
isSubmitting,
handleChange,
handleBlur,
handleSubmit,
setValues
};
}
3.5 性能优化技巧
1. 使用计算属性避免重复计算
// 不好的做法
export default {
data() {
return {
items: [1, 2, 3, 4, 5],
filter: 'even'
}
},
methods: {
getFilteredItems() {
// 每次渲染都会执行
return this.items.filter(item =>
this.filter === 'even' ? item % 2 === 0 : item % 2 === 1
);
}
}
}
// 好的做法
export default {
data() {
return {
items: [1, 2, 3, 4, 5],
filter: 'even'
}
},
computed: {
filteredItems() {
// 依赖变化时才重新计算
return this.items.filter(item =>
this.filter === 'even' ? item % 2 === 0 : item % 2 === 1
);
}
}
}
2. 使用v-memo优化长列表
<!-- 当item数据不变时,跳过diff -->
<div v-for="item in items" :key="item.id" v-memo="[item.id, item.status]">
<span>{{ item.name }}</span>
<span>{{ item.status }}</span>
</div>
3. 懒加载组件
// 使用动态导入
const LazyComponent = () => import('./components/HeavyComponent.belly');
export default {
components: {
LazyComponent
}
}
4. 虚拟滚动优化大数据列表
<template>
<div class="scroll-container" @scroll="handleScroll">
<div class="spacer" :style="{ height: `${totalHeight}px` }"></div>
<div class="visible-items" :style="{ transform: `translateY(${offsetY}px)` }">
<div v-for="item in visibleItems" :key="item.id" class="item">
{{ item.name }}
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
allItems: [], // 假设有10000条数据
itemHeight: 50,
containerHeight: 600,
scrollTop: 0
}
},
computed: {
totalHeight() {
return this.allItems.length * this.itemHeight;
},
startIndex() {
return Math.floor(this.scrollTop / this.itemHeight);
},
endIndex() {
const visibleCount = Math.ceil(this.containerHeight / this.itemHeight);
return Math.min(this.startIndex + visibleCount + 2, this.allItems.length);
},
visibleItems() {
return this.allItems.slice(this.startIndex, this.endIndex);
},
offsetY() {
return this.startIndex * this.itemHeight;
}
},
methods: {
handleScroll(e) {
this.scrollTop = e.target.scrollTop;
}
},
mounted() {
// 生成模拟数据
this.allItems = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `Item ${i}`
}));
}
}
</script>
<style scoped>
.scroll-container {
height: 600px;
overflow-y: auto;
position: relative;
border: 1px solid #ccc;
}
.spacer {
position: absolute;
top: 0;
left: 0;
right: 0;
}
.visible-items {
position: absolute;
top: 0;
left: 0;
right: 0;
}
.item {
height: 50px;
display: flex;
align-items: center;
padding: 0 16px;
border-bottom: 1px solid #eee;
}
</style>
第四部分:高级主题与生态系统
4.1 自定义指令
// directives/clickOutside.js
export const clickOutside = {
mounted(el, binding) {
el.__clickOutside__ = (event) => {
if (!(el === event.target || el.contains(event.target))) {
binding.value(event);
}
};
document.addEventListener('click', el.__clickOutside__);
},
unmounted(el) {
document.removeEventListener('click', el.__clickOutside__);
}
};
// main.js
import { createApp } from '@belly';
import { clickOutside } from './directives/clickOutside';
const app = createApp(App);
app.directive('click-outside', clickOutside);
app.mount('#app');
使用自定义指令
<template>
<div v-if="showMenu" v-click-outside="closeMenu" class="dropdown-menu">
<!-- 菜单内容 -->
</div>
<button @click="showMenu = !showMenu">切换菜单</button>
</template>
<script>
export default {
data() {
return {
showMenu: false
}
},
methods: {
closeMenu() {
this.showMenu = false;
}
}
}
</script>
4.2 插件开发
// plugins/logger.js
export default {
install(app, options) {
// 添加全局方法
app.config.globalProperties.$log = (message) => {
const prefix = options?.prefix || '[APP]';
console.log(`${prefix} ${message}`);
};
// 添加全局指令
app.directive('log', {
mounted(el, binding) {
console.log('Element mounted:', binding.value);
}
});
// 添加全局混入
app.mixin({
created() {
if (this.$options.name) {
this.$log(`${this.$options.name} created`);
}
}
});
}
};
// main.js
import { createApp } from '@belly';
import App from './App.belly';
import logger from './plugins/logger';
const app = createApp(App);
app.use(logger, { prefix: '[MyApp]' });
app.mount('#app');
4.3 测试策略
单元测试示例
// components/__tests__/UserCard.test.js
import { mount } from '@belly/test-utils';
import UserCard from '../UserCard.belly';
describe('UserCard', () => {
it('renders user information correctly', () => {
const wrapper = mount(UserCard, {
props: {
name: 'Alice',
bio: 'Developer',
isFollowing: false
}
});
expect(wrapper.find('h3').text()).toBe('Alice');
expect(wrapper.find('p').text()).toBe('Developer');
expect(wrapper.find('button').text()).toBe('关注');
});
it('emits follow event on button click', async () => {
const wrapper = mount(UserCard, {
props: {
name: 'Alice',
isFollowing: false
}
});
await wrapper.find('button').trigger('click');
expect(wrapper.emitted('follow')).toBeTruthy();
expect(wrapper.emitted('follow')[0]).toEqual(['Alice']);
});
});
组件测试
// pages/__tests__/Home.test.js
import { mount, createLocalVue } from '@belly/test-utils';
import Home from '../Home.belly';
import { store } from '../../store';
const localVue = createLocalVue();
describe('Home Page', () => {
it('displays login button when not logged in', () => {
store.state.user = null;
const wrapper = mount(Home, { localVue });
expect(wrapper.find('button').text()).toBe('登录');
});
it('displays user info when logged in', () => {
store.state.user = { name: 'Alice' };
const wrapper = mount(Home, { localVue });
expect(wrapper.text()).toContain('欢迎,Alice!');
});
});
4.4 部署与构建优化
构建配置
// belly.config.js
module.exports = {
// 开发服务器配置
devServer: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
},
// 构建配置
build: {
outDir: 'dist',
assetsDir: 'assets',
// 代码分割
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
},
// 压缩配置
terserOptions: {
compress: {
drop_console: true
}
},
// 生成source map
sourcemap: process.env.NODE_ENV !== 'production'
},
// 插件配置
plugins: [
// 配置插件
]
};
环境变量配置
# .env.development
VITE_API_URL=http://localhost:8080/api
VITE_DEBUG=true
# .env.production
VITE_API_URL=https://api.example.com
VITE_DEBUG=false
Docker部署
# Dockerfile
FROM node:16-alpine as builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 生产阶段
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
# nginx.conf
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# 启用gzip压缩
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
# 支持History模式路由
location / {
try_files $uri $uri/ /index.html;
}
# API代理
location /api/ {
proxy_pass http://backend:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
}
第五部分:常见问题解决方案
5.1 调试技巧
使用Chrome DevTools
// 在组件中添加调试信息
export default {
mounted() {
// 在控制台打印组件实例
console.log('Component instance:', this);
// 监听数据变化
this.$watch('someData', (newVal, oldVal) => {
console.log('数据变化:', oldVal, '->', newVal);
// 在这里设置断点
debugger;
});
}
}
性能分析
// 性能监控工具
export function createPerformanceMonitor(componentName) {
return {
start: null,
end: null,
measures: [],
begin(label) {
this.start = performance.now();
console.time(`${componentName}-${label}`);
},
end(label) {
this.end = performance.now();
const duration = this.end - this.start;
this.measures.push({ label, duration });
console.timeEnd(`${componentName}-${label}`);
console.log(`${label} took ${duration.toFixed(2)}ms`);
},
report() {
console.table(this.measures);
}
};
}
// 在组件中使用
export default {
mounted() {
const monitor = createPerformanceMonitor('UserList');
monitor.begin('data-fetch');
fetch('/api/users').then(() => {
monitor.end('data-fetch');
monitor.report();
});
}
}
5.2 错误处理
全局错误处理
// main.js
import { createApp } from '@belly';
import App from './App.belly';
const app = createApp(App);
// 全局错误处理器
app.config.errorHandler = (err, instance, info) => {
console.error('全局错误捕获:', err);
console.error('组件:', instance?.$options?.name);
console.error('错误信息:', info);
// 上报到错误监控平台
reportToMonitoring(err, { component: instance?.$options?.name, info });
};
// 全局警告处理器
app.config.warnHandler = (msg, instance, trace) => {
console.warn('警告:', msg);
console.warn('组件:', instance?.$options?.name);
console.warn('追踪:', trace);
};
app.mount('#app');
组件内错误处理
export default {
data() {
return {
error: null
}
},
errorCaptured(err, instance, info) {
// 捕获子组件错误
this.error = err;
console.error('子组件错误:', err, info);
// 返回false阻止错误继续传播
return false;
},
methods: {
async fetchData() {
try {
const data = await this.$http.get('/api/data');
this.data = data;
} catch (error) {
// 统一错误处理
this.handleError(error);
}
},
handleError(error) {
if (error.response) {
// 服务器返回了错误状态码
const { status, data } = error.response;
this.error = `服务器错误 (${status}): ${data.message}`;
} else if (error.request) {
// 请求已发出但没有收到响应
this.error = '网络错误,请检查网络连接';
} else {
// 发送请求时出错
this.error = '请求配置错误: ' + error.message;
}
// 3秒后清除错误
setTimeout(() => {
this.error = null;
}, 3000);
}
}
}
5.3 移动端适配
响应式布局
/* 使用CSS变量 */
:root {
--primary-color: #007bff;
--spacing-unit: 8px;
--font-size-base: 16px;
}
/* 移动端优先的响应式设计 */
.container {
width: 100%;
padding: calc(var(--spacing-unit) * 2);
box-sizing: border-box;
}
/* 平板 */
@media (min-width: 768px) {
.container {
max-width: 720px;
margin: 0 auto;
padding: calc(var(--spacing-unit) * 3);
}
}
/* 桌面 */
@media (min-width: 1024px) {
.container {
max-width: 1200px;
padding: calc(var(--spacing-unit) * 4);
}
}
触摸事件优化
export default {
methods: {
// 防止双击缩放
handleTouchStart(e) {
if (e.touches.length > 1) {
e.preventDefault();
}
},
// 防止页面滚动穿透
preventScrollPenetration() {
document.body.style.overflow = 'hidden';
document.body.style.position = 'fixed';
document.body.style.width = '100%';
},
restoreScroll() {
document.body.style.overflow = '';
document.body.style.position = '';
document.body.style.width = '';
}
},
mounted() {
// 移动端性能优化:减少重绘
this.$el.style.willChange = 'transform';
}
}
5.4 国际化(i18n)
国际化配置
// i18n/index.js
import { createI18n } from '@belly/i18n';
const messages = {
en: {
welcome: 'Welcome',
buttons: {
save: 'Save',
cancel: 'Cancel'
},
errors: {
required: 'This field is required',
email: 'Invalid email format'
}
},
zh: {
welcome: '欢迎',
buttons: {
save: '保存',
cancel: '取消'
},
errors: {
required: '该字段为必填项',
email: '邮箱格式不正确'
}
}
};
export const i18n = createI18n({
locale: 'en',
fallbackLocale: 'en',
messages
});
在组件中使用
<template>
<div>
<h1>{{ $t('welcome') }}</h1>
<button>{{ $t('buttons.save') }}</button>
<p>{{ $t('errors.required') }}</p>
<!-- 动态插值 -->
<p>{{ $t('user.greeting', { name: username }) }}</p>
<!-- 复数处理 -->
<p>{{ $t('cart.items', { count: cartCount }) }}</p>
<!-- 选择不同语言 -->
<select v-model="$i18n.locale">
<option value="en">English</option>
<option value="zh">中文</option>
</select>
</div>
</template>
<script>
export default {
data() {
return {
username: 'Alice',
cartCount: 5
}
}
}
</script>
第六部分:企业级开发实践
6.1 代码规范与团队协作
ESLint配置
// .eslintrc.js
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true
},
extends: [
'eslint:recommended',
'@belly/eslint-config-recommended'
],
parserOptions: {
ecmaVersion: 2021,
sourceType: 'module'
},
rules: {
// 自定义规则
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'@belly/html-closing-bracket-newline': ['error', {
singleline: 'never',
multiline: 'always'
}],
'@belly/html-self-closing': ['error', {
html: {
void: 'always',
normal: 'never',
component: 'always'
}
}]
},
overrides: [
{
files: ['**/__tests__/**'],
env: {
jest: true
}
}
]
};
Prettier配置
// .prettierrc
{
"printWidth": 100,
"tabWidth": 4,
"useTabs": false,
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf"
}
Husky + lint-staged 配置
# 安装
npm install --save-dev husky lint-staged
# 初始化husky
npx husky install
# 添加pre-commit钩子
npx husky add .husky/pre-commit "npx lint-staged"
// package.json
{
"lint-staged": {
"*.{js,jsx,belly}": [
"eslint --fix",
"git add"
],
"*.{css,scss,less}": [
"stylelint --fix",
"git add"
],
"*.{js,jsx,belly,json,md}": [
"prettier --write",
"git add"
]
}
}
6.2 CI/CD流程
GitHub Actions配置
# .github/workflows/ci.yml
name: CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run unit tests
run: npm run test:unit
- name: Run e2e tests
run: npm run test:e2e
- name: Build
run: npm run build
deploy-staging:
needs: test
if: github.ref == 'refs/heads/develop'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to staging
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.STAGING_HOST }}
username: ${{ secrets.STAGING_USER }}
key: ${{ secrets.STAGING_SSH_KEY }}
script: |
cd /var/www/staging
git pull origin develop
npm ci
npm run build
pm2 restart staging-app
deploy-production:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to production
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.PROD_HOST }}
username: ${{ secrets.PROD_USER }}
key: ${{ secrets.PROD_SSH_KEY }}
script: |
cd /var/www/production
git pull origin main
npm ci --production
npm run build
pm2 restart production-app
6.3 监控与日志
性能监控
// utils/monitor.js
export class PerformanceMonitor {
constructor() {
this.metrics = [];
this.observer = null;
}
// 监控页面加载性能
monitorPageLoad() {
window.addEventListener('load', () => {
const perfData = performance.getEntriesByType('navigation')[0];
this.metrics.push({
type: 'pageLoad',
dns: perfData.domainLookupEnd - perfData.domainLookupStart,
tcp: perfData.connectEnd - perfData.connectStart,
ttfb: perfData.responseStart - perfData.requestStart,
download: perfData.responseEnd - perfData.responseStart,
total: perfData.loadEventEnd - perfData.startTime
});
this.report();
});
}
// 监控组件渲染性能
monitorComponentRender(componentName, fn) {
const start = performance.now();
const result = fn();
const end = performance.now();
this.metrics.push({
type: 'componentRender',
component: componentName,
duration: end - start
});
return result;
}
// 监控API响应时间
monitorAPI(url, promise) {
const start = performance.now();
return promise.finally(() => {
const end = performance.now();
this.metrics.push({
type: 'apiCall',
url,
duration: end - start
});
});
}
// 上报数据
report() {
if (this.metrics.length === 0) return;
// 发送到监控服务器
navigator.sendBeacon('/api/metrics', JSON.stringify({
metrics: this.metrics,
timestamp: Date.now(),
userAgent: navigator.userAgent,
url: window.location.href
}));
// 清空指标
this.metrics = [];
}
// 性能观察器
observeLongTasks() {
if ('PerformanceObserver' in window) {
this.observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 50) {
console.warn('Long task detected:', entry);
this.metrics.push({
type: 'longTask',
duration: entry.duration,
name: entry.name
});
}
}
});
this.observer.observe({ entryTypes: ['longtask'] });
}
}
disconnect() {
if (this.observer) {
this.observer.disconnect();
}
}
}
// 在main.js中使用
import { PerformanceMonitor } from './utils/monitor';
const monitor = new PerformanceMonitor();
monitor.monitorPageLoad();
monitor.observeLongTasks();
// 在组件中使用
export default {
mounted() {
// 监控特定操作
this.monitor = new PerformanceMonitor();
},
beforeDestroy() {
this.monitor?.disconnect();
}
}
错误监控
// utils/error-monitor.js
export class ErrorMonitor {
constructor(config = {}) {
this.config = {
dsn: config.dsn || '',
environment: config.environment || 'production',
release: config.release || '1.0.0',
beforeSend: config.beforeSend || null
};
this.isReady = false;
}
init() {
// 捕获全局错误
this.captureGlobalErrors();
// 捕获未处理的Promise拒绝
this.captureUnhandledRejections();
// 捕获资源加载错误
this.captureResourceErrors();
this.isReady = true;
}
captureGlobalErrors() {
window.addEventListener('error', (event) => {
const error = {
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
error: event.error,
type: 'error',
timestamp: Date.now(),
url: window.location.href
};
this.send(error);
}, true);
}
captureUnhandledRejections() {
window.addEventListener('unhandledrejection', (event) => {
const error = {
message: event.reason?.message || String(event.reason),
reason: event.reason,
type: 'unhandledrejection',
timestamp: Date.now(),
url: window.location.href
};
this.send(error);
});
}
captureResourceErrors() {
window.addEventListener('error', (event) => {
if (event.target instanceof HTMLElement) {
const error = {
message: `资源加载失败: ${event.target.tagName}`,
src: event.target.src || event.target.href,
type: 'resource',
timestamp: Date.now(),
url: window.location.href
};
this.send(error);
}
}, true);
}
captureException(error, context = {}) {
const payload = {
message: error.message,
stack: error.stack,
type: error.name,
context,
timestamp: Date.now(),
url: window.location.href
};
this.send(payload);
}
captureMessage(message, level = 'info') {
const payload = {
message,
level,
timestamp: Date.now(),
url: window.location.href
};
this.send(payload);
}
async send(payload) {
if (!this.isReady || !this.config.dsn) return;
const data = {
...payload,
environment: this.config.environment,
release: this.config.release,
user: this.getUserContext(),
tags: this.getTags()
};
// 应用过滤器
if (this.config.beforeSend) {
const filteredData = this.config.beforeSend(data);
if (filteredData === null) return; // 丢弃事件
Object.assign(data, filteredData);
}
try {
await fetch(this.config.dsn, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
} catch (e) {
// 如果上报失败,可以降级到console
console.error('Error monitoring failed:', e);
}
}
getUserContext() {
// 获取用户上下文信息
return {
id: localStorage.getItem('userId'),
sessionId: sessionStorage.getItem('sessionId'),
userAgent: navigator.userAgent,
viewport: `${window.innerWidth}x${window.innerHeight}`
};
}
getTags() {
// 获取自定义标签
return {
app: 'belly-app',
version: this.config.release,
path: window.location.pathname
};
}
setContext(key, value) {
if (!this.context) this.context = {};
this.context[key] = value;
}
}
// 在main.js中使用
import { ErrorMonitor } from './utils/error-monitor';
const errorMonitor = new ErrorMonitor({
dsn: 'https://your-monitoring-server.com/api/errors',
environment: process.env.NODE_ENV,
release: process.env.APP_VERSION,
beforeSend: (event) => {
// 过滤掉已知的非关键错误
if (event.message && event.message.includes('ResizeObserver')) {
return null; // 丢弃
}
return event;
}
});
errorMonitor.init();
// 在组件中使用
export default {
methods: {
async fetchData() {
try {
await this.$http.get('/api/data');
} catch (error) {
// 上报具体错误
errorMonitor.captureException(error, {
component: 'UserList',
userId: this.userId
});
}
}
}
}
6.4 安全最佳实践
XSS防护
// utils/sanitize.js
export function sanitizeHTML(str) {
const temp = document.createElement('div');
temp.textContent = str;
return temp.innerHTML;
}
export function sanitizeHTMLAttribute(str) {
return str.replace(/"/g, '"').replace(/'/g, ''');
}
// 在组件中使用
export default {
data() {
return {
userInput: '',
safeHTML: ''
}
},
watch: {
userInput(newVal) {
// 永远不要直接渲染用户输入的HTML
this.safeHTML = sanitizeHTML(newVal);
}
}
}
CSRF防护
// utils/csrf.js
export function getCSRFToken() {
const token = document.querySelector('meta[name="csrf-token"]');
return token ? token.getAttribute('content') : '';
}
// 在HTTP客户端中使用
import axios from 'axios';
import { getCSRFToken } from './csrf';
const http = axios.create({
baseURL: '/api'
});
http.interceptors.request.use(config => {
// 对于非GET请求,添加CSRF token
if (config.method !== 'get' && config.method !== 'GET') {
config.headers['X-CSRF-TOKEN'] = getCSRFToken();
}
return config;
});
敏感数据保护
// 在.env文件中配置敏感信息
VITE_API_KEY=your_api_key_here
VITE_API_SECRET=your_api_secret_here
// 在代码中使用
const API_KEY = import.meta.env.VITE_API_KEY;
// 注意:前端代码中的敏感信息仍然可能被看到
// 重要:不要在前端代码中存储真正的敏感信息
// 应该使用后端代理或服务器端渲染来保护敏感数据
第七部分:性能优化深度指南
7.1 构建优化
代码分割策略
// router.js - 路由级代码分割
const routes = [
{
path: '/',
component: () => import(/* webpackChunkName: "home" */ './pages/Home.belly')
},
{
path: '/dashboard',
component: () => import(/* webpackChunkName: "dashboard" */ './pages/Dashboard.belly'),
meta: { requiresAuth: true }
},
{
path: '/admin',
component: () => import(/* webpackChunkName: "admin" */ './pages/Admin.belly'),
meta: { requiresAdmin: true }
}
];
// 组件级代码分割
const HeavyComponent = () => import(/* webpackPrefetch: true */ './components/HeavyComponent.belly');
// 动态导入带预加载
const LazyComponent = () => import(
/* webpackChunkName: "lazy" */
/* webpackPrefetch: true */
'./components/LazyComponent.belly'
);
Tree Shaking优化
// 不好的做法 - 会引入整个库
import _ from 'lodash';
_.debounce(fn, 300);
// 好的做法 - 只引入需要的函数
import debounce from 'lodash/debounce';
debounce(fn, 300);
// 或者使用ES模块
import { debounce, throttle } from 'lodash-es';
依赖优化
// belly.config.js
export default {
build: {
// 排除不需要打包的依赖
external: ['moment', 'lodash'],
// 优化依赖
rollupOptions: {
output: {
manualChunks: {
// 将React相关代码打包到一起
'react-vendor': ['react', 'react-dom'],
// 将UI库打包到一起
'ui-lib': ['@belly/ui', 'belly-icons']
}
}
}
}
};
7.2 运行时优化
虚拟DOM优化
// 使用key的正确方式
export default {
data() {
return {
items: []
}
},
methods: {
// 好的key - 唯一且稳定
goodKey(item) {
return item.id; // 假设id是唯一的
},
// 不好的key - 索引或随机值
badKey(item, index) {
return index; // 会导致不必要的DOM移动
}
}
}
避免不必要的响应式数据
// 不好的做法 - 所有数据都是响应式的
export default {
data() {
return {
// 大量数据
hugeList: [],
// 不需要响应式的配置
config: {
api: 'https://api.example.com',
timeout: 5000
}
}
}
}
// 好的做法 - 使用非响应式数据
export default {
data() {
return {
hugeList: []
}
},
created() {
// 非响应式配置
this.config = Object.freeze({
api: 'https://api.example.com',
timeout: 5000
});
}
}
优化事件处理
// 不好的做法 - 每次渲染都创建新函数
export default {
template: `
<div>
<button @click="() => handleClick(item.id)">点击</button>
</div>
`
}
// 好的做法 - 使用事件委托或缓存函数
export default {
methods: {
handleClick(id) {
return (event) => {
// 处理点击
console.log(id, event);
};
}
},
template: `
<div @click="handleClick">
<button data-id="1">按钮1</button>
<button data-id="2">按钮2</button>
</div>
`
}
7.3 内存优化
避免内存泄漏
export default {
data() {
return {
intervalId: null,
observer: null
}
},
mounted() {
// 定时器
this.intervalId = setInterval(() => {
// 定期任务
}, 1000);
// 事件监听器
this.resizeHandler = () => {
// 处理resize
};
window.addEventListener('resize', this.resizeHandler);
// 观察者
this.observer = new MutationObserver((mutations) => {
// 处理DOM变化
});
this.observer.observe(this.$el, { childList: true });
},
beforeDestroy() {
// 清理定时器
if (this.intervalId) {
clearInterval(this.intervalId);
}
// 移除事件监听器
if (this.resizeHandler) {
window.removeEventListener('resize', this.resizeHandler);
}
// 断开观察者
if (this.observer) {
this.observer.disconnect();
}
// 清理引用
this.$el = null;
}
}
大数据集处理
export default {
data() {
return {
// 使用Web Worker处理大数据
worker: null,
processedData: []
}
},
mounted() {
// 创建Web Worker
this.worker = new Worker('/workers/data-processor.js');
this.worker.onmessage = (event) => {
this.processedData = event.data;
};
// 发送大数据到Worker
this.worker.postMessage(this.hugeDataset);
},
beforeDestroy() {
if (this.worker) {
this.worker.terminate();
}
}
}
// workers/data-processor.js
self.onmessage = function(event) {
const data = event.data;
// 在Worker中处理数据,不阻塞主线程
const processed = data.map(item => ({
...item,
processed: true
}));
self.postMessage(processed);
};
第八部分:实战项目案例
8.1 项目1:任务管理器
项目结构
task-manager/
├── src/
│ ├── components/
│ │ ├── TaskItem.belly
│ │ ├── TaskForm.belly
│ │ └── TaskFilter.belly
│ ├── pages/
│ │ └── TaskBoard.belly
│ ├── store/
│ │ └── taskStore.js
│ ├── utils/
│ │ └── storage.js
│ ├── services/
│ │ └── taskService.js
│ └── App.belly
├── public/
│ └── index.html
└── package.json
核心代码实现
<!-- components/TaskItem.belly -->
<template>
<div class="task-item" :class="{ completed: task.completed }">
<input
type="checkbox"
:checked="task.completed"
@change="toggleComplete"
/>
<span class="task-title">{{ task.title }}</span>
<span class="task-priority" :class="priorityClass">{{ task.priority }}</span>
<button @click="editTask">编辑</button>
<button @click="deleteTask">删除</button>
</div>
</template>
<script>
export default {
props: {
task: {
type: Object,
required: true
}
},
computed: {
priorityClass() {
return `priority-${this.task.priority}`;
}
},
methods: {
toggleComplete() {
this.$emit('toggle', this.task.id);
},
editTask() {
this.$emit('edit', this.task);
},
deleteTask() {
if (confirm('确定要删除吗?')) {
this.$emit('delete', this.task.id);
}
}
}
}
</script>
<style scoped>
.task-item {
display: flex;
align-items: center;
padding: 8px;
margin: 4px 0;
border: 1px solid #ddd;
border-radius: 4px;
gap: 8px;
}
.task-item.completed {
opacity: 0.6;
text-decoration: line-through;
}
.task-title {
flex: 1;
}
.priority-high { color: #ff4444; font-weight: bold; }
.priority-medium { color: #ff8800; }
.priority-low { color: #888; }
</style>
<!-- store/taskStore.js -->
import { createStore } from '@belly/store';
export const taskStore = createStore({
state: {
tasks: [],
filter: {
status: 'all', // all, active, completed
priority: 'all', // all, high, medium, low
search: ''
}
},
mutations: {
setTasks(state, tasks) {
state.tasks = tasks;
},
addTask(state, task) {
state.tasks.push(task);
},
updateTask(state, updatedTask) {
const index = state.tasks.findIndex(t => t.id === updatedTask.id);
if (index !== -1) {
state.tasks.splice(index, 1, updatedTask);
}
},
deleteTask(state, taskId) {
state.tasks = state.tasks.filter(t => t.id !== taskId);
},
setFilter(state, filter) {
state.filter = { ...state.filter, ...filter };
}
},
actions: {
async loadTasks({ commit }) {
try {
const tasks = await taskService.getAll();
commit('setTasks', tasks);
} catch (error) {
console.error('加载任务失败:', error);
throw error;
}
},
async createTask({ commit }, taskData) {
try {
const task = await taskService.create(taskData);
commit('addTask', task);
return task;
} catch (error) {
console.error('创建任务失败:', error);
throw error;
}
},
async updateTaskStatus({ commit }, { taskId, completed }) {
try {
const updated = await taskService.update(taskId, { completed });
commit('updateTask', updated);
} catch (error) {
console.error('更新任务失败:', error);
throw error;
}
}
},
getters: {
filteredTasks(state) {
let tasks = state.tasks;
// 状态过滤
if (state.filter.status !== 'all') {
const completed = state.filter.status === 'completed';
tasks = tasks.filter(t => t.completed === completed);
}
// 优先级过滤
if (state.filter.priority !== 'all') {
tasks = tasks.filter(t => t.priority === state.filter.priority);
}
// 搜索过滤
if (state.filter.search) {
const search = state.filter.search.toLowerCase();
tasks = tasks.filter(t =>
t.title.toLowerCase().includes(search) ||
t.description?.toLowerCase().includes(search)
);
}
return tasks;
},
taskCount(state) {
return state.tasks.length;
},
completedCount(state) {
return state.tasks.filter(t => t.completed).length;
}
}
});
<!-- pages/TaskBoard.belly -->
<template>
<div class="task-board">
<header>
<h1>任务管理器</h1>
<div class="stats">
<span>总计: {{ taskCount }}</span>
<span>已完成: {{ completedCount }}</span>
<span>进度: {{ progress }}%</span>
</div>
</header>
<task-filter @filter-change="handleFilterChange" />
<task-form @submit="handleCreateTask" />
<div class="task-list">
<task-item
v-for="task in filteredTasks"
:key="task.id"
:task="task"
@toggle="handleToggle"
@edit="handleEdit"
@delete="handleDelete"
/>
</div>
<div v-if="filteredTasks.length === 0" class="empty-state">
<p>暂无任务</p>
</div>
</div>
</template>
<script>
import { taskStore } from '../store/taskStore';
import TaskItem from '../components/TaskItem.belly';
import TaskForm from '../components/TaskForm.belly';
import TaskFilter from '../components/TaskFilter.belly';
export default {
components: {
TaskItem,
TaskForm,
TaskFilter
},
computed: {
filteredTasks() {
return taskStore.getters.filteredTasks;
},
taskCount() {
return taskStore.getters.taskCount;
},
completedCount() {
return taskStore.getters.completedCount;
},
progress() {
if (this.taskCount === 0) return 0;
return Math.round((this.completedCount / this.taskCount) * 100);
}
},
methods: {
async handleCreateTask(taskData) {
try {
await taskStore.dispatch('createTask', taskData);
this.$log('任务创建成功');
} catch (error) {
alert('创建失败: ' + error.message);
}
},
async handleToggle(taskId) {
const task = this.filteredTasks.find(t => t.id === taskId);
if (task) {
await taskStore.dispatch('updateTaskStatus', {
taskId,
completed: !task.completed
});
}
},
handleEdit(task) {
// 可以打开编辑弹窗
console.log('编辑任务:', task);
},
async handleDelete(taskId) {
try {
await taskStore.dispatch('deleteTask', taskId);
} catch (error) {
alert('删除失败: ' + error.message);
}
},
handleFilterChange(filter) {
taskStore.commit('setFilter', filter);
}
},
mounted() {
// 加载初始数据
taskStore.dispatch('loadTasks').catch(error => {
console.error('初始化加载失败:', error);
});
}
}
</script>
<style scoped>
.task-board {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.stats {
display: flex;
gap: 16px;
font-size: 14px;
color: #666;
}
.task-list {
margin-top: 20px;
}
.empty-state {
text-align: center;
padding: 40px;
color: #999;
}
</style>
8.2 项目2:实时聊天应用
实时通信实现
<!-- services/chatService.js -->
export class ChatService {
constructor() {
this.socket = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectDelay = 1000;
}
connect() {
return new Promise((resolve, reject) => {
try {
this.socket = new WebSocket('ws://localhost:8080/chat');
this.socket.onopen = () => {
console.log('WebSocket connected');
this.reconnectAttempts = 0;
resolve();
};
this.socket.onmessage = (event) => {
const message = JSON.parse(event.data);
this.handleMessage(message);
};
this.socket.onerror = (error) => {
console.error('WebSocket error:', error);
reject(error);
};
this.socket.onclose = () => {
console.log('WebSocket closed');
this.handleReconnect();
};
} catch (error) {
reject(error);
}
});
}
handleMessage(message) {
// 触发自定义事件
const event = new CustomEvent('chat-message', { detail: message });
window.dispatchEvent(event);
}
send(message) {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify(message));
return true;
}
return false;
}
handleReconnect() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
setTimeout(() => {
console.log(`尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
this.connect().catch(() => {
// 重试失败,继续下一次
this.handleReconnect();
});
}, delay);
} else {
console.error('达到最大重连次数');
// 触发连接失败事件
const event = new CustomEvent('chat-connection-failed');
window.dispatchEvent(event);
}
}
disconnect() {
if (this.socket) {
this.socket.close();
this.socket = null;
}
}
}
export const chatService = new ChatService();
<!-- pages/ChatRoom.belly -->
<template>
<div class="chat-room">
<div class="chat-header">
<h2>聊天室 ({{ onlineUsers.length }}人在线)</h2>
<div class="connection-status" :class="{ connected: isConnected }">
{{ isConnected ? '已连接' : '连接中...' }}
</div>
</div>
<div class="chat-messages" ref="messagesContainer">
<div
v-for="msg in messages"
:key="msg.id"
class="message"
:class="{
'own-message': msg.senderId === currentUserId,
'system-message': msg.type === 'system'
}"
>
<div class="message-sender" v-if="msg.type !== 'system'">
{{ msg.senderName }}
</div>
<div class="message-content">{{ msg.content }}</div>
<div class="message-time">{{ formatTime(msg.timestamp) }}</div>
</div>
</div>
<div class="chat-input">
<input
v-model="inputMessage"
@keypress.enter="sendMessage"
placeholder="输入消息..."
:disabled="!isConnected"
/>
<button @click="sendMessage" :disabled="!isConnected || !inputMessage.trim()">
发送
</button>
</div>
<div v-if="typingUsers.length > 0" class="typing-indicator">
{{ typingUsers.join(', ') }} 正在输入...
</div>
</div>
</template>
<script>
import { chatService } from '../services/chatService';
import { store } from '../store';
export default {
data() {
return {
messages: [],
inputMessage: '',
isConnected: false,
onlineUsers: [],
typingUsers: [],
currentUserId: null,
typingTimeout: null
}
},
methods: {
async initializeChat() {
try {
await chatService.connect();
this.isConnected = true;
// 发送加入消息
chatService.send({
type: 'join',
userId: this.currentUserId,
userName: store.state.user.name
});
} catch (error) {
console.error('连接失败:', error);
this.isConnected = false;
}
},
sendMessage() {
if (!this.inputMessage.trim()) return;
const message = {
id: Date.now(),
type: 'text',
content: this.inputMessage.trim(),
senderId: this.currentUserId,
senderName: store.state.user.name,
timestamp: Date.now()
};
if (chatService.send(message)) {
this.messages.push(message);
this.inputMessage = '';
this.scrollToBottom();
// 停止输入指示
if (this.typingTimeout) {
clearTimeout(this.typingTimeout);
this.sendTypingIndicator(false);
}
}
},
handleInput() {
// 发送输入指示
this.sendTypingIndicator(true);
// 清除之前的定时器
if (this.typingTimeout) {
clearTimeout(this.typingTimeout);
}
// 设置新的定时器
this.typingTimeout = setTimeout(() => {
this.sendTypingIndicator(false);
}, 1000);
},
sendTypingIndicator(isTyping) {
chatService.send({
type: 'typing',
userId: this.currentUserId,
isTyping
});
},
scrollToBottom() {
this.$nextTick(() => {
const container = this.$refs.messagesContainer;
if (container) {
container.scrollTop = container.scrollHeight;
}
});
},
formatTime(timestamp) {
const date = new Date(timestamp);
return date.toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit'
});
},
handleChatMessage(event) {
const message = event.detail;
switch (message.type) {
case 'text':
this.messages.push(message);
this.scrollToBottom();
break;
case 'system':
this.messages.push(message);
this.scrollToBottom();
break;
case 'users':
this.onlineUsers = message.users;
break;
case 'typing':
this.handleTypingStatus(message);
break;
}
},
handleTypingStatus(data) {
if (data.userId === this.currentUserId) return;
const user = this.onlineUsers.find(u => u.id === data.userId);
if (!user) return;
if (data.isTyping) {
if (!this.typingUsers.includes(user.name)) {
this.typingUsers.push(user.name);
}
} else {
this.typingUsers = this.typingUsers.filter(name => name !== user.name);
}
},
handleConnectionFailed() {
this.isConnected = false;
alert('连接失败,请刷新页面重试');
}
},
mounted() {
this.currentUserId = store.state.user.id;
// 监听WebSocket消息
window.addEventListener('chat-message', this.handleChatMessage);
window.addEventListener('chat-connection-failed', this.handleConnectionFailed);
// 监听输入
this.$watch('inputMessage', this.handleInput);
// 初始化连接
this.initializeChat();
},
beforeDestroy() {
window.removeEventListener('chat-message', this.handleChatMessage);
window.removeEventListener('chat-connection-failed', this.handleConnectionFailed);
chatService.disconnect();
}
}
</script>
<style scoped>
.chat-room {
display: flex;
flex-direction: column;
height: 100vh;
max-width: 800px;
margin: 0 auto;
background: #f5f5f5;
}
.chat-header {
background: #007bff;
color: white;
padding: 16px;
display: flex;
justify-content: space-between;
align-items: center;
}
.connection-status {
font-size: 12px;
padding: 4px 8px;
border-radius: 4px;
background: #ff4444;
}
.connection-status.connected {
background: #44ff44;
color: #333;
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
.message {
max-width: 70%;
padding: 12px;
border-radius: 8px;
background: white;
align-self: flex-start;
}
.own-message {
background: #007bff;
color: white;
align-self: flex-end;
}
.system-message {
background: #666;
color: white;
align-self: center;
font-size: 12px;
font-style: italic;
}
.message-sender {
font-weight: bold;
margin-bottom: 4px;
font-size: 12px;
}
.message-content {
word-wrap: break-word;
}
.message-time {
font-size: 10px;
opacity: 0.7;
margin-top: 4px;
text-align: right;
}
.chat-input {
display: flex;
padding: 16px;
background: white;
gap: 8px;
}
.chat-input input {
flex: 1;
padding: 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
.chat-input button {
padding: 12px 24px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.chat-input button:disabled {
background: #ccc;
cursor: not-allowed;
}
.typing-indicator {
padding: 8px 16px;
background: #fff3cd;
color: #856404;
font-size: 12px;
text-align: center;
}
</style>
第九部分:扩展与插件生态
9.1 常用插件推荐
1. 状态管理插件
// @belly/store
import { createStore } from '@belly/store';
const store = createStore({
state: { count: 0 },
mutations: {
increment(state) { state.count++; }
},
actions: {
asyncIncrement({ commit }) {
setTimeout(() => commit('increment'), 1000);
}
}
});
2. 路由插件
// @belly/router
import { createRouter } from '@belly/router';
const router = createRouter({
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About }
]
});
3. HTTP客户端插件
// @belly/http
import { createHTTP } from '@belly/http';
const http = createHTTP({
baseURL: '/api',
timeout: 5000
});
4. 表单验证插件
// @belly/validate
import { createValidator } from '@belly/validate';
const validator = createValidator({
rules: {
email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
required: (value) => !!value && value.trim().length > 0
}
});
9.2 自定义插件开发
插件开发模板
// plugins/my-plugin.js
export default {
// 插件安装函数
install(app, options) {
// 1. 添加全局方法
app.config.globalProperties.$myMethod = function(message) {
console.log(`[MyPlugin] ${message}`);
return `[MyPlugin] ${message}`;
};
// 2. 添加全局指令
app.directive('my-directive', {
mounted(el, binding) {
el.style.color = binding.value || 'blue';
el.addEventListener('click', () => {
console.log('Directive clicked');
});
},
updated(el, binding) {
el.style.color = binding.value;
}
});
// 3. 添加全局混入
app.mixin({
created() {
const name = this.$options.name;
if (name) {
console.log(`${name} created`);
}
}
});
// 4. 添加全局组件
app.component('MyGlobalComponent', {
template: '<div>Global Component</div>'
});
// 5. 添加全局过滤器
app.config.globalProperties.$filters = {
uppercase: (value) => value.toUpperCase(),
currency: (value) => `$${value.toFixed(2)}`
};
// 6. 添加全局配置
app.config.myPluginOptions = options;
}
};
// 使用插件
import { createApp } from '@belly';
import App from './App.belly';
import MyPlugin from './plugins/my-plugin';
const app = createApp(App);
app.use(MyPlugin, {
debug: true,
prefix: '[MyApp]'
});
app.mount('#app');
// 在组件中使用
export default {
mounted() {
this.$myMethod('Hello from plugin');
console.log(this.$filters.uppercase('hello'));
}
}
9.3 第三方库集成
集成Chart.js
// plugins/chart.js
import Chart from 'chart.js/auto';
export default {
install(app) {
app.config.globalProperties.$chart = Chart;
// 创建图表组件
app.component('Chart', {
props: {
type: {
type: String,
default: 'line'
},
data: Object,
options: Object
},
template: '<canvas ref="canvas"></canvas>',
mounted() {
this.createChart();
},
watch: {
data: {
deep: true,
handler() {
this.updateChart();
}
}
},
methods: {
createChart() {
this.chart = new Chart(this.$refs.canvas, {
type: this.type,
data: this.data,
options: this.options
});
},
updateChart() {
if (this.chart) {
this.chart.data = this.data;
this.chart.update();
}
}
},
beforeDestroy() {
if (this.chart) {
this.chart.destroy();
}
}
});
}
};
// 使用
import { createApp } from '@belly';
import App from './App.belly';
import ChartPlugin from './plugins/chart';
const app = createApp(App);
app.use(ChartPlugin);
app.mount('#app');
<!-- 在组件中使用 -->
<template>
<div>
<chart
type="line"
:data="chartData"
:options="chartOptions"
/>
</div>
</template>
<script>
export default {
data() {
return {
chartData: {
labels: ['1月', '2月', '3月', '4月', '5月'],
datasets: [{
label: '销售额',
data: [12, 19, 3, 5, 2],
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
}]
},
chartOptions: {
responsive: true,
maintainAspectRatio: false
}
}
}
}
</script>
第十部分:性能监控与调优
10.1 性能指标监控
// utils/performance.js
export class BellyPerformanceMonitor {
constructor() {
this.metrics = {};
this.observers = [];
}
// 监控组件渲染时间
measureComponentRender(componentName, renderFn) {
const start = performance.now();
const result = renderFn();
const end = performance.now();
const duration = end - start;
this.recordMetric('componentRender', componentName, duration);
return result;
}
// 监控数据响应时间
measureDataReactivity(dataChangeFn) {
const start = performance.now();
dataChangeFn();
// 使用requestAnimationFrame确保DOM更新完成
requestAnimationFrame(() => {
const end = performance.now();
this.recordMetric('dataReactivity', 'data-change', end - start);
});
}
// 监控API调用
async measureAPICall(apiName, apiFn) {
const start = performance.now();
try {
const result = await apiFn();
const end = performance.now();
this.recordMetric('apiCall', apiName, end - start);
return result;
} catch (error) {
const end = performance.now();
this.recordMetric('apiCall', apiName, end - start, 'error');
throw error;
}
}
// 监控内存使用
measureMemory() {
if (performance.memory) {
const memory = performance.memory;
return {
usedJSHeapSize: memory.usedJSHeapSize,
totalJSHeapSize: memory.totalJSHeapSize,
jsHeapSizeLimit: memory.jsHeapSizeLimit
};
}
return null;
}
// 监控长任务
observeLongTasks() {
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 50) {
this.recordMetric('longTask', 'main-thread', entry.duration);
}
}
});
observer.observe({ entryTypes: ['longtask'] });
this.observers.push(observer);
}
}
// 监控资源加载
observeResourceTiming() {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'resource') {
this.recordMetric('resource', entry.name, entry.duration);
}
}
});
observer.observe({ entryTypes: ['resource'] });
this.observers.push(observer);
}
// 记录指标
recordMetric(type, name, duration, status = 'success') {
if (!this.metrics[type]) {
this.metrics[type] = [];
}
this.metrics[type].push({
name,
duration: Math.round(duration * 100) / 100,
status,
timestamp: Date.now()
});
// 控制台输出(开发环境)
if (process.env.NODE_ENV === 'development') {
console.log(`[Performance] ${type}:${name} - ${duration.toFixed(2)}ms`);
}
}
// 获取报告
getReport() {
const report = {};
for (const [type, measurements] of Object.entries(this.metrics)) {
const durations = measurements.map(m => m.duration);
report[type] = {
count: measurements.length,
avg: durations.reduce((a, b) => a + b, 0) / durations.length,
min: Math.min(...durations),
max: Math.max(...durations),
measurements: measurements.slice(-10) // 最近10条
};
}
return report;
}
// 清理观察者
disconnect() {
this.observers.forEach(observer => observer.disconnect());
this.observers = [];
}
}
// 全局实例
export const perfMonitor = new BellyPerformanceMonitor();
10.2 优化建议
基于监控数据的优化
// 在main.js中
import { perfMonitor } from './utils/performance';
// 开始监控
perfMonitor.observeLongTasks();
perfMonitor.observeResourceTiming();
// 定期报告
setInterval(() => {
const report = perfMonitor.getReport();
console.table(report);
// 如果发现性能问题,给出建议
if (report.componentRender && report.componentRender.avg > 16) {
console.warn('⚠️ 组件渲染平均时间超过16ms,建议优化');
}
if (report.longTask && report.longTask.count > 0) {
console.warn('⚠️ 检测到长任务,建议使用Web Worker');
}
// 上报到监控平台
if (report.apiCall && report.apiCall.avg > 1000) {
console.warn('⚠️ API调用平均时间超过1s,建议优化后端或添加缓存');
}
}, 30000); // 每30秒报告一次
总结
通过本文的详细学习,你已经掌握了Belly框架从入门到精通的各个方面:
- 基础入门:环境搭建、核心语法、组件开发
- 核心概念:响应式系统、生命周期、状态管理
- 实战技巧:表单处理、性能优化、错误处理
- 高级主题:插件开发、自定义指令、测试策略
- 企业级实践:代码规范、CI/CD、监控部署
- 性能优化:构建优化、内存管理、实时监控
- 实战项目:任务管理器、实时聊天应用
- 扩展生态:插件开发、第三方库集成
Belly框架以其简洁的API、优秀的性能和完善的生态系统,为现代Web开发提供了强大的支持。通过本文的指导,你应该能够:
- 快速搭建Belly项目并进行开发
- 理解并应用核心概念解决实际问题
- 编写高质量、可维护的代码
- 优化应用性能,提升用户体验
- 在团队中实施最佳实践
记住,掌握一个框架不仅仅是学习语法,更重要的是理解其设计思想和最佳实践。持续学习、实践和优化,你将成为Belly框架的专家开发者!
