React中用keypress事件实现Slack风格emoji时如何获取冒号后文本
Slack风格Emoji选择器实现方案
核心修改点
- 新增3个状态:emoji触发起始下标、当前搜索关键词、筛选后的emoji列表
- 新增光标位置获取逻辑,支持在文本任意位置触发emoji选择
- 优化触发/关闭逻辑,避免误触发
- 实现emoji快速插入,自动替换关键词为对应emoji
完整实现代码
import React, { Component, useRef, useState } from 'react'; import { render } from 'react-dom'; import ES from 'emoji-set'; const TextArea = () => { const [text, setText] = useState(''); const [openEmoji, setOpenEmoji] = useState(false); const [emojiStartIndex, setEmojiStartIndex] = useState(-1); const [filteredEmojis, setFilteredEmojis] = useState([]); const textareaRef = useRef<HTMLTextAreaElement>(null); // 关闭emoji选择器重置状态 const closeEmojiPicker = () => { setOpenEmoji(false); setEmojiStartIndex(-1); setFilteredEmojis([]); }; const keyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => { const cursorPos = e.currentTarget.selectionStart; // 按ESC直接关闭 if (e.key === 'Escape') { closeEmojiPicker(); return; } // 按回车如果emoji选择器开着,默认选第一个,可根据需求改 if (e.key === 'Enter' && openEmoji && filteredEmojis.length) { e.preventDefault(); insertEmoji(filteredEmojis[0].char); return; } // 触发emoji选择器 if (e.key === ':') { setOpenEmoji(true); setEmojiStartIndex(cursorPos); // 刚输入:的时候关键词为空,可返回前20个热门emoji避免全量加载 setFilteredEmojis(ES.getAll().slice(0, 20)); return; } // 回删到:之前关闭选择器 if (e.key === 'Backspace' && cursorPos <= emojiStartIndex + 1) { closeEmojiPicker(); return; } // 按空格关闭选择器 if (e.key === ' ') { closeEmojiPicker(); return; } }; const textChanged = (e: React.ChangeEvent<HTMLTextAreaElement>) => { const newText = e.target.value; setText(newText); if (!openEmoji || emojiStartIndex < 0) return; const cursorPos = textareaRef.current?.selectionStart || newText.length; // 提取:后面的搜索关键词 const searchKey = newText.slice(emojiStartIndex + 1, cursorPos).trim(); if (!searchKey) { // 关键词为空返回前20个常用emoji,避免全量加载 setFilteredEmojis(ES.getAll().slice(0, 20)); return; } // 匹配emoji关键词 const matched = ES.getKeywords(searchKey).map(key => ({ keyword: key, char: ES.get(key) })); setFilteredEmojis(matched); }; // 插入选中的emoji const insertEmoji = (emojiChar: string) => { const cursorPos = textareaRef.current?.selectionStart || text.length; const newText = text.slice(0, emojiStartIndex) + emojiChar + text.slice(cursorPos); setText(newText); closeEmojiPicker(); // 光标移到emoji后面 setTimeout(() => { textareaRef.current?.setSelectionRange(emojiStartIndex + 1, emojiStartIndex + 1); }, 0); }; return ( <div style={{ position: 'relative' }}> <textarea ref={textareaRef} value={text} onKeyDown={keyDown} onChange={textChanged} placeholder="Type here..." spellCheck={false} style={{ width: '350px', height: '50vh' }} /> {/* emoji选择器浮层 */} {openEmoji && filteredEmojis.length > 0 && ( <div style={{ position: 'absolute', bottom: 'calc(50vh + 10px)', left: 0, border: '1px solid #e0e0e0', borderRadius: '4px', background: '#fff', maxHeight: '200px', overflowY: 'auto', width: '200px', zIndex: 999 }}> {filteredEmojis.map((emoji, idx) => ( <div key={idx} onClick={() => insertEmoji(emoji.char)} style={{ padding: '6px 12px', cursor: 'pointer', display: 'flex', gap: '8px', alignItems: 'center' }} onMouseOver={(e) => e.currentTarget.style.background = '#f5f5f5'} onMouseOut={(e) => e.currentTarget.style.background = 'transparent'} > <span>{emoji.char}</span> <span style={{ fontSize: '12px', color: '#666' }}>{emoji.keyword}</span> </div> ))} </div> )} </div> ); }; class App extends Component { render() { return ( <div> <h1>React Slack Emoji</h1> <TextArea /> </div> ); } } render(<App />, document.getElementById('root'));
性能优化说明
- 只有触发emoji选择器时才执行搜索逻辑,平时输入无额外性能消耗
- 空关键词时默认只返回前20个常用emoji,避免全量emoji渲染卡顿
- emoji匹配使用emoji-set内置的原生方法,性能远高于自定义遍历匹配
- 如果emoji列表需求更大,可额外添加虚拟滚动组件进一步优化渲染性能
内容的提问来源于stack exchange,提问作者deadcoder0904
相关产品推荐
相关产品推荐

