はじめに
この記事は 第2のドワンゴ Advent Calendar 2018 の9日目の記事です。
2018年3月にdwangoに内定をもらい、8月からアルバイトとして働かせてもらっているhiraike32と言います。
2019年4月からは新卒としてお世話になります。
アルバイトの配属はWebフロントエンドで、この4ヶ月間はReact/Reduxをゴリゴリと書いてきました。
今回はアドベントカレンダーの担当をいただいたので、学んできたことを生かしてWebを作ってみます。
dwango.co.jpをリメイクする
ちょっと1からWebを作るのは荷が重かったので、dwangoの年代を感じる伝統のあるHPをReactで作り直してみました。
こんな感じです。
dwango-remake
まあ、ほとんど同じデザインで作ったので、見た目自体はさほど変わりません。
変わったのは、画像で表現されている部分がcssで書かれていたり、リンクをホバーしたときに表示がわかりやすくなった、くらいですかね。
ただ、設計や開発手法については色々とこだわってみたので、ちょっと紹介していきます。
設計について
基本的な設計は、atomicデザインに沿ってコンポーネントを作って、storybookで確認しながら組み立てていきます。
参考:Atomic Designってデザイナーには難しくない!?という話
単一の要素でてきているものはatoms、複数の要素を組み合わせているものはmolecules、意味を持つ集合体をorganismsとしてパーツごとに作りつつ、pagesで1つにまとめています。
実際にstorybookを見てもらったほうが早いかもしれません。
dwango-remake/storybook
今回は再利用できるパーツが多くなかったのですが、開発規模が大きくなってくるとコンポーネントごとに取り回しが簡単にできそうです。
文字数の確認もできたりするので、storybookはけっこう気に入っています。
コードについて
Reactでコードを書いて、TypeScriptで型づけをしています。
cssについては、sassで書いてcss modulesとして扱うことで、全てをローカルクラスとして処理しています。
コードの整形はPretiierにお任せしています。
【ファイル構成】
src
|-components
||-atoms
||└ HeaderMenu
|| |-index.tsx
|| |-HeaderMenu.tsx
|| |-HeaderMenu.scss
|| |-HeaderMenu.stories.tsx
||-molecules
||-organisms
||-pages
|-img
|-styles
実際のコンポーネントのコードはこんな感じ。
import React from "react";
// アイコンはfont-awesomeから拝借
import { library } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faChevronCircleRight } from "@fortawesome/free-solid-svg-icons";
// cssはmoduleとして受け取ってクラスを当てていく
import styles from "./HeaderMenu.scss";
library.add(faChevronCircleRight);
// 簡単に型を定義
type Props = {
text: string;
href: string;
};
// SFCで簡潔に書く
const HeaderMenu = ({ text, href }: Props) => (
<li className={styles.item}>
<FontAwesomeIcon icon="chevron-circle-right" className={styles.icon} />
<a href={href} className={styles.text} target="_blank">
{text}
</a>
</li>
);
export default HeaderMenu;
静的なページなので、ほとんどstateは使わずにSFCで書いています。
中に入れるデータは親コンポーネントから渡してあげることで、汎用性の高い設計に。
Reactを書くまでは、cssのクラスを流用したり、時にはコードをコピペして統一性を保っていたので、今ではReactに心から感謝しています。
最終的に作ったコンポーネントをPageに入れ込んで完成。
import React from "react";
import styles from "./Top.scss";
// 片っ端から作ったコンポーネントを読み込む
import {
Header,
Navigation,
LeadingBanner,
Information,
ServiceBanner,
Footer
} from "../../organisms";
const Top = () => (
// cssのグリッドレイアウトで、それぞれの位置を調整
<div className={styles.container}>
<div className={styles.header}>
<Header />
</div>
<div className={styles.navigation}>
<Navigation />
</div>
<div className={styles.leadingBanner}>
<LeadingBanner />
</div>
<div className={styles.information}>
<Information />
</div>
<div className={styles.serviceBanner}>
<ServiceBanner />
</div>
<div className={styles.footer}>
<Footer />
</div>
</div>
);
// このコンポーネントをRoot.tsxで読み込んで完了
export default Top;
コンポーネントごとに作るだけなので、作業の見通しを立てやすかったり、修正範囲を特定しやすかったりで、精神の安定も保てました。
大変だったこと
storybookの設定
TypeScriptでstorybookを使うための設定が地味に苦戦しました。
storybookの公式ページ(TypeScript Config)に書いてある通りにやればうまくいくはずですが、awesome-typescript-loader
がうまくいかなかったので、以下のようにやりました。
// 必要なライブラリをインストール
$ yarn add -D @storybook/react @storybook/addons @storybook/addon-knobs @storybook/addon-actions @types/storybook__addon-knobs @types/storybook__react
// storybookファイルを読み込むための設定を記載
import { configure } from "@storybook/react";
const req = require.context("../src", true, /.stories.tsx$/);
function loadStories() {
req.keys().forEach(filename => req(filename));
}
configure(loadStories, module);
// 設定したいaddonを記載
import "@storybook/addon-actions/register";
import "@storybook/addon-knobs/register";
// プロジェクトのものとは別に、storybook専用のものを作る必要がある
module.exports = config => {
config.module.rules.push(
{
// TypeScriptの設定
test: /\.tsx$/,
loader: "ts-loader"
},
{
// scssの設定
test: /\.s?css$/,
use: [
{
loader: "style-loader",
options: {
sourceMap: true
}
},
{
loader: "css-loader",
options: {
root: ".",
modules: true,
importLoaders: 1,
localIdentName: "[path]_[local]_[hash:base64:5]"
}
},
{
loader: "sass-loader",
options: { sourceMap: true }
}
]
},
{
// 画像ファイルの設定
test: /\.(png|jpg|gif)$/,
use: [
{
loader: "file-loader",
options: {
name: "[path][name].[ext]"
}
}
]
}
);
config.resolve.extensions.push(".ts", ".tsx");
return config;
};
これで、src以下の*.stories.tsx
ファイルはstorybookのファイルとして認識されて、反映されるようになります。
公式ドキュメントを読んで自分のコードに反映するのはけっこう難しいと思うので、参考までに載せておきました。
感想
普段は用意されている環境でReactを書いているので、自分で1から環境構築してみるのは良い勉強になりました。
もともとあるデザインをどうやってコンポーネントごとに分解して作っていくかを考えるのも楽しかったです。
初学者のうちは、1からサイトを作っていくのはなかなかにコストが高いですので、既存のサイトを作り直しながら手を動かすというのは、コスパ良く色々なことを学べて良いなと。
そしてTypeScriptの安心感はすごいなと。
フロントエンドの勉強として、自社のサイトをリメイクしてみてはいかがでしょうか?