受控contentEditable组件添加emoji重渲染后原有emoji变问号问题咨询
1. 未适配UTF-16代理对的字符串切片逻辑缺陷
JS中的字符串本质是按16位码元存储的,绝大多数超出基本多文种平面的emoji,都需要两个连续的16位码元组成代理对才能正常表示,并不是常规认知里的单字符。你使用的substring方法是严格按16位码元位置切割字符串的,即便你计算的caretPosition在DOM交互层面完全准确,只要切割位置落在了已有emoji的代理对中间,就会把原本合法的emoji码元序列拆成两个不完整的片段。
这类不完整的码元序列在浏览器渲染时会被识别为无效字符,最终显示为替换问号。新插入的emoji是完整拼接进去的码元序列,所以不会被切片逻辑破坏,可正常显示。
2. 隐式转码过程中的多字节字符截断
从childValue被获取、参与字符串拼接,到最终传入对应dangerouslySetInnerHTML的全流程中,大概率存在未做UTF-8兼容的隐式转码环节:
- 多字节存储的emoji在经过单字节长度计算、截断操作时,很容易被切去首尾的编码字节,形成无法被解析的无效序列
- 新插入的emoji因为是直接参与拼接的完整字符串,不会被这一转码环节截断,因此不受异常影响
3. HTML解析环节的实体匹配失效
因为使用dangerouslySetInnerHTML做受控渲染,已存在的emoji大概率在之前的渲染阶段被浏览器转换为了HTML实体编码。如果在获取childValue时直接抓取了实体的原始文本片段,没有将实体解码为对应的真实Unicode字符,拼接后生成的HTML字符串会出现实体格式残缺的问题,浏览器无法解析这类残缺实体,就会以问号形式展示。
4. 字符串规范化缺失
JS字符串在长链路传递中,可能因为浏览器版本、中间存储环节的差异,出现同个emoji对应不同Unicode规范化形式的问题。如果拼接前没有对childValue调用String.prototype.normalize()做规范化处理,部分形式不匹配的emoji会在渲染时被判定为无效字符,最终展示为问号。
内容的提问来源于stack exchange,提问作者Will Carter

