📱

10分で読めるReact Nativeの全公式コンポーネントまとめ

2024/12/14に公開

この記事はReact Native 全部俺 Advent Calendar 14日目の記事です。

https://adventar.org/calendars/10741

このアドベントカレンダーについて

このアドベントカレンダーは @itome が全て書いています。

基本的にReact NativeおよびExpoの公式ドキュメントとソースコードを参照しながら書いていきます。誤植や編集依頼はXにお願いします。

10分で読めるReact Nativeの全公式コンポーネントまとめ

React Nativeには意外と少ない数の公式コンポーネントしかありません。その分1つ1つのコンポーネントがよく考えられていて、必要十分な機能を持っています。今回は各コンポーネントの機能と使い方について詳しく解説していきます。

Core Components

View

最も基本的なUIコンポーネントで、iOSのUIView、Androidのandroid.view.View、そしてWebでいうdivタグに相当します。レイアウトの作成やスタイリングのためのコンテナとして使用します。

// Viewの基本的な使い方
<View style={{ padding: 20, backgroundColor: 'white' }}>
  <Text>これはViewの中身です</Text>
</View>

// flexboxを使った中央寄せレイアウト
<View style={{ 
  flex: 1,
  justifyContent: 'center',
  alignItems: 'center',
  backgroundColor: '#f5f5f5'
}}>
  <Text>中央に配置されたテキスト</Text>
</View>

// 影をつける(iOSのみ)
<View style={{
  shadowColor: '#000',
  shadowOffset: { width: 0, height: 2 },
  shadowOpacity: 0.25,
  shadowRadius: 3.84,
  elevation: 5, // Androidの場合はelevationで影をつける
}}>
  <Text>影のついたカード</Text>
</View>

Text

テキストを表示するためのコンポーネントです。React Nativeではテキストは必ずTextコンポーネントの中に入れる必要があります。

// 基本的な使い方
<Text style={{ fontSize: 16, color: 'black' }}>
  これはTextコンポーネントです
</Text>

// テキストのネスト
<Text style={{ fontSize: 16 }}>
  この文章は
  <Text style={{ fontWeight: 'bold' }}>
    太字の部分
  </Text>
  を含みます
</Text>

// 行数制限とellipsisMode
<Text 
  numberOfLines={2}
  ellipsisMode="tail"
  style={{ fontSize: 14 }}
>
  この文章は長いので2行に制限され、それを超える部分は...で省略されます。
  この部分は表示されません。
</Text>

// 様々なスタイリング
<Text style={{
  fontSize: 18,
  fontWeight: '600',
  color: '#333',
  textAlign: 'center',
  letterSpacing: 0.5,
  lineHeight: 24,
  textDecorationLine: 'underline',
  fontStyle: 'italic'
}}>
  スタイリングされたテキスト
</Text>

// onPressイベントの処理
<Text onPress={() => console.log('テキストがタップされました')}>
  タップ可能なテキスト
</Text>

Image

画像を表示するためのコンポーネントです。ローカルの画像もネットワーク上の画像も表示できます。

画像のリサイズモードはresizeModeプロパティで制御できます。covercontainstretchcenterrepeatの5つのモードがあります。

また、画像の読み込み状態をハンドリングするためのイベントも用意されています。

// ローカル画像の表示(サイズ指定は任意)
<Image 
  source={require('./logo.png')}
  style={{ width: 100, height: 100 }}
/>

// ネットワーク画像の表示
<Image 
  source={{ uri: 'https://example.com/image.jpg' }}
  style={{ width: 100, height: 100 }}
/>

// 画像のリサイズモード
<Image 
  source={{ uri: 'https://example.com/image.jpg' }}
  style={{ width: 200, height: 200 }}
  resizeMode="cover" // 画像が歪まないように、はみ出る部分をクリップ
/>

// 読み込み状態のハンドリング
<Image 
  source={{ uri: 'https://example.com/image.jpg' }}
  style={{ width: 200, height: 200 }}
  onLoadStart={() => console.log('読み込み開始')}
  onProgress={(e) => console.log(`読み込み中: ${e.nativeEvent.loaded / e.nativeEvent.total}`)}
  onLoad={() => console.log('読み込み完了')}
  onError={(e) => console.log('読み込みエラー', e.nativeEvent.error)}
  defaultSource={require('./placeholder.png')} // 読み込み中に表示する画像(iOSのみ)
/>

// 画像のキャッシュ制御
<Image 
  source={{ 
    uri: 'https://example.com/image.jpg',
    cache: 'reload' // キャッシュを無視して再読み込み
  }}
  style={{ width: 100, height: 100 }}
