问题
一个 React 项目(用的函数组件)需要支持可以回复表情的评论,看到这个需求,相信读者们能想到好几种解决方案,比如使用成熟的富文本编辑器、使用第三方表情组件库、自己封装组件……
考虑到开发成本不能太高、评论组件可以复用、需求只是支持表情即可,因此笔者选择了第二种解决方案,也就是说,要去技术调研以选择合适的第三方库。
技术选型
GitHub 上有很多开源的表情库,对比了一下,个人觉得 emoji-mart 是最好的一个,原因:
star
数达到了 5.4 k ;- 使用文档清晰易懂,支持中文;
- 作者持续维护,可以看到上一次的
commit
时间是 5天 前; - UI 设计高级,符合笔者审美。
到这里,就确定了技术选型(出来吧 emoji-mart 就决定是你了hhh)
实践
安装表情组件库
我们首先使用 npm install --save emoji-mart @emoji-mart/data
将库安装下来。
生成容器组件
然后把 demo 跑起来,这里新建了一个 EmojiPicker.js
文件,用于生成容器组件,其代码如下:
import React, { useEffect, useRef } from 'react';
import data from '@emoji-mart/data';
import { Picker } from 'emoji-mart';
function EmojiPicker(props) {
const ref = useRef();
useEffect(() => {
new Picker({ ...props, data, ref });
}, []);
return <div ref={ref} />;
}
export default EmojiPicker;
容器组件生成之后,就可以在其他地方使用,并调用它的 API 。在上面这段代码中,是不能在 div
中调用诸如 onEmojiSelect
之类的 API 的,因为此时容器组件还没生成,div
还仅仅只是 div
而已,这也是为什么要新建一个 EmojiPicker.js
的原因。
调用容器组件
在评论框处调用 EmojiPicker
,即可在页面中看到效果了。
<EmojiPicker
onEmojiSelect={emoji => searchEmoji(emoji)}
/>
这里只展示与表情组件库有关联的输入框,其他相关组件的布局这里就不一一写出来了,请读者自由发挥
<Input.TextArea
id='textarea'
value={chatContent} // 输入框文本值由 chatContent 决定,因此要维护 chatContent
onChange={e => onChatContentChange(e.target.value)} // 文本值发生改变时的回调
/>
到这里,我们离成功实现评论功能迈进了一小步,但还不够,可以看到上面代码有三个动态数据,分别是一个 chatContent
字段、onChatContentChange
函数和 searchEmoji
函数,我们继续往(写)下(bug)。
封装函数
- 第一版
在输入框进行键盘输入和表情插入,我们可以想到这样的逻辑:
- ①键盘输入时直接调用输入框的
onChange
API 更新文本值chatContent
即可 - ②插入表情时调用组件库的
onEmojiSelect
API 获取到要插入的表情,拼接到chatContent
后面
于是有了下面的代码:
const [chatContent, setChatContent] = useState('');
// ①输入框文本值发生改变的回调函数
const onChatContentChange = (value) => {
setChatContent(value);
};
// ②选中表情的回调函数
const searchEmoji = (emoji) => {
// 考虑到在一开始就插入表情的情况
const newChatContent = chatContent.length > 0 ? chatContent + emoji.native : emoji.native;
setChatContent(newChatContent);
};
然而这版的代码是有 bug 的,假如先输入“你好”,再插入表情,预计结果应该是“你好【表情】”,然而实际结果是“【表情】”,如图所示:
为什么会这样呢?这是因为 chatContent.length
为 0
,自然就被赋值为 emoji.native
了。那么,在 @飞哥 的提点下,用 tempTextAreaData
全局字段存储输入框的文本值,这样就可以在页面更新的时候依然也能拿到最新的文本值,因此代码变成这样:
+ let tempTextAreaData = '';
const [chatContent, setChatContent] = useState('');
// ①输入框文本值发生改变的回调函数
const onChatContentChange = (value) => {
setChatContent(value);
+ tempTextAreaData = value;
};
// ②选中表情的回调函数
const searchEmoji = (emoji, event) => {
// 考虑到在一开始就插入表情的情况
- const newChatContent = chatContent.length > 0 ? chatContent + emoji.native : emoji.native;
+ const newChatContent = tempTextAreaData.length > 0 ? tempTextAreaData + emoji.native : emoji.native;
+ tempTextAreaData = newChatContent;
setChatContent(newChatContent);
};
来看看效果:
- 第二版
我们完成了第一版的代码,实现了在输入框文本值后面插入表情,但有一个缺陷:不能在文本中间插入表情,因此我们的代码逻辑需要进一步完善。
笔者的思考方向是这样的:当光标移动到文本中间时,获取这个光标的位置,截断光标之前和光标之后的字符,将表情拼接到两者之间。
onChatContentChange
函数比较简单,第一版已经搞定了,现在我们完善 searchEmoji
函数:
// 由于在光标中间拼接表情稍微复杂了一点,这里单独抽成 insertAtCursor 函数,tempTextAreaData 的更新也将在 insertAtCursor 中完成
const searchEmoji = (emoji) => {
let dom = document.getElementById('textarea'); // 获取输入框的节点对象
insertAtCursor(dom, emoji.native); // 将表情插入到光标后面
};
insertAtCursor
函数:
const insertAtCursor = (myField, myValue) => {
if (document.selection) {
//IE support
myField.focus();
const sel = document.selection.createRange();
sel.text = myValue;
sel.select();
} else if (myField.selectionStart || myField.selectionStart == '0') {
//MOZILLA/NETSCAPE support
const startPos = myField.selectionStart;
const endPos = myField.selectionEnd;
const beforeValue = myField.value.substring(0, startPos);
const afterValue = myField.value.substring(endPos, myField.value.length);
tempTextAreaData = beforeValue + myValue + afterValue;
setChatContent(tempTextAreaData);
myField.selectionStart = startPos + myValue.length;
myField.selectionEnd = startPos + myValue.length;
myField.focus();
} else {
tempTextAreaData += myValue;
setChatContent(tempTextAreaData);
myField.focus();
}
}
这样一来,就可以在文本中插入表情了,效果如图:
思考
在第二版代码完成之后,这个项目需求就完成了,当然,这样的实现是基于这个评论功能只需要支持表情就好,对于图片的插入、上传等是不支持的,如果确实需要支持图片的话,其实可以考虑富文本编辑器,工具栏简洁一点,支持图片和表情,也就可以了。
关于富文本编辑器的技术选型,可以参考这篇文章:前端开发常用的10款富文本编辑器。
希望读者们能多多支持这个项目: onepiece-web(star 一下不亏的 ^_^)
以上就是全部内容了,希望对大家有所帮助~