Nx带来极致的前端开发体验——前言

0 阅读9分钟

阅读指南

作为一名前端工程师,开发体验和效率始终是我们关注的重点。毕竟,时间就是我们的生产力。我们追求在保证代码质量的前提下,更快、更高效地完成任务。因此,在日常工作中,我时常反思以下几个问题:

  • 随着项目体量和复杂性不断提升,如何优化构建流程以缩短构建时间?减少不必要的编译等待时间,可以让我们更专注于编码与优化。
  • 前端开发是否能够从传统的以 UI/UE 为中心,逐步转向更关注业务逻辑和功能开发?这种转变能否减少我们对视觉细节的过度依赖,让我们更专注于提升用户体验的核心逻辑?
  • 在开发 UI 组件时,是否能够将组件的开发与测试独立于项目,进行模块化的设计与复用?这样不仅能够提高开发的灵活性,还能提升代码的可维护性和扩展性。
  • 有没有一种代码生成工具,能够帮助我们自动生成符合团队编码标准和项目结构的代码?通过统一的代码风格和最佳实践,不仅能确保团队成员的一致性,还能减少重复劳动,提升整体开发效率。

这些问题不仅关乎日常的开发体验,还能直接影响我们项目的长期维护和可扩展性。作为开发者,我们始终在寻求更好的工具和方法,以优化工作流程,提高效率,实现持续交付的目标。对于上面的问题我们可以为三部分来介绍。

项目构建

说到前端开发的效率和体验,大家通常首先想到的是项目的构建时间问题。确实,随着项目规模的不断扩大,完整构建整个项目所花费的时间越来越长。在本地开发时,即使只修改了少量代码,依然需要进行全量构建,这极大地影响了开发效率,导致每次修改后的反馈时间过长。

近几年,随着前端技术的快速发展,出现了诸如 Vite、rspack、esbuild 等新的构建工具,它们在优化构建速度上表现出了显著的优势。同时,传统的构建工具如 webpack 也在不断进行优化,通过引入持久化缓存、并行构建等功能,进一步提升了构建性能。然而,无论是新兴工具还是对现有工具的改进,这些优化手段在提高构建速度的同时,也可能带来一些新的问题和局限性,这点后面会详细介绍。

代码开发

在代码开发过程中,有两种主要的方法论值得我们关注:测试驱动开发(TDD)和组件驱动开发(CDD)。这两种方法都旨在提高代码质量和开发效率,但它们的侧重点略有不同。让我们深入了解这两种方法:

测试驱动开发(TDD)

TDD 是测试驱动开发 (Test-Driven Development)的英文简称,旨在开发者在编写代码之前,首先编写测试代码,测试代码确定开发者对功能的预期,之后再编写实际功能代码,直到所有的功能代码都通过测试用例。

当前前端开发都以数据驱动为主,开发者通过声明式的方式编写 UI 代码,即开发者描述“渲染成什么样”,而不需要详细描述“如何渲染”,最终通过数据来控制和驱动用户界面的生成、更新和变化。而数据层作为应用的核心逻辑,承担了数据的获取、处理、存储和验证等多项职责,因此在数据层进行 TDD 开发是非常必要。

当前前端开发都以数据驱动为主,开发者通过声明式的方式编写 UI 代码,即开发者描述“渲染成什么样”,而不需要详细描述“如何渲染”,最终通过数据来控制和驱动用户界面的生成、更新和变化。而数据层作为应用的核心逻辑,承担了数据的获取、处理、存储和验证等多项职责,因此在数据层进行 TDD 开发非常必要。

作者认为以 TDD 的方式开发数据层有几大核心作用:

  • 前端开发者脱离面向 UE/UI 开发,转而面向功能逻辑开发。
  • 避免头重脚轻的情况,即数据层逻辑少,UI层逻辑多。
  • 确保代码的可测试性,提高代码质量和可维护性,长期减少回归 bug。

举个例子,PM 现在提了个需求,要我实现一个 TODO 记事本,正常的开发流程一般都是:

  • 需求评审
  • UE设计
  • UE评审
  • UI设计
  • 接口设计
  • 代码开发
  • 功能联调。

借助 TDD 我们前端开发者可以在 UI 设计出来之前完成基本功能的开发,下面我使用 mobx 作为数据层来演示:

store:

import {makeObservable, observable, flow, action} from 'mobx';
import {nanoid} from 'nanoid';

interface ITodo {
    id: string;
    /** todo 内容 */
    content: string;
}

export class TodoStore {
		/** 是否正在获取数据 */
		isFetching: boolean;
    /** 记录数据 */
    todos: ITodo[] = [];

    /** 添加记录 */
    addTodo = action(() => {
        this.todos = [...this.todos, {id: nanoid(), content: ''}];
    });

    /** 删除记录 */
    deleteTodo = action((id: string) => {
        this.todos = this.todos.filter(todo => todo.id !== id);
    });

