前端框架 San - 组件的概念

163 阅读3分钟

在现代前端开发中,组件化是构建复杂应用的重要思想。组件不仅仅封装了 UI,还将数据、逻辑和样式进行模块化管理,这使得开发过程更加高效,代码更加可维护。每个组件都有自己的视图和状态,组件之间可以通过数据绑定和事件来进行交互。San 框架正是基于这种组件化思想,通过提供简洁、灵活的 API 来构建前端应用。

以下内容参考自《高性能MVVM框架的设计与实现——San》这本书,书中详细阐述了组件化的设计理念和实现方式。

什么是组件?

我们可以通过一个简单的例子来理解组件的概念。假设我们需要在网页中展示一篇文章,文章包括标题、简介和点赞功能,如下图所示。随着应用的复杂性增加,我们希望能够复用这个展示文章的结构,如何才能做到这一点呢?

image.png

传统方式实现

如果不用框架,直接使用纯 HTML 和 JavaScript 实现,核心代码如下(完整代码示例可以参考 article-traditional.html):

HTML

<div class="article">
    <div class="title">
        <h1>文章标题</h1>
        <span class="like"><span class="like-count">
                0
            </span>
        </span>
    </div>
    <div class="content">文章简介:这是一篇简单的博客文章的介绍</div>
</div>

JS

let likeCount = 0;
const domLike = document.querySelector('.like');
const domLikeCount = document.querySelector('.like-count');

// 点赞事件处理
domLike.addEventListener('click', function (e) {
    likeCount++;
    domLikeCount.innerText = likeCount;  // 更新点赞数
}, false);

这段代码中,我们通过直接操作 DOM 来实现点赞的功能,事件处理逻辑也直接绑定在 DOM 元素上。虽然这种方式在简单场景下可以满足需求,但随着页面功能的增加和复杂度的提升,代码将变得难以维护和扩展。如果我们想要复用相同的结构(例如展示多篇文章),就需要复制粘贴这段 HTML 代码,这显然不是一个理想的做法。

提取为函数

为了提高复用性,我们可以将文章的结构提取成一个函数。该函数接受参数(例如文章标题和点赞数),并返回对应的 HTML 结构,核心代码如下(完整代码示例可以参考 article-function): HTML

<div id="wrapper"></div>

JS

function createArticle(title, likeCount) {
    return `
        <div class="article">
            <div class="title">
                <h1>${title}</h1>
                <span class="like">
                    ❤
                    <span class="like-count">${likeCount}</span>
                </span>
            </div>
            <div class="content">文章简介:这是一篇简单的博客文章的介绍</div>
        </div>
    `;
}
// 通过调用上面的 createArticle 函数,我们可以生成多个文章项并插入到页面中:
const wrapper = document.querySelector('#wrapper');
for (let i = 0; i < 10; i++) {
    wrapper.innerHTML += createArticle(`测试标题 ${i + 1}`, 0);
}     

通过上述方法,我们能够动态生成多个文章项。虽然这比传统的直接写 HTML 更灵活,但代码中仍然没有真正的隔离和封装,逻辑与视图仍然交织在一起。当应用需求进一步增加时,代码可能会变得更加复杂。

向组件化过渡

随着应用的复杂度增加,我们希望将 UI 组件化,使得每个组件都能独立处理自己的数据和逻辑。为了进一步提高代码的可维护性和扩展性,我们可以将文章项封装为一个类,使其具备更强的复用性和逻辑隔离性,核心代码如下(完整代码示例可以参考 article-class):

HTML

<div id="wrapper"></div>

JS

class Article {
    constructor(title, likeCount = 0) {
        this.initData({ title, likeCount });
    }

    // 初始化数据
    initData(args) {
        this.title = args.title;
        this.likeCount = args.likeCount;
    }

    // 生成文章的 HTML 模板
    getTemplate() {
        return `
            <div class="article">
                <div class="title">
                    <h1>${this.title}</h1>
                    <span class="like">
                        ❤
                        <span class="like-count">${this.likeCount}</span>
                    </span>
                </div>
                <div class="content">文章简介:这是一篇简单的博客文章的介绍</div>
            </div>
        `;
    }

    // 处理点赞事件
    handleClick(e) {
        this.likeCount++;
        e.target.querySelector('.like-count').innerText = this.likeCount;
    }

    // 渲染文章并绑定事件
    render() {
        const el = document.createElement('div');
        el.innerHTML = this.getTemplate();

        // 获取点赞按钮并绑定点击事件
        const likeButton = el.querySelector('.like');
        likeButton.addEventListener('click', (e) => this.handleClick(e));

        return el;
    }
}

// 现在,我们可以通过创建多个 Article 类的实例,来渲染一个包含多篇文章的列表:
const wrapper = document.getElementById('wrapper');
for (let i = 0; i < 5; i++) {
    const article = new Article(`文章 ${i + 1}`);
    wrapper.appendChild(article.render());
}

在这段代码中,我们将文章的 DOM 结构、数据和事件封装在了 Article 类中。每个 Article 实例都有自己的数据和行为,通过 initData 方法传入初始化数据,并通过 handleClick 方法处理事件,渲染时调用 render 方法来生成对应的 HTML 结构,并为点赞按钮绑定事件。

Article 类的实现中,我们将视图(HTML 模板)、数据(文章标题和点赞数)和逻辑(点赞事件)都封装在一起。通过这样的封装方式,我们的 Article 类实现了与 San 框架组件类似的结构,具备了独立的视图和逻辑,并能够处理自己的事件和数据。

总结

通过传统的 DOM 操作,到函数式的组件实现,再到面向对象的组件化设计,每一步的过渡都让我们更接近现代前端框架中的组件化思想。San 框架也正是通过这种组件化设计,将数据、模板和事件逻辑整合在一起,使得每个组件都可以独立处理自己的状态和交互,从而极大提高了代码的复用性和可维护性。

这里展示的代码结构只是一些组件化的设计思想。San 框架中不仅仅是将 UI 组件化,它还涉及了更复杂的功能,如高效的数据绑定、虚拟 DOM、全局状态管理以及跨端能力等。