Skip to content

原生js-基于class类实现自定义组件

约 1827 字大约 6 分钟

JavaScript自定义组件

2025-07-29

原生js-基于class类实现自定义组件

通过 JavaScript 类,用于动态创建和管理组件元素。它不是一个 Web Component(即不是自定义 HTML 标签),而是通过实例化类并在指定 DOM 元素中渲染 HTML。

该方法用于ES6及以后

1.创建Class类

class NavbarComponent {
    
}

2.构建构造函数

接受传递到组件的数据

/**
     * 构造函数
     * @param {string} targetElementId - 标题将被渲染到的目标元素的ID
     * @param {object} config - 标题的配置对象
     * @param {number} [config.level=1] - 标题的级别 (1-6)
     * @param {string} [config.text='默认标题'] - 标题的文本内容
     * @param {string} [config.color='#333'] - 标题的颜色
     * @param {function} [config.onClick=null] - 标题被点击时触发的回调函数
     */
    constructor(targetElementId, config) {
        this.targetElementId = targetElementId;

        // 合并默认配置和用户传入的配置
        const defaultConfig = {
            level: 1,
            text: '默认标题',
            color: '#333',
            onClick: null
        };
        this.config = { ...defaultConfig, ...config };

        // 绑定事件处理函数,确保 this 指向组件实例
        this._handleClick = this._handleClick.bind(this);
    }

3.内部定义方法

用于处理接受的数据 并进行数据的渲染

 /**
     * 内部方法:获取并验证标题级别
     * @returns {number} 经过验证的标题级别
     */
    _getValidatedLevel() {
        let level = parseInt(this.config.level, 10);
        if (isNaN(level) || level < 1 || level > 6) {
            level = 1; // 默认值或无效值处理
        }
        return level;
    }

    /**
     * 生成标题的HTML字符串
     * @returns {string} 标题的HTML字符串
     */
    _generateHeadingHtml() {
        const level = this._getValidatedLevel();
        const text = this.config.text;
        const color = this.config.color;
        const headingTag = `h${level}`; // 根据 level 动态生成标签名

        // 注意:这里没有使用 Shadow DOM,所以样式会直接应用到主文档,需要注意命名冲突
        // 为了避免冲突,可以考虑使用更具体的类名或内联样式
        return `
            <${headingTag} class="my-heading-component-title" style="color: ${color};">
                ${text}
            </${headingTag}>
        `;
    }

    /**
     * 内部方法:处理标题点击事件
     * @param {Event} event - 点击事件对象
     */
    _handleClick(event) {
        console.log(`标题被点击了: ${this.config.text}`);
        // 如果用户提供了 onClick 回调函数,则调用它
        if (typeof this.config.onClick === 'function') {
            this.config.onClick(event);
        }
    }

4.定义render函数将数据渲染

/**
     * 将标题组件渲染到DOM中
     */
    render() {
        const targetElement = document.getElementById(this.targetElementId);
        if (!targetElement) {
            console.error(`Target element with ID "${this.targetElementId}" not found for MyHeadingComponent.`);
            return;
        }

        // 清空目标元素内容,并插入标题HTML
        targetElement.innerHTML = this._generateHeadingHtml();

        // 获取刚刚插入的标题元素,并绑定点击事件
        const headingElement = targetElement.querySelector('.my-heading-component-title');
        if (headingElement) {
            headingElement.addEventListener('click', this._handleClick);
        }
    }

5.组件销毁 清理时 移除事件监听器和DOM元素

destroy() {
        const targetElement = document.getElementById(this.targetElementId);
        if (targetElement) {
            const headingElement = targetElement.querySelector('.my-heading-component-title');
            if (headingElement) {
                headingElement.removeEventListener('click', this._handleClick);
            }
            targetElement.innerHTML = ''; // 清空内容
        }
        console.log(`MyHeadingComponent for target ID "${this.targetElementId}" destroyed.`);
    }

6.在html文件中实例化类并传递节点和数据

//传递dom节点和对应的配置数据
const heading1 = new MyHeadingComponent('headingContainer1', {
            level: 1,
            text: '欢迎来到我的页面!',
            color: 'darkblue',
            onClick: (event) => {
                console.log('第一个标题被点击了!事件对象:', event);
                alert('你点击了第一个标题!');
            }
        });
        heading1.render();

7.完整代码

inex.js文件

class MyHeadingComponent {
    /**
     * 构造函数
     * @param {string} targetElementId - 标题将被渲染到的目标元素的ID
     * @param {object} config - 标题的配置对象
     * @param {number} [config.level=1] - 标题的级别 (1-6)
     * @param {string} [config.text='默认标题'] - 标题的文本内容
     * @param {string} [config.color='#333'] - 标题的颜色
     * @param {function} [config.onClick=null] - 标题被点击时触发的回调函数
     */
    constructor(targetElementId, config) {
        this.targetElementId = targetElementId;

        // 合并默认配置和用户传入的配置
        const defaultConfig = {
            level: 1,
            text: '默认标题',
            color: '#333',
            onClick: null
        };
        this.config = { ...defaultConfig, ...config };

        // 绑定事件处理函数,确保 this 指向组件实例
        this._handleClick = this._handleClick.bind(this);
    }

    /**
     * 内部方法:获取并验证标题级别
     * @returns {number} 经过验证的标题级别
     */
    _getValidatedLevel() {
        let level = parseInt(this.config.level, 10);
        if (isNaN(level) || level < 1 || level > 6) {
            level = 1; // 默认值或无效值处理
        }
        return level;
    }