    /** 修改记录内容 */
    updateTodo = action((todo: ITodo, content: string) => {
        todo.content = content;
    });

    constructor() {
        makeObservable(this, {
            todos: observable,
            isFetching: observable,
            fetchTodoList: flow.bound,
            saveTodoList: flow.bound
        });
    }

    /** 获取todo数据 */
    * fetchTodoList() {
		    try {
				    this.isFetching = true;
				    const data = yield fetchAPI.get('/api/todos');
		        this.todos = data;
		    }
		    finally {
				    this.isFetching = false;
		    }
        
    }

    /** 保存todo数据 */
    * saveTodoList(data: ITodo[]) {
        yield fetchAPI.post('/api/todos', {
            json: {
                data: this.todos
            }
        });
    }
}

单测:

import {TodoStore} from '.';

describe('功能测试', () => {
    const store = new TodoStore();
    it('测试添加数据', () => {
        store.addTodo();
        expect(store.todos.length).toBe(1);
        store.addTodo();
        expect(store.todos.length).toBe(2);
    });

    it('测试删除数据', () => {
        store.deleteTodo(store.todos[0].id);
        expect(store.todos.length).toBe(1);
    });

    it('测试修改数据', () => {
        store.updateTodo(store.todos[0], '123456');
        expect(store.todos[0].content).toBe('123456');
    });
});

describe('测试接口', () => {
		// 利用msw mock 接口
})

对于一些功能多并且复杂度高的场景,利用好 TDD 能明显的提高整体开发效率。

组件驱动开发(CDD)

前面说过,随着项目不断的开发迭代,他的体量会越来越大,项目的整体构建时间因此也会随之变长。在前端开发中我们前期大部分时间都是在写 UI 组件、调样式、写逻辑,这些操作会使得代码变更非常频繁,而且对于样式调整开发者更希望的是能够实时在页面上看到最终的效果。

如果每次进行功能开发都需要启动整个项目,然后在页面上查看效果,这会使得整个开发效率变得非常低,而 CDD 恰好能帮助开发者解决这一问题。

CDD开发,即组件驱动开发(Component-Driven Development),是一种现代前端开发方法论,是一种以组件为中心构建用户界面的开发方法,组件的开发能够在无需启动项目的情况下独立开发、测试,现在前端比较受欢迎的 CDD 框架就是 storybook,storybook 会创建一个独立的环境来渲染组件,而不会受到应用业务逻辑和上下文的干扰,开发者 可以在 storybook 中通过传入 props、模拟数据或伪造事件来单独渲染组件的特定变体。

代码生成器

代码生成器在前端开发中扮演着不可或缺的角色,特别是在提升开发效率和保持项目一致性方面,它的作用尤为突出。随着项目规模的增长和团队协作的增加,尤其是在 monorepo 这种多项目统一管理的场景下,代码生成器的重要性愈加显现。它不仅提高了开发效率,还确保了项目的长期可维护性。以下是代码生成器在前端开发中的几个关键作用:

  • 一致性:代码生成器确保所有生成的代码都遵循团队的 编码标准最佳实践,无论是文件组织、命名约定还是代码风格,生成器都能保证一致性。这在大团队或长时间维护的项目中尤为重要,因为一致的代码结构使得项目更易于维护和扩展。
  • 自动化:通过自动化处理重复性的任务,如创建新组件、服务、工具或模块,大大减少了开发者手动编写基础模板的时间。这意味着开发者可以将更多的精力集中在业务逻辑和功能创新上,而不是浪费时间在重复性的代码结构编写和配置工作上。

总体来说,代码生成器不仅提升了开发效率,更通过一致性、自动化的优势,推动了团队协作的顺利进行,特别是在 monorepo 这样的复杂环境中,它的作用不可替代。未来,随着开发工具链的不断演进,代码生成器将进一步提升前端开发的灵活性和可靠性。

总结

前端开发体验和效率对于前端工程师来说至关重要。随着项目规模的不断扩大和复杂性的增加,如何优化开发流程、提高工作效率成为每个前端工程师必须面对的挑战。本文探讨了影响前端开发体验的三个关键方面:项目构建、代码开发和代码生成器。

高效的项目构建可以显著缩短开发周期,让工程师更专注于核心开发任务。良好的代码开发实践,如测试驱动开发(TDD)和组件驱动开发(CDD),不仅能提高代码质量,还能增强团队协作。而代码生成器则通过自动化和标准化,大大提升了开发效率和项目一致性。

作为一线前端工程师,作者深刻认识到这些因素对团队整体开发效率的重要影响。在后续的系列文章中,作者将详细分享如何利用Nx这一强大工具来管理项目,并通过实际案例展示如何显著提高开发效率。Nx的特性不仅能解决当前面临的开发体验问题,还能为大型、复杂的前端项目提供全面的解决方案。