React Nativeでライブラリを使わずに枠線だけの吹き出しを作る

[Fringe81](https://www.fringe81.com)アドベントカレンダー1日目の記事です。

弊社の提供している[Unipos](https://unipos.me/ja/)というサービスではWeb版のほか、Android/iOS向けにReact Nativeを使ってアプリを開発しています。
今回はそのアプリの新機能開発において、「枠線だけの吹き出し」を作る必要が出たためやってみた、という軽めの記事でございます。

「枠線だけの吹き出し」というのはこんなやつです。

<img width="398" alt="スクリーンショット 2019-11-28 9.38.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20371/924fc7d4-5543-22a4-cdc7-d578a2def76f.png">

react-native-svgなどを使って作る方法もありそうですが、今回は依存ライブラリなしでReact Nativeの標準コンポーネントだけを使って作ってみました。

## 環境
- React Native 0.59.9
- TypeScript 3.7.2

今回使用した環境は上記ですが、おそらく他の環境でも問題なく使用できるはずです。

## 結論
仕組みはどうあれ、先に出来上がったコードを貼ります。

```tsx
import * as React from 'react';
import {View, Text, StyleSheet} from 'react-native';

const BALLOON_TRIANGLE_HEIGHT = 14;

export const CommentBalloon = ({text}: {text: string}) => {
  return (
    <View style={{justifyContent: 'flex-end', paddingTop: BALLOON_TRIANGLE_HEIGHT}}>
      <View style={styles.balloonContainer}>
        <View style={[styles.balloonTriangleBase, styles.balloonTriangleOuter]} />
        <View style={[styles.balloonTriangleBase, styles.balloonTriangleInner]} />
        <View style={styles.textContainer}>
          <Text style={styles.text}>{text}</Text>
        </View>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  balloonContainer: {
    width: '100%',
    backgroundColor: '#FFFFFF',
    borderWidth: 1,
    borderColor: '#E1E6E6',
    borderRadius: 2
  },
  balloonTriangleBase: {
    width: 0,
    height: 0,
    position: 'absolute',
    bottom: '100%',
    borderTopColor: 'transparent',
    borderLeftColor: 'transparent',
    borderRightColor: 'transparent'
  },
  balloonTriangleOuter: {
    left: 32,
    borderWidth: BALLOON_TRIANGLE_HEIGHT,
    borderBottomColor: '#E1E6E6'
  },
  balloonTriangleInner: {
    left: 33,
    borderWidth: BALLOON_TRIANGLE_HEIGHT - 1, // border分だけ引く
    borderBottomColor: '#FFFFFF'
  },
  textContainer: {
    padding: 15
  },
  text: {
    fontWeight: '300',
    fontSize: 14,
    lineHeight: 20,
    color: '#4A4A4A',
    maxWidth: '100%',
    letterSpacing: 0
  }
});
```

## 仕組み
触ったことがある方はすでにご存知の通り、React NativeではCSSと似た形式のプロパティを持ったオブジェクトを用いて画面のスタイルを定義します。
そのため、CSSでできることは大抵できるのですが、いくつかできないことがあります。その中の一つに、`:before`や`:after`といった擬似要素が挙げられます。

CSSを使って吹き出しを作る方法として有名なのは、`:before`や`:after`擬似要素を用いて、大きさのない要素に対して`border`を当てることによって、吹き出しの三角形の部分(余談ですがくちばしって呼ぶらしいですね)を作る方法があります。

[【コピペ改変OK】CSSで作れる吹き出しデザイン8選 | creive【クリーブ】](https://creive.me/archives/16911/)

Webでの方法はこちらの記事がとても分かり易かったです。

React Nativeでは擬似要素が使用できないため、代わりに中身のない`View`コンポーネントを配置して、そのコンポーネントに対してスタイルを当てています。

また、枠線のみの吹き出しにするため、大きさの違うくちばしを2つ用意し、外側の線部分と、内側の塗りの部分をずらして重ねています。(`balloonTriangleOuter`と`balloonTriangleInner`がそれ)

---

また、くちばしの部分は絶対位置指定(`position: 'absolute'`)で配置しているため、このままだと上に配置されたコンポーネントがくちばしの上に重なってしまうのを防止するため、 `<View style={{justifyContent: 'flex-end', paddingTop: BALLOON_TRIANGLE_HEIGHT}}>` というコンポーネントでラップすることでCommentBalloonコンポーネント全体で見たときにくちばしを含んだ高さとなるように調整しています。

![Simulator Screen Shot - iPhone 11 Pro Max - 2019-12-01 at 11.41.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20371/8a1ce38c-f9f8-9f3b-8ce5-fec958cc80e6.png)

## 所感
React NativeのstyleがWebのCSSと同じ挙動になるように実装されているおかげもあり、こうやってWebでよく用いられるテクニックを転用して、さほど悩むことなく画面を作れるのは幸せなことだと改めて思いました。