Edited at

文字列をフォーマットする関数のパラメータに装飾された仮想DOMを使いたい

More than 1 year has passed since last update.


何がやりたいか

{

"format": "こんにちは、{user.name}さん",
"params": {
"user": {
"name": "mpyw"
}
}
}

みたいなAPIレスポンスが返ってきたとして,これをフロント側でユーザ名の部分を自由に装飾して表示させたい。


  • 頻繁に新しいメッセージテンプレートが追加されるのに,いちいちそのたびにフロントエンドアプリの更新をさせたくない。(react-nativeとか使ってるとつらそう)

  • HTMLタグを文字列としてそのまま埋め込むのってなんか違う気がするし,dangerouslySetInnerHTMLも気軽には使いたくない。

文字列展開のみなら既に Matt-Esch/string-template などがあるんですが,なかなかJSXによる装飾に対応したのが無かったんですよね。例えばこうしたい。


こんにちは、mpywさん



実装してみる

String.prototype.replaceを使いたくなりますが,仮想DOMに対応できないので,String.prototype.splitで処理するのがミソです。


template.js

import React from 'react'

const renderValue = (Node) => {
if (typeof Node === 'function') {
return <Node />
}
if (Node === undefined) {
return null
}
return Node
}

const resolveParam = (params, key) => {
if (typeof key !== 'string') {
return params[key]
}
let param = params
for (const segment of key.split('.')) {
if (!param) return
param = param[segment]
}
return param
}

const template = (format, params) => {
const chunks = format.split(/\{(\{[\S\s]*?})}|\{([\S\s]*?)}/g)
const nodes = []

for (const [offset, value] of Object.entries(chunks)) {
switch (offset % 3) {
case 0:
nodes.push(value)
break
case 1:
if (value === undefined) continue
nodes.push(value)
break
case 2:
if (value === undefined) continue
nodes.push(renderValue(resolveParam(params, value)))
break
}
}

return nodes
}

export default template



使用例1

const { format, params } = {

format: 'こんにちは、{user.name}さん',
params: {
user: {
name: 'mpyw'
}
}
}

const decoratedParams = {
...params,
user: {
...params.user,
name: <span style={{ color: 'blue' }}>{params.user.name}</span>,
},
}

return <div>{template(format, decoratedParams)}</div>



こんにちは、mpywさん



使用例2

(<div>

{template(
'Hello, {p1}! Yeah, {p2}! Yo, {p3.foo}! {{This is escaped.}}',
{
p1: () => <span style={{ color: 'red' }}>World</span>,
p2: <span style={{ color: 'blue' }}>World</span>,
p3: {
foo: () => <span style={{ color: 'green' }}>World</span>,
},
}
)}
</div>)


Hello, World! Yeah, World! Yo, World! {This is escaped.}


仮想DOM生成関数と仮想DOM両対応,ドットチェインのネストにも対応,って感じで一通り必要そうなやつは実装しました。だいたいこれで事足りるはず。

ライブラリにするほどでもないよね?