/>

// 背景色とボーダーを設定
<Image 
  source={require('./logo.png')}
  style={{
    width: 100,
    height: 100,
    backgroundColor: '#f5f5f5',
    borderRadius: 50,
    borderWidth: 2,
    borderColor: '#ddd'
  }}
/>

TextInput

テキスト入力のためのコンポーネントです。様々な入力タイプに対応しており、キーボードの種類やオートコレクト、セキュリティ入力など、多くの機能をカスタマイズできます。

const [text, setText] = useState('');
const [focused, setFocused] = useState(false);

// 基本的な使い方(制御コンポーネント)
<TextInput
  value={text}
  onChangeText={setText}
  placeholder="ここに入力してください"
  style={{
    height: 40,
    borderColor: focused ? 'blue' : 'gray',
    borderWidth: 1,
    borderRadius: 5,
    paddingHorizontal: 10
  }}
  onFocus={() => setFocused(true)}
  onBlur={() => setFocused(false)}
/>

// パスワード入力
<TextInput
  value={password}
  onChangeText={setPassword}
  secureTextEntry={true}
  placeholder="パスワードを入力"
  autoCapitalize="none" // 自動大文字化を無効化
  autoCorrect={false} // オートコレクトを無効化
/>

// 複数行入力
<TextInput
  multiline
  numberOfLines={4}
  value={text}
  onChangeText={setText}
  style={{
    height: 100,
    textAlignVertical: 'top', // Androidで上寄せにする
    padding: 10,
    borderWidth: 1
  }}
/>

// キーボードタイプの指定
<TextInput
  keyboardType="email-address" // メールアドレス用キーボード
  autoCompleteType="email" // メールアドレスの自動補完
  textContentType="emailAddress" // iOSのパスワード自動入力
  returnKeyType="next" // キーボードの完了ボタンを"次へ"にする
  onSubmitEditing={() => passwordInput.current?.focus()} // 次の入力欄にフォーカスを移動
/>

// 入力の検証
<TextInput
  value={email}
  onChangeText={text => {
    setEmail(text);
    setIsValid(validateEmail(text));
  }}
  style={{
    borderColor: isValid ? 'green' : 'red',
    borderWidth: 1
  }}
  onBlur={() => {
    if (!isValid) {
      Alert.alert('エラー', '正しいメールアドレスを入力してください');
    }
  }}
/>

ScrollView

スクロール可能なビューを作成するためのコンポーネントです。主に固定長のコンテンツをスクロールさせる場合に使用します。

ScrollViewは全てのコンテンツを一度にレンダリングするため、大量のデータを表示する場合はFlatListの使用を検討してください。

// 基本的な使い方
<ScrollView style={{ flex: 1 }}>
  <View style={{ height: 1000 }}>
    <Text>スクロールできる長いコンテンツ</Text>
  </View>
</ScrollView>

// 横スクロール
<ScrollView 
  horizontal 
  showsHorizontalScrollIndicator={false}
  contentContainerStyle={{ padding: 10 }}
>
  {[1, 2, 3, 4, 5].map(i => (
    <View 
      key={i}
      style={{ 
        width: 100, 
        height: 100, 
        marginRight: 10, 
        backgroundColor: 'blue' 
      }} 
    />
  ))}
</ScrollView>

// スクロール位置の制御
const scrollViewRef = useRef<ScrollView>(null);

<ScrollView
  ref={scrollViewRef}
  onContentSizeChange={() => {
    // 常に最下部にスクロール
    scrollViewRef.current?.scrollToEnd({ animated: true });
  }}
  onScroll={event => {
    // スクロール位置の取得
    console.log(event.nativeEvent.contentOffset.y);
  }}
  scrollEventThrottle={16} // スクロールイベントの間隔(ミリ秒)
>
  {/* コンテンツ */}
</ScrollView>

// ページング機能(1画面ずつスクロール)
<ScrollView
  horizontal
  pagingEnabled
  onMomentumScrollEnd={event => {
    // 現在のページ番号を計算
    const page = Math.round(
      event.nativeEvent.contentOffset.x / Dimensions.get('window').width
    );
    console.log(`現在のページ: ${page}`);
  }}
>
  {[1, 2, 3].map(i => (
    <View 
      key={i}
      style={{ 
        width: Dimensions.get('window').width,
        height: '100%',
        justifyContent: 'center',
        alignItems: 'center'
      }}
    >
      <Text>ページ {i}</Text>
    </View>
  ))}
