20201223のReactに関する記事は15件です。

数値入力で input[type="number"]を使ったら、ユーザから問い合わせが増えてしまった話

この記事は SmartHR Advent Calendar 2020 23 日目の記事です。

こんばんは! @diescake です。

今年は、React で「そこそこの規模で、そこそこ機能のフォームアプリケーション」を設計・実装する機会がありました。
技術選定で formik + yup を選んだので、その選定理由と結果を共有しよう!

……そんなふうに考えていた時期が私にもありました。

驚くほど筆が進まず、いつの間にやら当日どころか既に夜になってしまったので tips 的な話に逃げる運びとなりました。ご了承ください。

というわけで、数値入力フォームに何気なく input[type="number"] を利用したら、ユーザから問い合わせが増えてしまった話です!

input[type="number"] とは?

今更説明するまでもないくらい基本的な要素ですが HTML において input はユーザの入力を受け付ける要素で、type 属性に "number" を指定すると、数値入力用のフォームを実現できます。

<input type="number" />

PC Chrome 87 の場合だと、こんな感じの UI でレンダリングされます。

スクリーンショット 2020-12-23 21.17.32.png

大体のブラウザでは、フォーカスするとクリッカブルな アイコンが表示されて、クリックすることでインクリメント・デクリメントできます。

加えて、通常のテキスト入力フォームである input[type="text"] とは異なり input[type="number"] の場合は、数値として valid な値しか入力を受け付けません。

そのため、数値の入力を期待しているフォームでは input[type="number"] を使うと安心です。
なんといっても、入力された value をそのまま parseInt() なり Number() に食わせれば数値として取り出せるのですから…。

そう考えて input[type="number"] を利用しました。

どんな問い合わせが発生したのか?

さて、ウェブアプリケーションをリリースしてユーザが触れるようになると、数値の入力ができない という問い合わせが発生しました。
キーボードを叩いても、数値入力フォームは無反応。テキスト入力フォームは入力できるのに、と……。

問い合わせを受け、プログラムを見直してもフォームに disabled な状態は定義していない……。
何より、テキスト入力は可能なのにどうして数値入力だけできないんや…… ?

うーん、何かしらのユーザ環境依存だったり、ブラウザ依存? そうか! IE ! やはり IE お前なのか!(ぐるぐる目)
などなど、色々と調査をしたものの……。

原因はなんだったのか?

わかってしまえばとても単純で、ユーザの IME で 全角入力 のまま数字キーを叩いてたことが原因でした。
全角数字は valid な数値として扱われないため、キーを叩いてもフォームは反応しません。

さらに悪いことに、この数値入力フォームでは 空入力を許容していませんでした。
つまり初期値の 0 を削除できないということですね。

なんでこんな仕様にしていたか言い訳をすると、フォームに対応する数値の型を number に絞っていたことが原因です。

type = {
  なんらかの数値A: number
  なんらかの数値B: number
  ...
}

こうすると、なんらかの数値 は non nullable として扱えるので、何かと楽なのはお分かりいただけます……よね?

……というわけで、開発都合で、BackSpace キー押しても無反応だし、(全角入力の状態で)数字キーを押しても無反応という、ユーザにとってとても分かりづらい状況が生まれていました。

どう対処したのか?

結局の所、数値入力フォームは input[type="text"] として自由に入力して貰って、onBlur のタイミングでサニタイズ、フォーマットすることにしました。
全角数値や といった入力も、半角文字列に置き換え valid な数値に変換しています。

合わせて、フォームに対応する型定義も、number から number | null や、素直に string に変更する必要がありますが、
ここは、入力中の文字列は、ローカルスコープの useState で保持するようにして、onBlur のタイミングでフォームに対応する値を更新することで number のまま凌ぎました。

いっそここまでくると、非制御コンポーネントにするという手段もありかもしれませんねー。

また、方針は変えずに半角数値のみを許容するにしても、
全角入力のままキーを叩いたら、tooltip で警告を出すような仕組みでも良いかもしれません。

例えば、OS のログイン画面だと、CapsLock のまま password を入力しようとすると tooltip で警告がでますよね。アレみたいなイメージです。

おわりに

話をスムーズにするため、そこはかとなく事実を湾曲しております!?

実際には input[type="number"] ではなくて、同等の動作をする自作コンポーネントに起因した話だったり、私は直接ユーザからの問い合わせを受ける立場になく、サポートチーム経由の話だったりします。

私は常々 SPA を書くエンジニアとして、使いやすさを大事にしていると思い込んでいたんですが、
いざ蓋をあけてユーザに触ってもらうと、多々反省もありました。

子供の頃読んだ 地獄先生ぬ〜べ〜不幸の手紙 の話をよく覚えているのですが、1 人 1 人のヘイトが小さくとも数十万人のヘイトが集まると巨大な怨念になりますよね。
いつしかこの怨念が生霊となって復習を果たしに来ることがないよう、フィードバックを受け止めてカイゼンを重ねていこうと思います!

では、明日は SmartHR の誇る催眠術師 @ouji-miyahara です!

参考文献

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TheGraph(Subgraph)を使って独自のERC20トークンの保有者一覧をフロント(React)に表示する

概要

Ethereumのエコシステムにはいくつか欠かせないツールが存在しますが、その中でもThe Graphの使い方をご紹介いたします。
The Graphは、Ethereumの情報にアクセスするためのAPIを、GraphQLを使って誰もが簡単に構築できるサービスです。

Who are the owners of the CryptoKitties born between January and February of 2018?

https://thegraph.com/docs/introduction

例えば、2018年の1月~2月の間にCryptoKittiesのオーナーだった人は誰か?という情報をEthereumのメインネットから取得しサービス上に表示してあげたいとします。
この場合、サービス事業者はEthereumのノードを立てて、ログを参照しながら独自のDBに情報を保存しつつ、APIを作り、フロント側に渡すみたいなことをしないといけません。

ここで、The Graphを使うことで、indexしておきたいデータをcontractのEventを軸に設定でき、簡単にGraphQLを使ったAPIを構築できます。

そこで今回は、ERC20のトークンをデプロイし、そのEvent情報をThe Graphを用いて取得し、Reactで構築されたフロント側に表示したいと思います。

環境やツール

  • MacOS Catalina
  • Node v14.5.0
  • graph-cli
  • Remix
  • OpenZeppelin
  • Github
  • CreateEthApp

ERC20のデプロイ

今回ERC20は OpenZeppelin を使って開発します。
OpenZeppelinは厳格にテストされたスマートコントラクトのライブラリを提供してくれるサービスです。

その中でも今回は、ERC20.sol を使っていきます。
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol

