23
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

第二のドワンゴAdvent Calendar 2018

Day 9

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

Last updated at Posted at 2018-12-08

はじめに

この記事は 第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の安心感はすごいなと。

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

23
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?