从零实现一个"就地编辑"组件:仿 B站个人简介的 EditInPlace 类
最近在刷 Bilibili 的时候,注意到个人主页的「个人简介」区域有一个非常流畅的交互体验:点击文字即可直接编辑,输入完成后点击"保存"或"取消",整个过程无需跳转页面,也没有传统表单的笨重感。这种"就地编辑(Edit In Place)"的交互模式在现代 Web 应用中越来越常见。
于是我想:能不能自己动手实现一个类似的组件?不依赖任何框架,纯原生 JavaScript + 面向对象编程(OOP)的方式封装成一个可复用的类?
今天,我就带大家一步步实现这个 EditInPlace 组件,并深入探讨其设计思路与封装技巧。
一、需求分析
我们要实现的功能很简单:
- 初始状态下显示一段文本(如用户简介);
- 点击文本后,切换为输入框 + 保存/取消按钮;
- 用户可编辑内容,点击"保存"则更新文本并隐藏输入框;
- 点击"取消"则放弃修改,恢复原始内容;
- 整个组件应具备良好的封装性,便于在不同项目中复用。
这正是典型的 就地编辑(Edit In Place) 场景。
二、设计思路:用 OOP 封装组件
为了提高代码的可维护性和复用性,我们采用 面向对象编程(OOP) 的方式,将整个功能封装成一个 EditInPlace 类。
核心要素
- 属性:存储组件的状态和 DOM 引用;
- 方法:负责创建 DOM、绑定事件、切换显示状态等;
- 封装:对外只暴露必要接口,内部实现细节对使用者透明。
三、代码实现
1. HTML 结构(入口)
xml
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>EditInPlace Demo</title>
</head>
<body>
<div id="app"></div>
<script src="./edit_in_place.js"></script>
<script>
const ep = new EditInPlace('slogan', '', document.getElementById('app'));
</script>
</body>
</html>
我们只需提供一个挂载点(#app),然后实例化 EditInPlace 即可。
2. JavaScript 实现(核心逻辑)
kotlin
/* edit_in_place.js */
/**
* @func EditInPlace 就地编辑组件
* @param {string} id - 组件唯一ID
* @param {string} value - 初始显示文本
* @param {HTMLElement} parentElement - 挂载的父容器
*/
function EditInPlace(id, value, parentElement) {
this.id = id;
this.value = localStorage.getItem(id) || value || '这是一个懒货,什么都没有留下';
this.parentElement = parentElement;
// DOM 引用
this.containerElement = null;
this.staticElement = null; // 显示文本的 span
this.fieldElement = null; // 输入框 input
this.saveButton = null; // 保存按钮
this.cancelButton = null; // 取消按钮
// 初始化
this.createElement();
this.attachEvent();
}
EditInPlace.prototype = {
/**
* 创建所有 DOM 元素并插入到页面
*/
createElement: function () {
this.containerElement = document.createElement('div');
this.containerElement.id = this.id;
// 文本显示区域
this.staticElement = document.createElement('span');
this.staticElement.textContent = this.value;
this.containerElement.appendChild(this.staticElement);
// 输入框
this.fieldElement = document.createElement('input');
this.fieldElement.type = 'text';
this.fieldElement.value = this.value;
this.containerElement.appendChild(this.fieldElement);
// 按钮
this.saveButton = document.createElement('input');
this.saveButton.type = 'button';
this.saveButton.value = '保存';
this.containerElement.appendChild(this.saveButton);
this.cancelButton = document.createElement('input');
this.cancelButton.type = 'button';
this.cancelButton.value = '取消';
this.containerElement.appendChild(this.cancelButton);
// 挂载到父容器
this.parentElement.appendChild(this.containerElement);
// 初始状态:仅显示文本
this.convertToText();
},
/**
* 切换为文本显示模式
*/
convertToText: function () {
this.staticElement.style.display = 'inline';
this.fieldElement.style.display = 'none';
this.saveButton.style.display = 'none';
this.cancelButton.style.display = 'none';
},
/**
* 切换为编辑模式
*/
convertToField: function () {
this.fieldElement.value = this.value; // 同步最新值
this.staticElement.style.display = 'none';
this.fieldElement.style.display = 'inline';
this.saveButton.style.display = 'inline';
this.cancelButton.style.display = 'inline';
},
/**
* 绑定事件监听器
*/
attachEvent: function () {
// 点击文本 → 进入编辑
this.staticElement.addEventListener('click', () => {
this.convertToField();
});
// 保存
this.saveButton.addEventListener('click', () => {
this.save();
});
// 取消
this.cancelButton.addEventListener('click', () => {
this.cancel();
});
},
/**
* 保存修改
*/
save: function () {
var value = this.fieldElement.value.trim();
// fetch 后端存储
this.value = value;
this.staticElement.innerHTML = value;
localStorage.setItem(this.id, value);
this.convertToText();
},
/**
* 取消编辑,恢复原值
*/
cancel: function () {
this.convertToText();
}
};
运行结果

点击"这是一个懒货,什么都没有留下"后-->

在输入框里我们可以修改我们的文本,达到类似B站简介的功能。
四、关键设计亮点
1. 状态分离清晰
staticElement负责展示;fieldElement负责编辑;- 通过
display控制显隐,逻辑简单可靠。
2. 事件委托 + 箭头函数
使用箭头函数确保 this 指向当前实例,避免作用域问题。
3. 高内聚低耦合
所有 DOM 操作、事件绑定、状态切换都封装在类内部,外部只需一行代码即可使用:
javascript
new EditInPlace('bio', 'Hello World', document.body);
4. 可扩展性强
未来若需支持:
- 后端自动保存(在
save()中加入fetch); - 输入校验;
- 快捷键(如按 Enter 保存);
- 多行文本(改用
<textarea>);
只需在现有结构上扩展,无需重写。
五、使用场景与延伸思考
这类组件非常适合用于:
- 用户资料编辑(昵称、签名、地址等);
- 后台管理系统中的表格字段快速修改;
- 协作工具中的实时备注更新。
进阶建议:
- 将类改为 ES6
class语法,更符合现代规范; - 支持传入配置项(如 placeholder、是否必填等);
- 添加 loading 状态,提升用户体验;
- 使用 Custom Elements 实现 Web Components,彻底解耦。
六、总结
通过这个小小的 EditInPlace 组件,我们不仅复刻了 B站简介的交互体验,更重要的是实践了 面向对象的封装思想:将复杂的 DOM 操作和状态管理隐藏在类内部,对外提供简洁、稳定的接口。
这正是优秀前端工程化的体现------让复杂留给自己,把简单留给他人。