ERC20のコードはこちら

pragma solidity ^0.6.0;

import "https://github.com/OpenZeppelin/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";

contract AdventCalendarToken is ERC20 {

    string private _name = "AdventCalendarToken";
    string private _symbol = "ACT";
    uint8 private _decimals = 18;

    address account = msg.sender;
    uint value = 10000000000000000000000;

    constructor() ERC20( _name, _symbol) public {
        _mint(account, value);
    }
}

トークンの名前を AdventCalendarToken
初期発行量を 10000000000000000000000
初期発行分の受け取りアドレスはコントラクトをデプロイしたアドレスです。

コントラクト開発やトークンのデプロイなどをまだやったことがないという方はこちらから具体的なトークン発行のフローを確認してください。
https://note.com/toshitoissho/n/n93eadf07fd47

今回ERC20は Rinkeby 環境にデプロイしています。

また、デプロイしたERC20トークンのコントラクトアドレスがこちらです。
0x45882688e3Ef40Aa727F4EEB1cba11D1b29E228D

Etherscanを参照:
https://rinkeby.etherscan.io/address/0x45882688e3Ef40Aa727F4EEB1cba11D1b29E228D

ERC20には2種類のEventが設定されています。
https://eips.ethereum.org/EIPS/eip-20#events

event Transfer(address indexed _from, address indexed _to, uint256 _value)
event Approval(address indexed _owner, address indexed _spender, uint256 _value)

Subgraphでは、このEvent情報を軸にデータを構築していきます。
ちなみに Transfer はトークンの送金を行った際に発火するEventで、 Approval は第三者アドレスに対してトークンのTransfer権限を与えた時(approveした時)に発火するEventです。

今回はTransferイベントの情報を表示したいと思いますので、事前に自分のウォレットから複数回他のウォレットアドレスに対してTransfer(送金)しておきましょう。

Subgraphの構築

コントラクトの準備ができたら、Subgraphを作っていきます。

アカウント登録

まずはThe Graph上でアカウントを登録します。
アカウント登録にはGithubアカウントが必要です。
https://thegraph.com/

アカウント登録できたら、次にgraph-cliをインストールします。

Subgraphのセットアップ

# NPM
$ npm install -g @graphprotocol/graph-cli

# Yarn
$ yarn global add @graphprotocol/graph-cli

次に作業用のディレクトリを作ります。

$ mkdir subgraph
$ cd subgraph

その後、Subgraphのプロジェクトを作成します。

$ graph init

以下のような問いが出てきますので、設定していきましょう。

✔ Subgraph name · toshiakitakase/advent-calendar-token
✔ Directory to create the subgraph in · advent-calendar-token
✔ Ethereum network · rinkeby
✔ Contract address · 0x45882688e3Ef40Aa727F4EEB1cba11D1b29E228D
✖ Failed to fetch ABI from Etherscan: ABI not found, try loading it from a local file
✔ ABI file (path) · ./abi.json
✔ Contract Name · AdventCalendarToken

Subgraph name<GITHUB_USERNAME>/<SUBGRAPH_NAME> で、ご自身の情報を設定していきます。
Ethereumのnetworkはコントラクトをデプロイした環境と同一の Rinkeby を使います。
Contract addressは先ほどERC20をデプロイしたコントラクトを使います。
ABIはローカルでコントラクトを開発してる場合はそのパスを指定します。
今回はRemixでコントラクトを開発したので、Remixからabiを取得し、 subgraph ディレクトリ内に保存し(abi.json)、そのパスを指定しました。
Contract nameは実装したコントラクトの名前を指定します。

これでプロジェクトができました。 lsすると advent-calendar-token ができていることが確認できます。

$ ls
abi.json        advent-calendar-token

Subgraphの実装

ここからSubgraphの実装を進めていきます。 dataSources 内に startBlock 追加します。

subgraph.yaml
specVersion: 0.0.2
schema:
  file: ./schema.graphql
dataSources:
  - kind: ethereum/contract
    name: AdventCalendarToken
    network: rinkeby
    source:
      address: "0x45882688e3Ef40Aa727F4EEB1cba11D1b29E228D"
      abi: AdventCalendarToken
      startBlock: 7751082 #ここを追加
    mapping:
      kind: ethereum/events
      apiVersion: 0.0.4
      language: wasm/assemblyscript
      entities:
        - Approval
        - Transfer
      abis:
        - name: AdventCalendarToken
          file: ./abis/AdventCalendarToken.json
      eventHandlers:
        - event: Approval(indexed address,indexed address,uint256)
          handler: handleApproval
        - event: Transfer(indexed address,indexed address,uint256)
          handler: handleTransfer
      file: ./src/mapping.ts

これはどのBlockから同期を開始するかを指定するものです。
未指定の場合、同期に時間がかかってしまうので、コントラクトをデプロイしたBlockから同期するようにしましょう。

次にSchemaを設定していきます。
今回は、トークンの所有者一覧を表示したいので、ウォレットのアドレス・トークンのアドレス・ウォレットの残高を含んだEntityを実装します。
初期状態で記載されているものは削除し、下記に置き換えます。

schema.graphql
type AccountTokenBalance @entity {
  id: ID! #wallet address
  token: String! #token address
  balance: BigInt! #現在のトークン残高
}

その後下記のコマンドを実行することで、/generated にschemaが生成されます。

$ graph codegen

そしてここからEventに応じたロジックを実装していきます。

mapping.ts
import { BigInt } from "@graphprotocol/graph-ts";
import { Transfer } from "../generated/AdventCalendarToken/AdventCalendarToken";
import { AccountTokenBalance } from "../generated/schema";

// Transferのイベントが発火された時に実行されるfunction
export function handleTransfer(event: Transfer): void {
  let tokenId = event.address.toHex();
  let toAddress = event.params.to.toHex();
  let toAccount = AccountTokenBalance.load(toAddress);
  if (toAccount == null) {
    toAccount = new AccountTokenBalance(toAddress);
    toAccount.balance = BigInt.fromI32(0);
  }
  toAccount.balance = toAccount.balance.plus(event.params.value);
  toAccount.token = tokenId;
  toAccount.save();

  let fromAddress = event.params.from.toHex();
  if (fromAddress == "0x0000000000000000000000000000000000000000") {
    return;
  }

  let fromAccount = AccountTokenBalance.load(fromAddress);
  if (fromAccount == null) {
    fromAccount = new AccountTokenBalance(fromAddress);
    fromAccount.balance = BigInt.fromI32(0);
  }
  fromAccount.balance = fromAccount.balance.minus(event.params.value);
  fromAccount.token = tokenId;
  fromAccount.save();
}