</ScrollView>

// ズーム機能
<ScrollView
  maximumZoomScale={3}
  minimumZoomScale={0.5}
  bouncesZoom={true}
>
  <Image 
    source={{ uri: 'https://example.com/large-image.jpg' }}
    style={{ width: '100%', height: 500 }}
  />
</ScrollView>

FlatList

大量のデータをリスト表示する際に使用する最適化されたコンポーネントです。表示されている部分のみをレンダリングする(window化)ことで、パフォーマンスを向上させています。

また、リスト項目の再利用や、スクロール位置の保持、プルリフレッシュ、無限スクロールなど、リスト表示に必要な機能が一通り揃っています。

// 基本的な使い方
const data = Array.from({ length: 100 }, (_, i) => ({
  id: String(i),
  title: `項目 ${i}`
}));

<FlatList
  data={data}
  renderItem={({ item }) => (
    <View style={{ padding: 20, borderBottomWidth: 1 }}>
      <Text>{item.title}</Text>
    </View>
  )}
  keyExtractor={item => item.id}
/>

// ヘッダーとフッター
<FlatList
  data={data}
  ListHeaderComponent={() => (
    <View style={{ padding: 20, backgroundColor: '#f5f5f5' }}>
      <Text style={{ fontSize: 20, fontWeight: 'bold' }}>リストのヘッダー</Text>
    </View>
  )}
  ListFooterComponent={() => (
    <View style={{ padding: 20, alignItems: 'center' }}>
      <Text>リストの最後です</Text>
    </View>
  )}
  ItemSeparatorComponent={() => (
    <View style={{ height: 1, backgroundColor: '#eee' }} />
  )}
  renderItem={({ item }) => (
    <Text>{item.title}</Text>
  )}
/>

// 無限スクロール
const [loading, setLoading] = useState(false);
const [data, setData] = useState<Item[]>([]);

<FlatList
  data={data}
  onEndReached={async () => {
    if (loading) return;
    setLoading(true);
    const newData = await fetchMoreData();
    setData([...data, ...newData]);
    setLoading(false);
  }}
  onEndReachedThreshold={0.5} // 末尾から50%の位置で読み込み開始
  ListFooterComponent={() => (
    loading ? <ActivityIndicator /> : null
  )}
  renderItem={({ item }) => (
    <Text>{item.title}</Text>
  )}
/>

// プルリフレッシュ
const [refreshing, setRefreshing] = useState(false);

<FlatList
  refreshControl={
    <RefreshControl
      refreshing={refreshing}
      onRefresh={async () => {
        setRefreshing(true);
        const newData = await refreshData();
        setData(newData);
        setRefreshing(false);
      }}
    />
  }
  data={data}
  renderItem={({ item }) => (
    <Text>{item.title}</Text>
  )}
/>

// パフォーマンス最適化
<FlatList
  data={data}
  initialNumToRender={10} // 初期表示数
  maxToRenderPerBatch={10} // 1バッチあたりのレンダリング数
  windowSize={5} // ウィンドウサイズ(画面の前後何画面分をレンダリングするか)
  removeClippedSubviews={true} // 画面外の要素をアンマウント
  getItemLayout={(data, index) => ({
    // 高さが固定の場合、位置計算を最適化
    length: 50,
    offset: 50 * index,
    index,
  })}
  renderItem={({ item }) => (
    <Text>{item.title}</Text>
  )}
/>

SectionList

FlatListにセクション(見出し)を追加できるバージョンです。設定項目やカテゴリー分けされたリストの表示に使用します。

データ構造が少し複雑になりますが、その分柔軟なリスト表示が可能です。また、FlatListと同様のパフォーマンス最適化機能も備えています。

// 基本的な使い方
const DATA = [
  {
    title: 'メイン設定',
    data: ['プロフィール', 'アカウント', 'セキュリティ'],
  },
  {
    title: '通知設定',
    data: ['メール通知', 'プッシュ通知', 'アラート音'],
  },
];

<SectionList
  sections={DATA}
  renderItem={({ item }) => (
    <View style={{ padding: 15 }}>
      <Text>{item}</Text>
    </View>
  )}
  renderSectionHeader={({ section: { title } }) => (
    <View style={{ 
      padding: 10, 
      backgroundColor: '#f5f5f5',
      borderBottomWidth: 1,
      borderBottomColor: '#eee'
    }}>
      <Text style={{ fontWeight: 'bold' }}>{title}</Text>
    </View>
  )}
/>

