Reactにおける子コンポーネントのステートを介した動作モード

製品の配列が次のようになっているとします:

jsxconst initProds = [
  {id: id(), name: 'prod1', cost: 'cost1', catg: 'catg1'},
  {id: id(), name: 'prod2', cost: 'cost2', catg: 'catg2'},
  {id: id(), name: 'prod3', cost: 'cost3', catg: 'catg3'},
];

これらの製品をHTMLテーブルとして表示してみましょう。 また、テーブルの任意のセルをクリックすると、そのセルに編集用の入力フィールドが表示されるようにします。 この問題を解決するために、3つのコンポーネントを作成します。

Productsコンポーネントは製品のステートを保持し、 製品を表示するためにProductコンポーネントを使用します。 Productコンポーネントは、今度は特定の製品フィールド(名前、価格、カテゴリ)を表示するために ProductFieldコンポーネントを使用します。

ProductFieldコンポーネントは、 フィールドのテキストを表示するか、その編集用の入力フィールドを表示します。 このとき、編集モードまたは表示モードはこのコンポーネント自身のステートによって制御されるようにします。

つまり、モードを親のステートに保存しません。 親に保存すると非常に不便になります。なぜなら、製品の各フィールドごとにモードを指定する必要があり、 ステートが次のようなものになってしまうからです:

jsxconst initProds = [
  [
    {field: 'name', value: 'prod1', isEdit: false},
    {field: 'cost', value: 'cost1', isEdit: false},
    {field: 'catg', value: 'catg1', isEdit: false},
  ],
  [
    {field: 'name', value: 'prod2', isEdit: false},
    {field: 'cost', value: 'cost2', isEdit: false},
    {field: 'catg', value: 'catg2', isEdit: false},
  ],
  [
    {field: 'name', value: 'prod3', isEdit: false},
    {field: 'cost', value: 'cost3', isEdit: false},
    {field: 'catg', value: 'catg3', isEdit: false},
  ],
]

しかし、私たちはそのようなステートを作らず、元のステートをそのままにします。 単に、各ProductFieldコンポーネントのインスタンスが、自身のステートを使って、 編集モードか表示モードかを制御するようにします。

これにより、親コンポーネントがデータのステートを保持し、 孫コンポーネントがそのデータをpropsを通じて受け取りながら、 自身のモードを変更するための独自のステートを持つ、という構造になります。

では、説明した内容を実装してみましょう。

Productsコンポーネント

jsxfunction Products() {
  const [prods, setProds] = useState(initProds);
  
  function changeField(id, field, event) {
    setProds(prods.map(prod => {
      if (prod.id === id) {
        prod[field] = event.target.value;
      }
      
      return prod;
    }));
  }
  
  const rows = prods.map(prod => {
    return <Product
      key ={prod.id}
      id  ={prod.id}
      name={prod.name}
      cost={prod.cost}
      catg={prod.catg}
      changeField={changeField}
    />;
  });
  
  return <div>
    <table>
      <tbody>
        {rows}
      </tbody>
    </table>
  </div>;
}

Productコンポーネント

jsxfunction Product({ id, name, cost, catg, changeField }) {
  return <tr>
    <ProductField id={id} text={name} type="name" changeField={changeField} />
    <ProductField id={id} text={cost} type="cost" changeField={changeField} />
    <ProductField id={id} text={catg} type="catg" changeField={changeField} />
  </tr>;
}

ProductFieldコンポーネント

jsxfunction ProductField({ id, text, type, changeField }) {
  const [isEdit, setIsEdit] = useState(false);
  
  return <td>
    {
      isEdit
      ? <input
        value={text}
        onChange={event => changeField(id, type, event)}
        onBlur={() => setIsEdit(false)}
      />
      : <span onClick={() => setIsEdit(true)}>{text}</span>
    }
  </td>;
}

実践タスク

Задание 1

これまでのレッスンで作成したUsersコンポーネントとUserコンポーネントに対しても、 同様の操作を実装してみてください。