Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
150
Help us understand the problem. What is going on with this article?
@Sotq_17

Reactのディレクトリ構成どうしてる?

前置き

Reactのスターターキットを作る上で、ディレクトリ構成について迷いながらも考えてみたので、その構成について紹介したいと思います。

構成について言及している記事はまだ少なく、ベスト構成とというものは存在しないと思います。
今回紹介する構成もまだまだ改良していかなければいけないと思いますが、構成について迷っている方に少しでも役立てればと思います。

開発環境について

node v12.13.1
npm v6.13.4

使用している技術について

  • React (reactHooks)
  • Redux (Redux-toolkit)
  • TypeScript

今回作成したスターターは、redux-toolkitを使っています。
小さいプロジェクトにはredux不要では??という考えもあると思います。(自分もちょっとそう思います)
ですが、redux-toolkitの登場によってreduxを簡単に使えるようになったこと、
またreduxを追加実装するコストを考えると最初から入れてしまった方がいいのでは、という考えのもと、初期から含めることにしました!
↓この記事にものすごく感銘を受けました!
TypeScriptでReactをやるときは、小さいアプリでもReduxを最初から使ってもいいかもねというお話

公式での扱いは:eyes:??

Reactは推奨されるディレクトリ構成はありません。
公式でも考えすぎるべきではないと書かれています。

React はファイルをどのようにフォルダ分けするかについての意見を持っていません。

まだプロジェクトを始めたばかりなら、ファイル構成を決めるのに 5 分以上かけないようにしましょう。上述の方法の 1 つを選ぶか、自分自身の方法を考えて、コードを書き始めましょう! おそらく実際のコードをいくらか書けば、なんにせよ考え直したくなる可能性が高いでしょう。

規模によって適切なディレクトリ構成が変わり、一つの答えはありません。
また、React周りは新しいツールがどんどん出てきて、何を使うか、どのように使用するかは目まぐるしく変わっているように感じます。
それも相まってディレクトリ構成を決めることが難しくなってるのだと思います。

構成について

さて本題です!
早速ですが実際作成したコードはこちら↓
https://github.com/Sotq17/rtk_starter

簡単にどのディレクトリでどういったファイルを扱うかを説明したいと思います。
(実際のコードを見てもらえると早いかもしれません…)
構成は以下の通りになっています。

─── src
    ├── img (画像置き場)
    ├── pug (render先を指定)
    ├── ts (react外でjsを使うときに使用)
    ├── tsconfig.json
    └── tsx
        ├── index.tsx (エントリーポイント)
        ├── stores
        │   ├── index.ts (slicesディレクトリで作られたSliceを結合する)
        │   └── slices (このディレクトリ下で各sliceファイルを扱う) 
        ├── style(各page,componentのcssを切り分けたい時に使用)
        │   ├── GlobalStyle.tsx
        │   ├── components
        │   │   ├── atoms
        │   │   └── block
        │   ├── pages
        │   ├── resetStyle.tsx
        │   └── variables.tsx
        ├── utils(定数などを管理)
        └── views
            ├── components(使い回しのできる要素)
            │   ├── atoms(最小単位のcomponent)
            │   ├── block(atomsを組み合わせたり、atomsでは管理しきれないcomponent)
            │   └── modules(機能を持ったcomponent)
            └── pages(各ページの呼び出し先)
                ├── login
                └── top

index.pugtsx/index.tsxをrenderする形なので、tsxディレクトリ以下を紹介していきます!

index.tsx

エントリーポイントです。
各ページを呼び出し、react-router-domでルーティングを行う役割です。

index.tsx
const app = document.getElementById('app')
// page
import Top from './views/pages/top/Top'
import Login from './views/pages/login/Login'

// react-router-domでページ遷移
ReactDOM.render(
  <Provider store={store}>
    <GlobalStyle />
    <Router>
      <Switch>
        <Route path="/top" component={Top} />
        <Route path="/" component={Login} />
      </Switch>
    </Router>
  </Provider>,
  app
)

views

