【React】mapによるJSXループ処理の実装方法パターン集

JavaScript

Reactではオブジェクトの配列をJSX内でループ処理するケースがよくあります。
今回はmapを利用したループ処理の実装パターンを紹介します。

今回はTodoアプリケーションを例にとり、JSXでのループ処理の方法を説明します。

コンポーネントを分割せず、1つのコンポーネントで完結させる

mapを利用したJSXのループの基本構文は以下の通りです。一番外側の中括弧({})はJSX内で式を埋め込むためのものです。

{items.map((item) => {
  return (
    // JSX
  );
})}

Todoリストの各アイテムをmapによってレンダーする場合は以下のようになります。

TodoApp.tsx

import "./styles.css";

type TodoItem = {
  id: number;
  content: string;
};

export const TodoApp = () => {
  const todos: TodoItem[] = [
    { id: 1, content: "do something" },
    { id: 2, content: "go somewhere" },
  ];
  return (
    <div className="todo-app">
      <h1>Todo List</h1>
      <ul className="todo-list">
        {todos.map((todo: TodoItem) => {
          return (
            <li className="todo-item" key={todo.id}>
              <span className="todo-item__text">
                {todo.content}
              </span>
            </li>
          );
        })}
      </ul>
    </div>
  );
};

export default TodoApp;

ループ処理でレンダーするJSXを別コンポーネントに分割する

mapでレンダーするJSXを別のコンポーネントで定義する方法です。

以下の例ではTodoコンポーネントを新たに作成し、map内の役割はTodoコンポーネントを呼ぶだけとなっています。

TodoApp.tsx

import "./styles.css";
import Todo from "./components/Todo";

type TodoItem = {
  id: number;
  content: string;
};

export const TodoApp = () => {
  const todos: TodoItem[] = [
    { id: 1, content: "do something" },
    { id: 2, content: "go somewhere" },
  ];
  return (
    <div className="todo-app">
      <h1>Todo List</h1>
      <ul className="todo-list">
        {todos.map((todo: TodoItem) => {
          return (
            <Todo key={todo.id} content={todo.content} />
          );
        })}
      </ul>
    </div>
  );
};

export default TodoApp;

Todoコンポーネントは以下のようになります。

Todo.tsx

type TodoProps = {
  content: String;
};

const Todo: React.FC<TodoProps> = ({ content }) => (
  <li className="todo-item">
    <span className="todo-item__text">{content}</span>
  </li>
);

export default Todo;

なお、アロー関数ではステートメントがreturnのみの場合、returnおよびそれを囲む波括弧({})は省略できます。
ですので、コンポーネントを分割する場合は以下のように記述するとシンプルになります。

// Before
{todos.map((todo: TodoItem) => {
  return (
    <Todo key={todo.id} content={todo.content} />
  );
})}
// After
{todos.map((todo: TodoItem) =>
  <Todo key={todo.id} content={todo.content} />
)}

アロー関数の省略記法の詳細解説はJavaScriptアロー関数入門。functionからの書き換えや、省略記法について理解するで紹介しています。

ループ処理をするJSXを別コンポーネントに分離する

今回の例でいうと、Todoリストに関するコンポーネントを新たに作成する方法です。
呼び出し元(ここでいうTodoApp.tsx)からmapの記述がなくなるため、呼び出し元のコードがシンプルになります。

具体的なコードは以下の通りです。なお、Todoコンポーネントは変更がないため省略します。

TodoApp.tsx

import "./styles.css";
import TodoList from "./components/TodoList";

type TodoItem = {
  id: number;
  content: string;
};

export const TodoApp = () => {
  const todos: TodoItem[] = [
    { id: 1, content: "do something" },
    { id: 2, content: "go somewhere" },
  ];
  return (
    <div className="todo-app">
      <h1>Todo List</h1>
      <TodoList todos={todos}/>
    </div>
  );
};

export default TodoApp;

TodoList.tsx

import Todo from "./Todo";

type TodoItem = {
  id: number;
  content: string;
};

type TodoListProps = {
  todos: TodoItem[];
};


const TodoList: React.FC<TodoListProps> = ({ todos }) => {
  return (
    <ul className="todo-list">
      {todos.map((todo: TodoItem) =>
        <Todo key={todo.id} content={todo.content} />
      )}
    </ul>
  );
};

export default TodoList;

コンポジションモデルを採用する

mapをリスト用のコンポーネント(ここでいうTodoList.tsx)ではなく、呼び出し元(ここでいうTodoApp.tsx)に記述する方法です。
リスト用のコンポーネントはchildrenを受け取るようにします。

コンポジションモデルを利用することで、ループ処理されるJSXの内容が呼び出し元のコンポーネントで可視化されます。
またchildrenの情報は事前に決まっていなくてもよいため、リスト用のコンポーネントとして使い回しがしやすくなるというメリットがあります。

以下はコンポジションモデルを採用したコードの例です。なお、Todoコンポーネントは変更がないため省略します。

TodoApp.tsx

import "./styles.css";
import TodoList from "./components/TodoList";
import Todo from "./components/Todo";

type TodoItem = {
  id: number;
  content: string;
};

export const TodoApp = () => {
  const todos: TodoItem[] = [
    { id: 1, content: "do something" },
    { id: 2, content: "go somewhere" },
  ];
  return (
    <div className="todo-app">
      <h1>Todo List</h1>
      <TodoList>
        <ul className="todo-list">
          {todos.map((todo: TodoItem) => {
            return <Todo key={todo.id} content={todo.content} />;
          })}
        </ul>
      </TodoList>
    </div>
  );
};

export default TodoApp;

TodoList.tsx

export const TodoList: React.FC = ({ children }) => {
  return <div>{children}</div>;
};

export default TodoList;

さいごに

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!