コンポーネントを分割する際の指標となるAtomic DesignについてUdemyのこちらの教材から学んだことをまとめさせていただいています。
Reactに入門した人のためのもっとReactが楽しくなるステップアップコース完全版
React特有の概念であるコンポーネント分割について、一概に分けるといっても、どれくらいの粒度で分割すればよいのかわからないもの。その一つの指標となるのがAtomicDesign。
AtomicDesign は以下の5つの粒度に分けられる。
粒度が小さい順に
- Atoms
それ以上分解できない要素 - Molecules
Atomの組み合わせで意味を持つデザインパーツ - Organisms
AtomやMoleculeの組み合わせで構成される単体である程度の意味を持つ要素群 - Templates
実際のデータは持たず、ページのレイアウトのみを表現する要素 - Pages
最終的に表示される一画面
これはあくまで、コンポーネント分割の判断材料になるものであって必ずしもこうしなければならないといったReactのルールがあるわけではない。
それでは、それぞれについて実装しながら見ていきたいと思います。
Atoms
・react-router-domをimport
・スタイルを当てるために、styled-componentsをimport
・componentsディレクトリを作成
・components > atoms > button > PrimaryButton.jsxを作成
propsとしてchildrenを受け取るようにする。
App.jsでPrimaryButtonを呼び出してテストします。
export const PrimaryButton = (props) => {
const { children } = props;
return <button>{children}</button>;
};
import { PrimaryButton } from "./components/atoms/button/PrimatyButton";
import "./styles.css";
export default function App() {
return (
<div className="App">
<PrimaryButton>テスト</PrimaryButton>
</div>
);
}
既存のコンポーネントに更にCSSを上書きする方法
“ボタンのベースは同じにしたいけど、色だけ変えたい”といったようなときに、コンポーネント化をすることで実現することができる。
今回は、テキストカラーは共通とし、背景色が異なるような場合を作成する。
共通要素を記述するコンポーネントBaseButton.jsxを作成し、共通CSSを記述
BaseButtonをimport
普通の書き方と違うところ
const SButton = styled.button`
↓
const SButton = styled(BaseButton)`
こう書くことで、コンポーネントのBaseButtonのCSSの要素を持ったまま、要素を加えていくことができる。
これで、ボタンのベース要素を共通化することが出来ました。
import styled from "styled-components";
import { BaseButton } from "./BaseButton";
export const PrimaryButton = (props) => {
const { children } = props;
return <SButton>{children}</SButton>;
};
const SButton = styled(BaseButton)`
background-color: blue;
`;
import styled from "styled-components";
export const BaseButton = styled.button`
color: white;
`;
Molecules
・components > molecules > SearchInput.jsxを作成
・inputにstyleを当てる
atom配下にinputディレクトリを作成し、inputコンポーネントを作成
・placeholderはpropsで受け取り動的に変えられるようにする。
const { placeholder = "" } = props;
こう記述することでデフォルトで空文字とすることができる。
できたのがこちら
import { PrimaryButton } from "../atoms/button/PrimatyButton";
import { Input } from "../atoms/input/Input";
export const SearchInput = () => {
return (
<div>
<Input placeholder="検索条件を入力" />
<PrimaryButton>検索</PrimaryButton>
</div>
);
};
import styled from "styled-components";
export const Input = (props) => {
const { placeholder = "" } = props;
return <SInput type="text" placeholder={placeholder} />;
};
const SInput = styled.input`
padding: 8px 16px;
border: solid #ddd 1px;
`;
Organismsの作成
・components > organisms > user ディレクトリを作成
・user > UserCard.jsx コンポーネントを作成
・プロフィール情報を記述
・imgに、画像を表示できる便利なサービスUnsplashを使う。
画像をURLで表示する方法
・選んだ画像のURLをコピー
・URLを少し編集
URLをJSXに貼り付ける際にはひと手間必要になる。
例えばURLが以下の場合、JSXに貼り付ける際に以下のように変更する。
URL: https://unsplash.com/photos/BJaqPaH6AGQ
<img src="https://source.unsplash.com/BJaqPaH6AGQ" alt="プロフィール" />
以上のように、photosを削除し、source.を追加する。
・styleを当てる
・UserCardにpropsを渡して表示する
・propsで渡すuserをApp.jsに定義する
ここまでのコードがこちら
import { UserCard } from "./components/organisms/user/UserCard";
const user = {
name: "グール",
image: "https://source.unsplash.com/BJaqPaH6AGQ",
email: "12345@example.com",
phone: "090-1234-5678",
company: {
name: "テスト会社"
},
website: "https://google.com"
};
export default function App() {
return (
<div className="App">
<UserCard user={user}/>
</div>
);
}
import styled from "styled-components";
export const UserCard = (props) => {
const { user } = props;
return (
<div>
<img height={160} src={user.image} alt={user.name} />
<p>{user.name}</p>
<SDL>
<dt>メール</dt>
<dd>{user.email}</dd>
<dt>tel</dt>
<dd>{user.phone}</dd>
<dt>会社名</dt>
<dd>{user.company.name}</dd>
<dt>WEB</dt>
<dd>{user.website}</dd>
</SDL>
</div>
);
};
const SDL = styled.dl`
text-align: left;
dt {
float: left;
}
dd {
padding-left: 32px;
padding-bottom: 8px;
}
`;
カードも他のところで使われることが考えられるため、コンポーネント化する。
コンポーネント化することで、カードデザインを共通化することができる。
・atoms > card > Card.jsx コンポーネントを作成
UserCard.jsxの以下の部分をCardコンポーネントにするようなイメージ
<div>
<img height={160} src={user.image} alt={user.name} />
<p>{user.name}</p>
<SDL>
<dt>メール</dt>
<dd>{user.email}</dd>
<dt>tel</dt>
<dd>{user.phone}</dd>
<dt>会社名</dt>
<dd>{user.company.name}</dd>
<dt>WEB</dt>
<dd>{user.website}</dd>
</SDL>
</div>
Cardコンポーネントでchildrenとして受け取る。
export const Card = (props) => {
const { children } = props;
return <div>{children}</div>;
};
・UserCard.jsx のdivタグをCardに置き換える
・imgとname のセットのコンポーネントを作る。
components > molecules > user > UserIconWithName.jsx
UserCardの以下の部分を移管する。
<img height={160} src={image} alt={name} />
<p>{name}</p>
UserIconWithNameコンポーネントに置き換えて、imageとnameのpropsを受け取る。
<UserIconWithName image={user.image} name={user.name} />
UserIconWithNameコンポーネントは以下のようになる。
export const UserIconWithName = (props) => {
const { image, name } = props;
return (
<div>
<img height={160} src={image} alt={name} />
<p>{name}</p>
</div>
);
};
Templatesの作成
・templatesディレクトリを作成
・templates > DefaultLayout.jsxとHeaderOnly.jsxを作成
・header以外の要素はchildrenにまとめる
・styleを当てる
export const HeaderOnly = (props) => {
const { children } = props;
return (
<>
<div style={{ height: "50px", backgroundColor: "green" }}></div>
{children}
</>
);
};
・App.jsのコンポーネントを囲っているdivタグをHeaderOnlyコンポーネントに置き換える。
import { HeaderOnly } from "./components/templates/HeaderOnly";
省略
export default function App() {
return (
<HeaderOnly>
<PrimaryButton>テスト</PrimaryButton>
<PrimaryButton>検索</PrimaryButton>
<SearchInput />
<UserCard user={user} />
</HeaderOnly>
);
}
headerは別のテンプレートでも使用するため、header部分を別のコンポーネントに分ける。
componets > atoms > layout > Header.jsx
・Linkを作成する
import { Link } from "react-router-dom";
import styled from "styled-components";
export const Header = () => {
return (
<SHeader>
<Link to="/">Home</Link>
<Link to="/users">Users</Link>
</SHeader>
);
};
const SHeader = styled.header`
background-color: green;
`;
import { Header } from "../atoms/layout/Header";
export const HeaderOnly = (props) => {
const { children } = props;
return (
<>
<Header />
{children}
</>
);
};
・BrowserRouterで囲う
Linkを付与したコンポーネントのRouteはBrowserRouterで囲う必要があるため。
Appの中のHeaderOnlyコンポーネント の中のHeaderコンポーネントでLinkを使用している。
import { BrowserRouter } from "react-router-dom";
import { HeaderOnly } from "./components/templates/HeaderOnly";
export default function App() {
return (
<BrowserRouter>
<HeaderOnly>
<UserCard user={user} />
</HeaderOnly>
</BrowserRouter>
);
}
Pagesの作成
・components > pages > Top.jsxとUsers.jsxを作成する
import styled from "styled-components";
export const Top = () => {
return (
<SContainer>
<h2>Topページです</h2>
</SContainer>
);
};
const SContainer = styled.div`
text-align: center;
`;
Usersコンポーネントも同様
・Routerを作成
src > router > Router.jsx
・AppのBrowserRouterをここに移管
・TopとUsersのpathを作る
import { BrowserRouter, Route, Switch } from "react-router-dom";
import { Top } from "../components/pages/Top";
import { Users } from "../components/pages/Users";
export const Router = () => {
return (
<BrowserRouter>
<Switch>
<Route exact path="/">
<Top />
</Route>
<Route path="/users">
<Users />
</Route>
</Switch>
</BrowserRouter>
);
};
そしてApp.jsのルーティングはここで作ったRouterに置き換える。
export default function App() {
return (
<BrowserRouter>
<HeaderOnly>
<PrimaryButton>テスト</PrimaryButton>
<PrimaryButton>検索</PrimaryButton>
<SearchInput />
<UserCard user={user} />
</HeaderOnly>
</BrowserRouter>
);
}
↓
export default function App() {
return <Router />;
}
・ユーザーのサンプルを作る
いくか作るためにユーザー配列を作る。
そしてその作成したusers配列をmapで展開しUserCardを割り当てる。
import styled from "styled-components";
import { SearchInput } from "../molecules/SearchInput";
import { UserCard } from "../organisms/user/UserCard";
const users = [...Array(10).keys()].map((val) => {
return {
id: val,
name: "グール",
image: "https://source.unsplash.com/BJaqPaH6AGQ",
email: "12345@example.com",
phone: "090-1234-5678",
company: {
name: "テスト会社"
},
website: "https://google.com"
};
});
export const Users = () => {
return (
<SContainer>
<h2>Usersページです</h2>
<SearchInput />
{users.map((user) => (
<UserCard key={user.id} user={user} />
))}
</SContainer>
);
};
const SContainer = styled.div`
text-align: center;
`;
ここでたくさん並べたカードをレスポンシブにしていくには、gridの使用が便利!詳細は割愛
まとめ
これでAtom~Pageまでを使いこなして、アプリが完成しました。
Atomic Designとは、コンポーネント分割の指標となるものです。
こうすることで、再利用や、保守の面で良くなるので迷ったときはこれを参照しましょう。
以上、ご覧いただきありがとうございました。
コメント