// セクションフッターとリストヘッダー/フッター
<SectionList
  sections={DATA}
  renderSectionFooter={({ section: { data } }) => (
    <View style={{ padding: 10, backgroundColor: '#f9f9f9' }}>
      <Text style={{ color: 'gray' }}>
        {data.length}個の項目
      </Text>
    </View>
  )}
  ListHeaderComponent={() => (
    <View style={{ padding: 20 }}>
      <Text style={{ fontSize: 24 }}>設定</Text>
    </View>
  )}
  ListFooterComponent={() => (
    <View style={{ padding: 20 }}>
      <Text style={{ color: 'gray' }}>バージョン 1.0.0</Text>
    </View>
  )}
  renderItem={({ item }) => (
    <Text>{item}</Text>
  )}
/>

// スティッキーヘッダー
<SectionList
  sections={DATA}
  stickySectionHeadersEnabled
  renderSectionHeader={({ section: { title } }) => (
    <View style={{ 
      padding: 10, 
      backgroundColor: '#fff',
      borderBottomWidth: 1,
      borderBottomColor: '#eee'
    }}>
      <Text style={{ fontWeight: 'bold' }}>{title}</Text>
    </View>
  )}
  renderItem={({ item }) => (
    <Text>{item}</Text>
  )}
/>

// セクションごとの展開/折りたたみ
const [expandedSections, setExpandedSections] = useState(new Set<string>());

<SectionList
  sections={DATA}
  renderSectionHeader={({ section: { title } }) => (
    <Pressable
      onPress={() => {
        setExpandedSections(prev => {
          const next = new Set(prev);
          if (next.has(title)) {
            next.delete(title);
          } else {
            next.add(title);
          }
          return next;
        });
      }}
      style={{ 
        flexDirection: 'row', 
        justifyContent: 'space-between',
        padding: 10,
        backgroundColor: '#f5f5f5'
      }}
    >
      <Text style={{ fontWeight: 'bold' }}>{title}</Text>
      <Text>{expandedSections.has(title) ? '▼' : '▶'}</Text>
    </Pressable>
  )}
  renderItem={({ item, section: { title } }) => (
    expandedSections.has(title) ? (
      <View style={{ padding: 15 }}>
        <Text>{item}</Text>
      </View>
    ) : null
  )}
/>

VirtualizedList

FlatListやSectionListの基となるコンポーネントです。独自のリスト表示コンポーネントを作成する場合に使用します。

通常は直接使用することは少ないですが、特殊なデータ構造を扱う場合やカスタムの最適化が必要な場合に便利です。

// 基本的な使い方
const data = Array.from({ length: 100 }, (_, i) => ({
  id: String(i),
  title: `項目 ${i}`
}));

<VirtualizedList
  data={data}
  initialNumToRender={4}
  renderItem={({ item }: { item: typeof data[0] }) => (
    <View style={{ padding: 20 }}>
      <Text>{item.title}</Text>
    </View>
  )}
  keyExtractor={item => item.id}
  getItemCount={data => data.length}
  getItem={(data, index) => data[index]}
/>

// カスタムデータ構造
type TreeNode = {
  id: string;
  title: string;
  children?: TreeNode[];
};

const flattenTree = (node: TreeNode): TreeNode[] => {
  const result: TreeNode[] = [node];
  if (node.children) {
    node.children.forEach(child => {
      result.push(...flattenTree(child));
    });
  }
  return result;
};

const treeData = {
  id: '1',
  title: 'ルート',
  children: [
    {
      id: '2',
      title: '子ノード1',
      children: [
        { id: '4', title: '孫ノード1' },
        { id: '5', title: '孫ノード2' }
      ]
    },
    {
      id: '3',
      title: '子ノード2'
    }
  ]
};

const flattenedData = flattenTree(treeData);

<VirtualizedList
  data={flattenedData}
  renderItem={({ item }) => (
    <View style={{ 
      padding: 20,
      paddingLeft: getNodeDepth(item.id) * 20 
    }}>
      <Text>{item.title}</Text>
    </View>
  )}
  getItemCount={data => data.length}
  getItem={(data, index) => data[index]}
  keyExtractor={item => item.id}
/>

