Textコンポーネントをインライン要素っぽく扱う


React Native v0.25.1でAndroidを中心に動作確認をしています。

## TL;DR

React Nativeの`<Text>`コンポーネントは、ネストするとHTMLのインライン要素のように振る舞ってくれるので、テキストの装飾や、特定の文字だけにクリックイベントを持たせる用途に使えます。

## React Nativeのテキストってインライン要素あったっけ

お仕事で本文中にハイパーリンクっぽいものを付けるようにUIデザインを書いてしまったのですが、よくよく考えてみたら、React NativeのTextコンポーネントはブロック要素っぽい振る舞いをしているところしか見たことがありませんでした。

例えば次のようなコードがあったとします。

```javascript:index.android.js
<View style={styles.container}>
  <Text style={styles.welcome}>
    Welcome to React Native!
  </Text>
  <Text style={styles.instructions1}>
    To get started, edit index.android.js
  </Text>
  <Text style={styles.instructions2}>
    Shake or press menu button for dev menu
  </Text>
</View>
```

実際に描画されたViewをダンプしてみると、次のような構造になっていることが分かりました。

![スクリーンショット_2016-05-09_23_21_49.png](https://qiita-image-store.s3.amazonaws.com/0/2276/8bb70e58-79e6-75d7-fd1e-f9e29ec00757.png "スクリーンショット_2016-05-09_23_21_49.png")

ひとつのTextコンポーネントが、そのままひとつのTextViewになっているようです。

これがReactJSなら、`<Text><a href="#">hogehoge</a>fugafuga</Text>`のようにコンポーネントを組んで、テキストの一部だけを装飾したりクリックイベントを持たせたりできるのですが、残念ながらTextより小さい単位のテキスト用コンポーネントは存在しません。

Androidネイティブ的には、本文を[SpannableString](http://flavienlaurent.com/blog/2014/01/31/spans/)で作ってしまえば装飾ができるのですが、Textコンポーネントではどうしたものやら。

HTMLのインライン要素的に一部のテキストだけを装飾する方法はないのでしょうか。

## Textの振る舞いはひとつではなかった

結論からいうと、 **Textの中にTextをネストすると、内側のTextがインライン要素のように振る舞う** という挙動になります。

```javascript:index.android.js
// ↓ViewではなくTextをコンテナとして使う
<Text style={styles.container}>
  <Text style={styles.welcome}>
    Welcome to React Native!
  </Text>
  <Text style={styles.instructions1}>
    To get started, edit index.android.js
  </Text>
  <Text style={styles.instructions2}>
    Shake or press menu button for dev menu
  </Text>
</Text>
```

![スクリーンショット_2016-05-09_23_27_35.png](https://qiita-image-store.s3.amazonaws.com/0/2276/67bb7e79-404e-586f-5555-95f7d4f054ac.png "スクリーンショット_2016-05-09_23_27_35.png")

Viewの階層としては全部まとめてひとつのTextViewになっていますが、しっかりと装飾されています。Textコンポーネント単位でクリックイベントも持たせられることも確認できました。

こんな振る舞いをする輩はAndroidネイティブ界にも多くはいません。そう、SpannableStringです。

2016.5.9現在、[v0.25のドキュメントではAndroid向けの挙動について言及されていない](http://facebook.github.io/react-native/releases/0.25/docs/text.html#nested-text)のですが、次バージョンである[v0.26のドキュメント](http://facebook.github.io/react-native/releases/0.26/docs/text.html#nested-text)では、次のように言及されています。

> Both iOS and Android allow you to display formatted text by annotating ranges of a string with specific formatting like bold or colored text (`NSAttributedString` on iOS, `SpannableString` on Android). In practice, this is very tedious. For React Native, we decided to use web paradigm for this where you can nest text to achieve the same effect.

ネイティブのテキストでも装飾はできるんだけど、面倒だからもっとWebっぽい手軽さでできるようにしようぜ！というマインドで作られた挙動のようです。うれしみ。

## まとめ

内部実装まで追えたらよかったのですが、[ネストの有無で使うコンポーネントを変えている](https://github.com/facebook/react-native/blob/f022681a1eb1f7ad63f86f376fcf9e578f1e126a/Libraries/Text/Text.js#L239-L243)あたりまでしか追えてないです。こういう風にrender対象のコンポーネントを差し替えていくの、Reactらしさだなあという感じはしました。

当初は[react-native-hypertext](https://github.com/agentcooper/react-native-hypertext)というプラグインを見つけて使おうとしていたのですが、実装をよく見たら単にTextコンポーネントのネストだったことで、この仕様を知った次第です。これくらいならプラグイン使わないほうが楽そう。

## おまけ

ウォーターセル株式会社では、Reactで仕事がしたいWebエンジニアや、ネイティブでモバイルアプリ開発をしたいモバイルエンジニアを募集しています。地球人口100億人時代の食糧問題を解決する礎になるお仕事に興味がある方はご連絡ください。

* [5/14(土)に渋谷で会社説明会あります](http://www.agri-note.jp/recruit/#setumeikai)
* http://www.agri-note.jp/recruit/
* https://www.wantedly.com/projects/53479