2018-02-05 15:56
写普通的文案时,我们有约定如何使用空格,但动态内容的处理有两种麻烦情况:
{nameA}赞同了{nameB}的回答
有时在表达式一侧,有时在表达式两侧<Link>{name}</Link>赞同了回答
更复杂的情况是循环中生成列表项、元素有多层有嵌套具体来说,将所有文本分成四类: CJK 字符、标点、空格、其他(西文字符),在 CJK 字符与其他(西文字符)之间添加空格,其他的情况不添加。
对于字符归类,使用三个标准化 ES2018 正则(unicode property escape,可通过 transpiler 预览 ES5 转换结果):
/* 标点 */
const punctuationRegex = /\p{Punctuation}/u
/* 空格 */
const spaceRegex = /\p{Separator}/u
/* CJK 字符,中日韩 */
const cjkRegex = /\p{Script=Han}|\p{Script=Katakana}|\p{Script=Hiragana}|\p{Script=Hangul}/u
对于字符串插值,可以利用模板标签(tagged template)来简化,它本质上是一个接受一组字符串和替换表达式的函数,把字符串和表达式挨个连接起来并去空,再添加空格:
const cjkspace = (string, ...subs) =>
join(
zip(string, subs.concat('')).filter(c => c !== ''),
(a, b) => (shouldSpace(a, b) ? ' ' : '')
)
对于节点之间,要求是有样式的节点(如链接)首尾不能加入空格,例如结构 (a > span > textA) + (span > textB)
,可靠的做法是将生成的文本节点放在两个文本之间的公共元素之下:
const findCommonAncestor = (a, b) => {
const pathA = unfold(a, n => n.parent)
const pathB = unfold(b, n => n.parent)
const index = pathA.findIndex(n => pathB.includes(n))
// 将在 CA 下插入,在靠近 a 的一侧祖先元素之后
return [pathA[index], pathA[index - 1]]
}
以 React 为例的处理过程是,遍历 ReactNode 节点树,转化成中间节点树(收集必要信息)并往其中添加空格节点,还原中间节点树为 ReactNode 节点树。
在字符串之间添加空格,使用模板标签:
import cjkspace from 'cjkspace'
const userA = 'Kiki'
const userB = '喵喵'
cjkspace`${userA}和${userB}赞同了你`
// => 'Kiki 和喵喵赞同了你'
在 React Node 之间添加空格,使用 React 组件:
import {CJKSpace} from 'cjkspace/react'
<CJKSpace>
<Link>{name}</Link>
赞同了{'Coco'}的回答
</CJKSpace>
// => <a href>Kiki<a> 赞同了 Coco 的回答