// パフォーマンス最適化
<VirtualizedList
  data={data}
  initialNumToRender={10}
  maxToRenderPerBatch={10}
  updateCellsBatchingPeriod={50}
  windowSize={5}
  removeClippedSubviews={Platform.OS === 'android'}
  getItemLayout={(data, index) => ({
    length: 50,
    offset: 50 * index,
    index,
  })}
  renderItem={({ item }) => (
    <View style={{ height: 50 }}>
      <Text>{item.title}</Text>
    </View>
  )}
  getItemCount={data => data.length}
  getItem={(data, index) => data[index]}
  keyExtractor={item => item.id}
  onEndReached={() => {
    // 無限スクロール
    loadMoreData();
  }}
  onEndReachedThreshold={0.5}
  refreshing={isRefreshing}
  onRefresh={() => {
    // プルリフレッシュ
    refreshData();
  }}
/>

Pressable

より柔軟なインタラクションを実装するためのコンポーネントです。タップ、長押し、ホバーなど様々なイベントに対応でき、プレス状態に応じたスタイリングも可能です。

Button、TouchableOpacityなどの他のタッチャブルコンポーネントと比べて、より細かいカスタマイズが可能です。

// 基本的な使い方
<Pressable
  onPress={() => console.log('押されました')}
  style={({ pressed }) => [
    {
      backgroundColor: pressed ? '#ddd' : '#fff',
      padding: 10,
      borderRadius: 5
    }
  ]}
>
  <Text>タップしてください</Text>
</Pressable>

// 様々なインタラクション
<Pressable
  onPressIn={() => console.log('タッチ開始')}
  onPressOut={() => console.log('タッチ終了')}
  onLongPress={() => console.log('長押し')}
  delayLongPress={500} // 長押しと判定されるまでの時間(ミリ秒)
  disabled={false} // タッチを無効化
  hitSlop={20} // タッチ判定領域を広げる
  style={({ pressed, focused, focused }) => [
    {
      opacity: pressed ? 0.5 : 1,
      backgroundColor: focused ? 'yellow' : 'white',
      borderWidth: focused ? 2 : 0
    }
  ]}
>
  <Text>インタラクティブなボタン</Text>
</Pressable>

// カスタムリップルエフェクト(Android)
<Pressable
  android_ripple={{
    color: 'rgba(0, 0, 0, 0.1)',
    borderless: false,
    radius: 20
  }}
  style={{
    padding: 10,
    backgroundColor: 'white',
    borderRadius: 5
  }}
>
  <Text>Androidのリップルエフェクト</Text>
</Pressable>

// ネストされたPressable
<Pressable
  onPress={() => console.log('外側')}
  style={{ padding: 20, backgroundColor: '#f5f5f5' }}
>
  <Text>外側のPressable</Text>
  <Pressable
    onPress={(e) => {
      e.stopPropagation(); // 親要素へのイベント伝播を止める
      console.log('内側');
    }}
    style={{ padding: 10, backgroundColor: '#ddd', marginTop: 10 }}
  >
    <Text>内側のPressable</Text>
  </Pressable>
</Pressable>

画面全体に表示されるオーバーレイコンポーネントです。確認ダイアログやフルスクリーンのメニューなど、様々な用途に使用できます。

iOSとAndroidで少し異なる動作をするため、プラットフォーム固有の挙動に注意が必要です。

// 基本的な使い方
const [visible, setVisible] = useState(false);

<View style={{ flex: 1 }}>
  <Button title="モーダルを開く" onPress={() => setVisible(true)} />
  
  <Modal
    visible={visible}
    animationType="slide" // slide, fade, noneから選択
    transparent={true}
    onRequestClose={() => {
      // Androidのバックボタン押下時の処理
      setVisible(false);
    }}
  >
    <View style={{
      flex: 1,
      justifyContent: 'center',
      alignItems: 'center',
      backgroundColor: 'rgba(0, 0, 0, 0.5)'
    }}>
      <View style={{
        backgroundColor: 'white',
        padding: 20,
        borderRadius: 10,
        elevation: 5
      }}>
        <Text>モーダルの内容</Text>
        <Button title="閉じる" onPress={() => setVisible(false)} />
      </View>
    </View>
  </Modal>
</View>

// ボトムシート
<Modal
  visible={visible}
  animationType="slide"
  transparent={true}
>
  <Pressable 
    style={{ flex: 1 }}
    onPress={() => setVisible(false)}
  >
    <View style={{
      position: 'absolute',
      bottom: 0,
      left: 0,
      right: 0,
      backgroundColor: 'white',
      borderTopLeftRadius: 20,
      borderTopRightRadius: 20,
      padding: 20,
      shadowColor: '#000',
      shadowOffset: {
        width: 0,
        height: -2,
      },
      shadowOpacity: 0.25,
      shadowRadius: 3.84,
      elevation: 5,
    }}>
      <View style={{
        width: 40,
        height: 4,
        backgroundColor: '#ccc',
        borderRadius: 2,
        alignSelf: 'center',
        marginBottom: 10
      }} />
      <Text>ボトムシートの内容</Text>
    </View>
  </Pressable>
