第6章 React Nativeのコアコンポーネントを知る
View・Text・Image・TextInputがWebのどの要素に当たるのかと、テキストを必ずTextで囲む理由が分かります。
ここからはReact Nativeの基礎パートに入ります。本章ではまず、画面を組み立てるときに最初に出会うコアコンポーネントを整理します。Webの<div>や<p>に相当するものを正確にマッピングしておくと、以降の章で「あれは何だったか」と立ち止まる回数を減らせます。
4つの基本コンポーネント
最初に押さえておきたいのは次の4つです。
View― レイアウト用のコンテナ。Webの<div>相当Text― テキストを表示する。Webの<span>/<p>相当Image― 画像を表示する。Webの<img>相当TextInput― テキスト入力欄。Webの<input type="text">/<textarea>相当
これだけで実はかなり多くの画面が組めます。
import { View, Text, Image, TextInput, StyleSheet } from "react-native";
function ProfileCard() {
return (
<View style={styles.card}>
<Image source={{ uri: "https://example.com/avatar.png" }} style={styles.avatar} />
<Text style={styles.name}>Taro</Text>
<TextInput style={styles.input} placeholder="メッセージを入力" />
</View>
);
}
const styles = StyleSheet.create({
card: { padding: 16, gap: 12 },
avatar: { width: 64, height: 64, borderRadius: 32 },
name: { fontSize: 18, fontWeight: "bold" },
input: { borderWidth: 1, borderColor: "#ddd", borderRadius: 8, padding: 8 },
});
JSXの形はWebと変わらず、要素名とstyleの書き方だけが違います。
なお、これ以降のスニペットでは特記なき限りreact-nativeからのimportを省略します(View/Text/Pressableなど)。手元で写経するときは、上の例のように冒頭で必要なものをまとめてimportしてください。
なぜテキストは必ず <Text> で囲む必要があるのか
Webから来ると最初に戸惑うのが、文字列を<View>に直接置けないことです。
// ✕ エラーになる
<View>こんにちは</View>
// ○ 必ずTextで囲む
<View>
<Text>こんにちは</Text>
</View>
これは、ネイティブのUIでは「文字を描画する専用の部品」と「レイアウト用の容れ物の部品」がそもそも別物として用意されている、という設計をそのまま反映しているためです(参考: iOSならUILabelとUIView、AndroidならTextViewとandroid.view.Viewが対応します)。
慣れてしまえば苦になりませんが、最初の壁として知っておくと、エラーメッセージで悩む時間を減らせます。
Textの中にViewを混ぜるときは控えめに
逆方向は完全に禁止というわけではありません。<Text>の中にインラインで小さな<View>(バッジやアイコンなど)を置くケースはあり、対応する場面ではそのまま使えます。ただ、ブロックレベルの<View>を入れ子にしすぎるとプラットフォーム間でレイアウトが崩れやすいので、込み入ったレイアウトはテキストの外に出すのが安全です。
<Text>の中に<Text>を入れて部分的なスタイルを当てるのは何も問題ありません。
<Text style={{ fontSize: 16 }}>
こんにちは
<Text style={{ fontWeight: "bold", color: "red" }}>Taro</Text>
さん
</Text>
Webで<span>を入れ子にするのと同じ感覚です。
iOS/Androidでテキストの見え方が変わる
同じ<Text>に同じスタイルを当てても、iOSとAndroidで「行の高さ」「上下の余白」「ベースラインの位置」がわずかに違って見える、ということがあります。デザインをピクセル単位で揃えようとすると、ここで詰まりがちです。
代表的なのが、Androidで<Text>に自動で付く上下の追加余白(font padding)です。何も指定しないとAndroid側だけ縦に少し膨らんで見えます。これを揃えたい場合はincludeFontPadding: falseを当てます。
<Text style={{ fontSize: 16, includeFontPadding: false }}>こんにちは</Text>
そのほか、固定高さの中で上下中央寄せしたい場合はtextAlignVertical: "center"(Android向け)、行間を揃えたい場合はlineHeightを明示する、といった対処があります。「iOSで合わせたデザインがAndroidで微妙にずれる」と感じたら、まずこの3つ(includeFontPadding / textAlignVertical / lineHeight)を疑うと早いです。
Image ― 画像表示
<Image>は、URLでもローカルアセットでも表示できます。
// リモート画像
<Image
source={{ uri: "https://example.com/photo.jpg" }}
style={{ width: 200, height: 200 }}
/>
// ローカル画像(プロジェクト内のファイル)
<Image source={require("./assets/logo.png")} style={{ width: 100, height: 100 }} />
Webと違って、<Image>は表示サイズを明示する必要があります。最低でも幅か高さのどちらか一方を指定しないと、表示領域が0として扱われ画面に何も描画されません。両方を数値で指定するか、片方だけを指定してaspectRatioプロパティで縦横比を補えばOKです(Webでは画像の元サイズが自動で使われましたが、React Nativeでは明示的にレイアウトを決める必要があります)。
resizeModeプロパティでcover/contain/stretch/center/repeatを切り替えられます。Webのobject-fitに相当する役目です。
より高機能なexpo-image
expo-imageを導入すると、
- 自動キャッシング
- プレースホルダ画像(blurhashなど)
- スムーズなフェードイン
- WebPやAVIFといった効率的なフォーマット対応
といった機能が揃います。本格的にアプリを作るなら、Imageよりexpo-imageを使うのがおすすめです。
npx expo install expo-image
import { Image } from "expo-image";
<Image
source={{ uri: "https://example.com/photo.jpg" }}
style={{ width: 200, height: 200 }}
placeholder={{ blurhash: "L6PZfSi_.AyE_3t7t7R**0o#DgR4" }}
contentFit="cover"
transition={300}
/>
contentFitがresizeModeに相当し、transitionでフェードイン時間(ms)、placeholderでblurhashなどのプレースホルダを指定できます。
TextInput ― テキスト入力
入力欄はWebの<input>に近い使い方ができます。
import { useState } from "react";
import { TextInput } from "react-native";
function NameInput() {
const [name, setName] = useState("");
return (
<TextInput
value={name}
onChangeText={setName}
placeholder="名前を入力"
style={{ borderWidth: 1, padding: 8 }}
/>
);
}
注意点はいくつかあります。
- 値の変更イベントは
onChangeではなくonChangeText(値だけが直接渡ってくる) - 複数行にしたい時は
multiline={true} - パスワード入力は
secureTextEntry={true} - キーボードの種類を切り替えたい時は
keyboardType="email-address"/"numeric"など
<TextInput>もAndroidとiOSで挙動が分かれやすい部品の代表格です。たとえば、
- 自動補完・自動大文字化のデフォルトが違う(
autoCorrect、autoCapitalizeを意識的に指定する) - 入力欄の高さがプラットフォームのフォント余白の影響を受ける(前述の
includeFontPaddingが関係する) - 日本語入力(IME)の確定タイミングや、変換中の文字列の扱いが微妙に異なる
実装時はiOS/Androidの両方で必ず触って、見え方と入力感を確かめるのが安全です。
キーボードの制御(キーボードに隠れない位置にフォーカスを送る、表示中はレイアウトをずらす)はモバイルならではのテーマで、第8章でまとめて扱います。
スクロール領域
ここがWeb開発者にとって最初の大きな分岐点です。
Webでは要素の高さを超えるコンテンツを置けば、自動でスクロールバーが出ました。React Nativeにはそういう振る舞いがありません。スクロールが必要な領域は、明示的に専用のコンポーネントで囲みます。
選択肢は主に2つです。
ScrollView― 中身を全部レンダリングしてからスクロールさせる。Webのoverflow: autoに近いFlatList― リストを仮想化(画面に出ているぶんだけレンダリング)する。大量項目向け

