引言:为什么选择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-ifv-else-ifv-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, '&quot;').replace(/'/g, '&#39;');
}

// 在组件中使用
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框架从入门到精通的各个方面:

  1. 基础入门:环境搭建、核心语法、组件开发
  2. 核心概念:响应式系统、生命周期、状态管理
  3. 实战技巧:表单处理、性能优化、错误处理
  4. 高级主题:插件开发、自定义指令、测试策略
  5. 企业级实践:代码规范、CI/CD、监控部署
  6. 性能优化:构建优化、内存管理、实时监控
  7. 实战项目:任务管理器、实时聊天应用
  8. 扩展生态:插件开发、第三方库集成

Belly框架以其简洁的API、优秀的性能和完善的生态系统,为现代Web开发提供了强大的支持。通过本文的指导,你应该能够:

  • 快速搭建Belly项目并进行开发
  • 理解并应用核心概念解决实际问题
  • 编写高质量、可维护的代码
  • 优化应用性能,提升用户体验
  • 在团队中实施最佳实践

记住,掌握一个框架不仅仅是学习语法,更重要的是理解其设计思想和最佳实践。持续学习、实践和优化,你将成为Belly框架的专家开发者!