└── views
    ├── components(使い回しのできる要素)
    │   ├── atoms(最小単位のcomponent)
    │   │   └── Button.tsx
    │   ├── block(atomsを組み合わせたり、atomsでは管理しきれないcomponent)
    │   │   └── Header.tsx
    │   └── modules(機能を持ったcomponent)
    │       └── ScrollTop.tsx
    └── pages(各ページの呼び出し先)
        └── 各ページのディレクトリ
            └── Login.tsx

pages

└── pages(各ページの呼び出し先)
       ├── Top
       │    ├── TopList.tsx
       │    └── Top.tsx
       └── Login
            └── Login.tsx

上記の tsx/index.tsxに呼び出されるコンポーネントです。
各ページに1つこの要素が存在し、そのページ内で使われるコンポーネントはさらにここから呼び出して使用します。
また、そのページにしかない固有の要素は同ディレクトリ内に配置します。
上記のtreeで言うと、TopList.tsが固有の要素にあたります。

例↓

top.tsx
const Top = () => {

  return (
    <div>
      <Header />
      <h1 css={TopTitle}>TOP</h1>
      <TopList />
      <Footer />
    </div>
  )
}

components

pageとは異なる、使い回すことのできる要素をこちらに置きます。
その中にも意味合いが異なる要素が多いため、さらに下記のディレクトリに分けていきます。
atomicデザインでできれば良かったのですが、知見がないためとりあえず下記のようにざっくり作ってます)

├── components(使い回しのできる要素)
       ├── atoms
       │  
       ├── block
       │   
       └── modules
  • atoms
  • block
  • module
atoms

ボタンなどの最小の要素です。変更出来るよう、styleや機能は流し込めるように作成します。

Button.tsx
import React from 'react'
import { css } from '@emotion/core'

export const Button = props => {
  const button = css({
    backgroundColor: `${props.bgColor}`,
    color: `${props.color}`,
    border: `1px solid ${props.bgColor}`,
    padding: '8px 16px',
    borderRadius: '4px',
    cursor: 'pointer'
  })

  return (
    <button css={button} onClick={props.onClick}>
      {props.name}
    </button>
  )
}

ちなみに呼び出すのはこんな感じです↓

Sample.tsx
const Sample = () => {
  const handleClick = () => {
   console.log("click!")
  }

  return (
    <div>
      <Button
        onClick={handleClick}
        name="Click!"
        color="#ffffff"
        bgColor="#33333"
      />
    </div>
  )
}
block

ページに1個しかないHeaderやFooter、また使い回すけどatomsを組み合わせて使う要素を置きます。

modules

atomsへの機能の流し込みが難しかったり、そのコンポーネント固有の機能があるものをここに置きます。
例えば、パンくずリストなどが該当します。

パンくずではないですが、サンプルです↓(この程度ならatomsでいいですけど…)

ScrollTopBtn.tsx
export const ScrollTop = () => {

  const scrollToTop = () => {
    scroll.scrollToTop()
  }

  return (
    <div css={ButtonContainer} onClick={scrollToTop}>
      <p css={ButtonContent}></p>
    </div>
  )
}

store

reduxで使用する要素は全てこちらに配置します。
redux-toolkitを使用しておりますが、使い方に関しては今回割愛します。

slicesディレクトリ以下で Reducer / Actionを定義し(createSlice)、
store/index.tsxでslicesディレクトリ以下で作られたファイルを結合するといった流れです。

├── stores
     ├── index.ts 
     └── slices
         └── userSlice.ts
index.ts
import { configureStore } from '@reduxjs/toolkit'
import loginReducer from './slices/userSlice'

// それぞれのSliceを呼び出して結合する
export default configureStore({
  reducer: {
    // 識別する名前: importしてきたReducer名
    login: loginReducer
  }
})
userSlice.ts
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import axios from 'axios'
import {apiURL} from "../../utils/constants"


// 非同期はSliceの外に出してcreateAsyncThunkを使用する
export const fetchAsyncLogin = createAsyncThunk('login/get', async () => {
  //  ログインAPIを叩く想定
  await axios.get(apiURL)
  //   console.log(res.data)
})

const loginSlice = createSlice({
  //   slice名
  name: 'login',
  //   初期値
  initialState: {
    auth: {
      username: '',
      password: ''
    },
    isLogin: false
  },
  //各reducer 第一引数でstate情報を受け取り、第二引数でユーザーが操作した情報を受け取る
  reducers: {
    editUsername: (state, action) => {
      state.auth.username = action.payload

    },
    editPassword: (state, action) => {
      state.auth.password = action.payload
    },
    logout: (state, action) => {
      state.isLogin = false
    }
  },
  //   非同期の結果を受け取る
  extraReducers: builder => {
    builder.addCase(fetchAsyncLogin.fulfilled, (state, action) => {
      state.isLogin = true
    })
  }
})

// actionをexport
export const { editUsername, editPassword, logout } = loginSlice.actions
// state情報をexport
export const selectUser = (state: any) => state.login
// reducerをexport → storeへ
export default loginSlice.reducer

style

resetCSSや、CSS変数などを定義するために使います。

また、emotionを使っているのでtsxファイル内に書くことができますが、あえて分けたいと言う場合に使用します。
例えば、tsxファイル内が肥大化してしまう場合や、同じcssを使い回す場合などです(コンポーネント切り分けるのが難しいこともあると思うので…)。

ディレクトリの分け方はとりあえずviewsディレクトリと同じ構成にしております。

├── style(各page,componentのcssを切り分けたい時に使用)
       ├── GlobalStyle.tsx
       ├── components
       │   ├── atoms
       │   │   └── Button.tsx
       │   └── block
       │       ├── Footer.tsx
       │       └── Header.tsx
       ├── pages
       │   ├── Login.tsx
       │   └── Top.tsx
       ├── resetStyle.tsx
       └── variables.tsx

utils

tsx内で使用する変数、定数など、全体で使用する要素を置きます。

├── utils(定数などを管理)
     └── constants.tsx
constants.tsx

export const apiURL = ''
// など ...

全体

src下の全体は以下のような形です!

─── src
    ├── img (画像置き場)
    │   └── common
    │       └── favicon.png
    ├── pug (render先を指定)
    │   └── index.pug
    ├── ts (react外でjsを使うときに使用)
    │   └── app.ts
    ├── tsconfig.json
    └── tsx
        ├── index.tsx (エントリーポイント)
        ├── stores
        │   ├── index.ts (slicesディレクトリで作られたSliceを結合する)
        │   └── slices (このディレクトリ下で各sliceファイルを扱う)
        │       └── userSlice.ts
        ├── style(各page,componentのcssを切り分けたい時に使用)
        │   ├── GlobalStyle.tsx
        │   ├── components
        │   │   ├── atoms
        │   │   │   └── Button.tsx
        │   │   └── block
        │   │       ├── Footer.tsx
        │   │       └── Header.tsx
        │   ├── pages
        │   │   ├── Login.tsx
        │   │   └── Top.tsx
        │   ├── resetStyle.tsx
        │   └── variables.tsx
        ├── utils(定数などを管理)
        │   └── constants.tsx
        └── views
            ├── components(使い回しのできる要素)
            │   ├── atoms(最小単位のcomponent)
            │   │   └── Button.tsx
            │   ├── block(atomsを組み合わせたり、atomsでは管理しきれないcomponent)
            │   │   ├── Footer.tsx
            │   │   └── Header.tsx
            │   └── modules(機能を持ったcomponent)
            │       └── ScrollTop.tsx
            └── pages(各ページの呼び出し先)
                ├── login
                │   └── Login.tsx
                └── top
                    ├── Top.tsx
                    └── TopList.tsx(そのページでしか使われないreact-componentは同階層に設置)

作ってみた感想

これを作る上でご意見、ご協力いただいた方々には最高に感謝です:raised_hands:

まだまだ雑なところが多く、使ってみて思うことはたくさんあるのですが、viewsstoreで分ける構成は悪くないんじゃないかな…と思ってます。
うちではこうしてる、ここはこうした方が良いなど、ご意見いただけますと幸いです:hugging:

150
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sotq_17
フロントエンド💻 趣味は東南アジアでのんびり安ビールを飲むことです🧟‍♂️
wiz_inc
Wizは、最新のIoTやICTサービスをお客様に届ける「ITの総合商社」です

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
150
Help us understand the problem. What is going on with this article?