今回も「TypeScriptとReact/Next.jsでつくる実践Webアプリケーション開発」で理解した内容を整理して紹介します。
前回の記事でReactで簡単なコンポーネントの作成方法を解説しました。
この知識だけではまだアプリケーションは開発できません。コンポーネントを組み合わせて初めて画面が構成されます。
コンポーネントに分けたことで再利用しやすくなり、デザイン変更はコンポーネントを修正することで画面を構成する該当のコンポーネントを一度に修正すること出来ます。
とは言え、再利用や汎用性を考えてコンポーネントを作成するとはどういうことかでしょうか。経験がない私には想像がつかないです。自分で一から考えるか、実際にどのように利用しているかを経験するか、書籍を参考にするかのいづれかでしょう。
自分で一から考えるのは相応の時間がかかります。実際の経験もその機会がなければ得られません。
そこで書籍で紹介されている設計を吸収して利用していくのが近道だと思います。
Atomic Designによるコンポーネント設計
Atomic DesignがReactのようなコンポーネント開発にマッチする設計として広く知られているようです。Atomic Designは概念のようなもので、実際に自分が作成するアプリケーションで、どのようにコンポーネントを設計するかは、自分で考える必要があります。
概念から実際のアプリケーションにするには、最初は何から手を付けてよいか分からなかったです。
また、Atomic Designとは関係ないですが、表現とビジネスロジックの分離をすることが良い設計とされています。それぞれPresentational ComponentとContainer Componentと呼ばれています。
表現(見た目・デザイン)とビジネスロジック(アプリケーション固有の処理)を分離することでコンポーネントの見通しが良くなるのいうものです。
Presentational Component
type ButtonProps = {
label: string
text: string
coloer: string
disabled: boolean
onClick: React.MouseEventHandler<HTMLButtonElement>
}
const Button = (props: ButtonProps) => {
const { label, text, coloer, disabled, onClick } = props
return (
<div>
<span>{label} </span>
<button
style={{ backgroundColor: coloer }}
disabled={disabled}
onClick={onClick}>
{text}
</button>
</div>
)
}
export default Button
まずはPresentational Compontneとしてボタン要素を例にします。このボタンは見た目だけ定義してボタン名やボタンをクリックした処理の中身は親から受け取るようになっています。
そうすることで画面のボタンをすべてこのコンポーネントで統一する事ができて見た目が揃いますす。見た目を変更したければこちらのコンポーネントだけ修正すれば個のコンポーネントを利用しているボタンはすべて修正されます。
Container Component
import { useCallback, useState } from 'react'
import Button from './Button'
const CountButton = () => {
const label = 'Count Button'
const max = 5
const [count, setCount] = useState(0)
const onClick = useCallback(() => {
const newCount = count + 1
setCount(newCount)
if (newCount >= max) {
console.log(`最大値${max}に到達`)
}
}, [count])
const disabled = count >= max
const text = disabled ? 'もうクリック出来ません' : `あなたは${count}回クリックしました`
return (
<Button
disabled={disabled}
onClick={onClick}
coloer='red'
label={label}
text={text}></Button>
)
}
export default CountButton
次はContainer Componentの例です。いわゆるビジネスロジックを記載してPresentational Componentを実装する部分です。Presentational Compontneとして上記で定義したボタン要素を組み込んでいます。
処理が間違えていればこのコンポーネントだけ修正すれば済みます。
処理と見た目のコンポーネントが分かれていれば処理を直しているつもりが、見た目にかかわる部分を変更してしまったという事もなくなります。その逆もしかりです。
複数で開発するのにもデザイン担当と処理担当で同時進行で開発が出来ますね。
Atomic Design
デザインを5つの階層に分け、最小から最大の順で、Atoms、Molcules、Organisms、Templates、Pagesです。
- Atoms: 最小の要素、ボタンやテキストなど
- Molcules: 複数のAtomsを組み合わせた要素、ラベル付きテキストボックスなど
- Organisms: AtomsとMolculesを組み合わせてUI、入力フォームなど
- Templates: ページ全体のレイアウト、各要素のレイアウト
- Pages: ページ
AtomsやMolculesは振る舞いを持たず、親からpropsで必要なパラメータを受け取り、配置等の見た目のスタイルなどを実装します。
OrganismsはAtomsやMolculesを組み合わせて具体的なUIを実装します。PresentationalとContainerに分離しておきます。
Templatesはページ全体のレイアウトです。AtomsやMolcules、Organismsのレイアウトを実装します。
Pagesは全体のページです。状態の管理やAPIコールの実行を実装します。
styled-componentsによるスタイル
CSS in JSと呼ばれるライブラリの一つです。JavaScript内にCSSを書くためのものです。ReactでPresentationalなコンポーネントをJavaScriptで書くことができCSSファイルを使うことなく同等の表現力でスタイルを定義出来るようです。
クラス名の重複などを内部で回避してくれて目的のコンポーネントに着目した定義ができて管理しやすくなるメリットがあるようです。
ただし、ライブラリの使い方は別途知っておく必要があります。公式のドキュメントを参照してください。公式ドキュメントのソースは直接変更して動きを確認することが出来ます。
ライブラリを利用するとその分学習コストは増加しますが、長期的に見ると開発効率が上がるので必要に応じて採用を判断する必要があります。今回は書籍に記載されたライブラリをとにかく使ってコンポーネント設計を体験するために、まずは使ってみてみることにします。
Storybook
コンポーネント単位でデザインを確認すること出来るオープンソースツールです。カタログのようにコンポーネントを確認したりテストも出来ます。デザイナー担当者の資料やCSSを別の開発者が組み込む体制より、直接ソースを変更してもらって見た目を確認してもらったほうが開発効率向上が望めます。ソースを修正して保存すると見た目が反映されます。しっかりした公式ドキュメントがあり、日本語で解説した情報もたくさんあります。
参考までにReact(テンプレートにTypeScript)、styled-components、Storybookを初期インストールして小さなサンプルコードで動作させたソースです。
書籍のコンポーネントを流用する
書籍で例としてECショップのサイトを構築しています。AtomsやMolculeは他のサイトでも流用出来ると思いますしそもそもコンポーネントとは再利用性を上げるためのものです。そこでgithubでダウンロード出来るソースを使ってコンポーネントを再利用してみました。
前段で記述したように、Reactの開発経験もないのに、紹介されている再利用等のアイデアは簡単に短時間で思いつくものではないと思います。私は既存のソースを理解するだけでも時間がかかりました。
書籍ではNextjsを利用すること前提で構築されているのでNextjsのライブラリをインストールせず、Reactのみにしてみました。フロントエンドのReactの技術にフォーカスするためと、サーバーサイドが別のフレームワークでも利用したいためです。
現在までにReact、styled components、Storybookは初期インストールしました。ここにコンポーネントを移植してEslintのインストールと設定しました。
なぜかエラーが出るのでどうにかこうにかエラーが出ないように修正してみました。Storybookが起動するまで相応の時間が掛かってしまいました。詳細の経緯が知りたい方はgithubのコメントとその差分を参照してください。
コンポーネントを使ってページを作成
書籍で紹介されたコンポーネント等のAtomsやMolcule、テンプレートなどをつかってページを作成します。レスポンシブルな画面に対応可能なように設計されています。
ページの作成はコンポーネントを配置するだけです。
テンプレート
書籍で紹介されているサンプル画面の構成としてヘッダーとフッターがあってその間にコンテンツを配置するテンプレートです。
src/components/templates/Layout/index.tsx
import Separator from 'components/atoms/Separator'
import Box from 'components/layout/Box'
import Footer from 'components/organisms/Footer'
import Header from 'components/organisms/Header'
interface LayoutProps {
children: React.ReactNode
}
const Layout = ({ children }: LayoutProps) => {
return (
<>
<Header />
<main>{children}</main>
<Separator />
<Box padding={3}>
<Footer />
</Box>
</>
)
}
export default Layout
別のページも構成が同じならこのテンプレートを使用します。別の構成のページが有るならそのテンプレートを追加すれば良いです。
コンテンツ部分のみに注力するだけで画面が作成されて、他のページと統一が取れた画面構成になります。
src/pages/page01.tsx
import Text from 'components/atoms/Text'
import Box from 'components/layout/Box'
import Flex from 'components/layout/Flex'
import Layout from 'components/templates/Layout'
import { Link } from 'react-router-dom'
import styled from 'styled-components'
const Anchor = styled(Text)`
cursor: pointer;
&:hover {
text-decoration: underline;
}
color: white;
`
const Page01 = () => {
return (
<Layout>
<Flex padding={2} justifyContent='center' backgroundColor='primary'>
<Flex
width={{ base: '100%', md: '1040px' }}
justifyContent='space-between'
alignItems='center'
flexDirection={{ base: 'column', md: 'row' }}
>
<Box width='100%'>
<Text as='h1' marginBottom={0} color='white' variant='extraLarge'>
ぺーじ01です。
</Text>
<Text as='h1' marginTop={0} color='white' variant='extraLarge'>
<Link to='/'>
<Text style={{ textDecoration: 'underline' }} variant='mediumLarge' color='white'>
ホーム画面に戻る。
</Text>
</Link>
</Text>
</Box>
<Box width='100%'>
(中略)
</Box>
</Flex>
</Flex>
</Layout>
)
}
export default Page01
テンプレートで作成したコンポーネントの <Layout>
で挟むことでヘッダーとフッターの間にコンテンツを配置して表示されます。
Styled Componentsのtheme機能でシステムの共通的なスタイルを適用する
Styled Componentsのtheme機能でシステム全体で共通に使うスタイルを定義します。
開発者デザインにかかわる値を、ポイント単位で細かく設定したCSSを使っているとデザイン調整に統一が取れないことが後で起こることがあります。
事前に、数パターンの値を決めておいてそれを指定することで画面を作成したあとから微調整するのも、該当した設定値を変更するだけで済みます。
また、Atomsなどで定義したコンポーネントはいわゆるプレゼンテーションコンポーネントです。見た目のみを実装したコンポーネントなので、デザイン担当者とビジネスロジックの実装者が分かれている場合に同時並行で作業ができるメリットがあります。
デザインの調整はデザイン担当者だけで行える設計になっているので、StoryBookを使ってパーツ単位でパラメータを変更するだけです。プログラムに慣れてなくてもCSS感覚で対応可能だと思います。
NextjsのLinkをReact RouterのLinkに変更
書籍はサーバーサイドはNextjsを前提にしているので、NextjsのLinkコンポーネントをReact Routerに変更しました。フロントエンドのReactに注力したいのと、サーバーサイドを他のフレームワークで利用することもしたかったためです。
Styled ComponentsのthemeとReact Routerは各種プロバイダーで挟み込むだけです。
import { createGlobalStyle, ThemeProvider } from 'styled-components'
import { theme } from 'themes'
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
import HomePage from 'pages'
import Page01 from 'pages/page01'
// グローバルのスタイル
const GlobalStyle = createGlobalStyle`
html,
body,
textarea {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
(中略)
`
const router = createBrowserRouter([
{
path: '/',
element: <HomePage />,
},
{
path: '/page01',
element: <Page01 />,
},
])
function App() {
return (
<>
<GlobalStyle />
<ThemeProvider theme={theme}>
<RouterProvider router={router} />
</ThemeProvider>
</>
)
}
export default App
React Routerの使い方は公式ページのチュートリアルで慣れておくと理解が早いです。
まとめ
すでに基本的なコンポーネントとレスポンシブ対応の設計でここまで準備が整っていれば、Reactで開発するのはスピーディーに行えると感じました。スタイルについてはそれぞれのシステムで微調整が必要な場合もありますが、デザイン担当と実装担当で分かれて開発が行えると開発者の負担が大きく減少すると感じました。
ただし、ここまで準備するのを一から行うと相応のオーバーヘッドが掛かります。へたをすると開発の半分くらいの工数を使うのではないでしょうか。
はじめてReactを利用するなら、Reactのみならず、StoryBook、Styaled Components、ESLint、React Routerなどのライブラリを必要に応じて知っておく必要があり、相応の学習コストが掛かります。
このコストを見積もりに入れていないで概算スケジュールを組むと大幅にスケジュールが遅れてしまうと感じました。
事前にコンポーネント設計が出来ている前提でコンポーネントの再利用性を活かして開発すれば、ページの作成とビジネスロジックを担当するコンポーネント開発がシステム全体の工数の大部分になるなあという印象です。
ここまで準備とコンポーネントがあれば、どのシステムでも使い回すことができそうです。