2023年8月10日木曜日

Reactのディレクトリ構成でAtomicデザインをやめた話

https://zenn.dev/brachio_takumi/articles/2ab9ef9fbe4159


2022/03/31に公開約5,500字

Atomic デザインをやめた

結論から言うと Atomic デザインを React のディレクトリ構成に当て込むのをやめました。結構ツラミが出てきてしまった感じです。とはいえ、Atomic デザインを批判するわけではなく、むしろ概念というか考え方は好きな部類です。
あくまでデザインシステムであり、ディレクトリ構成に当て込むべきものではないなと。

以前までのディレクトリ構成はこんな感じ

Atomic デザインが話題になった頃からそれに倣ったディレクトリ構成にしていました。Next.js のプロジェクトになってしまいますが大体こんな感じ。

├── common
│   ├── config
│   ├── styles
│   ├── types
│   └── utils
├── components
│   ├── atoms
│   ├── molecules
│   ├── organisms
│   └── templates
└── pages
    ├── 404.tsx
    ├── _app.tsx
    ├── _document.tsx
    ├── blog
    ├── contact-thanks.tsx
    ├── flow.tsx
    ├── index.tsx
    └── works

Atomic デザインの限界

React や Next.js のディレクトリ構成に Atomic デザインの理論を落とし込むことによる問題点というか弊害というか限界が見えてきたなと思っています。個人的には以下の理由で Atomic デザインをやめました。

  1. 名が体を表していない
  2. Organisms が増えすぎてしまう病
  3. pages に依存した Component などがどうしても発生する

この 3 点が主な理由なんですが、一つづつ解説をします。

名が体を表していない

Atomic デザインが見た目の意味になっているので、無理やり React のディレクトリ構成に落とし込んでもしんどいんですよね。Atomic デザインを知らない人からすると organisms って言われても分からんのですよね。一般的な言葉ではないので。。。

organisms が増えすぎてしまう病

おそらくですが、organisms が増えてしまいがちなのは私の構成だからだと思います。人によっては molecules の場合もあるかなと。

私の場合は organisms ではデータの取得や state の更新をして良い 紳士協定 にしていました。なので一覧を表示する Table があったとすると UsersTable や HogesTable などが出来すぎてしまっていました。これについては私の Component の作り方が悪かったのも原因の一つではありますし、そもそも Pages でデータの取得や state の更新をすべきというのが Atomic デザインの考え方ではあると思うんですが、props でバケツリレーもしんどいんですよね。

そうなるともう少し小さい粒度でデータの取得や state の更新をしなければならず、organisms などで似たような Component が乱立するというジレンマに陥っていました。

多分この時点で Atomic デザインの限界だったんですよね笑

pages に依存した Component などがどうしても発生する

上記からの派生なんですが、小さい粒度でデータの取得や state の更新するためにorganisms コンポーネントを作っていくと結局 pages に依存していくんですよね。例えば

const UsersPage: React.FC = () => {
  return (
    <>
      <PageMeta title="ユーザー一覧" />
      <UsersSearchFields />
      <UsersTable />
    </>
  );
};

export default UsersPage;

このような形で organisms を配置して pages を構成していくイメージですが、ユーザーページでしかこれらのコンポーネントは名前の通りで使わないんですよね。なので users 配下で使うコンポーネントとして下記のようなディレクトリ構成を試したりもしました。もうこうなってくるとディレクトリ構成を Atomic デザインにしていく必要ってないんですよね笑

└── users
    ├── organisms
    │   ├── UsersSearchFields.tsx
    │   └── UsersTable.tsx
    └── index.tsx

今の所たどり着いたディレクトリ構成

10 月からやっている新しいプロジェクトでは試行錯誤の上こんな感じでやり始めています。

