コメントのための絵文字反応—反応におけるリアルタイムコメントシステムの構築[第3部/3]
68625 ワード
にfirst part このシリーズの中で、我々はsecond one 最後にネストしたコメントを追加しました.この3番目と最後の記事では絵文字の反応を追加します.人々がコメントを書く必要なしであなたの内容と対話することができるので、これは役に立ちそうです.代替案はRedditのような投票システムですが、私はEmojisが我々のコメントに少しの色を加えると思うので、私は彼らのために行くことに決めました.
発表:プロジェクトのこの部分をスタンドアローンライブラリにしました.あなたは今、簡単に、パフォーマンスに影響を与えることなく絵文字の反応を追加することができます!ここでチェックしてください.lepre on npm .
我々は笑顔の顔の束だけですべてを遅くしたくないようにEmojisはリアルタイムを更新し、軽量にする必要があります.私はいろいろなライブラリを試みました、しかし、彼ら全員は重すぎました(我々はメガバイトを話しています)、あるいは、遅くなります.私たちは、それぞれのコメントのための反応を必要とし、ライブラリが速くない場合、我々は非常に簡単にサイトを破ることができます.そのため、もちろんいくつかの制限を受けて自分の絵文字ピッカーを作ることにしました. Emojisの限られた選択(これは偉大なものTBH、私はすぐに説明するつもりです). いいえ肌の色の選択肢は、誰もがシンプソン(再び、偉大な)です それぞれの絵文字がそれ自身のカウンタでレンダリングされて、コメントの近くに表示されるので、これらの制限は実際に役に立ちます、そして、現在3304 Emojisで、それは彼ら全員を描くために不可能です.また、我々はちょうどコンテキストに応じてテーマEmojisを使用するように選択することができます.あなたはあなたのブログ料理を使用したいですか?ちょうどあなたのブログをもっと楽しくするためにいくつかの料理関連Emojisを追加します.
私たちはすでに最初の記事でデータスキーマを作成したので、すぐに構造を説明するつもりです.
基本的なものから始めて、それぞれの段階で何かを加えるようにしましょう.新しいフォルダを作成する
アクセシビリティのために正しい属性で絵文字をレンダリングする基本的なコンポーネント
それを選択した回数を示すカウンタと絵文字.
続ける前に、私はこれらの2つの構成要素の違いを説明する必要があると感じます、なぜならば、私が通わなければならなくて、2つの異なるコールバックを呼ぶ必要があった理由に若干の混乱があるかもしれないので
違いは全く簡単です.あなたが記事の冒頭のスクリーンショットで見たように、そこに“選択されていない”emojisとボックスを選択されたEmojisの行をカウンターにthe demo を返します.だから、我々は
このコンポーネントは、選択されていないEmojisの開閉を扱います.我々は、どこでも選択されたものが表示される必要がありますので、すべてのEmojisとのコメントを乱雑にしたくない.また、選択しないEmojisメニューをレンダリングします.
useContext グローバルな状態のような何かを提供することができる反応フックです.それを説明することは、この記事の範囲外です、あなたがより多くを知っているならば、反応ドキュメンテーションは始まる良い場所です.
我々はすべての反応をすべてのコメントに追加保持するコンテキストを作成するつもりです.私は、健全性バックエンドへの呼び出しを減らすためにこれをすることに決めました.
それでは、オープンしましょう
このファイルの完全なコードはthe repo .
この記事の冒頭で述べたように、利用可能なEmojisを定義する必要があります.
必要に応じて、あなたの反応で使用するEmojisの配列を保持するファイルを作成します.
私は
時間はすべてをアセンブルする!
まず、必要なすべてをインポートし、後で必要なグローバル変数を作成します.
最後に、我々は反応をマップし、すべてをレンダリングします.
最後になりますが、少なくとも、データベースを更新するためのServerless関数が必要です.これはコメント作成機能より簡単です.
約束通り、いくつかの基本的なスタイルがあります.
このシリーズは現在完了です.私はそれが誰かにとって有用であることを望みます、そして、すべてはそうでした-大部分-明確な.
あなたが疑問を持っているならば、あなたはここでコメントすることができるか、私のソーシャルメディアで私に手紙を書くことができます.
フルレポGitHub .
デモhere .
フルシリーズ: 1/3Building a Real-Time Commenting System in React
2/3Making Nested Comments
3/3Emoji Reactions for Comments
発表:プロジェクトのこの部分をスタンドアローンライブラリにしました.あなたは今、簡単に、パフォーマンスに影響を与えることなく絵文字の反応を追加することができます!ここでチェックしてください.lepre on npm .
機能
我々は笑顔の顔の束だけですべてを遅くしたくないようにEmojisはリアルタイムを更新し、軽量にする必要があります.私はいろいろなライブラリを試みました、しかし、彼ら全員は重すぎました(我々はメガバイトを話しています)、あるいは、遅くなります.私たちは、それぞれのコメントのための反応を必要とし、ライブラリが速くない場合、我々は非常に簡単にサイトを破ることができます.そのため、もちろんいくつかの制限を受けて自分の絵文字ピッカーを作ることにしました.
データスキーマ
私たちはすでに最初の記事でデータスキーマを作成したので、すぐに構造を説明するつもりです.
commentId
IDかキー(通常、異なるパラメータです、しかし、我々のケースでは、彼らは同じです)、たとえそれが親か子供であるならば、コメント.reactions
は、そのコメントと関連するすべての反応を含む配列です.反応は以下の通りである.emoji
, 絵文字そのものcounter
たびに絵文字をクリック/選択されたlabel
, アクセシビリティのためにコンポーネント
基本的なものから始めて、それぞれの段階で何かを加えるようにしましょう.新しいフォルダを作成する
components
ものをきちんとしておくもの.私は、単に私のものを呼びましたEmoji
.絵文字コンポーネント
アクセシビリティのために正しい属性で絵文字をレンダリングする基本的なコンポーネント
role="img"
and aria-label
.// components/Emoji/Emoji.js
export default function Emoji({ emoji, label, className, onClickCallback }) {
return (
<span
className={
className ? className + " emoji" : "emoji"
}
role="img"
aria-label={label ? label : ""}
aria-hidden={label ? "false" : "true"}
onClick={onClickCallback}
>
{emoji}
</span>
);
}
このコンポーネントは単に絵文字をレンダリングします.小道具emoji
and label
我々は、正気から得るでしょう.className
オプションの追加クラスです.onClickCallback
はonClick
イベント.その後、いくつかの基本的なスタイルを行いますので、今回はクラスも定義します.カウンター付き絵文字
それを選択した回数を示すカウンタと絵文字.
// components/Emoji/EmojiWithCounter.js
import Emoji from "./Emoji";
export default function EmojiWithCounter({emoji, emojiLabel, initialCounter, onIncrease}) {
return (
<span
className="emoji-container"
id={emojiLabel}
onClick={() => onIncrease(emoji)}
>
<Emoji emoji={emoji} label={emojiLabel} />
<div className="emoji-counter-div">
<span className="emoji-counter">{initialCounter}</span>
</div>
</span>
);
}
かなり自己説明、これはその上にカウンタを絵文字をレンダリングします.onIncrease
はonClick
イベント.続ける前に、私はこれらの2つの構成要素の違いを説明する必要があると感じます、なぜならば、私が通わなければならなくて、2つの異なるコールバックを呼ぶ必要があった理由に若干の混乱があるかもしれないので
onClick
イベント.違いは全く簡単です.あなたが記事の冒頭のスクリーンショットで見たように、そこに“選択されていない”emojisとボックスを選択されたEmojisの行をカウンターにthe demo を返します.だから、我々は
Emoji
選択されていないEmojisのコンポーネント.コールバックはデータベース内の新しいオブジェクトを作成し、カウンタを1から開始する.また、それは未選択のボックスから絵文字を削除し、選択したものの行に移動します.EmojiWithCounter
選択したEmojisをレンダリングするコンポーネントです.加算器加算器
このコンポーネントは、選択されていないEmojisの開閉を扱います.我々は、どこでも選択されたものが表示される必要がありますので、すべてのEmojisとのコメントを乱雑にしたくない.また、選択しないEmojisメニューをレンダリングします.
// components/Emoji/EmojiAdder.js
import Emoji from "./Emoji";
import { Fragment, useState } from "react";
import { nanoid } from 'nanoid'
export default function EmojiAdder({selectedEmojis, updateEmojiCount, EMOJI_OPTIONS}) {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const toggleMenu = () => setIsMenuOpen(!isMenuOpen);
// We have an array of already selected emojis
const alreadySelectedEmojis = selectedEmojis.map(e => e.emoji);
// We create an array of Emoji components that are not already selected
const emojiOptions = EMOJI_OPTIONS.filter(
e => !alreadySelectedEmojis.includes(e.emoji)
).map(singleEmoji => (
<Emoji
key={nanoid()}
emoji={singleEmoji.emoji}
label={singleEmoji.label}
onClickCallback={() => {
updateEmojiCount(singleEmoji.emoji); // We pass a callback which will add the emoji to the selected ones on click
toggleMenu();
}}
/>
));
return (
<Fragment>
{emojiOptions.length > 0 && (
<span className="reaction-adder-emoji">
<Emoji
onClickCallback={toggleMenu}
emoji={"+"}
label="emoji-adder"
/>
<EmojiMenu />
</span>
)}
</Fragment>
);
function EmojiMenu() {
return (
<div
className={
isMenuOpen
? "emoji-adder-menu-open"
: "emoji-adder-menu-closed"
}
>
{emojiOptions}
</div>
);
}
}
我々は今、一緒にこれらのコンポーネントのすべてをステッチする必要がありますが、我々はそれを行う前に何か他の必要があります.絵文字文脈
useContext グローバルな状態のような何かを提供することができる反応フックです.それを説明することは、この記事の範囲外です、あなたがより多くを知っているならば、反応ドキュメンテーションは始まる良い場所です.
我々はすべての反応をすべてのコメントに追加保持するコンテキストを作成するつもりです.私は、健全性バックエンドへの呼び出しを減らすためにこれをすることに決めました.
それでは、オープンしましょう
components/Comments/AllComments.js
ファイル.import { useState, useEffect, createContext } from "react";
[...]
const ReactionsContext = createContext(undefined);
export default function AllComments() {
const [reactions, setReactions] = useState();
[...]
useEffect(async () => {
[...]
client
.fetch(`*[_type == "commentReactions"]`)
.then(r => setReactions(r));
}
[...]
return (
<ReactionsContext.Provider value={reactions}>
<ul>{commentList}</ul>
</ReactionsContext.Provider>
);
}
これらの追加で我々は現在アクセスすることができますReactionsContext
とreactions
我々のアプリケーションの至る所から.このファイルの完全なコードはthe repo .
絵文字選択
この記事の冒頭で述べたように、利用可能なEmojisを定義する必要があります.
必要に応じて、あなたの反応で使用するEmojisの配列を保持するファイルを作成します.
私は
lib
フォルダと内部emojiConfig.js
ファイル.const DEFAULT_EMOJI_OPTIONS = [
{
emoji: "😄",
label: "happy",
},
{
emoji: "📚",
label: "books",
},
{
emoji: "😟",
label: "suprised",
},
{
emoji: "🐱",
label: "cat",
},
{
emoji: "🐼",
label: "panda",
},
];
export { DEFAULT_EMOJI_OPTIONS };
今、我々は戻って、我々の反応ブロックを終えることができます.完全反応ブロック
時間はすべてをアセンブルする!
まず、必要なすべてをインポートし、後で必要なグローバル変数を作成します.
import EmojiWithCounter from "./EmojiWithCounter";
import EmojiAdder from "./EmojiAdder";
import { ReactionsContext } from "../Comments/AllComments";
import { DEFAULT_EMOJI_OPTIONS } from "../../lib/emojiConfig";
import {nanoid} from "nanoid";
import { useState, useEffect, useContext } from "react";
import { client } from "../../lib/sanityClient";
let dbDebouncerTimer;
let querySub;
今状態を準備します.export default function ReactionBlock({ commentId }) {
// We get the initial reactions we previously fetched from the Context
// and filter them so we only have the ones for this comment.
// Also, I wanted to sort them by their amount.
const contextReactions = useContext(ReactionsContext)
?.filter(r => r.commentId === commentId)
.map(r => r.reactions)
?.sort((a, b) => (a.counter < b.counter ? 1 : -1))[0];
const [reactions, setReactions] = useState([]);
const [shouldUpdateDb, setShouldUpdateDb] = useState(false);
今、我々はuseEffect
フックは、クエリを購読し、リアルタイムの更新を取得します.useEffect(() => {
// If there are reactions in the context, set them
if (contextReactions) setReactions(contextReactions);
// Subscribe to the query Observable and update the state on each update
const query = `*[_type == "commentReactions" && commentId=="${commentId}"]`;
querySub = client.listen(query).subscribe(update => {
if (update) {
setReactions([
...update.result.reactions.sort((a, b) =>
a.counter < b.counter ? 1 : -1
),
]);
}
});
// Unsubscribe on Component unmount
return () => {
querySub.unsubscribe();
};
}, []);
今、我々は絵文字をクリックするたびにデータベースを更新する機能が必要です.const updateEmojiCount = emoji => {
setShouldUpdateDb(false);
let emojiFromState = reactions.filter(em => em.emoji === emoji)[0];
// If the selected emoji wasn't in the state, it's a new one
if (!emojiFromState) {
emojiFromState = DEFAULT_EMOJI_OPTIONS.filter(
em => em.emoji === emoji
)[0];
emojiFromState.counter = 1;
setReactions(reactions =>
[...reactions, emojiFromState].sort((a, b) =>
a.counter < b.counter ? 1 : -1
)
);
} else {
emojiFromState.counter++;
setReactions(reactions =>
[
...reactions.filter(
rea => rea.emoji !== emojiFromState.emoji
),
emojiFromState,
].sort((a, b) => (a.counter < b.counter ? 1 : -1))
);
}
setShouldUpdateDb(true);
};
この関数はshouldUpdateDb
状態と我々は別の関数を呼び出すためにその変更を聞くことができます.useEffect(() => {
if (shouldUpdateDb) updateReactionsOnDatabase();
setShouldUpdateDb(false);
}, [shouldUpdateDb]);
function updateReactionsOnDatabase() {
clearTimeout(dbDebouncerTimer);
dbDebouncerTimer = setTimeout(() => {
fetch("/api/addReaction", {
method: "POST",
body: JSON.stringify({
commentId: commentId,
reactions: reactions,
}),
});
dbDebouncerTimer = null;
}, 1000 * 1);
}
すべては、データベースの更新を議論するために必要です.私たちの反応ブロックは、データベースの更新を実行しない10のクリックを意味する最後のクリック後にデータベースを1秒後に更新されます.最後に、我々は反応をマップし、すべてをレンダリングします.
const mappedReactions = reactions.map(reaction => (
<EmojiWithCounter
key={nanoid()}
emoji={reaction.emoji}
emojiLabel={reaction}
initialCounter={reaction.counter}
onIncrease={updateEmojiCount}
/>
));
return (
<div className="reaction-block">
{mappedReactions}
<EmojiAdder
selectedEmojis={reactions}
updateEmojiCount={updateEmojiCount}
EMOJI_OPTIONS={DEFAULT_EMOJI_OPTIONS}
/>
</div>
);
完全なコード(同じ順序ではない)は以下の通りです.import EmojiWithCounter from "./EmojiWithCounter";
import {nanoid} from "nanoid";
import EmojiAdder from "./EmojiAdder";
import { useState, useEffect, useContext } from "react";
import { ReactionsContext } from "../Comments/AllComments";
import { client } from "../../lib/sanityClient";
import { DEFAULT_EMOJI_OPTIONS } from "../../lib/emojiConfig";
let dbDebouncerTimer;
export default function ReactionBlock({ commentId }) {
// We get the initial reactions we previously fetched from the Context
const contextReactions = useContext(ReactionsContext)
?.filter(r => r.commentId === commentId)
.map(r => r.reactions)
?.sort((a, b) => (a.counter < b.counter ? 1 : -1))[0];
const [reactions, setReactions] = useState([]);
const [shouldUpdateDb, setShouldUpdateDb] = useState(false);
let querySub = undefined;
useEffect(() => {
// If there are reactions in the context, set them
if (contextReactions) setReactions(contextReactions);
// Subscribe to the query Observable and update the state on each update
const query = `*[_type == "commentReactions" && commentId=="${commentId}"]`;
querySub = client.listen(query).subscribe(update => {
if (update) {
setReactions([
...update.result.reactions.sort((a, b) =>
a.counter < b.counter ? 1 : -1
),
]);
}
});
// Unsubscribe on Component unmount
return () => {
querySub.unsubscribe();
};
}, []);
useEffect(() => {
if (shouldUpdateDb) updateReactionsOnDatabase();
setShouldUpdateDb(false);
}, [shouldUpdateDb]);
// Onclick, update the emoji counter and start a timer to update the database
const updateEmojiCount = emoji => {
setShouldUpdateDb(false);
let emojiFromState = reactions.filter(em => em.emoji === emoji)[0];
if (!emojiFromState) {
emojiFromState = DEFAULT_EMOJI_OPTIONS.filter(
em => em.emoji === emoji
)[0];
emojiFromState.counter = 1;
setReactions(reactions =>
[...reactions, emojiFromState].sort((a, b) =>
a.counter < b.counter ? 1 : -1
)
);
} else {
emojiFromState.counter++;
setReactions(reactions =>
[
...reactions.filter(
rea => rea.emoji !== emojiFromState.emoji
),
emojiFromState,
].sort((a, b) => (a.counter < b.counter ? 1 : -1))
);
}
setShouldUpdateDb(true);
};
// Debouncer to avoid updating the database on every click
function updateReactionsOnDatabase() {
clearTimeout(dbDebouncerTimer);
dbDebouncerTimer = setTimeout(() => {
fetch("/api/addReaction", {
method: "POST",
body: JSON.stringify({
commentId: commentId,
reactions: reactions,
}),
});
dbDebouncerTimer = null;
}, 1000 * 1);
}
const mappedReactions = reactions.map(reaction => (
<EmojiWithCounter
key={nanoid()}
emoji={reaction.emoji}
emojiLabel={reaction}
initialCounter={reaction.counter}
onIncrease={updateEmojiCount}
/>
));
return (
<div className="reaction-block">
{mappedReactions}
<EmojiAdder
selectedEmojis={reactions}
updateEmojiCount={updateEmojiCount}
EMOJI_OPTIONS={DEFAULT_EMOJI_OPTIONS}
/>
</div>
);
}
バックエンド
最後になりますが、少なくとも、データベースを更新するためのServerless関数が必要です.これはコメント作成機能より簡単です.
// pages/api/addReaction.js
import { writeClient } from "../../lib/sanityClient";
export default (req, res) => {
return new Promise(resolve => {
const body = JSON.parse(req.body);
const _id = body.commentId;
const reactions = body.reactions;
reactions.forEach(r => (r._key = r.label));
const query = `*[_type == "commentReactions" && commentId == "${_id}"]{_id}[0]`;
writeClient.fetch(query).then(comment => {
if (comment) {
writeClient
.patch(comment._id)
.set({ reactions: reactions })
.commit()
.then(() => {
resolve(res.status(200).end());
});
} else {
writeClient
.create({
_type: "commentReactions",
commentId: _id,
reactions: reactions,
})
.then(() => {
resolve(res.status(200).end());
});
}
});
});
};
スタイリング
約束通り、いくつかの基本的なスタイルがあります.
.emoji {
margin: 10px;
font-size: 25px;
display: flex;
align-items: center;
cursor: pointer;
vertical-align: middle;
transform: translateZ(0);
box-shadow: 0 0 1px rgba(0, 0, 0, 0);
backface-visibility: hidden;
-moz-osx-font-smoothing: grayscale;
transition-duration: 0.1s;
transition-property: transform;
}
.reaction-div {
margin-top: 5px;
display: inline-flex;
flex-flow: wrap;
}
.emoji-container {
position: relative;
user-select: none;
display: flex;
}
.emoji-counter-div {
position: absolute;
top: -2px;
right: 3px;
z-index: -5;
}
.emoji-counter {
font-weight: bold;
padding: 2px 5px;
border-radius: 30%;
background-color: #f55742;
color: #fefefe;
}
.emoji:hover,
emoji:focus,
emoji:active {
transform: scale(1.1);
}
.comment-info {
margin: auto 0px;
}
.comment-info-container {
height: 40px;
display: flex;
}
.reaction-block {
display: inline-flex;
flex-flow: wrap;
}
.reaction-adder-emoji {
user-select: none;
position: relative;
display: inline-block;
}
.emoji-adder-menu-open {
position: absolute;
display: flex;
top: 0px;
left: 35px;
border-radius: 10px;
box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
background-color: #fefefe;
flex-wrap: wrap;
z-index: 10;
width: 400%;
}
.emoji-adder-menu-closed {
display: none;
}
結論
このシリーズは現在完了です.私はそれが誰かにとって有用であることを望みます、そして、すべてはそうでした-大部分-明確な.
あなたが疑問を持っているならば、あなたはここでコメントすることができるか、私のソーシャルメディアで私に手紙を書くことができます.
フルレポGitHub .
デモhere .
フルシリーズ:
Reference
この問題について(コメントのための絵文字反応—反応におけるリアルタイムコメントシステムの構築[第3部/3]), 我々は、より多くの情報をここで見つけました https://dev.to/pandasekh/emoji-reactions-for-comments-building-a-real-time-commenting-system-in-react-part-3-3-4m6テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol