Firebase と React を使用して独自のいいねとコメントのシステムを構築する方法


React アプリに関する私の ToDo 項目の 1 つは、静的な Web サイトにパフォーマンス優先の動的なコメントといいね! システムを導入することでした.なんで?それは、コンテンツを超えて、ユーザーの関与を促す機能を追加する機能をもたらすからです.

Cusdis と Disqus はどちらも Cumulative Layout Shift (CLS) に大きな影響を与えるため、パフォーマンス フレンドリではありません。



そこで、 FirebaseTailwindCSS 、および React を使用してシステムの作成に着手しました. TailwindCSS は強制ではありませんが、私のお気に入りのライブラリです.始めましょう.

Firebase の設定


  • 次のコマンドを使用して、Firebase (クライアント側) をインストールします.

  • npm i firebase
    


  • 次の構成で firebase.js を作成します.

  • // File: @/lib/firebase.js
    
    import 'firebase/firestore'
    import firebase from 'firebase/app'
    
    // More about firebase config on https://firebase.google.com/docs/web/setup#config-object
    var firebaseConfig = {
      apiKey: '',
      authDomain: '',
      projectId: '',
      storageBucket: '',
      messagingSenderId: '',
      appId: '',
    }
    
    if (!firebase.apps.length) {
      firebase.initializeApp(firebaseConfig)
    } else {
      firebase.app()
    }
    
    export const firestore = firebase.firestore()
    export default firebase
    


    類似コンポーネントの作成


  • like.js ファイルを作成します.

  • // File: components/blog/like.js
    
    import { firestore } from '@/lib/firebase'
    


  • ブログページのスラッグを受け取る getLikes 関数と、必要に応じてコールバック関数を追加します.

  • export const getLikes = (slug, callBackFunction) => {
      firestore
        .collection('likes')
        .doc(slug)
        .get()
        .then((doc) => {
          if (doc.exists) {
            callBackFunction(Object.keys(doc.data()).length)
          }
        })
        .catch((err) => {
          console.error(err)
        })
    }
    


  • ブログページのスラッグを受け取る postLike 関数と、必要に応じてコールバック関数を追加します.

  • export const postLike = (slug, callBackFunction) => {
      fetch('https://api.ipify.org/?format=json', {
        method: 'GET',
      })
        .then((res) => res.json())
        .then((res) => {
          firestore
            .collection('likes')
            .doc(slug)
            .set(
              {
                [res['ip']]: null,
              },
              { merge: true }
            )
            .then(callBackFunction)
        })
        .catch((err) => {
          console.error(err)
        })
    }
    


    コメント コンポーネントの作成


  • comment.js ファイルを作成します.

  • // File: components/blog/comments.js
    
    import { useState } from 'react'
    import firebase, { firestore } from '@/lib/firebase'
    


  • ブログページのスラッグを受け取る getComments 関数と、必要に応じてコールバック関数を追加します.

  • export const getComments = (slug, callBackFunction) => {
      firestore
        .collection('comments')
        .get()
        .then((snapshot) => {
          const posts = snapshot.docs
            .map((doc) => doc.data())
            .filter((doc) => doc.slug === slug)
            .map((doc) => {
              return { id: doc.id, ...doc }
            })
          callBackFunction(posts)
        })
        .catch((err) => {
          console.log(err)
        })
    }
    


  • ブログページのスラッグを受け取る writeComment 関数と、必要に応じてコールバック関数を追加します.

  • export const writeComment = (name, slug, content, email, callBackFunction) => {
      let temp = {
        name,
        slug,
        content,
        time: firebase.firestore.Timestamp.fromDate(new Date()),
      }
      if (email.length > 0) temp['email'] = email
      firestore
        .collection('comments')
        .add(temp)
        .then(() => {
          callBackFunction()
        })
        .catch((err) => {
          console.error(err)
        })
    }
    


  • 表示するコメントのセットを取り込む LoadComments 関数の作成

  • export const LoadComments = ({ comments }) => {
      return comments
        .sort((a, b) =>
          a.time.toDate().getTime() > b.time.toDate().getTime() ? -1 : 1
        )
        .map((item) => (
          <div
            key={item.time.seconds}
            className="border dark:border-gray-500 rounded p-5 w-full mt-5 flex flex-col"
          >
            <span className="text-lg text-gray-500 dark:text-gray-300 font-medium">
              {item.name} &middot; {item.time.toDate().toDateString()}
            </span>
            <span className="mt-3 text-md text-gray-500 dark:text-gray-300">
              {item.content}
            </span>
          </div>
        ))
    }
    


  • ブログページのスラッグを取り込む WriteComment コンポーネントと、表示される新しいコメントセットを設定するための setComments を作成します.

  • const WriteComment = ({ slug, setComments }) => {
      const [name, setName] = useState('')
      const [email, setEmail] = useState('')
      const [comment, setComment] = useState('')
    
      return (
        <form
          onSubmit={(e) => {
            e.preventDefault()
            writeComment(name, slug, comment, email, () =>
              getComments(slug, setComments)
            )
            setName('')
            setEmail('')
            setComment('')
          }}
          className="mt-10 flex flex-col w-full"
        >
          <h1 className="font-semibold text-lg">Write a comment</h1>
          <div className="flex flex-col sm:flex-row sm:space-x-5 items-start">
            <input
              required
              value={name}
              placeholder="Name*"
              onChange={(e) => setName(e.target.value)}
              className="mt-5 w-full sm:w-1/2 appearance-none outline-none ring-0 px-5 py-2 border dark:hover:border-white hover:border-black rounded hover:shadow text-black dark:bg-black dark:text-gray-300 dark:border-gray-500"
            />
            <div className="mt-5 w-full sm:w-1/2 flex flex-col space-y-1">
              <input
                value={email}
                placeholder="Email (Optional)"
                onChange={(e) => setEmail(e.target.value)}
                className="w-full appearance-none outline-none ring-0 px-5 py-2 border dark:hover:border-white hover:border-black rounded hover:shadow text-black dark:bg-black dark:text-gray-300 dark:border-gray-500"
              />
              <span className="text-sm text-gray-400">
                Email will remain confidential.
              </span>
            </div>
          </div>
          <textarea
            required
            value={comment}
            onChange={(e) => setComment(e.target.value)}
            placeholder={'Comment*\nMaximum of 500 characters.'}
            className="mt-5 appearance-none outline-none ring-0 pt-5 px-5 pb-10 border dark:hover:border-white hover:border-black rounded hover:shadow text-black dark:bg-black dark:text-gray-300 dark:border-gray-500"
          />
          <button
            type="submit"
            className="w-[200px] appearance-none mt-5 py-2 px-5 text-center rounded border hover:bg-gray-100 dark:hover:bg-[#28282B] dark:border-gray-500"
          >
            Post a comment
          </button>
        </form>
      )
    }
    
    export default WriteComment
    


    動的ブログ コンポーネントの作成


  • 動的ブログの [slug].js ファイルにコンポーネントをロードします.

  • import WriteComment, {
      getComments,
      LoadComments,
    } from '@/components/blog/comments'
    
    export default function Post({ post }) {
      const [comments, setComments] = useState([])
      return <>
        <WriteComment setComments={setComments} slug={post.slug} />
        <div className="mt-10 pt-10 w-full border-t dark:border-gray-500">
          <button
            onClick={() => getComments(post.slug, setComments)}
            className="w-[200px] appearance-none py-2 px-5 text-center rounded border hover:bg-gray-100 dark:hover:bg-[#28282B]   dark:border-gray-500"
          >
            Load Comments
          </button>
        </div>
        <LoadComments comments={comments} />
      </>
    }
    




    私の blog page で例を見ることができます!ソースコードは here で入手できます.