Cells
前のページの最後に挙げた機能(ロード状態、エラーメッセージ、空白のテキスト)は、ほとんどのWebアプリで一般的なものです。私たちは、典型的なコンポーネントにこれらの機能を追加する際に、開発者の生活を楽にするために何ができるか考えました。私たちは、そのための方法を編み出しました。これを Cells (セル)と呼んでいます。セルはデータ取得のための、よりシンプルで宣言的なアプローチを提供します(セルに関する完全なドキュメントを読む)。
これらのロード状態に加えて、セルは自分自身のデータ取得にも責任を持ちます。つまり、親コンポーネントでデータを取得して、それを必要とする子コンポーネントに props を渡すのではなく、セルは完全に自己完結しており、自分自身のデータを取得して表示します!ブログにセルを追加して、セルがどのように動作するか見てみましょう。
セルを作成する際には、いくつかの特別な名前の定数をエクスポートし、Redwoodがそれを処理します。典型的なセルは次のようなものです:
- JavaScript
- TypeScript
export const QUERY = gql`
query FindPosts {
posts {
id
title
body
createdAt
}
}
`
export const Loading = () => <div>Loading...</div>
export const Empty = () => <div>No posts yet!</div>
export const Failure = ({ error }) => (
<div>Error loading posts: {error.message}</div>
)
export const Success = ({ posts }) => {
return posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<div>{post.body}</div>
</article>
))
}
import type { FindPosts } from 'types/graphql'
import type { CellSuccessProps, CellFailureProps } from '@redwoodjs/web'
export const QUERY = gql`
query FindPosts {
posts {
id
title
body
createdAt
}
}
`
export const Loading = () => <div>Loading...</div>
export const Empty = () => <div>No posts yet!</div>
export const Failure = ({ error }: CellFailureProps) => (
<div>Error loading posts: {error.message}</div>
)
export const Success = ({ posts }: CellSuccessProps<FindPosts>) => {
return posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<div>{post.body}</div>
</article>
))
}
React がこのコンポーネントをレンダリングすると、Redwood は QUERY
を実行し、レスポンスを受け取るまでは Loading
コンポーネントを表示します。
クエリ結果が返ってきたら、3つの状態のうち1つが表示されます:
- エラーが発生した場合は
Failure
コンポーネントが表示される - 返されたデータが空だった場合(
null
または空の配列)はEmpty
コンポーネントが表示される - それ以外の場合は
Success
コンポーネントが表示される
また beforeQuery
( QUERY
に渡す前の props を操作する)や afterQuery
(GraphQL から返されるが Success
コンポーネントに送られる前のデータを操作する)といったライフサイクルヘルパーも用意されています。
セルで最低限必要なのは QUERY
と Success
のエクスポートです。 Empty
コンポーネントをエクスポートしない場合、空の結果が Success
コンポーネントに送信されます。 Failure
コンポーネントがない場合、コンソールにエラー出力が送られます。
セルをいつ使うかのガイドラインは、コンポーネントがデータベースや他のサービスから何らかのデータを必要とし、その応答が遅れる可能性がある場合です。いつ何を表示するかはRedwoodに任せて、受け取ったデータでレンダリングされたコンポーネントというハッピーパスに集中することができます。
Our First Cell
通常、ブログではホームページに最近のブログ記事のリストが表示されます。このリストは、私たちの最初のセルにピッタリです。
データベースからデータが必要な場合、一般的に セル を使いたいと思うでしょう。Redwoodのベストプラクティスは、アプリが持つ固有のURLごとにページを作成し、セルでデータを取得して表示することです。そのため、既存の HomePage はこの新しいセルを子ページとしてレンダリングします。
これから繰り返し見ていきますが、Redwoodにはこの機能のためのジェネレータがあります! "Posts" はすでに scaffold ジェネレータで使われていたので、これを "Articles" セルと呼ぶことにしましょう(雛形ファイルは Post
ディレクトリに作成されました)。名前がお互いにかなり異なる方が、頭の中で整理しやすいでしょう。複数のものを表示するつもりなので、 "Article" ではなく、複数形の "Articles" を使うことにします。
yarn rw g cell Articles
このコマンドは /web/src/components/ArticlesCell/ArticlesCell.tsx
に新しいファイルを作成します( test.tsx
と mock.ts
と stories.tsx
ファイルも作成されます)。詳しくはチュートリアルの第5章で詳しく説明します!)。このファイルには、使い始めに必要な boilerplate がいくつか含まれています:
- JavaScript
- TypeScript
export const QUERY = gql`
query ArticlesQuery {
articles {
id
}
}
`
export const Loading = () => <div>Loading...</div>
export const Empty = () => <div>Empty</div>
export const Failure = ({ error }) => (
<div style={{ color: 'red' }}>Error: {error.message}</div>
)
export const Success = ({ articles }) => {
return (
<ul>
{articles.map((item) => {
return <li key={item.id}>{JSON.stringify(item)}</li>
})}
</ul>
)
}
import type { ArticlesQuery } from 'types/graphql'
import type { CellSuccessProps, CellFailureProps } from '@redwoodjs/web'
export const QUERY = gql`
query ArticlesQuery {
articles {
id
}
}
`
export const Loading = () => <div>Loading...</div>
export const Empty = () => <div>Empty</div>
export const Failure = ({ error }: CellFailureProps) => (
<div style={{ color: 'red' }}>Error: {error.message}</div>
)
export const Success = ({ articles }: CellSuccessProps<ArticlesQuery>) => {
return (
<ul>
{articles.map((item) => {
return <li key={item.id}>{JSON.stringify(item)}</li>
})}
</ul>
)
}
セルを生成するときには、大文字小文字いずれも使うことができ、Redwoodは命名するときにこれを正します。これらはすべて同じファイル名( web/src/components/BlogArticlesCell/BlogArticlesCell.tsx
)で作成されます:
yarn rw g cell blog_articles
yarn rw g cell blog-articles
yarn rw g cell blogArticles
yarn rw g cell BlogArticles
複数の単語を使用していることを示す 何らかの しるしが必要です:スネークケース( blog_articles
)、ケバブケース( blog-articles
)、キャメルケース( blogArticles
), パスカルケース(BlogArticles
)のいずれかです。
(2つの単語を使っていることを示さずに) yarn redwood g cell blogarticles
を実行すると、 web/src/components/BlogarticlesCell/BlogarticlesCell.tsx
にファイルが生成されます。
できるだけ早く実行できるように、ジェネレータはセルと同じ名前のルートGraphQLクエリを持っていると仮定し、データベースから何かを取得するために必要な最小限のクエリを提供します。この場合、クエリの名前は articles
です:
- JavaScript
- TypeScript
export const QUERY = gql`
query ArticlesQuery {
articles {
id
}
}
`
export const QUERY = gql`
query ArticlesQuery {
articles {
id
}
}
`
しかし、これは既存の Posts SDL ( api/src/graphql/posts.sdl.ts
) やサービス ( api/src/services/posts/posts.ts
)では有効なクエリ名ではありません(これらのファイルがどこから来たのかは Getting Dynamic の Creating Post Editor セクション をご覧ください)。
Redwoodは便宜上、クエリ要素にセル自体の名前を付けていますが(多くの場合、特定のモデルのためにセルを作成します)、今回はセル名がモデル名と一致していないので、手動で微調整をする必要があります。
クエリ名と Success
の props の両方を posts
に変更する必要があります:
- JavaScript
- TypeScript
export const QUERY = gql`
query ArticlesQuery {
posts {
id
}
}
`
export const Loading = () => <div>Loading...</div>
export const Empty = () => <div>Empty</div>
export const Failure = ({ error }) => (
<div style={{ color: 'red' }}>Error: {error.message}</div>
)
export const Success = ({ posts }) => {
return (
<ul>
{posts.map((item) => {
return <li key={item.id}>{JSON.stringify(item)}</li>
})}
</ul>
)
}
import type { ArticlesQuery } from 'types/graphql'
import type { CellSuccessProps, CellFailureProps } from '@redwoodjs/web'
export const QUERY = gql`
query ArticlesQuery {
posts {
id
}
}
`
export const Loading = () => <div>Loading...</div>
export const Empty = () => <div>Empty</div>
export const Failure = ({ error }: CellFailureProps) => (
<div style={{ color: 'red' }}>Error: {error.message}</div>
)
export const Success = ({ posts }: CellSuccessProps<ArticlesQuery>) => {
return (
<ul>
{posts.map((item) => {
return <li key={item.id}>{JSON.stringify(item)}</li>
})}
</ul>
)
}
このセルを HomePage
に差し込んで、どうなるか見てみましょう:
- JavaScript
- TypeScript
import { MetaTags } from '@redwoodjs/web'
import ArticlesCell from 'src/components/ArticlesCell'
const HomePage = () => {
return (
<>
<MetaTags title="Home" description="Home page" />
<ArticlesCell />
</>
)
}
export default HomePage
import { MetaTags } from '@redwoodjs/web'
import ArticlesCell from 'src/components/ArticlesCell'
const HomePage = () => {
return (
<>
<MetaTags title="Home" description="Home page" />
<ArticlesCell />
</>
)
}
export default HomePage
ブラウザには、データベースにあるブログ記事の id
と GraphQL 固有の __typename
プロパティが実際に表示されるはずです。もし "Empty" と表示されるだけなら 前回作成した scaffold に戻り、いくつかブログ記事を追加してください。いいですね!
Success
コンポーネントの posts
はどこからきたのでしょうか?
この QUERY
ステートメントで呼び出しているクエリは posts
です。このクエリの名前が何であれ、それが Success
でデータを利用できるようになる props の名前になります。
- JavaScript
- TypeScript
export const QUERY = gql`
query ArticlesQuery {
posts {
id
}
}
`
export const QUERY = gql`
query ArticlesQuery {
posts {
id
}
}
`
また、GraphQLクエリの結果を格納した変数名をエイリアスにすると、それが props の名前になります:
- JavaScript
- TypeScript
export const QUERY = gql`
query ArticlesQuery {
articles: posts {
id
}
}
`
export const QUERY = gql`
query ArticlesQuery {
articles: posts {
id
}
}
`
これで posts
の代わりに articles
が Success
で使えるようになります:
- JavaScript
- TypeScript
export const Success = ({ articles }) => { ... }
export const Success = ({ articles }: CellSuccessProps<ArticlesQuery>) => { ... }
それでは前述のエイリアスを使用して、セルの名前と反復処理するデータの一貫性を保つようにしましょう:
- JavaScript
- TypeScript
export const QUERY = gql`
query ArticlesQuery {
articles: posts {
id
}
}
`
export const Loading = () => <div>Loading...</div>
export const Empty = () => <div>Empty</div>
export const Failure = ({ error }) => (
<div style={{ color: 'red' }}>Error: {error.message}</div>
)
export const Success = ({ articles }) => {
return (
<ul>
{articles.map((item) => {
return <li key={item.id}>{JSON.stringify(item)}</li>
})}
</ul>
)
}
export const QUERY = gql`
query ArticlesQuery {
articles: posts {
id
}
}
`
export const Loading = () => <div>Loading...</div>
export const Empty = () => <div>Empty</div>
export const Failure = ({ error }: CellFailureProps) => (
<div style={{ color: 'red' }}>Error: {error.message}</div>
)
export const Success = ({ articles }: CellSuccessProps<ArticlesQuery>) => {
return (
<ul>
{articles.map((item) => {
return <li key={item.id}>{JSON.stringify(item)}</li>
})}
</ul>
)
}
ジェネレータによって query
に追加された id
に加えて、 title
、 body
、 createdAt
の値も取得しましょう:
- JavaScript
- TypeScript
export const QUERY = gql`
query ArticlesQuery {
articles: posts {
id
title
body
createdAt
}
}
`
export const QUERY = gql`
query ArticlesQuery {
articles: posts {
id
title
body
createdAt
}
}
`
このページには、scaffoldで作成したブログ記事のデータのすべてが表示されます:
さて、ここからは古き良きReactコンポーネントの領域です。ブログ記事をいい感じに表示するために、Success
コンポーネントを作り上げます:
- JavaScript
- TypeScript
export const Success = ({ articles }) => {
return (
<>
{articles.map((article) => (
<article key={article.id}>
<header>
<h2>{article.title}</h2>
</header>
<p>{article.body}</p>
<div>Posted at: {article.createdAt}</div>
</article>
))}
</>
)
}
export const Success = ({ articles }: CellSuccessProps<ArticlesQuery>) => {
return (
<>
{articles.map((article) => (
<article key={article.id}>
<header>
<h2>{article.title}</h2>
</header>
<p>{article.body}</p>
<div>Posted at: {article.createdAt}</div>
</article>
))}
</>
)
}
ついに私たちはブログサイトを開設しました!インターネットを飾る最も基本的なブログかもしれませんが、それがどうした!ブログ記事の作成、編集、削除ができ、ホームページで世界中の人が見ることができます(心配しないでください、もっと多くの機能を追加する予定です)。
Summary
おさらいですが、ここまで実際に何をしてきたのでしょうか?
- ホームページを作成
- ブログのレイアウトを作成
- データベーススキーマを定義
- マイグレーションを実行してデータベースを更新しテーブルを作成
- データベーステーブルへのCRUDインターフェイスの足場作り(scaffold)
- データをロードするセルを作成し、loading/empty/failure/successの状態を管理
- セルをページに追加
最後の数ステップは、Redwoodアプリで新機能を構築する際の標準的なライフサイクルです。
今のところ、ちょっとしたHTML以外は、手作業はほとんどありません。特に、データをある場所から別の場所に移動するためだけのコード(plumbing)をたくさん書く必要はありませんでした。こうすると、Web開発はもうちょっと楽しくなると思いませんか?
これからこのアプリにいくつかの機能を追加していきますが、まずは少し寄り道して、Redwoodがデータベースにアクセスする方法と、SDLやサービスファイルが何のためにあるのかを知りましょう。