</Modal>

// 画面の向きに対応
<Modal
  visible={visible}
  supportedOrientations={[
    'portrait',
    'portrait-upside-down',
    'landscape',
    'landscape-left',
    'landscape-right'
  ]}
>
  {/* モーダルの内容 */}
</Modal>

// ステータスバーの制御
<Modal
  visible={visible}
  statusBarTranslucent={true} // Androidでステータスバーを透過
>
  <View style={{ 
    flex: 1, 
    marginTop: Platform.OS === 'ios' ? 
      getStatusBarHeight() : 0 // iOSでステータスバーの高さを考慮
  }}>
    {/* モーダルの内容 */}
  </View>
</Modal>

ActivityIndicator

ローディング状態を表示するためのシンプルなスピナーコンポーネントです。OSネイティブのデザインが使用されます。

// 基本的な使い方
<ActivityIndicator size="large" color="#0000ff" />

// サイズとカラーのカスタマイズ
<ActivityIndicator
  size={Platform.OS === 'ios' ? 'large' : 50}
  color="#00ff00"
/>

// ローディングオーバーレイ
const LoadingOverlay = ({ loading }: { loading: boolean }) => {
  if (!loading) return null;
  
  return (
    <View style={{
      position: 'absolute',
      top: 0,
      left: 0,
      right: 0,
      bottom: 0,
      backgroundColor: 'rgba(0, 0, 0, 0.4)',
      justifyContent: 'center',
      alignItems: 'center'
    }}>
      <View style={{
        backgroundColor: 'white',
        padding: 20,
        borderRadius: 10,
        flexDirection: 'row',
        alignItems: 'center'
      }}>
        <ActivityIndicator style={{ marginRight: 10 }} />
        <Text>読み込み中...</Text>
      </View>
    </View>
  );
};

// プルリフレッシュとの組み合わせ
<FlatList
  refreshControl={
    <RefreshControl
      refreshing={refreshing}
      onRefresh={onRefresh}
      tintColor="#0000ff" // ActivityIndicatorの色(iOS)
      colors={['#0000ff']} // ActivityIndicatorの色(Android)
    />
  }
  data={data}
  renderItem={({ item }) => (
    <Text>{item.title}</Text>
  )}
  ListFooterComponent={() => (
    loading ? (
      <View style={{ padding: 20 }}>
        <ActivityIndicator />
      </View>
    ) : null
  )}
/>

StatusBar

ステータスバー(画面上部の時刻やバッテリー残量を表示する領域)の見た目をカスタマイズするためのコンポーネントです。

// 基本的な使い方
<StatusBar
  barStyle="dark-content" // ステータスバーのテキストカラー
  backgroundColor="white" // 背景色(Androidのみ)
  hidden={false} // ステータスバーの表示/非表示
/>

// プラットフォーム別の制御
<StatusBar
  barStyle={Platform.OS === 'ios' ? 'dark-content' : 'light-content'}
  backgroundColor={Platform.OS === 'android' ? '#000' : undefined}
  translucent={Platform.OS === 'android'} // ステータスバーを透過(Android)
/>

// アニメーションの制御(iOSのみ)
<StatusBar
  animated={true}
  hideTransitionAnimation="fade" // slide, fade, noneから選択
/>

// ネットワーク接続状態の表示(iOSのみ)
<StatusBar
  networkActivityIndicatorVisible={isLoading}
/>

// コンテキストごとに切り替え
const Navigation = () => {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{
          headerStatusBarHeight: Platform.OS === 'ios' ? 
            getStatusBarHeight() : undefined,
          // ヘッダーにステータスバーの高さを追加
        }}
      />
      <Stack.Screen
        name="Details"
        component={DetailsScreen}
        options={{
          headerTransparent: true,
          // ヘッダーを透過させる場合はステータスバーも考慮
        }}
      />
    </Stack.Navigator>
  );
};

KeyboardAvoidingView

キーボードが表示された時に、自動的にコンテンツの位置を調整するコンポーネントです。フォームの実装時に特に重要です。

// 基本的な使い方
<KeyboardAvoidingView
  behavior={Platform.OS === "ios" ? "padding" : "height"}
  style={{ flex: 1 }}
