25
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

React ComponentのPropsには何を渡すかよく考えて欲しい

Posted at

React ComponentのPropsには好きなものを渡せる

使いやすいReact Componentを作るために、何をPropsとして渡せば良いか考えたことはありますか?

Propsに何でも渡せるのがReact Componentの良さですが、自由度が高いために何も考えないと使いにくいコンポーネントになってしまいます。

使いにくいコンポーネントの例

TODOリストのアプリの新しいTODOを追加する部分を思い浮かべて下さい。イメージとしてTODO MVCを拝借しますが、この部分です。

todomvc.png

TODO MVCのコードでは無いことを断っておきますが、コードはこんな感じでしょうか。APIとStoreは適当にRESTやReduxを思い浮かべてくれれば良いです。

import { useState } from 'react';
import { postTodoInAPI } from '@api';

const NewTodoForm = ({ addTodo, postTodoInAPI }) => {
  const [title, setTitle] = useState('');

  const handleChange = (e) => setTitle(e.target.value);

  const handleSubmit = (e) => {
    e.preventDefault();
    const newTodo = { title };
    postTodoInAPI(newTodo);
    addTodo(newTodo);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name='title' value={title} onChange={handleChange} />
    </form>
  );
};

const TodoList = () => {
  const { todos, addTodo } = useStore();

  return (
    <div>
      <NewTodoForm addTodo={addTodo} postTodoInAPI={postTodoInAPI} />
      <ul>
        {todos.map(({ title }, i) => (
          <li key={i}>{title}</li>
        ))}
      </ul>
    </div>
  );
};

このコードにはPropsの渡し方のせいで使いにくいコンポーネントになっている部分があります。

NewTodoFormaddTodopostTodoInAPI を渡しているせいで、TODOを追加するときのロジックを NewTodoForm の中で組み立てています。このせいで NewTodoForm がAPIとStoreに依存するようになってしまいます。

例えば、インフラのコスト削減のためにDBを廃止して、代わりに localStorage を使うようになったとします。APIが不要になるので postTodoInAPI を廃止して、代わりに addTodoInLocalStorage を使うようになります。

  import { useState } from 'react';
- import { postTodoInAPI } from '@api';
+ import { addTodoInLocalStorage } from '@localstorage';

- const NewTodoForm = ({ addTodo, postTodoInAPI }) => {
+ const NewTodoForm = ({ addTodo, addTodoInLocalStorage }) => {
    const [title, setTitle] = useState('');

    const handleChange = (e) => setTitle(e.target.value);

    const handleSubmit = (e) => {
      e.preventDefault();
      const newTodo = { title };
-     postTodoInAPI(newTodo);
+     addTodoInLocalStorage(newTodo);
      addTodo(newTodo);
    };

    return (
      <form onSubmit={handleSubmit}>
        <input name='title' value={title} onChange={handleChange} />
      </form>
    );
  };

  const TodoList = () => {
    const { todos, addTodo } = useStore();

    return (
      <div>
-       <NewTodoForm addTodo={addTodo} postTodoInAPI={postTodoInAPI} />
+       <NewTodoForm addTodo={addTodo} addTodoInLocalStorage={addTodoInLocalStorage} />
        <ul>
          {todos.map(({ title }, i) => (
            <li key={i}>{title}</li>
          ))}
        </ul>
      </div>
    );
  };

このときに NewTodoFormTodoList の2つのコンポーネントで改修が発生していまいます。NewTodoForm を別のコンポーネントでも使っていた場合、そこに影響しないように注意を払わなければならず、最悪の場合 NewTodoForm を共通で使えなくなってしまうかもしれません。

ここで言えるのは NewTodoForm は再利用しやすいコンポーネントではなかったということです。それは責務を考えずに NewTodoForm にPropsを渡していたからです。

使いやすいコンポーネントの例

NewTodoForm の責務は「新しいTODOを追加すること」であり、新しいTODOをどこ(DB or localStorage)に追加するかは NewTodoForm の関与するところでは無いのです。NewTodoForm は新しいTODOを追加するためのfunctionを知っていれば良く、新しいTODOをAPIや localStorate や Store に追加するfunctionは知る必要がないのです。

それはどういうコードなのかというと、こうなります。

  import { useState } from 'react';
  import { postTodoInAPI } from '@api';

- const NewTodoForm = ({ addTodo, postTodoInAPI }) => {
+ const NewTodoForm = ({ onAdd }) => {
    const [title, setTitle] = useState('');

    const handleChange = (e) => setTitle(e.target.value);

    const handleSubmit = (e) => {
      e.preventDefault();
      const newTodo = { title };
-     addTodo(newTodo);
-     postTodoInAPI(newTodo);
+     onAdd(newTodo);
    };

    return (
      <form onSubmit={handleSubmit}>
        <input name='title' value={title} onChange={handleChange} />
      </form>
    );
  };

  const TodoList = () => {
    const { todos, addTodo } = useStore();

+   const handleAdd = (newTodo) => {
+     postTodoInAPI(newTodo);
+     addTodo(newTodo);
+   }
+
    return (
      <div>
-       <NewTodoForm addTodo={addTodo} postTodoInAPI={postTodoInAPI} />
+       <NewTodoForm onAdd={handleAdd} />
        <ul>
          {todos.map(({ title }, i) => (
            <li key={i}>{title}</li>
          ))}
        </ul>
      </div>
    );
  };

これならAPIから localStorage に乗り換えても TodoListhandleAdd を変えるだけで、 NewTodoForm には影響しません。

  import { useState } from 'react';
- import { postTodoInAPI } from '@api';
+ import { addTodoInLocalStorage } from '@localstorage';

  const NewTodoForm = ({ onAdd }) => {
    const [title, setTitle] = useState('');

    const handleChange = (e) => setTitle(e.target.value);

    const handleSubmit = (e) => {
      e.preventDefault();
      const newTodo = { title };
      onAdd(newTodo);
    };

    return (
      <form onSubmit={handleSubmit}>
        <input name='title' value={title} onChange={handleChange} />
      </form>
    );
  };

  const TodoList = () => {
    const { todos, addTodo } = useStore();

    const handleAdd = (newTodo) => {
-     postTodoInAPI(newTodo);
+     addTodoInLocalStorage(newTodo);
      addTodo(newTodo);
    }
 
    return (
      <div>
        <NewTodoForm onAdd={handleAdd} />
        <ul>
          {todos.map(({ title }, i) => (
            <li key={i}>{title}</li>
          ))}
        </ul>
      </div>
    );
  };

終わりに

今回はシンプルの例だったので重要性が伝わりにくかったかもしれないですが、このような小さな積み重ねが日々の開発効率の改善に繋がります。せっかくReactをつかっているのですから、コンポーネントは再利用しやすいようにしておくに越したことはないのではないでしょうか。

25
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?