ScrollView
要素数が少ない、もしくは内容が決まり切っているスクロール領域に使います。
import { ScrollView, Text } from "react-native";
function AboutScreen() {
return (
<ScrollView contentContainerStyle={{ padding: 16 }}>
<Text>長い説明文がここに続きます……</Text>
</ScrollView>
);
}
contentContainerStyleにスクロール内側のレイアウトを書きます(styleはスクロールビュー自体のサイズ・背景に効きます)。
FlatList
データの数が増える可能性がある一覧画面では、FlatListを使います。仮想化(画面外のセルをレンダリングしない)が効くため、何千件のデータでもメモリやパフォーマンスを抑えられます。

ScrollViewは中身を全部レンダリングするのに対し、FlatListは画面内(viewport)の項目だけをレンダリングし、スクロールに応じて動的に描画・破棄します。件数が増えるほど差が効いてきます。
import { FlatList, Text, View } from "react-native";
type Todo = { id: string; title: string };
function TodoList({ todos }: { todos: Todo[] }) {
return (
<FlatList
data={todos}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<View style={{ padding: 12 }}>
<Text>{item.title}</Text>
</View>
)}
/>
);
}
主要なpropsの意味は、
data― 配列keyExtractor― 各項目のkeyを取り出す関数(Reactのkeyと同じ役割)renderItem― 各項目をどう描画するかを返す関数
「Webのmapで並べる感覚」を、仮想化対応に置き換えたものだと思ってください。
リストを下に引っ張って更新するUXは、Webには無いモバイル定番の動きです。FlatListのrefreshControl propに<RefreshControl>を渡すと実装できます。
import { FlatList, RefreshControl, Text, View } from "react-native";
import { useState } from "react";
function TodoList({ todos, onRefresh }: { todos: Todo[]; onRefresh: () => Promise<void> }) {
const [refreshing, setRefreshing] = useState(false);
const handleRefresh = async () => {
setRefreshing(true);
try {
await onRefresh();
} finally {
setRefreshing(false);
}
};
return (
<FlatList
data={todos}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<View style={{ padding: 12 }}>
<Text>{item.title}</Text>
</View>
)}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />}
/>
);
}
refreshingは「いま読み込み中か」のフラグで、onRefreshは引っ張ったときに呼ばれる関数です。サーバ再取得だけでなく、ローカルキャッシュの読み直しにも使えます。
行の中身が複雑になってスクロールがカクつく規模になったら、Shopify製の@shopify/flash-listへの置き換えも検討してください。インターフェースはFlatListとほぼ同じで、行の高さ推定をベースに描画コストを抑える作りです。シンプルなリストではFlatListで十分なので、本書ではFlatListを基本にします。
ボタン代わりのコンポーネント
タップ可能な領域を作るには、Webで<button>を使う代わりに、React Nativeでは<Pressable>を使うのが現代の第一選択です。
import { Pressable, Text } from "react-native";
<Pressable
onPress={() => console.log("tapped")}
style={({ pressed }) => ({
opacity: pressed ? 0.6 : 1,
padding: 12,
})}
>
<Text>タップしてください</Text>
</Pressable>
タッチ系コンポーネントは複数あります(TouchableOpacity、TouchableHighlight、TouchableWithoutFeedbackなど)が、Pressableが後発でこれらを統合しているため、新規実装はPressableを第一選択にしてください。既存コードに残っている分はそのままで問題ありません。詳しくは第8章で扱います。
アクセシビリティの基本
スクリーンリーダー(iOSのVoiceOver、AndroidのTalkBack)対応は、Webのaria-*属性とよく似たプロパティで指定します。最低限押さえておきたいマッピングは次のとおりです。
| Web | React Native |
|---|---|
aria-label |
accessibilityLabel |
role="button" |
accessibilityRole="button" |
aria-disabled |
accessibilityState={{ disabled: true }} |
aria-checked |
accessibilityState={{ checked: true }} |
aria-hidden |
accessibilityElementsHidden(iOS)/ importantForAccessibility="no-hide-descendants"(Android) |
なお、aria-label、aria-hidden、aria-checked、roleといったWeb標準の名前は、React Nativeでもそのまま受け付けます(roleはaccessibilityRoleより優先されます)。第1章で「Webの書き方に近づき続けている」と書いたのは、こういう部分です。既存コードや周辺の資料ではaccessibility*の形が主流なので本書もそちらで統一しますが、Webの手癖のまま書いても動く、と知っておくと迷いが減ります。
最低でも、テキストを持たないPressable(アイコンボタン、+ボタンなど)にはaccessibilityLabelを入れます。「+」だけだとVoiceOverが「プラス」と読んでしまうので、目的を表す文言を渡しておきます。
<Pressable
onPress={handleAdd}
accessibilityRole="button"
accessibilityLabel="TODOを追加"
>
<Text>+</Text>
</Pressable>
ストア審査でもアクセシビリティは見られるので、リリース前に最低限のラベル付けは済ませておきましょう。
本章で押さえたい対応関係
| Web | React Native |
|---|---|
<div> |
<View> |
<span> <p> |
<Text> |
<img> |
<Image> (本格運用はexpo-image) |
<input type="text"> <textarea> |
<TextInput> |
<button> |
<Pressable> |
<a> |
<Link> (Expo Router) |
overflow: auto |
<ScrollView> |
| 大量リスト | <FlatList> |
Webのタグの代替を表で持っておくと、新しい画面を組む時の判断が早くなります。
本章のまとめ
- View / Text / Image / TextInputが基本
- 文字列は必ずTextで囲む(ネイティブUIの設計思想を反映)
- 画像はサイズを明示する。本格運用はexpo-image
- スクロールは明示的に。少量はScrollView、多量はFlatList。引っ張って更新はRefreshControl
- アクセシビリティはWebの
aria-*に対応するaccessibility*プロパティで指定する - タップ可能領域はPressableが第一選択
次章では、これらに見た目を与えるスタイリングについて見ていきます。