├── common
│   ├── constants
│   │   └── index.tsx
│   ├── hooks
│   │   └── index.tsx
│   ├── styles
│   │   ├── app.css.ts
│   │   ├── globals.css
│   │   └── theme.css.ts
│   ├── types
│   │   └── index.tsx
│   └── utils
│       ├── apiClient.ts
│       └── imageLoader.ts
├── components
│   ├── functional
│   │   └── Authenticator.tsx
│   ├── layouts
│   │   ├── Header
│   │       ├── Header.stories.tsx
│   │       ├── index.tsx
│   │       └── style.css.ts
│   ├── ui-elements
│   │   ├── Button
│   │       ├── Button.stories.tsx
│   │       ├── index.tsx
│   │       └── style.css.ts
│   └── ui-parts
│       ├── FieldCheckbox
│           ├── FieldCheckbox.stories.tsx
│           └── index.tsx
├── features
│   ├── Users
│       ├── api
│       │   └── index.ts
│       ├── components
│       │   └── index.tsx
│       ├── hooks
│       │   └── index.ts
│       ├── helper
│       │   └── index.ts
│       ├── index.ts
│       ├── test
│       │   └── index.test.ts
│       └── types
│           └── index.ts
└── pages
	   ├── 404.tsx
	   ├── _app.tsx
	   ├── _error.tsx
	   └── index.tsx

先ほども述べた通り概念というか考え方として Atomic デザインは 小さく作って組み合わせる というものなので、React の思想とも近くとても優れていると思います。まずやったこととして大きいのは

  1. components 内の仕分けする際の名称の変更
  2. features の導入

の 2 点です。

components 内の仕分けする際の名称の変更

こちらを参考にさせていただいて、汎用的に使うコンポーネントの名称を ui-elementsui-partsfunctionalに分けました。

ui-elements

ui-elements は、Atomic デザインでいうところの atoms に相当するものになります。state も更新などは行うことはしません。

例えば Button コンポーネントであればこのような感じです。

export const Button: FCX<Props> = ({
  color,
  size,
  onClick,
  children,
  className,
  type,
}) => {
  return (
    <button
      className={classnames(
        ButtonRecipe({
          color,
          size,
        }),
        className
      )}
      onClick={onClick}
      type={type}
    >
      {children}
    </button>
  );
};

また、stories や style もコンポーネントと同じ階層に入れるようにしています。

ui-parts

ui-parts は Atomic デザインでいうところの molecules 相当になります。ui-elementsを組み合わせたりしてコンポーネントを成形します。また、useStateなどで state の更新・保持をしても良いことにしています。ここについては結構悩んだんですけど、データの取得など API が絡まなければ良いかなと。

functional

これは参考にさせていただいたもので、良いなーっと思ったので即導入!!
見た目を伴わない系のコンポーネントなどを配置するようにしたりしてます。例えばログイン判定などですね。

export const Authenticator: FC = ({ children }) => {
  const { isAuthenticated } = useAuth0();
  const router = useRouter();
  useEffect(() => {
    if (!isAuthenticated) {
      router.push("/");
    }
  }, [isAuthenticated, router]);
  return <Main>{children}</Main>;
};

layouts

layouts は文字通りレイアウトに関するコンポーネントを配置します。結構実験的に入れてみてる感じなので今後いらなくなるかも。

Header や Footer などを入れています。

features の導入

こちらを参考にさせていただいて、今まで私がやっていた pages 配下に organisms を配置したり、該当ページでしか使わないメソッドや型定義を機能ごとに配置するようにしています。

これが良いなと思った点としては

  1. 機能ごとなので、ページを跨いで使っても違和感がない
  2. 機能ごとにテスト書ける

かなと思っています。pages 配下に organisms を配置していた時には結構しんどいというか違和感があってやらなかったんですが、例えばユーザー一覧をこっちのページでも使いたいといった場合、page を跨いで使うので気持ち悪いんですよね。

なので、他ページでもユーザー一覧用コンポーネントを作るか、汎用的に扱う organisms ディレクトリを作ってそこに置くかなどをしていました。ですが features であればそういった場合でも違和感なく使えて、しかもテストも機能ごとにまとめられる!これを考えた人天才です!

終わりに

現在サービスを絶賛開発中ですが結構気に入っているディレクトリ構成になりました。また、今後は components 配下に providers を作っても良いかなとかも考えていたりしています。規模がもっと大きくなったらどうなるかなというのもあるので、しばらくしたら悩んでいるかもしれません。

こういった構成とか考えるのはやっぱ酒飲みながらできると楽しいんですよね〜。コロナが収束すれば楽しみ増えるなと思いながら記事にしました。

Discussion

ログインするとコメントできます


0 コメント:

コメントを投稿