ここでは、TransferのEventが発火するたびに、トークンのfromアドレスとtoアドレスの残高を書き換えています。
トークンが最初にmintされた時から全てのEventをチェックして計算してくれるので、現在の残高を取得できます。
これを利用して、トークンの保有者一覧を生成していきます。

ここまででSubgraphの実装は完了です。

Subgraphのデプロイ

次にアカウントの認証を実行します。

$ graph auth https://api.thegraph.com/deploy/ <access-token>

access-token は, https://thegraph.com/explorer/dashboard/ でログインすると dashboard から確認できます。

次に advent-calendar-token に入り、プロジェクトをデプロイします。

$ cd advent-calendar-token
$ yarn deploy

しかし、ここでおそらくエラーになります。

✖ Failed to deploy to Graph node https://api.thegraph.com/deploy/: subgraph name not found: toshiakitakase/advent-calendar-token

You may need to created it at https://thegraph.com/explorer/dashboard.

SubgraphのデプロイにはThe GraphのDashboardでもプロジェクトの作成が必要です。
https://thegraph.com/explorer/dashboard/ にアクセスし、プロジェクトの設定を行います。

Dashboard上から新規にSubgraphを作成します。

Screen Shot 2020-12-20 at 15.37.26.png

SUBGRAPH NAMEは事前に作ったSubgraph nameを advent-calendar-token としているため、 Advent Calendar Tokenとします。
(この名前をみてデプロイしているので、正しい名前をつけましょう)

一般公開したくないSubgraphの場合、 HIDE をONにします。

その後、再度Deployを実行します。

$ cd advent-calendar-token
$ yarn deploy

以下のように表示されれば成功です。

Build completed: Qmdhv61aXCnsyMUmdAikDxWRXvn81M54RfEgYmnafHFNpu

Deployed to https://thegraph.com/explorer/subgraph/toshiakitakase/advent-calendar-token

Subgraph endpoints:
Queries (HTTP):     https://api.thegraph.com/subgraphs/name/toshiakitakase/advent-calendar-token
Subscriptions (WS): wss://api.thegraph.com/subgraphs/name/toshiakitakase/advent-calendar-token

※注意点※
なお、SubgraphはEthereumのブロックと同期し、Event情報を取得しているため、デプロイしてすぐに使えるわけではありません。
下の画像の通り、Sync(同期)までには一定の時間がかかりますのでお気をつけください。
Screen Shot 2020-12-20 at 15.46.39.png

同期が完了したらSubgraphの作成成功です。

Reactを使ったフロント構築

ここからはフロントエンドの実装を進めていきます。
フロントの実装にはCreate React Appのように簡単にEthereumのDappを作れるCreate Eth Appを使っていきます。

Create Eth App
https://github.com/paulrberg/create-eth-app

まずは token_tracker という名でプロジェクトを作ります。

$ yarn create eth-app token_tracker
$ cd token_tracker
$ yarn react-app:start

http://localhost:3000/ にアクセスしい以下が表示されればOKです。

Screen Shot 2020-12-20 at 20.37.16.png

