devlog

フロントエンドエンジニアの技術ブログ

React コンポーネントをカスタムフックで Presentational Component と Container Component に分離する

Presentational Component(コンポーネント) と Container Component(コンテナー)

コンポーネント を Presentational Component(コンポーネント) と Container Component(コンテナー) の2種類に分類する事で、再利用性を高める事ができる考え方です。 これは、Presentational and Container Components という記事で説明されています。

ただし、記事の冒頭にこのように記載されています。

Update from 2019: I wrote this article a long time ago and my views have since evolved. In particular, I don’t suggest splitting your components like this anymore. If you find it natural in your codebase, this pattern can be handy. But I’ve seen it enforced without any necessity and with almost dogmatic fervor far too many times. The main reason I found it useful was because it let me separate complex stateful logic from other aspects of the component. Hooks let me do the same thing without an arbitrary division. This text is left intact for historical reasons but don’t take it too seriously.

フックの登場によってコンポーネントをこのような考え方で分割する事はおすすめしないという事です。 せっかく学んだので記事にしますが...フックがある以上このように考える必要はないのかなと... 思っています。

それぞれのざっくりとした役割を私はこのように解釈しています。

Presentational Component(コンポーネント)

見た目を担うコンポーネント

Container Component(コンテナー)

Presentational Component(コンポーネント)または、Container Component(コンテナー)を内包してデータや動作を提供する

カウンターコンポーネントを Presentational Component(コンポーネント) と Container Component(コンテナー)に分けて実装する

カウンターコンポーネントの Presentational Component(コンポーネント)

import React from 'react';

interface CounterProps {
  count: number;
  increment: () => void;
  decrement: () => void;
}

const Counter: React.FC<CounterProps> = ({count, increment, decrement}) => {
  return (
    <div>
      <div>Counter: {count}</div>
      <div>
        <button onClick={increment}>+1</button>
        <button onClick={decrement}>-1</button>
      </div>
    </div>
  );
};

export default Counter

カウンターコンポーネントの Container Component(コンテナー)

import React, {useState} from 'react'
import CounterComponent from '../../components/Counter/Counter'

const useCounter = (initialCount: number): [number, () => void, () => void] => {
  const [count, setCount] = useState(initialCount);

  const increment = () => {
    setCount(prevCount => prevCount + 1)
  }

  const decrement = () => {
    setCount(prevCount => prevCount - 1);
  };

  return [count, increment, decrement];
};

const CounterContainer: React.FC = () => {
  const [count, increment, decrement] = useCounter(0)
  return <CounterComponent count={count} increment={increment} decrement={decrement} />
}

export default CounterContainer

@ 2019 devlog