React/Redux/redux-saga/TypeScript/Cloud Firestore/Cloud Functionあたりを使ってブログを作った!


こんにちは、アオキです。

今回、タイトルにもありますが
React/Redux/redux-saga/TypeScript/Cloud Firestore/Cloud Function
あたりの技術を使って
Atomic Design
っぽい構成の
ブログサイトを作成したので、工夫した点・苦労した点など書いていければと思います。

実際に作成したサイトがこちらになります
オープンソースとなっていて、githubリポジトリはこちらになります。

ざっくり、こんな感じのUIです↓

はじめに

皆さんはブログを書きたい!と思ったことはありますでしょうか?
人生で一度くらいはありますよね。(?)

僕にもその時期が来ました。

そこで、はてブとかに書こうかなーと考えていたのですが、
せっかくの機会なので自分でオリジナルのブログを作ろうと思いました。

アーキテクチャ

載せる必要ないかもしれないですが、アーキテクチャはこんな感じです。

SPAなサイトで、サーバレスです。

Cloud Functionsは、OGP画像の設定のみに利用しています。

また、デザイン手法として、Atomic Designを採用しました。
Atomic Designについては、こちらの記事がわかりやすいです。

主要機能

今回作成したブログサイトの主要機能は以下の通りです。

・記事の投稿/編集/削除/取得
・タグ機能
・いいね機能

まぁ普通のブログサイトにはあるような機能ですね。
それぞれ、簡単に使用したライブラリなどを説明していきます。

・記事の投稿/編集/削除/表示

こちらはFireStoreに用意されているメソッドを叩くだけです。
公式ドキュメントに詳しく書いてあります。

例えば記事の取得に関して、以下のようなコードになっています。

記事を全て取得するコード
export const getArticles = async () => {
  try {
    const articles: Model.Article[] = [];
    await firebase
      .firestore()
      .collection("articles")
      .orderBy("date", "desc")
      .get()
      .then(snapshot => {
        if(snapshot.empty) {
          return;
        }
        snapshot.forEach(doc => {
          articles.push({
            uid: doc.id,
            content: doc.data().content,
            subTitle: doc.data().subTitle,
            title: doc.data().title,
            date: doc.data().date.toDate(),
            tagIds: doc.data().tagIds,
            goodCount: doc.data().goodCount,
            thumbnailImagePath: doc.data().thumbnailImagePath
          });
        });
      })
      .catch(err => {
        throw new Error(err.message);
      });
    return { articles };
  } catch(error) {
    return { error };
  }
};

このあたりに書いています

⚠︎ このソースのコーディングがペストプラクティスな書き方とは限りません(以下同様)

・タグ機能

react-tag-inputという、ライブラリを利用しました。
以下のようなサジェストも簡単に実装できて、便利でした!

サジェストを表示するコード
import React, { FC, useState, useEffect } from "react";
import { WithContext as ReactTags } from "react-tag-input";

// import css
import "./react-input-tag.css";

// import methods
import * as tagMethod from "methods/tagMethods";

// import models
import * as Model from "models/tagModel";

interface DefaultProps {
  onBlur: (tags: { id: string; text: string; }[]) => void;
  defaultTags?: Model.Tag[];
}

const KeyCodes = {
  comma: 188,
  enter: 13
};

const delimiters = [KeyCodes.comma, KeyCodes.enter];

const AdminCreateArticleTags: FC<DefaultProps> = ({ onBlur, defaultTags }) => {
  const [tags, setTags] = useState<Model.Tag[]>(defaultTags ? defaultTags : []);
  const [suggestions, setSuggestions] = useState<Model.Tag[]>([]);

  useEffect(() => {
    getData();
  }, []);

  const getData = async () => {
    const data = await tagMethod.getTags();
    setSuggestions(data);
  };

  const handleDelete = (i: number) => {
    setTags(tags.filter((tag: Model.Tag, index: number) => index !== i));
  };

  const handleAddition = (tag: Model.Tag) => {
    setTags([...tags, tag]);
  };

  const handleInputBlur = () => {
    tags.forEach(tag => {
      tagMethod.addTag(tag.text);
    });
    onBlur(tags);
  };

  const handleDrag = (tag: Model.Tag, currPos: number, newPos: number) => {
    const newTags = tags.slice();

    newTags.splice(currPos, 1);
    newTags.splice(newPos, 0, tag);
    setTags(newTags);
  };

  return (
    <ReactTags
      tags={tags}
      suggestions={suggestions}
      handleDelete={handleDelete}
      handleAddition={handleAddition}
      handleDrag={handleDrag}
      handleInputBlur={handleInputBlur}
      delimiters={delimiters}
    />
  );
};

export default AdminCreateArticleTags;

このあたりに書いています

・いいね機能


↑みたいな感じで、いいね数を把握するために、firestoreに一つフィールドを追加してあります。

それを更新していく形で実装しました。

コードに関しては、記事の更新とほとんど変わらないので、省略します。

開発してみて

実は、上で挙げた主要機能以外のところにもこだわっていて、

・画像を圧縮してストレージに保存(canvasを利用し、クライアント側で画像を圧縮)
・OGP画像のために、Cloud Functionsを利用
・無駄なimportを減らすために、動的なimportを採用
・記事を5件読み込み、スクロールに応じて5件ずつ取得する機能

などです。

このような機能群を、0から個人開発することで、成長することができました。

皆さんも、何か作ってみたい!と思えるものがあれば是非作ってみてください!

以上になります。