Create Eth Appにはweb3Modal( https://github.com/Web3Modal/web3modal )が組み込まれており、Connect Wallet でMetamaskに簡単接続されるなど、DApp開発で便利な実装が最初から含まれています。

今回はSubgraphの呼び出しのみを行うので、それらに関する実装を進めていきましょう。
まずは subgraph.js 内でクエリを書いていきます。

src/graphql/subgraph.js
import { gql } from "apollo-boost";

const GET_TRANSFERS = gql`
  {
    accountTokenBalances(orderBy: balance) {
      id
      token
      balance
    }
  }
`;

export default GET_TRANSFERS;

次に、/src/index.js にSubgraphのAPIのURLをセットします。

src/index.js
const client = new ApolloClient({
  uri:
    "https://api.thegraph.com/subgraphs/name/toshiakitakase/advent-calendar-token",
});

これで設定は完了です。
あとは App.js内で呼び出していきましょう。

初期に記載されている内容は今回ほとんど利用しないため、一旦削除し、下記のように実装します。

src/App.js
import React, { useEffect, useState } from "react";
import { useQuery } from "@apollo/react-hooks";
import GET_TRANSFERS from "./graphql/subgraph";

function App() {
  const { loading, error, data } = useQuery(GET_TRANSFERS);
  const [tokenHolders, setTokenHolders] = useState([]);

  useEffect(() => {
    if (!loading && !error && data) {
      setTokenHolders(data.accountTokenBalances);
    }
  }, [loading, error, data]);

  return (
    <div>
      {tokenHolders.map((tokenHolder) => (
        <div key={tokenHolder.id} style={{ margin: "20px" }}>
          <div>アドレス {tokenHolder.id}</div>
          <div>残高 {tokenHolder.balance}</div>
        </div>
      ))}
    </div>
  );
}

export default App;

これで完成!

画面をみると4つのアドレスがトークンを保有していることがわかります。

Screen Shot 2020-12-23 at 23.23.31.png

このようなトークン保有者一覧や、いつの時点でいくつ持っていたのか?といった情報を管理するにはノードの運用やDBの構築などで開発者にとってはコストになっていきます。
これを簡単な実装のみでSubgraphにデータのIndexを任せて、フロントから簡単にデータを取得できるのは開発者にとってはとっても楽ですね。

これからDAppを作る予定の方は是非TheGraphを試してみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React】Qiita 週間LGTM数ランキング【自動更新】

他のタグ

AWS Android Docker Git Go iOS Java JavaScript Linux Node.js PHP Python Rails React Ruby Swift TypeScript Vim Vue.js

集計について

  • 期間: 2020-12-17 ~ 2020-12-24
  • 条件: ストック数が 2 以上の記事

ソースコード:

LGTM 数ランキング

1 位: フロントエンドエンジニアがSPAを開発する上で理解しておきたいこと

JavaScript SPA Vue.js React 新人プログラマ応援

130 LGTM
@shohta-noda さん ( 2020-12-22 10:16 に投稿 )

2 位: ぴえん?かどうか判定するクソアプリをつくった

React TensorFlow クソアプリ

88 LGTM
@kokushin さん ( 2020-12-18 17:29 に投稿 )

3 位: Next.jsの自分なりのベストプラクティスを6点紹介する

TypeScript React next.js

16 LGTM
@redshoga さん ( 2020-12-18 22:40 に投稿 )

4 位: React開発者必見!!先日発表されたReact Server Componentsとは?

JavaScript AdventCalendar TypeScript React ssr

15 LGTM
@Rui1009 さん ( 2020-12-23 00:51 に投稿 )

5 位: AWSとReactで始めるShopifyアプリ開発イベントレポート

AWS React classmethod shopify

15 LGTM
@t-kurasawa さん ( 2020-12-17 18:49 に投稿 )

6 位: React初案件獲得までの流れ(学習・案件探し・面接・契約まで)&Udemy活用術

TypeScript React reactnative redux next.js

13 LGTM
@newt0 さん ( 2020-12-19 17:47 に投稿 )

7 位: 【SSRとは】シンプルなHTML・CSRと比べながらSSRを理解する

Vue.js React CSR ssr nuxt.js

12 LGTM
@mtoyopet さん ( 2020-12-22 11:24 に投稿 )

8 位: JavaScriptエキスパートになるための36の概念

JavaScript Node.js プログラミング 翻訳 React

10 LGTM
@baby-degu さん ( 2020-12-21 09:54 に投稿 )

9 位: Next.jsを使うべき5つの理由 + 実装Tips

JavaScript Node.js nginx React next.js

10 LGTM
@Yuki_Oshima さん ( 2020-12-19 00:04 に投稿 )

10 位: React開発効率を3倍にするVS Code拡張機能&環境設定

React reactnative VSCode redux next.js

9 LGTM
@newt0 さん ( 2020-12-19 00:44 に投稿 )

11 位: styled-componentsと一緒に使うと便利なstyled-systemの紹介

React css-in-js styled-components styled-system

7 LGTM
@kourin1996 さん ( 2020-12-17 17:57 に投稿 )

12 位: React Server Componentについてまとめてみた

JavaScript TypeScript React

6 LGTM
@Yuki-Kurita さん ( 2020-12-23 11:40 に投稿 )

13 位: Next.jsでサウナで整いたいお気持ち度合い記録アプリを3時間で作る

JavaScript Node.js React next.js Vercel

6 LGTM
@yutaroshimamura さん ( 2020-12-19 01:35 に投稿 )

14 位: バリデーションにreact-hook-formが便利だった

Validation React next.js react-hook-form

5 LGTM
@282Haniwa さん ( 2020-12-24 17:32 に投稿 )

15 位: Next.js+GrapqhQLでShopifyアプリを開発する

React GraphQL next.js shopify

4 LGTM
@hal_256 さん ( 2020-12-21 20:21 に投稿 )

16 位: React Hooksについてと、便利な独自フックのご紹介

JavaScript hooks React

4 LGTM
@sai1023 さん ( 2020-12-18 23:31 に投稿 )

17 位: 色々な角度から見た GraphQL

初心者向け swagger React GraphQL NestJS

4 LGTM
@a-tsu さん ( 2020-12-17 13:07 に投稿 )

18 位: React高階コンポーネント(HOC)について

React HOC

3 LGTM
@nouka さん ( 2020-12-22 19:09 に投稿 )

19 位: React Hooks でキーワードによる絞り込み検索を実装する

JavaScript TypeScript React react-hooks

3 LGTM
@takf-jp さん ( 2020-12-20 23:55 に投稿 )

20 位: React Naivigation v5 画面遷移時のアニメーション

React reactnative

3 LGTM
@impl_chamuji さん ( 2020-12-17 19:36 に投稿 )

21 位: TheGraph(Subgraph)を使って独自のERC20トークンの保有者一覧をフロント(React)に表示する

Ethereum React solidity GraphQL TheGraph

2 LGTM
@toshiaki_takase さん ( 2020-12-23 23:45 に投稿 )

22 位: React Recoil事始め

React Recoil

1 LGTM
@taka4sato さん ( 2020-12-22 03:38 に投稿 )

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React】Qiita 週間 LGTM 数ランキング【自動更新】

他のタグ

AWS Android Docker Git Go iOS Java JavaScript Linux Node.js PHP Python Rails React Ruby Swift TypeScript Vim Vue.js 初心者

集計について

  • 期間: 2020-12-20 ~ 2020-12-27
  • 条件: ストック数が 2 以上の記事

ソースコード:

LGTM 数ランキング

1 位: フロントエンドエンジニアがSPAを開発する上で理解しておきたいこと

JavaScript SPA Vue.js React 新人プログラマ応援

253 LGTM
@shohta-noda さん ( 2020-12-22 10:16 に投稿 )

2 位: React開発者必見!!先日発表されたReact Server Componentsとは?

JavaScript AdventCalendar TypeScript React ssr

18 LGTM
@Rui1009 さん ( 2020-12-23 00:51 に投稿 )

3 位: 【SSRとは】シンプルなHTML・CSRと比べながらSSRを理解する

Vue.js React CSR ssr nuxt.js

12 LGTM
@mtoyopet さん ( 2020-12-22 11:24 に投稿 )

4 位: JavaScriptエキスパートになるための36の概念

JavaScript Node.js プログラミング 翻訳 React

11 LGTM
@baby-degu さん ( 2020-12-21 09:54 に投稿 )

5 位: バリデーションにreact-hook-formが便利だった

Validation React next.js react-hook-form

9 LGTM
@282Haniwa さん ( 2020-12-24 17:32 に投稿 )

6 位: React Server Componentについてまとめてみた

JavaScript TypeScript React

8 LGTM
@Yuki-Kurita さん ( 2020-12-23 11:40 に投稿 )

7 位: Rails(APIモード)× React × TypeScriptで作るシンプルなTodoアプリ

Rails TypeScript React Todoアプリ

4 LGTM
@kazama1209 さん ( 2020-12-24 19:41 に投稿 )

8 位: Next.js+GrapqhQLでShopifyアプリを開発する

React GraphQL next.js shopify

4 LGTM
@hal_256 さん ( 2020-12-21 20:21 に投稿 )

9 位: TheGraph(Subgraph)を使って独自のERC20トークンの保有者一覧をフロント(React)に表示する

Ethereum React solidity GraphQL TheGraph

3 LGTM
@toshiaki_takase さん ( 2020-12-23 23:45 に投稿 )

10 位: React高階コンポーネント(HOC)について

React HOC

3 LGTM
@nouka さん ( 2020-12-22 19:09 に投稿 )

11 位: React Hooks でキーワードによる絞り込み検索を実装する

JavaScript TypeScript React react-hooks

3 LGTM
@takf-jp さん ( 2020-12-20 23:55 に投稿 )

12 位: Reactで開発してみた 〜色々気づいたことプチまとめ〜

React react-hooks

2 LGTM
@nakamo-03 さん ( 2020-12-25 20:04 に投稿 )

13 位: React Recoil事始め

React Recoil

1 LGTM
@taka4sato さん ( 2020-12-22 03:38 に投稿 )

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactのプロジェクトにexlint-config-airbnbを導入する

きっかけ

普通のeslintを入れるよりも、exlint-config-airbnbを入れた方が、コードの規約が厳しいので、可読性が上がるとのこと。

https://speakerdeck.com/watanabeyu/du-miyasuikodofalseshu-kifang-falsesusume?slide=11

前提

create-react-appで作成したプロジェクトに導入します。

導入

必要なパッケージをインストールする。

$ npm i -D eslint-config-airbnb eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react

.eslintrcをプロジェクトディレクトリの直下に作成し、以下のように記述する。

{
  "extends": "airbnb"
}

以上で導入は完了。

propsを型チェックする

以下のようなコンポーネントがあった時、

import React from 'react';

const Title = (props) => (
  <h1>{props.title}</h1>
);

export default Title;

propsの型をチェックしていないので、ESlintに怒られてしまう。

まずは、以下のようにpropsを受け取るようにする。

  const {title} = props;

受け取ったら、prop-tyoesで型をチェックする。

パッケージをインストール

$ npm i -S prop-types

以下のような形で、型をチェックする。

import React from 'react';
import PropTypes from 'prop-types';

const Title = (props) => {
  // 追加
  const { title } = props;
  return (
    <h1>{title}</h1>
  );
};

// 追加
Title.propTypes = {
  title: PropTypes.string.isRequired,
};

export default Title;

参考

https://qiita.com/sugx2/items/ed58605e4e12519876fd
https://ja.reactjs.org/docs/typechecking-with-proptypes.html
https://speakerdeck.com/watanabeyu/du-miyasuikodofalseshu-kifang-falsesusume

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[初学者向け]Django・Reactおすすめ書籍まとめ

こんにちは、文系からITのお仕事について1年目のくまきちです。
初心者の方向けに、何を学べるかを書きながら、特によかった本やウェブサイトを紹介していきます。

勉強を始めたばかりの大学時代は、周りにITに造詣の深い友人や先輩がいなかったため本やウェブサイトに頼りきりでした。
知識がなかったので、やりたいことと関係のない本もたくさん買ってしまいましたね・・・。
皆さんにはお金を無駄にして欲しくないので(笑)、ちょこっと概要も書きつつおすすめを紹介していきます。勉強し始める方の一助になりますように!

Django

Djangoを始める

:notebook_with_decorative_cover: 掌田津耶乃 「Python Django3超入門」
一番初めにやりました。Djangoでどうやってインストールするの?から教えてくれる。
知識ゼロからウェブサイト作成まで。

:computer: Django Brothers https://djangobrothers.com
ブログ、メモアプリを作れる無料チュートリアル。
構造を理解しやすくて勉強になるので個人的にはおすすめ。

少し応用「会員機能」「デプロイ」

:notebook_with_decorative_cover: Django Brothers「Django チュートリアルで学ぶ Web 開発入門 」
会員機能、カスタムユーザーモデルを用いた会員機能について学べる。ECショップの作り方も。

:notebook_with_decorative_cover: 大高 隆「動かして学ぶ! Python Django開発入門」
会員機能、Bootstrapの導入方法、Django + AWSでのデプロイについて勉強できる。SES導入なども。
(AmazonLinux2AMIになり、Let’s Encryptの部分のコードはそのままでは実行できなくなってしまった。)

番外編

:computer: Django Narito Blog https://blog.narito.ninja/
わからないことはまずこちらのブログで検索(笑)。
DRF + Vue.jsの情報もたくさん。

:notebook_with_decorative_cover: 森本哲也,中野正輝,池 徹, 岡田幸大「できる 仕事がはかどるPython自動処理 全部入り。」
Python ファイル書き込み、読み出しなどの編集などの自動処理。使えると便利!

React

Reactを始める

:computer: たかぴろ(@takapiro_99) 「React ハンズオン 2020 夏」
知識ゼロから外部APIの利用とSPAの構築、firebaseでデプロイするところまで学べる。
環境構築についてもとても参考になるので、他の言語を学び始める人もぜひ参考に!

:computer: React Tutorial https://react-tutorial.app/
Reactの基礎について手を動かしながら学べる。後半は有料だけど、無料枠が充実している。おすすめ!

DRF + ReactでSPAを作る

:computer: CodingEntrepreneurs 「Create a Twitter-like App with Python Django JavaScript and React. Full TUTORIAL」
YouTubeのチュートリアル動画。DRF + Reactでツイッターみたいなサービスの構築。
(途中のdjango埋め込み認証機能はそのままではうまくいかなかった。できた方いたら教えて欲しいです。)

番外編

:computer: JavaScript | MDN https://developer.mozilla.org/ja/docs/Web/JavaScript
JavaScriptについてわかりやすく説明してくれる。
React以前の問題だ・・・と感じた時に。


まだまだちゃんと理解している部分は少ないですが、できることを増やしつつ理解していけたらなと思っています!
もっと知識をつけて本を間違って買わないようにしたいですね・・・:laughing:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.jsで__dirnameを使いたい

__dirnameで欲しい値が返ってきてない

nextjs内に使用しているAPIでpg-promiseを利用しているのですが、pg-promiseでsqlファイルを読み込む際に上手くパスが設定できませんでした。

process.cwd()を使うだけでした

ちゃんとドキュメンテーションに書いてありました。

参考:Reading files: Use process.cwd()

ちなみにpg-promiseのsql読み込みは以下のようになりました。

import {QueryFile} from 'pg-promise';
import {join} from 'path';

const sql = (file) => {
  //process.cwd()が元々は__dirname
  const fullPath = join(process.cwd(), file); 
  return new QueryFile(fullPath, {minify: true});
};

どうやらsqlファイルを何度も読み込んでるみたいなので、対策を別途調べてみます。

WARNING: Creating a duplicate QueryFile object for the same file 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reducerにswitchを使いたくない

Reducerをオブジェクト(Object)で作りたい

ReactのフックAPIのリファレンスを見ると、以下のようにswitchを使ってReducerを定義している。
しかし、私はswitchがあまり好きではないので、オブジェクトで書くようにしている。

example.js
const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

引用: フックAPIリファレンス(https://ja.reactjs.org/docs/hooks-reference.html#usereducer)

createReducer関数をつくる

以下のようにReducerを書けるようにcreateReducerという関数を作った。

{
    [アクションタイプ1]: (state, action) => ({変更後のステート}),
    [アクションタイプ2]: (state, action) => ({変更後のステート}),
    ...
}
createReducer.js
export const createReducer = (initialState={}, handlers={}) =>
  (state = initialState, action) =>
    handlers[action.type] && handlers[action.type]({...state}, action) || {...state}

説明は割愛するが、これを利用することで、前述のフックAPIリファレンスのサンプルコードを以下のように書くことができる。

example.js
const initialState = {count: 0};

const reducer = createReducer(initialState, {
  ['increment']: state => ({...state, count: state.count + 1}),
  ['decrement']: state => ({...state, count: state.count - 1})
}), initialState)

また、 payloadがある場合には、以下のように書くことができる。

example.js
const reducer = createReducer(initialState, {
  [UPDATE_SUGGESTION_ITEMS]: (state, {payload}) => ({...state, suggestionItems: payload}),
  [FETCH_POSTS]: (state, {payload}) => ({...state, tmpResult: payload}),
  [FETCH_POST_DETAIL]: (state, {payload}) => ({...state, post: payload}),
}), initialState)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React Server Componentについてまとめてみた

