Skip to main content
Version: 5.3

Building a Component the Redwood Way

このブログに足りないものは何でしょう?コメントですね。簡単なコメントエンジンを追加して、人々が私たちのブログ記事に完全に合理的で十分なコメントを残せるようにしましょう。インターネットですからね。何か問題ありますか?

私たちが構築しなければならない機能は、大きく2つあります:

  1. コメントフォームと作成
  2. コメントの取得と表示

どの順番で構築するかは、私たち次第です。簡単にするために、まずコメントの取得と表示から始めて、それからより複雑な新しいコメントを作成するためのフォームとサービスを追加する作業に移りましょう。もちろんRedwoodですから、フォームやサービスだって それほど 複雑ではありません!

Storybook

それでは1つのコメントを表示するコンポーネントを作りましょう。まずはジェネレータです:

yarn rw g component Comment

Storybook が更新され、 "生成された" コメントのストーリーが準備されるはずです:

image

ユーザに何を要求するか、コメントで表示させたい内容を考えてみましょう。名前とコメントそのものの内容だけではどうでしょうか?あと、そのコメントが作成された日付/時刻を投げ込みます。 Comment コンポーネントを更新して、 comment オブジェクトが3つのプロパティを受け付けるようにしましょう。

web/src/components/Comment/Comment.js
const Comment = ({ comment }) => {
return (
<div>
<h2>{comment.name}</h2>
<time dateTime={comment.createdAt}>{comment.createdAt}</time>
<p>{comment.body}</p>
</div>
)
}

export default Comment

そのファイルを保存してStorybookをリロードすると、ぶっ壊れます:

image

このコメントオブジェクトを取り込んで props として渡すよう、ストーリーを更新しなければなりません:

web/src/components/Comment/Comment.stories.js
import Comment from './Comment'

export const generated = () => {
return (
<Comment
comment={{
name: 'Rob Cameron',
body: 'This is the first comment!',
createdAt: '2020-01-01T12:34:56Z'
}}
/>
)
}

export default {
title: 'Components/Comment',
component: Comment,
}
info

GraphQLから送られてくる日付は ISO8601形式 なので、ここでも同じフォーマットにしなければなりません。

Storybook は再読み込みしてよりhappyになるでしょう:

image

この Comment コンポーネントに、少しスタイリングと日付変換を足して、素敵な完成されたデザイン要素にしましょう:

web/src/components/Comment/Comment.js
const formattedDate = (datetime) => {
const parsedDate = new Date(datetime)
const month = parsedDate.toLocaleString('default', { month: 'long' })
return `${parsedDate.getDate()} ${month} ${parsedDate.getFullYear()}`
}

const Comment = ({ comment }) => {
return (
<div className="bg-gray-200 p-8 rounded-lg">
<header className="flex justify-between">
<h2 className="font-semibold text-gray-700">{comment.name}</h2>
<time className="text-xs text-gray-500" dateTime={comment.createdAt}>
{formattedDate(comment.createdAt)}
</time>
</header>
<p className="text-sm mt-2">{comment.body}</p>
</div>
)
}

export default Comment

image

いい感じ!では、このコンポーネントが私たちの望むとおりに動くかどうか、テストしてみましょう。

Testing

サンタさんが来ないと困るので、きちんと Comment コンポーネントをテストしましょう。作者の名前とコメントの本文が表示されるか、投稿された日付が表示されるかをテストできます。

生成されたコンポーネントに付属するデフォルトのテストは、エラーが投げられないことを確認するだけで、これは私たちがコンポーネントに求める最低限のことです!

テストにサンプルコメントを追加して、各部が描画されることを確認してみましょう:

web/src/components/Comment.test.js
import { render, screen } from '@redwoodjs/testing'

import Comment from './Comment'

describe('Comment', () => {
it('renders successfully', () => {
const comment = {
name: 'John Doe',
body: 'This is my comment',
createdAt: '2020-01-02T12:34:56Z',
}
render(<Comment comment={comment} />)

expect(screen.getByText(comment.name)).toBeInTheDocument()
expect(screen.getByText(comment.body)).toBeInTheDocument()
const dateExpect = screen.getByText('2 January 2020')
expect(dateExpect).toBeInTheDocument()
expect(dateExpect.nodeName).toEqual('TIME')
expect(dateExpect).toHaveAttribute('datetime', comment.createdAt)
})
})

ここでは、出力される createdAt タイムスタンプの両方の要素についてテストしています:出力される実際のテキスト(記事の本文を切り詰めるテストと同様)だけでなく、そのテキストをラップしている要素が <time> タグであり、 comment.createdAt という生の値を持つ datetime 属性を含んでいるかどうかです。これはやりすぎのように思えるかもしれませんが、 datetime 属性のポイントは、ブラウザが(理論的には)フックして何かできるような、機械的に読み取れるタイムスタンプを提供することなのです。このテストでその能力を維持していることを確認します。

もしテストがまだ別のターミナルウィンドウで実行されていないなら、今すぐ開始できます:

yarn rw test
What happens if we change the formatted output of the timestamp? Wouldn't we have to change the test?

そうです。ちょうど、切り詰める長さを変更したら切り詰めテキストを変更しなければならないのと同じです。フォーマットされた出力をテストするための別のアプローチとして、日付フォーマットの形式を Comment コンポーネントからエクスポートできる関数に移動させることができます。 そして、それをテストにインポートして、フォーマットされた出力をチェックするために使うことができます。 これで形式を変更しても、 Comment と関数を共有しているため、テストはパス(成功)し続けることができます。