Edited at

dwango.co.jpをReact/TypeScript/Storybookでリメイクしてみた。


はじめに

この記事は 第2のドワンゴ Advent Calendar 2018 の9日目の記事です。

2018年3月にdwangoに内定をもらい、8月からアルバイトとして働かせてもらっているhiraike32と言います。

2019年4月からは新卒としてお世話になります。

アルバイトの配属はWebフロントエンドで、この4ヶ月間はReact/Reduxをゴリゴリと書いてきました。

今回はアドベントカレンダーの担当をいただいたので、学んできたことを生かしてWebを作ってみます。


dwango.co.jpをリメイクする

dwango.co.jp

スクリーンショット 2018-12-06 13.04.18.png

ちょっと1からWebを作るのは荷が重かったので、dwangoの年代を感じる伝統のあるHPをReactで作り直してみました。

こんな感じです。

dwango-remake

スクリーンショット 2018-12-09 0.38.19.png

まあ、ほとんど同じデザインで作ったので、見た目自体はさほど変わりません。

変わったのは、画像で表現されている部分がcssで書かれていたり、リンクをホバーしたときに表示がわかりやすくなった、くらいですかね。

ただ、設計や開発手法については色々とこだわってみたので、ちょっと紹介していきます。


設計について

基本的な設計は、atomicデザインに沿ってコンポーネントを作って、storybookで確認しながら組み立てていきます。

参考:Atomic Designってデザイナーには難しくない!?という話

単一の要素でてきているものはatoms、複数の要素を組み合わせているものはmolecules、意味を持つ集合体をorganismsとしてパーツごとに作りつつ、pagesで1つにまとめています。

実際にstorybookを見てもらったほうが早いかもしれません。

dwango-remake/storybook

スクリーンショット 2018-12-06 12.40.22.png

今回は再利用できるパーツが多くなかったのですが、開発規模が大きくなってくるとコンポーネントごとに取り回しが簡単にできそうです。

文字数の確認もできたりするので、storybookはけっこう気に入っています。

スクリーンショット 2018-12-06 14.07.28.png


コードについて

ソースコード

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

実際のコンポーネントのコードはこんな感じ。


HeaderMenu.tsx

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に入れ込んで完成。


Top.tsx

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/config.js

// 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);



/.storybook/addons.js

// 設定したいaddonを記載

import "@storybook/addon-actions/register";
import "@storybook/addon-knobs/register";


/.storybook/webpack.config.js

// プロジェクトのものとは別に、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の安心感はすごいなと。

フロントエンドの勉強として、自社のサイトをリメイクしてみてはいかがでしょうか?