先日、ReactがReact Server Componentsを予告しました。まだ開発中とのことなので使えるのは先になりそうですが、非常に面白い内容となっているのでこちらで共有したいと思います。

What is React Server Component

React Server Componentとは、サーバーサイドのみで実行され、バンドルサイズへの影響を与えません。React Server Componentはクライアントでダウンロードされないため、アプリの起動時間などの向上などが期待できます。

  • 従来のReact Component(以下Client Comonentとする)
    client_react.jpg

  • React Server Component
    server_react.jpg

また、Server Componentは、データベースなどサーバサイドのデータソースにアクセスすることができたり、レンダリングするClient Componentを動的に選択することができます。
ここで簡単な例に触れてみましょう。

React Server Componentを使った例

以下はノートのタイトルと本文をサーバーサイドから取得し表示し、ノートのエディタをClient Reactコンポーネントとしてレンダリングする例です。

まず、Server Componentを実装するには拡張子を.server.js または.server.jsx.server.tsxなどにします。

import db from 'db.server'; 
// (A1)
import NoteEditor from 'NoteEditor.client';

function Note(props) {
  const {id, isEditing} = props;
  // (B) 
  const note = db.posts.get(id);

  return (
    <div>
      <h1>{note.title}</h1>
      <section>{note.body}</section>
      {/* (A2)  */}
      {isEditing 
        ? <NoteEditor note={note} />
        : null
      }
    </div>
  );
}

この例からいくつか重要なことがわかります。

  • A1 :Client Reactコンポーネントをインポートする時は.client.jsまたは .client.jsx, .client.tsx の拡張子をつける。
  • B : データベースなどのサーバーサイドのデータソースに直接アクセスしている
  • A2: Client ComponentはisEditingtrueの時のみクライアントにロードされる。つまり必要に応じて動的にロードされることになる

続いて動的にロードされるNoteEditorコンポーネント(Client Component)について見ていきましょう。

export default function NoteEditor(props) {
  const note = props.note;
  const [title, setTitle] = useState(note.title);
  const [body, setBody] = useState(note.body);
  const updateTitle = event => {
    setTitle(event.target.value);
  };
  const updateBody = event => {
    setTitle(event.target.value);
  };
  const submit = () => {
    // ...save note...
  };
  return (
    <form action="..." method="..." onSubmit={submit}>
      <input name="title" onChange={updateTitle} value={title} />
      <textarea name="body" onChange={updateBody}>{body}</textarea>
    </form>
  );
}

この例で重要な点は、Server Componentの結果をクライアントにレンダリングするときに、以前にレンダリングされた可能性のあるClient Componentの状態を保持することです。具体的には、Reactは、サーバーから渡された新しいPropsを既存のクライアントコンポーネントにマージし、これらのコンポーネントの状態(およびDOM)を維持して、focus、stateや進行中のアニメーションなどを保持します。

React Server Componentのメリット

他にもたくさんのメリットがあるので今確認できるものを紹介していきます。

Zero-Bundle-Size Components

冒頭に述べたように、React Server Componentはサーバーサイドのみで実行されるので、バンドルサイズが0となります。そのため開発時にコードサイズによるパフォーマンス低下を回避することができます。

// NoteWithMarkDown.js
// 従来のReactコンポーネントなのでそのままのバンドルサイズとなる
import marked from 'marked'; // 35.9K (11.2K gzipped)
import sanitizeHtml from 'sanitize-html'; // 206K (63.3K gzipped)

function NoteWithMarkdown({text}) {
  const html = sanitizeHtml(marked(text));
  return (/* render */);
}

// NoteWithMarkdown.server.js
// ServerComponentなのでバンドルサイズが0になる
import marked from 'marked'; // zero bundle size
import sanitizeHtml from 'sanitize-html'; // zero bundle size

function NoteWithMarkdown({text}) {
  // same as before
}

バックエンドへのフルアクセス

Reactでアプリを作成する際の課題として、データへのアクセス方法及び、データの保存場所が挙げられるそうです。Server Componentはバックエンドに直接アクセスできるので、例えば新しいアプリを作成し始めた時などに、データの保存場所がわからない場合にはファイルシステムを用いることもできます。

// Server Componentなのでバックエンドにアクセスできる
import fs from 'react-fs';

function Note({id}) {
  const note = JSON.parse(fs.readFile(`${id}.json`));
  return <NoteWithMarkdown note={note} />;
}

自動コード分割

コード分割により、アプリケーションを小さなバンドルに分割することができ、クライアントに送信するコードを減らせます。これの一般的なアプローチは、ルート毎にバンドルを遅延ロードするか、実行時になんらかの基準により異なるモジュールを遅延ロードすることが挙げられます。

// Client component
import React from 'react';

// これらの1つは、クライアントでレンダリングされるとロードを開始する
const OldPhotoRenderer = React.lazy(() => import('./OldPhotoRenderer.js'));
const NewPhotoRenderer = React.lazy(() => import('./NewPhotoRenderer.js'));

function Photo(props) {
  // Switch on feature flags, logged in/out, type of content, etc:
  if (FeatureFlags.useNewPhotoRenderer) {
    return <NewPhotoRenderer {...props} />; 
  } else {
    return <PhotoRenderer {...props} />;
  }
}

コード分割はパフォーマンス向上に役立ちますが、import時にReact.lazyを用いて動的インポートする必要が出てきます。また、このアプローチはコンポーネントのロード開始タイミングを遅らせるため、コードのロードする量を減らすといった利点を弱めてしまいます。

そこで、Server Componentを用いることでこれらの問題に対応することができます。コード分割を自動化するために、Server Componentは全てのClient Componentを潜在的なコード分割点として扱います。

加えて、Server Componentは開発者により早く使用するコンポーネントを選ばせることができるので、クライアントはレンダリングプロセスの早期段階でコンポーネントをダウンロードできます。

// Server Componentなので自動コード分割される
import React from 'react';

