読者です 読者をやめる 読者になる 読者になる

脳汁portal

アメリカ在住(だった)新米エンジニアがその日学んだIT知識を書き綴るブログ

Reactのstate(prop)内で改行文字を使いたい場合

HTML JavaScript React

JSX構文

ReactはJSXを採用しているので、構文の中で以下のようにhtmlタグを直接書くようににコーディングできます。

var Test = React.createClass(
    {
        // 処理とか
    },
    render: function() { 
        return(
            <div>
              {this.state.foobar}
            </div>
        );
    }
);

しかし、このhtmlタグライクなものは、実際のhtmlタグ(DOMノード)ではなく、React側で用意したコンパイラになります。
この<div>は、React.createElement(div)を呼び出しています。
以上のことより、ReactはHTML文字列を生成していないため、XSS対策の必要がありません。

問題

しかし同時に、HTML文字列を直接生成していないということは自分の意図したタグ(改行とか)を入れられないということになります。
<br>に関しては、<br />を使えば改行できますが、8行目のようなReactが管理している値の中に<br>や"\r\n"のような改行文字があっても、それらは単なるStringとして表されるので改行されることはありません。

ここで改行したい<br>さらにここでも改行したい<br>これが最終行

↑こんな風に無改行で表示されてしまします。

対策

ここで改行したい
さらにここでも改行したい
これが最終行

こんな風にするには一工夫が必要です。

var Test = React.createClass(
    {
        this.setState({foobar: 'ここで改行したい<br>さらにここでも改行したい<br>これが最終行'});
    },
    render: function() { 
        var lines = this.state.foobar.split('<br>').map(function(line) {
            return (<p>{line}</p>);
        });
        return(
            <div>
              {lines}
            </div>
        );
    }
);
  1. まずstateに入っている文字列を改行文字(<br>や\r\n)で分割します
  2. その後に、mapを使ってそれぞれを<p>タグで囲みます
  3. 実際のreturn内ではlinesを呼び出すだけにします

これで<p>タグによって改行される文字列が完成です。
実際は以下のようになります

<p>ここで改行したい</p>
<p>さらにここでも改行したい</p>
<p>これが最終行</p>

このままだと行間がすごいあいてしまうので、p タグにクラス属性をつけて、cssを設定してやれば行間もなくなります。
React

.
.
            return (<p className='res-line'>{line}</p>);
.
.

css

p.res-line {
    margin: 0;
}

※classNameに関しては以下を参照してください
http://portaltan.hatenablog.com/ReactのJSX構文内でclass属性を設定する場合 - 脳汁portal


この問題は色んな人がひっかかってるようで、調べると日本語でも色んなブログが出てきます。
Reactはこういう細かいところの融通がまだきかない気がするので、今後に期待したいです。


(2015/08/11追記)

ちょっと改良

このままだと一つのstateにしか対応してないので、表示するstate毎に処理を書かなければいけない。
ので改良した。

var Test = React.createClass(
    {
        getInitialState() {
            return {
                foo: '',
                bar: ''
            };

     },
     .
     .
     .
     render: function() {
         function lines(line) {
             if (line) {
                 return (<p className='no-margin'>{line}</p>);
             } else {
                 return (<p className='no-margin'>&nbsp;</p>);
             }
         }
         return (
             <div>
               {this.state.foo.split('<br>').map(lines)}
               {this.state.bar.split('<br>').map(lines)}
             </div>
         );
     }
変更点
  • まずpタグを作成しているところのメソッドを切り出した
  • mapのcallbackfuncとして上記のメソッドを指定する
  • mapに渡す<br>入りの文字列はstateから取得する
  • initialize時にundefined なのでsplitできないと怒られないようにInitialStateを設定しておく
  • 空白文字を入れることで、'<br><br>'などの複数行改行にも対応した

これで

this.state.${state名}.split(${改行文字}).map(lines)

でどのstateでも改行して表示させることが可能になりました。