>
  <ScrollView>
    <TextInput
      placeholder="名前"
      style={{ padding: 10, borderWidth: 1, marginBottom: 10 }}
    />
    <TextInput
      placeholder="メールアドレス"
      style={{ padding: 10, borderWidth: 1, marginBottom: 10 }}
    />
    <TextInput
      placeholder="パスワード"
      style={{ padding: 10, borderWidth: 1, marginBottom: 10 }}
    />
  </ScrollView>
</KeyboardAvoidingView>

// モーダル内でのキーボード対応
<Modal visible={visible}>
  <KeyboardAvoidingView
    behavior={Platform.OS === "ios" ? "padding" : undefined}
    style={{ flex: 1 }}
  >
    <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
      <View style={{ flex: 1, justifyContent: 'center', padding: 20 }}>
        <TextInput
          style={{ 
            padding: 10, 
            borderWidth: 1, 
            marginBottom: 10,
            backgroundColor: 'white'
          }}
        />
        <Button title="送信" onPress={onSubmit} />
      </View>
    </TouchableWithoutFeedback>
  </KeyboardAvoidingView>
</Modal>

Platform Specific Components

プラットフォーム固有のコンポーネントについても解説していきます。

SafeAreaView (iOS)

iPhoneのノッチやパンチホールなどの領域を避けてコンテンツを表示するためのコンポーネントです。最近のデバイスではノッチが標準的なのでよく使うコンポーネントですが、Androidに対応していないため、 react-native-safe-area-context などのサードパーティーライブラリを使う方がおすすめです。

// 基本的な使い方
<SafeAreaView style={{ flex: 1 }}>
  <View style={{ flex: 1 }}>
    <Text>安全な領域に表示されるコンテンツ</Text>
  </View>
</SafeAreaView>

// 部分的な適用
const Screen = () => (
  <View style={{ flex: 1 }}>
    <SafeAreaView>
      <Header />
    </SafeAreaView>
    
    <ScrollView style={{ flex: 1 }}>
      <Content />
    </ScrollView>
    
    <SafeAreaView>
      <Footer />
    </SafeAreaView>
  </View>
);

// 色付きの背景
<SafeAreaView style={{ flex: 1, backgroundColor: '#fff' }}>
  <StatusBar barStyle="dark-content" />
  <View style={{ flex: 1 }}>
    <Text>コンテンツ</Text>
  </View>
</SafeAreaView>

DrawerLayoutAndroid (Android)

Android特有のドロワーメニューを実装するためのコンポーネントです。

// DrawerLayoutAndroidの続き
<DrawerLayoutAndroid
  ref={drawerRef}
  drawerWidth={300}
  drawerPosition="left"
  renderNavigationView={() => (
    <View style={{ flex: 1, backgroundColor: '#fff' }}>
      <Text style={{ margin: 10, fontSize: 15 }}>
        ドロワーメニュー
      </Text>
      <Button 
        title="閉じる"
        onPress={() => drawerRef.current?.closeDrawer()}
      />
      <View style={{ padding: 10 }}>
        <Pressable 
          style={{ padding: 10 }}
          onPress={() => {
            navigation.navigate('Home');
            drawerRef.current?.closeDrawer();
          }}
        >
          <Text>ホーム</Text>
        </Pressable>
        <Pressable 
          style={{ padding: 10 }}
          onPress={() => {
            navigation.navigate('Settings');
            drawerRef.current?.closeDrawer();
          }}
        >
          <Text>設定</Text>
        </Pressable>
      </View>
    </View>
  )}
  drawerBackgroundColor="#fff"
  onDrawerOpen={() => console.log('ドロワーが開きました')}
  onDrawerClose={() => console.log('ドロワーが閉じました')}
>
  <View style={{ flex: 1 }}>
    <Button 
      title="ドロワーを開く"
      onPress={() => drawerRef.current?.openDrawer()}
    />
    <Text>メインコンテンツ</Text>
  </View>
</DrawerLayoutAndroid>

// ジェスチャーの制御
<DrawerLayoutAndroid
  drawerLockMode="locked-closed" // ジェスチャーでの開閉を無効化
  keyboardDismissMode="on-drag" // ドロワーを開くときにキーボードを閉じる
>
  {/* コンテンツ */}
</DrawerLayoutAndroid>

// スライド時のアニメーション
<DrawerLayoutAndroid
  drawerSlideAnimationStyle="slide" // fade, noneも選択可能
  onDrawerSlide={(event) => {
    // スライド量(0-1)に応じた処理
    const offset = event.nativeEvent.offset;
    // メインコンテンツのアニメーションなど
  }}