    /**
     * 生成标题的HTML字符串
     * @returns {string} 标题的HTML字符串
     */
    _generateHeadingHtml() {
        const level = this._getValidatedLevel();
        const text = this.config.text;
        const color = this.config.color;
        const headingTag = `h${level}`; // 根据 level 动态生成标签名

        // 注意:这里没有使用 Shadow DOM,所以样式会直接应用到主文档,需要注意命名冲突
        // 为了避免冲突,可以考虑使用更具体的类名或内联样式
        return `
            <${headingTag} class="my-heading-component-title" style="color: ${color};">
                ${text}
            </${headingTag}>
        `;
    }

    /**
     * 内部方法:处理标题点击事件
     * @param {Event} event - 点击事件对象
     */
    _handleClick(event) {
        console.log(`标题被点击了: ${this.config.text}`);
        // 如果用户提供了 onClick 回调函数,则调用它
        if (typeof this.config.onClick === 'function') {
            this.config.onClick(event);
        }
    }

    /**
     * 将标题组件渲染到DOM中
     */
    render() {
        const targetElement = document.getElementById(this.targetElementId);
        if (!targetElement) {
            console.error(`Target element with ID "${this.targetElementId}" not found for MyHeadingComponent.`);
            return;
        }

        // 清空目标元素内容,并插入标题HTML
        targetElement.innerHTML = this._generateHeadingHtml();

        // 获取刚刚插入的标题元素,并绑定点击事件
        const headingElement = targetElement.querySelector('.my-heading-component-title');
        if (headingElement) {
            headingElement.addEventListener('click', this._handleClick);
        }
    }

    /**
     * 清理组件,移除事件监听器和DOM元素
     */
    destroy() {
        const targetElement = document.getElementById(this.targetElementId);
        if (targetElement) {
            const headingElement = targetElement.querySelector('.my-heading-component-title');
            if (headingElement) {
                headingElement.removeEventListener('click', this._handleClick);
            }
            targetElement.innerHTML = ''; // 清空内容
        }
        console.log(`MyHeadingComponent for target ID "${this.targetElementId}" destroyed.`);
    }
}

index.html文件

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JavaScript Class 标题组件示例</title>
    <!-- 引入自定义组件的 JavaScript 文件 -->
    <script src="index.js"></script>
    <style>
        body {
            font-family: sans-serif;
            margin: 20px;
            background-color: #f4f4f4;
        }
        .component-container {
            background-color: #fff;
            padding: 20px;
            margin-bottom: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
            border: 1px dashed #ccc; /* 标记出组件渲染的容器 */
        }
        .my-heading-component-title {
            /* 默认样式,可以被组件的 color 配置覆盖 */
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            margin: 0; /* 移除默认的 h 标签外边距 */
            padding: 0;
            line-height: 1.2;
            cursor: pointer; /* 提示可点击 */
        }
        /* 可以添加一些额外的样式来区分不同级别的标题 */
        .my-heading-component-title[style*="font-size: 2.5em"] { border-bottom: 2px solid #eee; padding-bottom: 8px; }
        .my-heading-component-title[style*="font-size: 2em"] { font-style: italic; }

        button {
            padding: 10px 15px;
            background-color: #007bff;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            margin-top: 10px;
            margin-right: 10px;
        }
        button:hover {
            background-color: #0056b3;
        }
    </style>
</head>
<body>
    <h1>JavaScript Class 标题组件 `MyHeadingComponent` 示例</h1>

    <div class="component-container" id="headingContainer1">
        <!-- 标题组件将渲染到这里 -->
    </div>

    <div class="component-container" id="headingContainer2">
        <!-- 另一个标题组件将渲染到这里 -->
    </div>

    <div class="component-container" id="headingContainer3">
        <!-- 第三个标题组件将渲染到这里 -->
    </div>

    <button id="updateHeading2">更新第二个标题</button>
    <button id="destroyHeading3">销毁第三个标题</button>

    <script>
        // 示例 1: 基本使用
        const heading1 = new MyHeadingComponent('headingContainer1', {
            level: 1,
            text: '欢迎来到我的页面!',
            color: 'darkblue',
            onClick: (event) => {
                console.log('第一个标题被点击了!事件对象:', event);
                alert('你点击了第一个标题!');
            }
        });
        heading1.render();

        // 示例 2: 不同的级别和颜色,动态更新
        const heading2 = new MyHeadingComponent('headingContainer2', {
            level: 3,
            text: '这是一个可更新的标题',
            color: 'green',
            onClick: () => {
                console.log('第二个标题被点击了!');
            }
        });
        heading2.render();

        document.getElementById('updateHeading2').addEventListener('click', () => {
            // 改变配置,然后重新渲染
            heading2.config.level = (heading2.config.level % 6) + 1;
            heading2.config.text = `更新后的标题 (级别 ${heading2.config.level})`;
            const randomColor = '#' + Math.floor(Math.random()*16777215).toString(16); // 随机颜色
            heading2.config.color = randomColor;
            heading2.render(); // 重新渲染会重新绑定事件监听器
            console.log('第二个标题已更新和重新渲染。');
        });

        // 示例 3: 默认配置,并演示销毁
        const heading3 = new MyHeadingComponent('headingContainer3', {
            text: '点击我,然后尝试销毁我',
            onClick: () => {
                console.log('第三个标题被点击了!');
            }
        });
        heading3.render();

        document.getElementById('destroyHeading3').addEventListener('click', () => {
            heading3.destroy();
            console.log('第三个标题已销毁。');
        });
    </script>
</body>
</html>