[パート20 ] GraphSQL、TypesScript、および反応(retweet)でTwitterのクローンを作成する
みんな.
として、私はこのTweeter challengeをやっている
Db diagram
私はretweetsを扱うつもりだった方法を簡素化することを決めた.retweetsは“好き”のように扱われます.私は同じ原理を使うつもりです.
src/db/migrations/createRound - retweetsCountテーブル.TS
src/utils/utilsTS
私はときにつぶやきがretweettedされたときに知っている私のつぶやきエンティティにプロパティを追加しました.
src/entity/tweet.TS
src/resolvers/tweetResolver.TS
“like”関数と同じ動作をするので、コードを少しリファクタリングするつもりです.
src/component/tweets/action/tweetActionButton.TSX
別のカウンタをレンダリングする新しいコンポーネントを作成します.
src/component/tweets/tweetstats.TSX
今日はこれだけです.)
さあさあ.
として、私はこのTweeter challengeをやっている
Db diagram
バックエンド
私はretweetsを扱うつもりだった方法を簡素化することを決めた.retweetsは“好き”のように扱われます.私は同じ原理を使うつもりです.
src/db/migrations/createRound - retweetsCountテーブル.TS
import * as Knex from 'knex'
export async function up(knex: Knex): Promise<void> {
return knex.schema.createTable('retweets', (t) => {
t.increments('id')
t.integer('user_id').unsigned().notNullable()
t.integer('tweet_id').unsigned().notNullable()
t.unique(['user_id', 'tweet_id'])
t.foreign('user_id').references('id').inTable('users').onDelete('CASCADE')
t.foreign('tweet_id').references('id').inTable('tweets').onDelete('CASCADE')
})
}
export async function down(knex: Knex): Promise<void> {
return knex.raw('DROP TABLE retweets CASCADE')
}
src/resolvers/retweetResolver.TSimport { ApolloError } from 'apollo-server'
import { Arg, Authorized, Ctx, Mutation, Resolver } from 'type-graphql'
import { MyContext } from '../types/types'
@Resolver()
class RetweetResolver {
@Mutation(() => String)
@Authorized()
async toggleRetweet(
@Arg('tweet_id') tweet_id: number,
@Ctx() ctx: MyContext
) {
const { db, userId } = ctx
const [tweet] = await db('tweets').where('id', tweet_id)
if (!tweet) {
throw new ApolloError('Tweet not found')
}
const data = {
user_id: userId,
tweet_id: tweet_id,
}
try {
const [alreadyRetweeted] = await db('retweets').where(data)
if (alreadyRetweeted) {
// Delete the retweet and return
await db('retweets').where(data).del()
return 'Retweet deleted'
}
await db('retweets').insert(data)
return 'Retweet added'
} catch (e) {
throw new ApolloError(e.message)
}
}
}
export default RetweetResolver
retweetscountを取得する方法を変更する必要があります.src/utils/utilsTS
export const selectCountsForTweet = (db: Knex) => {
return [
db.raw(
'(SELECT count(tweet_id) from likes where likes.tweet_id = tweets.id) as "likesCount"'
),
db.raw(
`(SELECT count(t.parent_id) from tweets t where t.parent_id = tweets.id and t.type = 'comment') as "commentsCount"`
),
// What I've changed
db.raw(
`(SELECT count(tweet_id) from retweets where retweets.tweet_id = tweets.id) as "retweetsCount"`
),
'tweets.*',
]
}
また、私はTweetResolverのaddTweet機能でretweetのケースを扱う部分を取り除きました.私はときにつぶやきがretweettedされたときに知っている私のつぶやきエンティティにプロパティを追加しました.
src/entity/tweet.TS
@Field()
isRetweeted: boolean
そして、TweetResolverで@ fieldResolverでこれを扱います.src/resolvers/tweetResolver.TS
@FieldResolver(() => Boolean)
async isRetweeted(@Root() tweet: Tweet, @Ctx() ctx: MyContext) {
const {
userId,
dataloaders: { isRetweetedDataloader },
} = ctx
if (!userId) return false
const isRetweeted = await isRetweetedDataloader.load({
tweet_id: tweet.id,
user_id: userId,
})
return isRetweeted !== undefined
}
src/DataLoader/DataLoader.TSisRetweetedDataloader: new DataLoader<any, any, unknown>(async (keys) => {
const tweetIds = keys.map((k: any) => k.tweet_id)
const userId = keys[0].user_id
const retweets = await db('retweets')
.whereIn('tweet_id', tweetIds)
.andWhere('user_id', userId)
return tweetIds.map((id) => retweets.find((r) => r.tweet_id === id))
}),
さあ、フロントエンドの世話をしましょう.フロントエンド
“like”関数と同じ動作をするので、コードを少しリファクタリングするつもりです.
src/component/tweets/action/tweetActionButton.TSX
import Button from '../../Button'
type TweetActionButton = {
id: number
isSth: boolean | undefined
icon: JSX.Element
activeIcon?: JSX.Element
text: string
activeText: string
activeClass: string
onClick:
| ((event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void)
| undefined
}
const TweetActionButton = ({
id,
isSth,
icon,
activeIcon,
text,
activeText,
activeClass,
onClick,
}: TweetActionButton) => {
return (
<Button
text={`${isSth ? activeText : text}`}
variant={`${isSth ? activeClass : 'default'}`}
className={`text-lg md:text-sm`}
onClick={onClick}
icon={isSth && activeIcon ? activeIcon : icon}
alignment="left"
hideTextOnMobile={true}
/>
)
}
export default TweetActionButton
ボタンとretweetbuttonボタンは次のようになります.import { useMutation } from '@apollo/client'
import React, { useEffect } from 'react'
import { MdFavorite, MdFavoriteBorder } from 'react-icons/md'
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
import { TOGGLE_LIKE } from '../../../graphql/tweets/mutations'
import { isLikedState, singleTweetState } from '../../../state/tweetsState'
import Button from '../../Button'
import TweetActionButton from './TweetActionButton'
const LikeButton = ({ id }: { id: number }) => {
const [isLiked, setIsLiked] = useRecoilState(isLikedState(id))
const setTweet = useSetRecoilState(singleTweetState(id))
const [toggleLike, { error }] = useMutation(TOGGLE_LIKE, {
variables: {
tweet_id: id,
},
update(cache, { data: { toggleLike } }) {
setIsLiked(toggleLike.includes('added'))
setTweet((oldTweet) => {
if (oldTweet) {
let count = oldTweet.likesCount
toggleLike.includes('added') ? count++ : count--
return {
...oldTweet,
likesCount: count,
}
}
})
},
})
useEffect(() => {
if (error) {
console.log('Toggle like error', error)
}
}, [error])
return (
<TweetActionButton
id={id}
isSth={isLiked}
icon={<MdFavoriteBorder />}
activeIcon={<MdFavorite />}
onClick={() => toggleLike()}
text="Like"
activeText="Liked"
activeClass="red"
/>
)
}
export default LikeButton
import { useMutation } from '@apollo/client'
import React, { useEffect } from 'react'
import { MdLoop } from 'react-icons/md'
import { useRecoilState, useSetRecoilState } from 'recoil'
import { TOGGLE_RETWEET } from '../../../graphql/tweets/mutations'
import { isRetweetedState, singleTweetState } from '../../../state/tweetsState'
import TweetActionButton from './TweetActionButton'
const RetweetButton = ({ id }: { id: number }) => {
const setTweet = useSetRecoilState(singleTweetState(id))
const [isRetweeted, setIsRetweeted] = useRecoilState(isRetweetedState(id))
const [toggleRetweet, { error }] = useMutation(TOGGLE_RETWEET, {
variables: {
tweet_id: id,
},
update(cache, { data: { toggleRetweet } }) {
setIsRetweeted(toggleRetweet.includes('added'))
setTweet((oldTweet) => {
if (oldTweet) {
let count = oldTweet.retweetsCount
toggleRetweet.includes('added') ? count++ : count--
return {
...oldTweet,
retweetsCount: count,
}
}
})
},
})
useEffect(() => {
if (error) {
console.log('Toggle retweet error', error)
}
}, [error])
return (
<TweetActionButton
id={id}
isSth={isRetweeted}
icon={<MdLoop />}
onClick={() => toggleRetweet()}
text="Retweet"
activeText="Retweeted"
activeClass="green"
/>
)
}
export default RetweetButton
閉じるこの動画はお気に入りから削除されています.それで、私はフックconst settweet = usesetrecoilstate(singletweetstate(id))を通して私のつぶやきを取り戻しました.これは、ローカルつぶやきを更新することができます.別のカウンタをレンダリングする新しいコンポーネントを作成します.
src/component/tweets/tweetstats.TSX
import { useRecoilValue } from 'recoil'
import { singleTweetState } from '../../state/tweetsState'
import { pluralize } from '../../utils/utils'
const TweetStats = ({ id }: { id: number }) => {
const tweet = useRecoilValue(singleTweetState(id))
return (
<div className="flex justify-end mt-6">
<p className="text-gray4 text-xs ml-4">
{pluralize(tweet!.commentsCount, 'Comment')}
</p>
<p className="text-gray4 text-xs ml-4">
{pluralize(tweet!.retweetsCount, 'Retweet')}
</p>
<p className="text-gray4 text-xs ml-4">
{pluralize(tweet!.likesCount, 'Like')}
</p>
</div>
)
}
export default TweetStats
ここでは、私は私のつぶやきをRecolで取得し、結果として、コンポーネントがツイートのように毎回再調整されます.今日はこれだけです.)
さあさあ.
Reference
この問題について([パート20 ] GraphSQL、TypesScript、および反応(retweet)でTwitterのクローンを作成する), 我々は、より多くの情報をここで見つけました https://dev.to/ipscodingchallenge/part-20-creating-a-twitter-clone-with-graphql-typescript-and-react-retweet-2haiテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol