#前置き
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を最初から使ってもいいかもねというお話
公式での扱いは??
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.pug
にtsx/index.tsx
をrenderする形なので、tsxディレクトリ以下を紹介していきます!
###index.tsx
エントリーポイントです。
各ページを呼び出し、react-router-domでルーティングを行う役割です。
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
が固有の要素にあたります。
例↓
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や機能は流し込めるように作成します。
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>
)
}
ちなみに呼び出すのはこんな感じです↓
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でいいですけど…)
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
import { configureStore } from '@reduxjs/toolkit'
import loginReducer from './slices/userSlice'
// それぞれのSliceを呼び出して結合する
export default configureStore({
reducer: {
// 識別する名前: importしてきたReducer名
login: loginReducer
}
})
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
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は同階層に設置)
#作ってみた感想
これを作る上でご意見、ご協力いただいた方々には最高に感謝です
まだまだ雑なところが多く、使ってみて思うことはたくさんあるのですが、views
とstore
で分ける構成は悪くないんじゃないかな…と思ってます。
うちではこうしてる、ここはこうした方が良いなど、ご意見いただけますと幸いです