// これらの1つは、レンダリングされてクライアントにストリーミングされると、ロードを開始する
import OldPhotoRenderer from './OldPhotoRenderer.client.js';
import NewPhotoRenderer from './NewPhotoRenderer.client.js';

function Photo(props) {
  // Switch on feature flags, logged in/out, type of content, etc:
  if (FeatureFlags.useNewPhotoRenderer) {
    return <NewPhotoRenderer {...props} />;
  } else {
    return <PhotoRenderer {...props} />;
  }
}

No waterfall

パフォーマンスが低下する原因の1つとして、アプリケーションがデータをフェッチするために連続してリクエストを行うときに発生します。たとえば、以下の例のように最初にコンポーネントをレンダリングしてから、useEffect()内でデータをフェッチすることで生じます。

// Note.js
function Note(props) {
  const [note, setNote] = useState(null);
  useEffect(() => {
        // 子のwaterfallによりレンダリング後にロードされます
    fetchNote(props.id).then(noteData => {
      setNote(noteData);
    });
  }, [props.id]);
  if (note == null) {
    return "Loading";
  } else {
    return (/* render note here... */);
  }
}

親コンポーネントと子コンポーネント両方でこのアプローチをとってしまうと、子コンポーネントは親コンポーネントがデータをロードし終わるまでデータをロードし始めることができません。ただし、このアプローチをとるとアプリケーションが必要なデータを正確にフェッチすることができ、レンダリングされていないUIの部分のデータをフェッチしないようにするといったメリットがあります。

Server Componentを使用すると、サーバとクライアントの連続した往復処理をサーバに移すことでこのメリットをそのままに、問題点を解決することができます。またこの往復処理をサーバーに移動することで、リクエストのレイテンシーを減らし、パフォーマンスを向上させることができます。さらに、Server Componentはコンポーネント内から必要最小限のデータを直接フェッチし続けることができます。

// Note.server.js - Server Component

function Note(props) {
  // サーバに低いレイテンシでデータにアクセスし、レンダリング中にロードされます
  const note = db.notes.get(props.id);
  if (note == null) {
    // handle missing note
  }
  return (/* render note here... */);
}

まとめ

今回説明したServer Componentの特徴を以下にまとめます。

  • React Server Componentはサーバーサイドのみで実行されるので、バンドルサイズが0となる
  • Client Component(従来のReactコンポーネント)とServer Componentをわけるために.client.js.server.js のように拡張子を変える
  • データベース、ファイルシステムサーバー側のデータソースにアクセスできる
  • レンダリングするClient Componentを動的にロードできるのでクライアントではページのレンダリングに必要な最小限のコードのみダウンロードできる
  • リロード時にクライアントの情報を保持する

さらに詳しい情報が知りたい場合は、公式からデモ動画を見て、デモ用のプロジェクトを試してみてください?

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

typescriptでnumberが自然数かどうか、booleanで返す関数

typescriptで自然数を判別したい

調べると、型でなんとかしようとする記事が目立った。
でもなんかむずそうだから、普通に関数でやろうと思った。

// 自然数だったらtrueを返す
const isNatureNum = (num: number): boolean => num > 0 && isInteger(num);

使い所

cssのプロパティって、自然数しか使えない場面ってある。
自分の場合、grid使うときにrowを制限したいときとか使った。
この実装だとlimitRownull , undefined , 0 , '' らへんは全部grid-auto-rowsになるはず(間違ってたらごめんね)
そもそも型でnumberに絞ってるけどね。

isNatureNum(prop.limitRow)
    ? {
        overflow: 'hidden',
        gridTemplateRows: `${prop.gridHeight}`,
        height: 'auto',
        maxHeight: `calc(${prop.gridHeight} * ${prop.limitRow})`,
      }
    : { gridAutoRows: `${prop.gridHeight}` };

もっといい方法あったら教えてください

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React import/exportについて

export名とimport名が違うのはなんで??となったので調べたことをメモしておく。

import

①defaultでexportされているものについてはimport時にファイルパスさえあっていれば自由に名付けて良い。(当然同じ名前でも構わない)
import 〇〇 from 'ファイルのPath等'
import 『export時の名前』AS 〇〇 from 'ファイルのPath等'

②defaultではないものについては
import { 〇〇 } from 'ファイルのPath等'と記載する。
この際、export時と同じ名前を使用する必要がある。

③②で名前を変えたい時は、
import { 〇〇 AS つけたい名前 } from 'ファイルのPath等'で変更することができる。

export

default
・export 〇〇でファイル・変数を自由にexortできる。
・クラス名・変数名に関わらず、export default 〇〇で自由に名付けられる。(1ファイルにつき1つのみ)

以上、参考になれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React.StrictModeをApolloClientと一緒に使う時は気をつけろ

下のページを見れば分かる通り、React.StrictModeとApolloClientの併用は2020/12/23現在いくつかの問題を抱えている。

https://github.com/apollographql/apollo-client/search?q=StrictMode&type=issues

これらの問題が解決される前は、React.StrictModeは使うべきではないかもしれない。

使うにしてもStrictModeが問題を引き起こす可能性があることを頭の片隅においておくべきである。

私みたいに残り少ない人生の貴重な半日を無駄にしたくなければね。

create react appで生成したプロジェクトはindex.jsでReact.StrictMode使ってるからはずそう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gatsby.jsで"EMFILE: too many open files"というエラーが出たときの対処法(Vercel)

シチュエーション

Gatsby.jsで大量の画像ノードを作成したところ、あるとき突然以下のようなエラーが出ました。

error_on_vercel.png

このエラーはローカルで開発しているMac上では再現せず、Vercelでデプロイした場合のみ発生しました。

解決方法

gatsby-node.jsの上部に、以下を追加し、再度デプロイします。

const realFs = require('fs')
const gracefulFs = require('graceful-fs')
gracefulFs.gracefulify(realFs)

このエラーはデフォルトのfsを使用していると起こる一般的なエラーらしく、その対策として存在するgraceful-fsを使用するようにgatsby-source-filesystemに伝えている感じなのかな、と思います。

参考

この対処法は以下のissueに乗っていました。
https://github.com/gatsbyjs/gatsby/issues/12011

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gatsbyでのコンテンツ管理方法

Gatsbyではマークダウン、Googleスプレッドシート、Wordpressなどの従来のCMSなどの様々なデータソースを選択することができます。
そして、1つのデータソースに限る必要もなく、複数のデータソースを組み合わせることもできます。

Contentfulについてについて

Contentful】入門。Conentfulとは?導入方法・設定手順の解説
https://fromscratch-y.work/ja/blog/technology/other/start-contentful/