>
  {/* コンテンツ */}
</DrawerLayoutAndroid>

TouchableNativeFeedback (Android)

Android特有のリップルエフェクトを実装するためのコンポーネントです。Material Designのタッチフィードバックを実現できます。

// 基本的な使い方
<TouchableNativeFeedback
  background={TouchableNativeFeedback.SelectableBackground()}
  onPress={() => console.log('タップされました')}
>
  <View style={{ padding: 10, backgroundColor: '#fff' }}>
    <Text>タップするとリップルエフェクトが表示されます</Text>
  </View>
</TouchableNativeFeedback>

// 様々な背景エフェクト
<TouchableNativeFeedback
  background={TouchableNativeFeedback.Ripple('#2196F3', false)}
  useForeground={true}
>
  <View style={{ 
    padding: 10, 
    backgroundColor: '#fff',
    borderRadius: 5 
  }}>
    <Text>カスタムカラーのリップル</Text>
  </View>
</TouchableNativeFeedback>

// 境界のある/なしリップル
<TouchableNativeFeedback
  background={TouchableNativeFeedback.Ripple('#2196F3', true)} // trueで境界なし
  useForeground={true}
>
  <View style={{ padding: 10 }}>
    <Text>境界なしリップル</Text>
  </View>
</TouchableNativeFeedback>

// APIレベルに応じた分岐
<TouchableNativeFeedback
  background={
    Platform.Version >= 21
      ? TouchableNativeFeedback.Ripple('#2196F3', false)
      : TouchableNativeFeedback.SelectableBackground()
  }
>
  <View style={{ padding: 10 }}>
    <Text>APIレベルに応じたエフェクト</Text>
  </View>
</TouchableNativeFeedback>

InputAccessoryView (iOS)

iOS特有のキーボードアクセサリービュー(キーボードの上に表示されるツールバー)を実装するためのコンポーネントです。

// 基本的な使い方
const inputAccessoryViewID = 'uniqueID';

<View style={{ flex: 1 }}>
  <TextInput
    inputAccessoryViewID={inputAccessoryViewID}
    // このIDを持つInputAccessoryViewがキーボード上に表示される
  />
  
  <InputAccessoryView nativeID={inputAccessoryViewID}>
    <View style={{ 
      backgroundColor: '#f1f1f1', 
      borderTopWidth: 1,
      borderColor: '#ccc',
      padding: 10,
      flexDirection: 'row',
      justifyContent: 'space-between'
    }}>
      <Button title="キャンセル" onPress={() => {}} />
      <Button title="完了" onPress={() => {}} />
    </View>
  </InputAccessoryView>
</View>

// カスタムツールバー
const CustomInputAccessory = () => {
  return (
    <InputAccessoryView nativeID="customToolbar">
      <View style={{
        backgroundColor: '#f1f1f1',
        padding: 10,
        flexDirection: 'row',
        justifyContent: 'space-around'
      }}>
        <Button title="B" onPress={() => {/* 太字 */}} />
        <Button title="I" onPress={() => {/* イタリック */}} />
        <Button title="U" onPress={() => {/* 下線 */}} />
        <Button title="完了" onPress={Keyboard.dismiss} />
      </View>
    </InputAccessoryView>
  );
};

// キーボードの高さに追従
const [keyboardHeight, setKeyboardHeight] = useState(0);

useEffect(() => {
  const keyboardWillShow = Keyboard.addListener(
    'keyboardWillShow',
    e => setKeyboardHeight(e.endCoordinates.height)
  );
  const keyboardWillHide = Keyboard.addListener(
    'keyboardWillHide',
    () => setKeyboardHeight(0)
  );

  return () => {
    keyboardWillShow.remove();
    keyboardWillHide.remove();
  };
}, []);

<InputAccessoryView nativeID="customToolbar">
  <View style={{
    backgroundColor: '#f1f1f1',
    padding: 10,
    marginBottom: keyboardHeight
  }}>
    {/* ツールバーの内容 */}
  </View>
</InputAccessoryView>

まとめ

このように、React Nativeの公式コンポーネントには基本的なUIパーツが一通り揃っています。プラットフォーム固有のコンポーネントを使いこなすことで、より自然なアプリ体験を提供することができます。

実際のアプリ開発では、これらのコンポーネントを組み合わせて使用したり、サードパーティのライブラリで拡張したりすることになります。特に複雑なUIを実装する場合は、React Native ElementsReact Native PaperなどのUIライブラリの利用を検討するとよいでしょう。

Discussion