📱

今日からReact Nativeを始めたい人のための速習React(前編)

2024/12/05に公開

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

https://adventar.org/calendars/10741

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

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

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

今日からReact Nativeを始めたい人のための速習React (前編)

モバイルアプリ開発のためにReact Nativeを始めようとしている方向けに、Reactの基礎を解説します。この記事では、Reactの基本的な概念とコンポーネントの書き方を中心に説明します。

Reactとは?

Reactは、FacebookによってWebアプリケーションのUIを構築するために開発されたJavaScriptライブラリです。最大の特徴は、UIを小さな部品(コンポーネント)に分割して開発できる点です。

例えば、以下のようなプロフィールカードのUIを作る場合:

type ProfileCardProps = {
  name: string;
  age: number;
  bio: string;
};

const ProfileCard = ({ name, age, bio }: ProfileCardProps) => {
  return (
    <div className="card">
      <h2>{name}</h2>
      <p>年齢: {age}</p>
      <p>{bio}</p>
    </div>
  );
};
   <ProfileCard name="太郎" age={10} bio="説明文" />

このように、再利用可能なコンポーネントとして実装できます。

ReactとReact Nativeの関係

ReactとReact Nativeは、同じ考え方と原則に基づいています。主な違いは出力される結果です:

  • React → HTML要素(<div>, <p> など)
  • React Native → ネイティブUIコンポーネント(<View>, <Text> など)
// Reactの場合
const WebButton = () => {
  return <button>クリック</button>;
};

// React Nativeの場合
const NativeButton = () => {
  return <Pressable><Text>クリック</Text></Pressable>;
};

つまり、Reactの知識はReact Nativeでも直接活用できます。

クラスコンポーネントと関数コンポーネント

Reactのコンポーネントには2つの書き方があります:

クラスコンポーネント(古い書き方)

class Welcome extends React.Component<{ name: string }> {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

関数コンポーネント(現在主流な書き方)

const Welcome = ({ name }: { name: string }) => {
  return <h1>Hello, {name}</h1>;
};

クラスコンポーネントは非推奨というわけではありませんが、新しいReactの機能の多くは関数コンポーネントを前提としており、これから始める方は関数コンポーネントに集中して学習することをお勧めします。以降の説明も関数コンポーネントを前提として書いていきます。

関数コンポーネントの書き方

基本的な構文

type TodoItemProps = {
  title: string;
  completed: boolean;
};

const TodoItem = ({ title, completed }: TodoItemProps) => {
  return (
    <div>
      <input type="checkbox" checked={completed} />
      <span>{title}</span>
    </div>
  );
};

変数の埋め込み

JSX(ReactのHTML-likeな構文)内で変数や式を使用する場合は、中括弧 {} で囲みます:

const Greeting = () => {
  const user = "太郎";
  const currentTime = new Date().getHours();
  
  return (
    <div>
      <p>{user}さん、こんにちは!</p>
      <p>現在の時刻は{currentTime}時です。</p>
    </div>
  );
};

Props(プロパティ)の受け取り方

コンポーネントは引数としてpropsを受け取ります:

type ButtonProps = {
  label: string;
  onClick: () => void;
  disabled?: boolean;  // オプショナルなprop
};

const Button = ({ label, onClick, disabled = false }: ButtonProps) => {
  return (
    <button onClick={onClick} disabled={disabled}>
      {label}
    </button>
  );
};

条件分岐の書き方

Reactでは、条件分岐を以下のような方法で実装できます:

三項演算子

const UserStatus = ({ isOnline }: { isOnline: boolean }) => {
  return (
    <div>
      {isOnline ? (
        <span className="online">オンライン</span>
      ) : (
        <span className="offline">オフライン</span>
      )}
    </div>
  );
};

論理演算子(&&)

const Notification = ({ message }: { message?: string }) => {
  return (
    <div>
      {message && <p className="notification">{message}</p>}
    </div>
  );
};

変数への代入

const UserProfile = ({ user }: { user?: User }) => {
  let content: ReactNode;
  
  if (!user) {
    content = <p>ユーザーが見つかりません</p>;
  } else if (user.isAdmin) {
    content = <AdminView user={user} />;
  } else {
    content = <NormalUserView user={user} />;
  }
  
  return <div className="profile">{content}</div>;
};

リストの表示

配列の要素を表示する場合は、mapメソッドを使用します:

type Todo = {
  id: string;
  title: string;
  completed: boolean;
};

const TodoList = ({ todos }: { todos: Todo[] }) => {
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>  {/* keyは必須! */}
          <input type="checkbox" checked={todo.completed} />
          <span>{todo.title}</span>
        </li>
      ))}
    </ul>
  );
};

keyの重要性

mapでリストを生成する際は、必ず各要素に一意のkeyプロパティを指定する必要があります:

// 良い例
{items.map(item => <Item key={item.id} {...item} />)}

// 避けるべき例
{items.map((item, index) => <Item key={index} {...item} />)}

indexをkeyとして使用するのは、リストの要素が追加・削除される可能性がある場合は避けるべきです。

React Nativeでのリスト

React Nativeで長いリストを扱う場合は、FlatListコンポーネントを使用することが推奨されます。.mapを使ったレンダリングに比べてビューポート(画面に表示されている範囲)の外に表示される部分の処理をスキップできるため、パフォーマンスに優れています。

const TodoList = ({ todos }: { todos: Todo[] }) => {
  return (
    <FlatList
      data={todos}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => (
        <TodoItem title={item.title} completed={item.completed} />
      )}
    />
  );
};

コンポーネントの純粋性

Reactの関数コンポーネントは、UIが変更されるたびに何度でも繰り返し呼ばれます。そのため可能な限り純粋な関数として実装すべきです。つまり

  • 同じpropsに対して常に同じ結果を返す
  • 副作用(APIコール、タイマー、データの変更など)は避ける
// 良い例
const PriceDisplay = ({ price }: { price: number }) => {
  return <p>{price.toLocaleString()}</p>;
};

// 避けるべき例
const BadPriceDisplay = ({ price }: { price: number }) => {
  // コンポーネント内で直接副作用を起こすのは避ける
  fetch('/api/log-price', { method: 'POST', body: String(price) });
  
  return <p>{price.toLocaleString()}</p>;
};

副作用が必要な場合は、後編で解説するReact Hooksを使用します。

まとめ

この前編では、Reactの基本的な概念とコンポーネントの書き方について解説しました。後編では、より高度な機能を実現するためのReact Hooksについて詳しく説明します。

Discussion