【画像解説】Contentfulの使い方【活用方法も紹介】
https://colorfree-map.com/howto-contentful

GraphQLでContentfulのデータを扱うための準備

プラグインの準備

yarn add gatsby-plugin-contentful

gatsby-config.jsにプラグインの設定を追加します。 optionsではContentfulのSpaceID,AccessToken, HOSTを指定する。
ただし、ファイルを直接記述せず環境変数を利用します。(ファイル中にトークンを残したくないため、環境変数を使用する)

*環境変数とは、OSが設定値などを永続的に保存し、利用者や実行されるプログラムから設定・参照できるようにしたもの。プログラムの実行時などに必要となる、利用者やコンピュータごとに内容が異なる設定値などを記録するために用いられる。

{
resolve: `gatsby-source-contentful`,
options: {
  spaceId: process.env.CONTENTFUL_SPACE_ID;
  accessToken: process.env.CONTENTFUL_ACCESS_TOKEN;
  host: process.env.CONTENTFUL_HOST;
 },
},

開発サーバーの起動

環境変数にした3つの値を指定して、開発サーバーを起動します。(デプロイ時にも環境変数が必要になる)

CONTENTFUL_SPACE_ID=***** CONTENTFUL_ACCESS_TOKEN=***** CONTENTFUL_HOST=cdn.contentful.com gatsby develop -H 0.0.0.0

GraphiQLで確認する

GraphiQLにアクセスして、プラグインが機能していることを確認します。

参考文献:
Webサイト高速化のための静的サイトジェネレーター GatsbyJSで実現する高速&実用的なサイト構築
IT用語辞典:http://e-words.jp/w/%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React開発者必見!!先日発表されたReact Server Componentsとは?

はじめに

12月21日、Facebookから予期せぬクリスマスプレゼントが配られました?
reactjsによって、こちらにReact Server Componentsなるものが突如発表され、React界隈でとても話題になっています。
僕もTwitterでこのニュースを見つけて、ReactでServer???なんかやばそう。そう思いながらこのページを覗くと、説明はどうやら動画のみっぽい。
しゃーねーなーっつって、動画開いたら再生時間57分。当然全部英語...。おいおい、頼むよほんと。
まあ眠くなりかけながらなんとか見終えて、個人的にまとめてみました。
僕がこれ書いてる時点では、ある程度体系的な日本語での記事はほんとにいくつか散見される程度だったので、
日本最速(ほぼ)で紹介します?
それじゃー早速どうぞ。

対象の読者

  • Reactへの知見がある程度ある方
  • React Server Components興味あるけど、動画長げーしって方

React Server Componentsの概要

React Server Componentsは、サーバーサイドにComponentsを置いて、そこからデータ取得を行うことで、余分なネットワーク通信を介したデータのやりとりを減らし、アプリケーションの高速化を実現します。
Componentの種類には
- ${Component名}.client.js (フロントでレンダリング)
- ${Component名}.server.js (サーバーサイドでのレンダリング)
- ${Component名}.js (Shared Component、詳細は後述)
の3つがあります。

何がいいか

1.backend内で直接データをfetchできるため、取得速度が早い

NoteList.server.js
export default function NoteList() {
  const notes = fetch('http://localhost:4000/notes').json(); // ラップしなくて良い

  return notes.length > 0 ? (
    <ul>
      {
        notes.map(note => (
          <li>
            <SidebarNote note={note} />
          </li>
        ))
      }
    </ul>
  )
}

動画では上記のようなコードが紹介されています。server.jsファイルでも、普段のReactの記法とに大した差分はありません。
言うとすれば、notesのfetch処理は同期的に扱われるため、普段のようにuseEffect等のhooksでラップする必要はありません。

こちらは.server.jsファイルなので、サーバーサイドでのレンダリングがされます。
このようにデータの取得をserverレンダリングのコンポーネントで行うことで、スムーズなデータの受け渡しが可能となり、レンダリングが高速になります。

2. server.jsのcomponentで使われるライブラリはnode_modulesに入らないため、clientでの余分なダウンロードがいらない

動画では日付をフォーマットするために、date-fnsというライブラリを用いて説明がされていました。これを用いた日付のフォーマット処理をserver.jsで行うことにより、date-fnsがフロントにビルドされるnode_modulesのディレクトリに居なくなります。
これによって少しでもビルドするモジュールの容量を減らすことができ、アプリケーションの高速化に繋がります。

3. SSRと異なり、client-sideのStateが保持される

ここまで聞いて、いやいやSSRやんけ。って思いましたね?
動画内では、SSRとの違いとしてStateの扱いについての違いが挙げられていました。
React Server Componentsでは、useState等で管理されるクライアントのstateが初期化されず、クライアント側でそのまま保持されます。
これはReact Server Componentsがページ全体のHTMLへのレンダリングをせず、必要なComponentのみ再レンダーが走るよう、特殊なフォーマット形式(一見jsonに似てるけどそれともちょっと違う)への変換を行うために可能なそう。

4. SharedComponentを使って、server / clientでのレンダリングが柔軟に切り替えられる

サーバーサイドでレンダリングを行うserver.jsファイルに書かれたComponentはフロントとのインタラクティブ性を持つような実装はできません。
つまり、eventListener(onClickとかね)やuseStateは使えません。
そのため、これらのフロントとのインタラクティブ性が必要なComponentはclientでのレンダリングを行い、そうでない部分に関してはサーバーサイドでのレンダリングをするのが理想的といえます。
Shared Component(${Component名}.jsファイルに定義される)はどんなComponentをimportしているかによって、クライアントによるレンダリングを行うか、サーバーサイドでのレンダリングにするかが決定されます。
動画ではnoteのプレビュー機能を例に解説されていました。
noteは完成された投稿を閲覧するだけの場合であれば、フロントとのインタラクティブ性は特にないため、サーバーサードによるレンダリングで十分そうです。ですが、プレビュー機能となると、ユーザーの修正に合わせて常に表示内容が変更される必要があります。この場合はフロントでのレンダリングが必須です。

Componentの切り分けを適切に行うことで、Componentを共通化しながら柔軟にどちらでレンダリングするかをハンドリングできます。

終わりに

いかがだったでしょうか?基本的にはデータのfetchをサーバーサイドでレンダリングするComponentで行い、フロントとのインタラクティブ性があるComponentで使う場合はpropsとして渡すと言う感じになりそうです。
詳細については、下記の動画を参考にしてください、また、demoのrepositoryも公開されているので、自分も後で触ってみようかと思います。
正式に公開されるのが楽しみですね。

参考

https://www.youtube.com/watch?v=TQQPAU21ZUw

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む