📖

7/25に発売されたばかりの、TypeScript/React/Next.js本読んでみた感想(随時更新)

2022/08/14に公開
4

7/25に発売されたばかりの、TypeScript/React/Next.js本読んでみた感想(随時更新)

TypeScript
とReact/Next.jsでつくる実践Webアプリケーション開発 表紙


  • 9/4 moleculesの実装進める、問題点解決?
  • 9/3 どーしても直せないので方針変更。一度styled-components導入。
  • 8/29 6.6途中。ドロップダウンまで実装。
  • 8/26 わからんところ復習したり。
  • 8/25 6.5章完了まで。しかしわからんことだらけ
  • 8/24 6.5章途中まで。Atoms/TextAreaまで実装。
  • 8/23 6.5章途中まで。Atoms/Buttonの実装に苦戦中。
  • 8/22 5章完了、昨日放置した問題の解決。
  • 8/21 5章途中、storybookインスコした直後まで
  • 8/20 コンポーネントテスト追加、コメント指摘反映
  • 8/19 4章途中(4.4コンポーネントテスト直前まで完了)
  • 8/18 3章完了
  • 8/17 3章途中(React全部)までうp
  • 8/16 3章途中までうp
  • 8/15 2章途中まで更新
  • 8/14 初投稿、1章まで完了して投稿

完走してないけど、所感

  • React/Next.jsミリ知らからスタート
  • 驚きの情報量と密度。学びになる。
  • とりあえず手を動かして感覚で学ぶタイプの人にピッタリ!!

kugyu10はJavaScriptでjQueryを2〜3年くらい
ゴリゴリしてた人です。
モダンなフレームワークに興味あったものの
あんまり手をだせていませんでした。

今回、長めの夏休みになったので勉強がてら買ってみました。

基礎からじっくり学ぶには
けっこうすっとばされます。

「細かいことはいいからモダンフロントエンドスキルセット使って
 動くアプリ書かせて練習させてくれよ!!」

ってぼくみたいなタイプには本当にピッタリです。

毎日フルタイムで勉強にとりかかり、
1週間で完走するつもりだったけど
2週間くらいかかる見込みです。。。

正誤表とサンプルコード

初版はけっこう正誤あるので、最初に(紙の本なら)書き込みしてしまうのがいいかも
https://gihyo.jp/book/2022/978-4-297-12916-3/support

1章 THE読み物

概念的で少々退屈
だけど重要だから書いてあるはず、完走したらもう一度見直そう

SPA

Single Page Application
一度HTML全体をロードし、その後Ajaxで動的にページを更新する。
TwitterやFacebookライクなアプリはだいたいコレ。

某週刊誌は関係ない

SSR

Server Side Rendering
その名の通り、サーバーサイドで描画してしまおうぜ!という仕組み

  • ◯クライアント側は表示爆速
  • ◯SEO向上にもなる
  • ×サーバー側の負荷が増える

ソシャゲのガチャは関係ない

SSG

Static Site Generation
ビルド・デプロイ時に静的サイト作っちゃおうぜ!という仕組み

  • ◯SSRと同じメリット
  • ◯リクエスト時に負荷がかからない
  • ×その代わりビルド時に負荷がかかる、SSRと使い分けが重要

ミンゴスは関係ない

props

コンポーネントの外側から受け取ることのできる値、内部からトリガーされたときに呼び出される関数を渡すことも可

state

コンポーネント内部で保持するデータ

Flux

大規模アプリで状態遷移をシンプルにするアーキテクチャ Action→Dispatcher→Store→View→Action→Dispatcher の順にしか遷移しないというルール

Context

どのコンポーネントでも利用可能な値
グローバル領域のオブジェクトみたいな?

Atomic Design

  • 原子・・・UI最小単位それ以上分解できない要素 ボタン
  • 分子・・・1つ以上の原子を組み合わせて作られる要素 検索フォーム
  • 生体・・・1つ以上の分子を組み合わせて作られる要素 ヘッダーなど
  • テンプレ・・・生体を組み合わせて作られる、1つの画面として成り立つもの
  • ページ・・・テンプレにアプリケーションとして動作するデータが注ぎ込まれたもの

相性の良いツールとして Storybook があるので、活用してみよう

Next.jsのメリット

複雑化するフロントエンド開発をまるっとめんどう見てくれる

  • コンポーネント設計
  • コンパイル
  • ビルド
  • dev環境
  • 本番環境へのデプロイ
  • テスト
  • 画像最適化
  • ブラウザ互換性
  • SEO対策

優れたFWはこうした「開発あるある」を一定の解決策をもって、
テンプレ的に記述するだけで解決してくれます。

(kugyu10個人的には、
 Vue/Nust.jsというライバルもいるのも大きいと思ってます。
 Nextのいい部分をNuxtがパクり、Nuxtのいい部分はNextがパクる、
 切磋琢磨しあう状況がもう数年は続くでしょう。)

Hydration

SSR/SSGではクライアント側には静的サイトとして処理される。
インタラクティブな動作を実現させるためには、
サーバーサイドで静的サイトを動的なReactコンポーネントに戻す必要がある。

水分を得る、みたいなイメージでハイドレーションなどと読んでいる


2章 TypeScriptのキホン

1日でTypeScript学ぶのは無理があったな、うん

TypeScript = 静的型付けつきのJavaScript
基本的な文法のお話になるので
写経しつつ手を動かしながら学んでいく。

(kugyu10はJavaの経験もあるので
 静的型付き言語の良さと面倒くささを理解しているつもりです。
一言で言うと早くバグが見つけやすくなる良さ・安心感と、
イチイチ型を指定してあげないと何もできないところです。
後者はIDEの補完機能である程度マシになります)

Github

2章でTypeScriptの練習(主に型の練習)したコードは
Githubにも公開してます。(誰得?)

[https://github.com/kugyu10/practice-typescript]

TypeScriptが"覇権"を握った?

(AltJSはいろいろあるけど、kugyu10が聞いたことあるのは
 CoffeeScript、Dart、TypeScript。
本書ではTypeScriptが覇権を握った
強い表現を使っていたので、一応裏付けしてみた

Googleトレンドで調べてみると一目瞭然

2016年2月ごろからTypeScriptが頭角を現し、2022年現在ぶっちぎりである。)

TypeScriptのHelloWorld

//hello.js
function sayHello (firstName) {
  console.log('Hello, ' + firstName)
}

let fname = "kugyu10"
sayHello(fname)
#.jsでも.tsでも動作する
$ node hello.js
Hello, kugyu10

$ mv hello.js hello.ts
$ tsc hello.ts
$ node hello.js
Hello, kugyu10

firstName引数にString型を指定してみると、

//sayHello.ts
function sayHello (firstName: string) {
  console.log("Hello, " + firstName)
}
let fname = "kugyu10"
sayHello ( fname )
$ tsc hello.ts
$ node hello.js
Hello, kugyu10

動いた!

コンパイル方法

tscコマンドでコンパイルできる。
本では--strictNullChecksオプション使ってた。

現場では、全jsファイルをwatchさせてたり、
Next.jsが適時コンパイルしてくれたりするので
いちいちtscコマンドを打つ必要はない

String型にnumber型を入れようとするとコンパイル時に教えてくれる
→早期バグ発見に繋がり、開発効率と堅牢性アップ
(Java経験者のkugyu10としては嬉しい限り)

変数定義 varとletとconstの違い

const・・・定数。再代入不可能。スコープはletと同じ
let・・・ブロックスコープで宣言された変数は、その変数を含むブロック内でしか参照可能にならない。
var・・・関数内全体で利用できる

とりあえずconstで書いて、
再代入するよ!と主張したい変数ならletで書いて
var使いたくなったら、即時関数使うとかで上手く書く、(varはなるべく避ける)

というのが無難らしい、という一応の理解。

型について

string、number、boolean型は
プリミティブ型と呼ばれるよ!

string型(文字列)

JavaではStringクラスだったが、tsではプリミティブ型
シングルクオートで囲む

const firstName: string = 'kugyu10

number型(数値)

integerとかfloatとかないらしい。数値は全部コレ。

const age: number = 17

boolean(真偽)

ture or false
変数の前に!をつけて真偽反転したり、
!!して中身があるかを返したり、とかは相変わらずできます。

const isDone: boolean = !!firstName

配列

const array: string[] = []
array.push('天海春香')
array.push(17) //エラーになる

オブジェクト型

オブジェクトっていう型らしい。キーと値を(複数個)持てる
JavaのBeanみたいな?

any

全ての型を許容する型。
js→ts移行過程で困ったときはanyは使えるけど
(tsの良さを失ってしまうので)なるべく使わない方が良い。

関数

引数を関数にできる例が個人的にややこしかったので練習。

//引数にも関数を指定できる
function genSongInfo(songs: string): string[]{
  return songs.split(',')
}

function getSongs(songInfo: (x: string) => string[]): string{
  return songInfo('ティアドロップス,キズナミュージック♪,イニシャル')[0] + ' is 最高.'
}

console.log(getSongs(genSongInfo))

型推論

const age=10

ってやると、ageはNumber型として扱われるよ!ってヤツ。便利。
Javaだと(10未満は)いちいち書かないといけないし、
いちいち書く文化が良しとされてたけど、

jsなら、明示的に書かなくても文字列やNumber、booleanは明らかだからね。便利。

複雑なオブジェクト以外は宣言時に書かなくても良さそう。

型アサーション(as)

//Canvasじゃないものを取得してしまったらエラー
const myCanvas = document.getElementById('main_canvas') as HTMLCanvasElement

型エイリアス(type)

何度も使うオブジェクト型とか、なが〜い型とかを
使い回す時に使える表現。

JavaでいうクラスとかC#でいう構造体に近いものかな?
キー名が定まらない時にキー名を指定しないエイリアスを作ることも可能!

IDEである程度名前置換できるけど、こういう
名前迷うはあるあるなので助かる機能。

type typeName = {
  x: number;
  y: number;
  [key: string]: string
}

インターフェース(interface)

どんどん抽象的になっていく・・・

interface Colorful {
  color: string;
}

//extends可能
interface Point extends Colorful{
  x: number;
  y: number;
  z: number;
  name?: string
}

クラス

Javascript ES2015で導入されたクラス記法に
静的型付けが可能。

メソッド持っていたり、Interfaceを実装できたり、
public、privateなどアクセス修飾子があり、
Javaのクラスの概念に似ている

Enum型

あると便利な列挙型

enum Direction {
  'Up', //0
  'Down', //1
  'Left', //2
  'Right' //3
}

ジェネリック型(T)

クラス内の型を抽象化し、後から型を指定できるようにする機能?

class Queue<T> {
  private array: T[] = []

  public push(item: T) {
    this.array.push(item)
  }

  public pop(): T | undefined {
    return this.array.shift()
  }
}

const queue = new Queue<number>()
queue.push(765)
queue.push(346)
queue.push(315)
queue.push(283)
console.log(queue.pop())
//queue.pop('876') //コンパイルエラーとなる

Reactもpropsを外側から型指定できるので
ジェネリック型の概念は有効(かつ重要)

Union型( | ) と Intersection( & )

type EmailAddress = {
  name: string
  domain: string
}

type User = {
  name: string
  userId: number
}

//Union
type UserOrEmailAddress = User | EmailAddress
//Intersection
type UserAndEmailAddress = User & EmailAddress

const user1: UserOrEmailAddress = {
  name: 'haruka' ,
  domain: 'example.com'
}

const user2: UserOrEmailAddress = {
  name: 'chihaya' ,
  userId: 72
}

const user3: UserAndEmailAddress = {
  name: 'miki' ,
  userId: 86 ,
  domain: 'example.com'
}

console.log(user1)
console.log(user2)
console.log(user3)

リテラル( | )

Enumに似てるが、変数に特定の値しか入らないようにすること

let thisWeekTrend: 'Visual' | 'Dance' | 'Vocal'
thisWeekTrend = 'Visual' //OK
//thisWeekTrend = 'Passion' //NG

function compare(a: string, b:string): ( -1 | 0 | 1 ){
  return (a===b) ? 0 : ( a>b ? 1 : -1)  //三項演算子が二重
}

console.log( compare('hoge','fuga'))

never

決して発生しないことを宣言した型
開発途中で今後考慮しておきたい分岐で
コンパイルエラーが出ちゃったときに通す時に使う?

function testError(message: string) :never /*ここのnever省略すると、コンパイルエラー*/{
  console.log('エラーをキャッチしました')
  throw new Error(message)
}

function foo(a: string|number|boolean) :boolean {
  if (typeof a === 'number'){
    console.log('aはnumberです:' + a)
    return true
  }else if(typeof a === 'string'){
    console.log('aはstringです:' + a)
    return true
  }else{
    testError('ここへは到達しないはず')
  }
}

foo('bar')
foo(true) //neverって書いてあるけど普通に到達するし、投げられる。

Optional Chaining( ? )

undefinedの子要素を取得しようとしたらエラーでるけど
それを回避できる

interface Idol {
  name: string
  age?: {
    age: number
    note: string    
  }
  birthday: string
}


const uzuki: Idol = {name: '島村卯月', age: {age:17, note: ''}, birthday:'0424'}

console.log(uzuki.name)
console.log(uzuki.age?.age)
console.log(uzuki.birthday)

const nana: Idol = {name: '安部菜々', birthday: '0515'}
console.log(nana.name)
console.log(nana.age?.age) //エラーにならない優しい世界!
console.log(nana.birthday)

ちなみに、コンパイルされた、undefinedエラー回避しているjs文は以下のようになってた

(_b = nana.age) === null || _b === void 0 ? void 0 : _b.age)

もうこれだけで読みにくい。jsのキホンも学び直す必要があるようだorz

void 0は、undefinedを返すらしい。tscが作ったということは
undefinedを返す表現のうち、パフォーマンス的にいいのだろうと予想。

_bにnullかundifinedが入っていたら、undefinedを返す、
そうでないなら、中身を返す、という処理になっているようだ。

Non-null Assertion Operator( ! )

tsc --strictNullChecksオプションを使う場合は
nullの可能性のあるオブジェクトへのアクセスは
コンパイルエラーが発生する。

それを回避する演算子。
コンパイルエラーを回避するだけで、普通に実行時エラーになる。

用途はなんだろう?never的な使い方?

型ガード

ifやswitchで型チェック分岐行った時、
elseなどの型推論する機能

if (user.info) {
  //user.infoがnullの時でも安全、--strictNullChecksでもコンパイル通る
  console.log(user.info.name)
}

Java8みたいにガチガチに型付けしたい!って感じじゃなく、
JavaScriptらしく、(最低限の型付けはしつつも)
ゆるっとよしなに書きたいぜ!という圧を感じる。。。

keyofオペレーター

型に対してkeyof オペレーターを使うと、リテラル型になる

インデックス型

オブジェクトのプロパティ数が可変の時、
まとめて定義できる。

とある魔術のは関係ない

type Productions = {
  [env: number]: string;
}

let imasBrands : Productions = {
  765: '765AS' ,
  876: 'Dealy Stars' ,
  346: 'シンデレラガールズ' ,
  315: 'SideM' ,
  283: 'シャイニーカラーズ' ,
  1054: '東豪寺' ,
  //まだまだ出てくるかもしれない...
}

readonlyプロパティ と Readonlyジェネリック型

readonlyのプロパティは再代入不可能になる。

Readonly<Type>とすると、
全てのプロパティがreadonlyな型になる。

unknown

anyと同じくどんな型でも入る
けどそのままではアクセスできない。
(typeof分岐など使ってしっかり処理する必要がある)

しっかり静的型付けよりもゆるいけど、anyほどガバガバではない。

非同期関数とPromise async await

非同期関数を使う時に必要な機能。

非同期関数とは、時間のかかる処理(外部DBを舐めるとか)の
結果を待たずに次の処理を実行できる仕組み。

(写経してみたり、非同期関数についてぐぐってみたりしたけど
 よくわからん。TypeScriptというかJavaScriptの仕組みなのであとで復習予定。)

型定義ファイル

①@types/(ライブラリ名) をインストールすることで拡張できる
②型の定義をexportやimportできる(hoge.d.ts というファイル名にする)
②' import typeを使うと、型のみインポートでき、中身の記述は消える

TypeScriptの開発設定

tsconfig.js

コンパイルのオプションやコンパイル対象を書くことができる。
tsc --initでデフォルトのtsconfig.jsを作れるので試してみる。

Prettier

コードのフォーマッター。プリティアと読む。

プリキュアとは関係がない

大抵のエディタはオートインデントがついてると思うが、
・こういう場合は改行する、とか。

Gitでしょーもない変更が入ったりしないように
厳しめなフォーマッタを通しておくと見通しがよくなる。

慣れておくために導入してみる。

初期設定したあとは
npm run prettier-format
で整形できる。

どういうスタイルを採用するか考える時、
有名どころからパクるのも手。

JavaScript Standardスタイルガイド
Airbnbスタイルガイド
Googleスタイルガイド
TypeScript Deep Dive スタイルガイド

ESLint

Prettierがコードフォーマット用なら
ESLintはコード解析・問題の検知を目的としている。

自分の環境にはVSCでESLintが入っていたっぽい

コンパイルオプション

コンパイル時にもいろいろチェック、問題の検知ができる
オプションがあるので活用すると吉。

noIMplicitAny
anyすら指定のない型(で、推測もできないとき)をエラーにする

strictNullChecks
nullやundefinedを代入しようとするとエラー。本書推奨。
Non-null assertion( ! )と両立可能。

target
少し古いJavaScriptにコンパイルできるようになる
IE11なくなった今は不要か?将来的にまた必要になるか?

よーしこれでTypeScript完全に理解し・・・なんもわからん・・・


3章 React/Next.jsのキホン、千里の道も一歩から

FWのキホンを学ぶため、やはり覚えることが多い。
本から写経そのままなので
サンプルコードの多くは割愛。

Github

ほとんど本の写経のまんまだけど、
練習に書いたコードあげておきます。

React App:
https://github.com/kugyu10/practice-react

Next.js App:
https://github.com/kugyu10/practice-next/tree/feature/chapter-3

始め方

npx create-react-app@latest プロジェクト名 --template typescript

3分間だけ待ってやって、

cd プロジェクト名
npm run start

するだけで、

http://localhost:3000/
で、Reactアプリが始められたことが分かる。

.jsx/.tsxファイル

通常のjs/tsファイルに、
HTMLタグやコンポーネントを記述できるファイル。
純粋なjs/tsと区別するために拡張子を変えている。

class→className
for→htmlFor
background-color→backgroundColor
など、JavaScriptの都合で、属性名などが変わっているものもある

Reactコンポーネント

見た目と振る舞いをセットにしたUIの部品の単位

コンポーネントに外部から値を与えるのに、propsを使う

propsとContext

親から子へデータを渡す方法は2つある。

  • props引数でバケツリレーしていく
  • ContextのProviderで保持した値をConsumerやuseContextフックで受け取る

Reactフック

コンポーネント内からいろいろできる機能
10個のフックがある!

①useState

状態を扱うためのフック

const [状態, 更新関数] = useState(初期状態)

②useReducer

useStateより複雑な場合に使える

reducer(現在の状態, action){
  return 次の状態
}

const [現在の状態, dispatch] = useReducer(reducer, 初期状態)

メモ化

Reactのコンポーネントの再描画タイミング

  • propsや内部状態が更新された時
  • コンポーネント内で参照されているContextが更新された時
  • 親コンポーネントが再描画された時

この自動再描画機能のおかげで、
内部状態が変更したときにいちいち再描画の命令しなくても
よしなに、リアルタイムに表示変更してくれるのが
Reactのいいところ。

一方、

親コンポーネントが再描画されると、
基本的には子コンポーネントも再描画される

そこを(時間がかかるので)スキップしたい時に
「メモ化」という方法がある。

メモ化のフックは
③useCallback
④useMemo
の2つが用意されている。

よくわからんから写経して動かしてみる。

③useCallback

useCallbackは関数をメモ化するフック

const 関数名 = useCallback(() => {
  /* 処理 */
},依存配列)

④useMemo

useMemoは値のメモ化をするフック

const 関数名 = useMemo(() => {
  /* 処理 */
}, 依存配列)

⑤useEffect

副作用(?)を実行するために使うフックです。
DOMの手動変更、ロギング、タイマー、データ取得などに使えるそう。

こちらは比較的分かりやすい。

  useEffect(() => {
    /* 処理 */
  }, [依存配列])

依存配列を[]とすれば、初期描画時のみ実行する。

⑥useLayoutEffect

useEffectは依存配列が変更され、画面に描画された後で実行するけど
useLayoutEffecctはDOMが更新、画面に描画される"前"で実行する。

こっちのが表示が自然なケースがある。
同期的に実行されるので、重い処理には向かない。

  useLayoutEffect(() => {
    /* 処理 */
  }, [依存配列])

⑦useContext

Contextを使ったフック

//Contextにセットする側(Providerを用いるのは今までと一緒)
const HogeContext = React.createContext< T | null >(null)
<UserContext.Provider value={変数名}>
  ...
</UserContext.Provider>

//呼び出し側(Consumerよりシンプルかな?)
const 変数名 = useContext(コンテクスト名)

⑧useRef

書き換え可能なrefオブジェクトを作成、利用する

useStateやuseReducerは、状態が更新される度に再描画される
refは再描画しないので、描画に関係ないデータを保持するのに便利

//定義時
const ref名 = useRef< T | null>(null)
//①データの保持をするときは、currentの中に入れるだけ
ref名.current =
//②DOM参照する場合はref要素を指定するだけ
return <input ref={ref名} ...>

⑨useImperativeHandle

やや複雑
子コンポーネントが持つデータを参照したり、
子コンポーネントで定義されている関数を親から使うことができる。

多くの場合propsで間に合うのであまり使われないらしい。

⑩useDebugValue

Chromeの拡張機能
をインストールしないと見れない

useDebugValue(文字列)

console.logより現在値が見やすい

カスタムフック

普通のフックは、ループ、条件分岐、コールバック関数の中では呼び出せない。
↑を実現したいときはカスタムフックが助けになる

Next.jsをスタート

以前やったNext.jsチュートリアルと
若干かぶると思うけど、学習のために
やっていく。

Next.jsもReactと同じく、npxコマンド一行でスタートできる

npx create-next-app@latest --ts プロジェクト名
cd プロジェクト名
npm run dev

本では書いてない手順だけど、慣れるために
今回は2章に紹介してあったPrettierもインスコする

npm install prettier --save-dev eslint-plugin-unused-imports 
touch .prettierrc

また、ついでだからTailwind CSSも導入テストしてみる

参考記事:
(Next.jsにTailwind CSSを導入する)[https://zenn.dev/shimakaze_soft/articles/0ce52691b6fc3e]

npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
npx tailwindcss init -p

Next.jsの4つのレンダリング手法

1章の復習も含むけど

  • SSG Static Site Generation(含むStatic)
  • CSR Client Side Rendering
  • SSR Server Side Rendering
  • ISR インクリメンタル静的再生性

複数のレンダリング手法が組み合わされて利用されることも多い?

SSG Static Site Generation(含むStatic)

ビルド時に静的サイト作っちゃって、本番時は静的サイトを表示するだけ。

実装時はgetStaticPropsという関数を使う。
このときDB参照したりとかでき、結果をpropsとして返す。
propsを元に静的ファイル(HTML/CSS/js)をビルド時にまとめて作っちゃう仕組み。

高速表示が可能な反面、リアルタイム性が求められるのには向かない。

CSR Client Side Rendering

Reactアプリ、SPAなアプリに近い。

SEOに弱いらしい?

(kugyu10疑問:現状、SEO向けコンテンツにはWordPressが
 圧倒的に使われている。WordPressはMySQLに都度アクセスする、
つまり思いっきりCSRのはずである。

→(8/20追記)コメントで、WordPressはSSRでは?という指摘頂きました。

でも検索エンジンでは上位に表示されている。
下手な静的サイトよりずっと強いのがWordPressである。
 
表示は少し遅いかもだけどクロール時にもデータとれるなら、
ちゃんとインデックスされるわけで、CSRなだけで「SEOに弱い」は言い過ぎでは?
だったらNextみたいなSSGが、SEO無双することになっちゃうのでは?

もちろん、SEOは、アルゴリズムが非公開なので、
強い・弱いの比較が難しい話であることは理解している。
本書終わったら個人ブログをNext.js+microCMSで
作ってみる予定なので手応えを確認してみたい)

SSR Server Side Rendering

getServerSidePropsを用いて実装する。
これはリクエストのたびに呼び出されるので
リアルタイム性が求められるページ向き。

動的部分を隠すことができるので
セキュリティ的にいい?

当然、サーバー側の負担が増える

ISR インクリメンタル静的再生性

SSGの応用。有効期限がきれたら再度getStatiProps実行し、
ページデータを更新できる。(バックグラウンドビルドが走る)

SSGとSSRの中間的方法と言える

SSG(getStaticProps)の実装

export async function getStaticProps(context) {
  return {
    props: {}
  }
}

dev環境だとリクエストの度に呼ばれているらしい。

トラブル

サンプルコード通りでできたけど、ここでトラブル
なんかCSSがおかしい。
Tailwind CSSがあたってないのもそうだけど、
bodyが display:none;になってしまっている・・・

Sample.tsx時には通ってたので、
getStaticProps時に上手く通ってない可能性がある。

(多分これ)[https://github.com/vercel/next.js/discussions/16104]

npm run devを止めて、
npm run buildしなおして
npm run dev再起動したら治った、なぜだ!?

ダイナミックルーティング(getStaticPaths)

/pages/post/[id].tsxみたいなファイルを作ると、
idの数だけページを作ってくれる機能。

export async function getStaticPaths() {
  return {
    paths: [
      { params: {...} }
    ],
    fallback: false
  }
}

useRouterフック

router.isFallbackで、フォールバックか否かを取得できたり
router.push('/hogehoge')で、ページ遷移させたりできる

SSR(getServerSideProps)の実装

export const getServerSideProps: GetServerSideProps<T> = async (context) => {
  /* 処理 */
  return {
    props: {...},
  }
}

contextにはreqやres、queryなども取得できるらしい。

ISR(revalidateを返すgetStaticProps)の実装

export const getStaticProps: GetStaticProps<T> = async(context) => {
  /* 処理 */
  return {
    props: {...},
    revalidate: 60, //ページの有効期限(秒)
  }
}

やっぱりdevではリクエストの度よばれているっぽい。

Next.jsのその他の機能

リンク

<a>タグをラップする<Link>

<Link href="/post/1"><a>1</a><Link>みたいに、
Link側にhrefをつける。

リンク先は非同期に取得されているので高速遷移が可能。

クエリパラメータを指定したり、それをオブジェクトで渡したり

router.reload()router.back()などもある。

画像

スマホ対応、lazy対応、レイアウト崩れを防ぐ領域確保、
何かとゴチャりがちなimgタグも
Nextならある程度キレイに書くことができる。

placeholderも置けるのでUX良くなるかも。

外部リソース(S3に置くことも多いでしょう)の画像を表示する場合は

①layoutがfill以外の場合は、
 widthとheightを与えてサイズを指定する必要がある

②next.config.jsのdomainsに追加する必要がある。

の2点に注意する。

{/* 外部リソース画像の例 */}
<Image
  src={uota}
  width='510' //必須
  height='510' //必須、あるいはlayout='fill'の指定
  placeholder='blur'
  blurDataURL={uota}
  alt='uota'
/>

APIルートと内部APIを叩く

/api/helloへルートすると
/api/hello.tsへアクセスされる。

//単に、{name: 'kugyu10'} jsonを返すだけ。
export default function handler(req: NextApiRequest, res: NextApiResponse<Data>) {
  res.status(200).json({ name: 'kugyu10' });
}

ブラウザでhttp://localhost:3000/api/hello叩けば希望どおりのJSONが見える

  useEffect(() => {
    fetch('api/hello')
      .then((res) => res.json())
      .then((profile) => {
        setData(profile)
      })
  }, [])

APIを叩くという、極めて大事な部分だが、
本章での解説はアッサリ。

後ろの章で出てこなかったら
公開APIを叩く練習が必要かな

env 環境変数

.envは無条件で使える環境変数

接続情報やパスワード、seckeyなど
gitにいれたくない情報は
.env.local

に入れるといい

開発環境用変数は
.env.development

本番環境用変数は
.env.productにいれる

同様に、開発環境用の、パスワードやseckeyなどは
.env.development.local に記述できる

#.env
#S3のbucketsドメイン
S3DOMAIN=kugyu10-post.s3.ap-northeast-1.amazonaws.com
//next.config.js
const nextConfig = {
  ...
  images: {
    domains: [process.env.S3DOMAIN,],
  }
}

4章 コンポーネント開発でNext.jsらしく

Presentitional Component

見た目を担当するコンポーネント。
本書ではpropsから、ラベル、テキスト、onClickイベントをpropsで受け取って
ボタンを表示するだけのButtonコンポーネントが例示されている。

Container Component

振る舞い、ビジネスロジックのみを担当するコンポーネント。
見た目以外を担当するコンポーネント、という風に考えると

本書の例では、CountButtonの例があった。

return <Button {props} />

と、見た目に関する部分は別コンポーネントに丸投げし、returnは1〜3行ですんでいる。

この、ふるまいと見た目を分割ふる思想は面白い、と思った。

CountButtonだけでなく、FormSendButtonでも、UserRegistButtonでも
なんでもButtonの見た目はButtonコンポーネントを使い回すことができる。

Atomic Designによるコンポーネント設計

階層名 説明
Atoms(原子) これ以上分割できないもの ボタン、テキスト
Molecules(分子) 複数のAtomsを組み合わせて構築 ラベル付きテキストボックス
Organisms(生体) Moleculesよりも具体的な要素 共通ヘッダー、入力フォーム、CTA、
Templates(テンプレ) ページ全体のレイアウト 1カラム2カラムとか?PCなら両脇のマージンとか?
Pages(ページ) ページそのもの ページ

小さなコンポーネント(原子や分子)をくみあわせて
複雑な機能を実現するコンポーネント(生体やテンプレ)を
作り、それを組み合わせてページを作る設計思想。

で、何をどのくらいまで細分化するのか?というのは
開発者のセンスに問われるのだが、その指標となる考え方が
Atomic Design。

Atom

これ以上分割できないコンポーネントとして設計する。
基本的にはふるまいを持たない、Presentitialコンポーネント。
propsから色やテキスト、大きさなどを受け取り描画する役割。

Molecules

複数のAtomsを組み合わせて構築したUIコンポーネント
これもPresentitialコンポーネント。

Organismsの違いとして、「1Moleculesは1役割UI」
で設計するらしい

Organisms

そうすると、ここの範囲デカすぎじゃね?

サインインフォームやヘッダーなど、より具体的なUIコンポーネントを実装。
ここでは振る舞いも実装する。(見た目と別コンポーネントになる)
Contextを参照したり、副作用実装したり。

Templates

ページ全体のレイアウトを実装。
複数のOrganismsを配置する役割。

Pages

ページ。コンポーネントの集合で
最終的にできあがったもの、というイメージ。

状態の管理、router関係の処理、APIコールなどの副作用の実行、
Contextに値を渡すなどのふるまい実装が多くなるらしい?

(やはり、5章以降で実装してみないとピンとこなさそう)

styled-components

CSS in JSと呼ばれるライブラリの1つ。
JavaScript内にCSSを効率的に書く方法。

実行時にはユニークなクラス名が与えられるので
クラス名衝突の危険性がない。

Tailwind CSSを採用したい場合は!?

(絶対先駆者がたくさんいるはずなので、後で調べる)

とりあえず、本書のとおりにインスコ。練習してみるここは練習割愛。

5章以降の実装でTailwindできるか試す。
ちょっとやってだめそうなら素直にstyled-componentsに逃げる構えで。

Storybookとは?

StorybookはUIコンポーネント開発者向け支援ツール。
独立した環境で、個々のコンポーネントを確認できる。

基本的な使い方

本書どおりStyledButtonを実装し、
Storybookで表示・ふるまいを確認してみる

storybook用の10行程度の記述を足すだけで、ボタンの見た目をテストできる

ボタンの実物の一覧をグルーピングされたまま見れるので

「あれ?似たようなコンポーネント作ってない?」

「これはあのコンポーネントでいいな」

とか、チーム内の意思疎通に便利そう。

Actionを追加

んんーなぜstoriesの方にふるまいを書くんだ?

まあ、storiesの方にもいろいろと
テスト用ふるまい書けるよ、という例だと理解しておく

Controlタブを使ったpropsの制御

テキストボックスの横幅を固定値で入れちゃった時
何文字ならスクロール無しで表示できるかな、
とかみるときに便利そう

アドオン

Docsをいじれるらしいけど、どこのファイルやねん!

サンプルコードをダウンロードして確認。
前者は今までいじっていた/stories/StyledButton.tsxで、
後者は同ディレクトリに作る/stories/StyledButton.mdxだった。

ビューポート(iphoneだとどう見えるか?)とか
backgroundを変えられたりとか
ストーリーの別ページに遷移できたり、
いろいろ便利!

便利すぎて、例の広告画像状態になりました。

ウソ・・・私の(前職の)開発環境、古すぎ・・・!?

コンポーネントのユニットテスト

わぁいテスト、あかりテスト大好き

React推奨で現在主流の
「React Testing Library」を使って
Next.jsコンポーネントテストを書こう!

セットアップ

jest@testing-library/react
@testing-library/jest-domjest-environment-jsdom

何やら4つもパッケージをインスコする

その後、jest.setup.jsjest.config.jspackage.json
3つも設定ファイルを作成/更新

ちょっとめんどくさいけどインスコ終わると

npm run test

するだけでテストができる。

けっこうテストは時間かかる印象。

ユニットテストの書き方

/components/コンポーネント名/index.tsxのユニットテストは
/components/コンポーネント名/index.spec.tsx
/components/コンポーネント名/index.test.tsxという名前で書く

fireEventとかactとか、別のライブラリからimportされ、
意味不明なエラーが頻発しがちなので、

import { render, screen, RenderResult, fireEvent, getByRole, act, } from '@testing-library/react'

describe('コンポーネント名', () => {
  beforeEach(() => {
    renderResult = render(<'コンポーネント名' ... />)
  })
    afterEach(() => {
    renderResult.unmount()
  })

  it('should show '初期値' on initial render', () => {
    const node = screen.getByTestId('テストID') as T

    expect(node).toHaveTextContent('初期値')
  })
}

みたいな、テンプレか行頭スニペットか用意した方がいいだろう

テストは気合いれて挑んだが、アッサリ風味でした。


5章 C2Cフリマアプリを作る(ための設計と開発環境)

設計というと、エクセルゴリゴリを
つい連想してしまうkugyu10です。

この規模(個人開発も考えているので参考になる規模)では
このくらいの粒度の設計があるといいんだな、と参考になります。

あと、せっかく作ったなら自分でも使ってみたいので
「オタク缶バッチ専門フリマアプリ」
にしてみたいと思います。

そこ、メルカリでいいじゃん、とか言わない!

ユースケース

アクターとユースケースと紐付ける図です。
ユースケース図はmermaid.js使ったことないので省略します。

どのユースケースにどのページが必要か、までまとめてますね。

アクター ユースケース 詳細 関連ページ
だれでも 商品を検索 商品一覧を表示(検索機能?) トップページ、検索(結果?)ページ、商品詳細ページ
購入者、出品者 商品を購入 商品を買い物カートに入れ、購入 買い物カートページ
出品者 商品を出品 必要な情報(?)を入力し、商品を投稿 出品ページ
だれでも 出品者のプロフィール確認 出品者のプロフ表示、出品者の商品一覧 ユーザーページ
購入者、出品者 サインイン ユーザー名とパスワードでサインイン サインインページ

購入者と出品者にアカウントの違いはなし

こうしてみると、最低5機能、7ページ。

アーキテクチャ

  • フロントエンド on Vercel
    • バックエンドAPIを叩いたりSSG/IRGなどのサーバーサイド
    • HTML/JS/CSSをクライアントに渡すフロントエンド
  • APIバックエンド on Heroku
    • フロントエンドへコンテンツ渡したり、クライアントから動的にCSRも行う

と、フロントとバックを分けたよくある構成です。

SSG+CSRは、全部SSRするより複雑だけど、
SSGで生成した共通部分はキャッシュさせつつ、
必要な箇所だけCSRする、といった芸当が可能
主に初期表示速度向上、UX向上が狙える。

(え?そんなん作らされるの?すご)

シーケンス図は省略

みんな大好き環境構築

  • Next.jsプロジェクト作成
  • Tailwind CSS(本ではstyled-componentsが手順にありますが、あえて逆らって)
  • ESLint
  • Storybook
  • React Hook Form(初出)
  • SWR(初出)
  • React Content Loader(初出)
  • Material Icons
  • 環境変数の設定
  • テスト環境構築
  • バックエンド用のJSON Severの設定

をセットアップしていきます。

Next.jsプロジェクト作成

npx create-next-app@latest --ts (プロジェクト名)

cd (プロジェクト名)
npm run dev

起動確認したら(本書にはない手順だけど)
ここでGithub(やBitBucketなど)にinitial commitをpushしておきます。

#Githubに推奨手順は書いてあるけど
git remote add origin (リポジトリURL)
git push -u origin main

[https://github.com/kugyu10/can-badge-market-nextjs]

このリポジトリに本書完走までの内容を実装していきます。

src/ディレクトリを作って、

pages/
styles/
に移動、tsconfig.jsonをいじります。

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
+    "baseUrl": "src"
  },
-  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
+  "include": ["next-env.d.ts", "src/**/*.ts", "src/**/*.tsx"],
  "exclude": ["node_modules"]
}

tsconfig.json
変えたらnpm run devしなおして、表示されればOK。

Tailwind CSS

本書ではstyled-componentsを採用してますが、
逆らってTailwind CSS導入します。

https://tailwindcss.com/docs/guides/nextjs

↑参考に

npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
npx tailwindcss init -p
tailwind.config.js
/** @type {import('tailwindcss').Config} */ 
module.exports = {

 content: [
 "./src/pages/**/*.{js,ts,jsx,tsx}", //追加
 "./src/components/**/*.{js,ts,jsx,tsx}", //追加
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
/* globals.cssファイルに3行追加 */
@tailwind base;
@tailwind components;
@tailwind utilities;

動作確認:

npm run devしなおす。
index.tsxにテキトーにTailwindクラス(text-yellow-300とか)つっこんで、
index.tsxを上書き保存、
開いているlocalhost:3000画面がリロードなしに変更されるか確認する

ESLintの設定

npm install --save-dev prettier \
eslint \
typescript-eslint \
@typescript-eslint/eslint-plugin \
@typescript-eslint/parser \
eslint-config-prettier \
eslint-plugin-prettier \
eslint-plugin-react \
eslint-plugin-react-hooks \
eslint-plugin-import

インストールしすぎぃ!
(ログたどってエラーでてるパッケージないか確認する)

あとは各種設定ファイルを編集

.eslintrc.json
{
  "extends": [
    "next",
    "next/core-web-vitals",
    "eslint:recommended",
    "plugin:prettier/recommended",
    "plugin:react/recommended",
    "plugin:react-hooks/recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:import/recommended",
    "plugin:import/typescript"
  ],
  "rules": {
    "react/react-in-jsx-scope": "off",
    "import/order": [2, {"alphabetize": {"order": "asc"} }],
    "prettier/prettier" : [
      "error",
      {
        "trailingComma": "all",
        "endOfLine": "lf",
        "semi": false,
        "singleQuote": true,
        "printWidth": 80,
        "tabWidth": 2
      }
    ]
  }
}

npm run lintでリントエラーを検出(そのままやると、hello.tsが怒られる)
npm run formatで修正できるようになったはず(git diffとかで差分を眺めてみたり)

Storybook

npx sb init
#yする

npm run storybook
npm install --save-dev @storybook/addon-postcss \
 tsconfig-paths-webpack-plugin \
 @babel/plugin-proposal-class-properties \
 @babel/plugin-proposal-private-methods \
 @babel/plugin-proposal-private-property-in-object \
 tsconfig-paths-webpack-plugin \
 @mdx-js/react

インストールしすぎぃ!
(ログたどってエラーでてるパッケージないか確認する)

ここで何やら気になる警告メッセージでる

Severity: high
Uncontrolled Resource Consumption in trim-newlines - https://github.com/advisories/GHSA-7p7h-4mm5-852v
fix available via `npm audit fix`
node_modules/trim-newlines
  meow  3.4.0 - 5.0.0
  Depends on vulnerable versions of trim-newlines
  node_modules/meow

21 high severity vulnerabilities

To address issues that do not require attention, run:
  npm audit fix

To address all issues (including breaking changes), run:
  npm audit fix --force

trim-newlinesとやらに脆弱性があるよ、とのこと

npm audit fixでもなおらない

まあ、壊れたらまた1からインスコしよう、ぽちっとな

npm audit fix --force

結果は

26 vulnerabilities (1 moderate, 22 high, 3 critical)

To address issues that do not require attention, run:
  npm audit fix

To address all issues, run:
  npm audit fix --force

な、なおらない・・・

今回はスルーします。
直せました

https://zenn.dev/kugyu10/articles/d297123ba0eae3

storybookのセットアップ

mkdir .storybook/public

.storybook/main.jsラスト2行目に追加

  "staticDirs": ['public'],

本の通りにstorybookの設定

npm run storybook

styled-componentsないよ、と怒られる。

styled-componentsに依存する場所はコメントアウト、
Tailwind CSSとstorybookを使う方法は

https://zenn.dev/youichiro/articles/d625e602ed47c1

を参考に設定する。神。

@storybook/addon-docsが解決できないよ、とか怒られる。

ぐぐりながらpackage.jsonに
@storybook/addon-docs": "^6.3.12",
の行を追加で解決。

React Hook Form

強力なフォームバリデーションライブラリ

npm install react-hook-form

SWR

キャッシュから高速表示するが、裏で非同期でキャッシュ更新し、
高速化とリアルタイム性を両立するライブラリ。

npm install swr

React Content Loader

npm install react-content-loader
npm install --save-dev @types/react-content-loader

Material Icons

npm install @mui/material @mui/icons-material @emotion/react @emotion/styled

本書のとおり.envも設定

テスト環境

npm install --save-dev @testing-library/jest-dom \
@testing-library/react \
jest \
jest-environment-jsdom

ルートディレクトリにjest.setup.js、jest.config.jsを作成、本書の通りに記述

package.jsonの"scripts"に
"test": "jest"を追加

next.config.jsを記述

JSON Serverの設定

REST APIの(ダミーの)エンドポイントを作成する

本書からgit cloneするという手順なのでアッサリ。

cd ../
git clone https://github.com/gihyo-book/ts-nextbook-json
mv ts-nextbook-json can-badge-market-backend #自分のプロジェクト名
cd can-badge-market-backend
npm ci
npm start
curl -X GET -i https://localhost:8000/users

・・・だんだん記事にする内容がなくなって参りました・・・
全て、「本書通りやりました」なので・・・


6章 大ボリュームの実装、ここからが本当の地獄だ

以下は実装時に疑問に思ったことをメモ。
読み進めていくうちに自然に解決することも多いかもだけど、
解決しなかったら、一周後に調べたい

APIクライアントの実装

関数名 API パス
signin 認証API /auth/signin
signout 認証API /auth/signout
getAllUsers ユーザーAPI /users
getUser ユーザーAPI users{id}
users/me
getAllProducts プロダクトAPI /products
addProduct プロダクトAPI /products/{id}
purchase 購入API /purchases

fetcher関数

// eslint-disable-next-line @typescript-eslint/no-explicit-any
多分、次の行のanyは許してね、という記述。

  • 他の記述方法は?
  • なぜanyになる? < T | null > みたいにできないのか?

引数init?の?の意味は?
→TypeScriptでその引数はオプションだよ、と明記する方法らしい。
Rubyのメソッドempty?みたいな、
boolean返す目印みたいに見えてややこしい。

ここでなげたerrorは何がどうキャッチする?その後の処理は?
意図的にerrorを投げるテストは書けるのか?

APIクライアントの実装

/src/servies/auth/signin.ts

'types'を後で書く理由は?

fetcher関数の引数もよくわからんが、
ここでは「こういうもの」と理解
もしheaders以下も定型文なら、fetcher関数でまとめることはできないのか?

今回はAPI認証がないんだけど、認証がある場合はどこらへんに書く?(7章以降にでるかな?)

/src/servies/users/get-user.ts

サンプルレスポンス書く場所、あれでいいの?
/**だから、いい感じにJSDOC(TSDOC?)になるんだろうけど

method: 'GET',は書かなくていい?省略できる?

/src/types/data.d.ts

プロジェクト定義の型は
なるべくこのディレクトリに書こう、というやつかな?

「他のソースコードはリポジトリをご確認ください」、オイオイ

せめて、ここで何を実装したか、
ブランチなりファイル名なり欲しかった・・・

多分/src/services/以下全部だとあたりをつけて写経。

ファイル 説明
/src/servies/auth/signin.ts 本書で解説済み
/src/servies/auth/signout.ts
/src/servies/products/add-product.ts
/src/servies/products/get-all-products.ts
/src/servies/products/get-product.ts
/src/servies/products/use-product.ts
/src/servies/products/use-search.ts
/src/servies/purchases/purchase.ts
/src/servies/users/get-all-users.ts
/src/servies/users/get-user.ts 本書で解説済み
/src/servies/users/use-user.ts

ここだけで残り9ファイルもあるじゃん・・・

あと、なんか import { User } from 'types'が解決できないな、と思ったら

/src/types/index.d.ts
export * from './data'
export * from './styles'

というファイルがあることが判明。これで解決。
(これ先に書けばよかったのでは?)

get-all-products.ts

conditionsにforEachを使っているのはなぜ?
→多分、カテゴリは単一しか指定検索できないけど、
状態は複数選択可、という検索フォームなんでしょう。

検索パラメータ渡す箇所、

  const params = new URLSearchParams()

  category && params.append('category', category)
  conditions &&
    conditions.forEach((condition) => params.append('condition', condition))
  userId && params.append('owner.id', `${userId}`)
  page && params.append('_page', `${page}`)
  limit && params.append('_limit', `${limit}`)
  sort && params.append('_sort', sort)
  order && params.append('_order', order)

`${userId}`とバッククオート引用のところと
sort と引数そのままわたしてる箇所ある
numberはstringにしないとappendできない?
(このあとにtoString()してるけど)

stringにするだけが目的なら、
String(userID)の方がリーダブルじゃない?そうでもない?

appendするキーを_pageとアンスコってるところもあれば
categoryのようにそのままのところもある
ここらへんの記述がバラつく理由は?

Omit<T, key>は便利だと思った。
わざわざ別の型を用意しないといけないと思っていたので。

あとは、update系はなくていいんかな

オリジン間リソース共有(CORS)

オリジン間リソース共有(Cross-Origin Resource Sharing)は、
あるオリジン(dev環境なら、localhost:3000、本番ならvercel)から
別のオリジン(dev環境なら、localhost:8000、本番ならheroku)にアクセスする仕組み。

セキュリティ上の関係から、ブラウザは
基本的にクロスオリジンなアクセスを許可していません。

Next.jsのRewrites機能で、指定URLを別のURLに変換することで、
よしなにできるらしい。

  async rewrites() {
    return [
      {
        // ex. /api/proxy
        source: `${process.env.NEXT_PUBLIC_API_BASE_PATH}/:match*` ,
        // ex. http://localhost:8000
        destination: `${process.env.API_BASE_URL}/:match*` ,
      },
    ]
  },

(ここも一周後、もう少し勉強します)

コンポーネント実装の準備

共通スタイルやthemeなどを設定していく節です。
本書ではstyled-componentsを使ってますが
ぼくは勝手にTailwind CSSを使っているのでほぼほぼ省略します。

componentsのクラスが崩壊したりしたら、
ここにもどって構造的に書けるように調整します。

レスポンシブ

Tailwindでは接頭辞を使ってレスポンシブは実現できます、
Responsive型は用意しなくてもOKかと思います。

ブレークポイント名 記述 一般的な対象
(接頭辞なし) スマホ向け
sm: @media (min-width: 640px) { ... } タブレット向け
md: @media (min-width: 768px) { ... } タブレット向け
lg: @media (min-width: 1024px) { ... } パソコン向け
xl: @media (min-width: 1280px) { ... } パソコン向け
2xl: @media (min-width: 1536px) { ... } テスクトップ向け

というか細々とした設定全部styled-components用っぽいので
いったん飛ばします。

Atomic Designによるコンポーネント設計の実施

Atomic Designとは、以下の5つに分解して
デザインしていく手法でした。

Atoms
Molecules
Organisms
Template
Pages

特に、

  • Atomsの粒度はどのくらいにするのか?
  • MoleculesとOrganismsの境目はどこ?

が気になります。

ヘッダー・フッター

種類 コンポーネント
Atoms ボタン、ロゴ、テキスト、シェイプイメージ、スピナー、バッジ、アイコンボタン
Molecules バッジアイコンボタン
Organisms ヘッダー
Templates なし

サインインページ

種類 コンポーネント
Atoms ボタン、ロゴ、テキストインプット、テキスト、スピナー
Molecules なし
Organisms サインインフォーム、グローバルスピナー
Templates レイアウト

(他略)

Oh、ほとんどがAtomsだった・・・

Atomの実装

Button

Atom、という名に反して、けっこうコード量は多くなりそうですね。

サンプルコードはいきなり

const Button = styled.button<ButtonProps>`

と、styled componentsを使っていたので、困ってしまいました。
returnも見当たらないし、どう動いているのか・・・謎・・・

とりあえず、本書のコードは忘れ、ぐぐりながら
単にJSX.Elementをreturnしてくれる
シロモノを作りました。

そして、ちょっとずつTailwindのクラスを追加していきます。

ありがたいことに、このボタンのstorybookの実装は
styled-componentsに依存する部分はないっぽいので
先にそっちを書き、
storybookがイメージ通り動くまで記述していく、という方針でいきます。

//ボタンのバリアント
export type ButtonVariant = 'primary' | 'secondary' | 'danger'

export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
  variant?: ButtonVariant
  width?: number
  height?: number
  disabled?: boolean
  //onClick?: function
}

const variants = {
  //プライマリ
  primary: ['text-white', 'bg-blue-600', 'border-0', 'hover:bg-blue-800'],
  secondary: ['text-white', 'bg-pink-600', 'border-0', 'hover:bg-pink-800'],
  danger: ['text-white', 'bg-red-600', 'border-0', 'hover:bg-red-800'],
}

const Button = (button: ButtonProps) => {
  //ButtonType
  const buttonType = 'button' //FIXME とりあえずbutton固定、submitやreset対応必要
  let buttonClasses = 'rounded px-4 py-2 m-2 '

  //バリアントのスタイルの適用
  if (button.variant && variants[button.variant]) {
    buttonClasses += variants[button.variant].join(' ') + ' '
  }
  //Disable
  if (button.disabled) {
    buttonClasses += 'text-white bg-gray-600 hover:bg-gray-800'
  }

  //横幅
  if (button.width) {
    buttonClasses += `w-${button.width} `
  }

  //縦幅
  if (button.height) {
    buttonClasses += `h-${button.height} `
  }

  return (
    <button
      type={buttonType}
      className={buttonClasses}
      onClick={button.onClick}
    >
      {button.children}
    </button>
  )
}

export default Button

なんか縦幅・横幅は後から動かせない。
.w-40とか.w-60とかのスタイルが動的に適応されているが、
 その後から追加するスタイルが定義されてないっぽい)

↓解決しました。

onClickの指定の仕方があってるのかよくわからない。
ふるまいを記述するコンポーネントの方で書くのか?

理解度30%のまま、Textコンポーネントへ

storybookのpreviewにTailwind CSSのCDNを記述

/.storybook/preview-head.htmlを作成、以下の一行を追加

<link rel="stylesheet" href="https://unpkg.com/tailwindcss@2.2.19/dist/tailwind.min.css">

これで、ある程度のクラスはCDNから読んでくれるので、
widthやmarginなども動的に動かせます。

Text

バリアント名 font-size letterSpacing lineHeight Tailwindクラス
'extraSmall' 12pt 0.06px 17px .text-xs
'small' 14pt 0.07px 19px .text-sm
'medium' 16pt 0.08px 22px .text-base
'mediumLarge' 20pt 0.09px 26px .text-xl
'large' 24pt 0.1px 28px .text-2xl
'extraLarge' 32pt 0.1px 37px .text-3xl

厳密にはletterSpacingもlineHeightもあってないが、
個別に指定するほど大きな差ではないので、割愛します。

storybookでhtmlを表示できるようにする

npm install --save-dev @storybook/addon-storysource
npm install --save-dev @whitespace/storybook-addon-html #error

一通りぐぐったあと--forceを試してみます

npm install --save-dev --force @whitespace/storybook-addon-html

このあと`storybook/main.jsを追記

...
"addons": [
    "@whitespace/storybook-addon-html",
]

これでできました。

参考:

https://zenn.dev/mym/articles/69badd52494031

Atoms/ShapeImage

コンポーネントの実装をどんどん進めていきます。
画像を四角や丸く表示するためのコンポーネントです。

Imageにshapeという引数を追加して拡張したようなモノを作るのですが
ts初心者のぼくには呪文に見える・・・

Input

また違った書き方・・・

TextArea

引数でextends使い始めたー

なんとかstyled-componentsを使わないで
実装とstoriesかけたけど、
これであっているかよくわからん・・・

Badge

これはシンプルに実装できた

Atoms残り

本書にないAtomsで、残り7つ、

  • ロゴ
  • パンくずリスト要素
  • スケールイメージ
  • スピナー
  • セパレーター
  • レクトローダー
  • アイコンボタン

をGithubから写経・実装する必要がある

オー!ノーッ!めちゃくちゃ多い!!
おれの嫌いな言葉は一番が「努力」で二番目が「ガンバル」なんだぜーッ!

これはシンプルにsvgを表示するだけなので問題なし

パンくずリスト

li {
  &:not(:first-child) {
    /* styles */
  }
  a {
    /* styles */
  }
}

って、Tailwindだとどう書けばいいんだ・・・?
パンくずリスト下のLinkをAtomsに追加しないとダメ?

後回し

スケールイメージ

Wrapper側と画像側と、両方にsizeをもたせることが可能。
Imageはsizeもたせないとエラーになることがあるので
こうしているんだろうけど。

スピナー・レクトローダー

svgを描画するAtoms
他にもいろんなバリエ作れそうだけど
今回はこれだけ、ってことなのだろう。

本の仕様通りなのか自信ないので、
明日、Githubからのソース、storybook起動して
見た目やふるまいが正しいかチェックしたい

アイコンボタン

アイコンは@mui/icons-materialから借りている。
こういうのはfont awesome使うことが多かったが、
違いは?

記述がカンタンなのがいいけど
表示の速さ(は、SSG、SSRで問題ないのか?)も
考慮したい。

また、このAtomsは
他のAtomsのように引数を持つのではなく、

  • PersonIcon
  • GitHubIcon
  • SerchIcon

と、アイコン名で固定してたくさんexportする形にしている。

記述方法のブレ

多分、学習のためなんだろうけど、

↑の引数ではなくエクスポート名を
複数にしたり、

returnを使ったり、省略した関数にしたり、

既存の引数を拡張するのに
extendsを使ったり、&を使ったり、

いろんな記述法を練習させている感じがある。

書いていたり調べているうちに、

「TypeScriptぜんぜん分からん・・・」
状態になってきました。

そうでなくても、Tailwindでの記述方法に
悩むことも多いです。

TypeScriptぜんぜんわからん・・・

と困っていたところ、
サバイバルTypeScript
なるものと出会ったので、それ読んで勉強してました。

脇道にそれるので別記事に記述してます。

https://zenn.dev/kugyu10/articles/048d99ad1f4021

あと、本書のサンプルコードのstorybookを
実際に動かしてみて、ちょっと挙動が違ったところを直したりしてたら
1日が終わってました。

Moleculesの実装

今まで使ったAtomを組み合わせたりなんだりして
Moleculesを実装していきます。

CheckBox

CheckBoxは Text と Flex と IconButton と IconButton で
できている。

問題1 なぜかimportできない

import { CheckBoxIcon } from '../../atoms/IconButton'
なら通るけど

import { CheckBoxIcon } from 'component/atoms/IconButton'
import { CheckBoxIcon } from '/src/component/atoms/IconButton'
は通らない。

componentのpathを通す的な設定、見落とした?

https://nextjs-ja-translation-docs.vercel.app/docs/advanced-features/module-path-aliases

ts.config.jsonいじることで解決できそうなんだけど、
サンプルコードにはその記述ないんですよね・・・

あとで解決するとして、今回は相対パス指定でいきます。

→(9/3)時間たったらなぜだか解決してました・・・なぜ・・・?

問題2 Flexってlayout(この後実装するやつ)じゃね?

あれ?どこか読み落とした・・・?
→一度、 divで代用

実装してみたけど、ラベルがうまく動かない。

→普通に本書P226あたりを見落としてただけでした。

  • useStateを用いている
  • ドロップダウン外観
  • ドロップダウンの値
  • ドロップダウンの矢印の外観
  • ドロップメニュー
  • ドロップメニューの選択肢
  • ドロップダウン要素
  • ドロップダウン本体
  • マウスダウンしたときのイベント
  • 選択と画面外クリックのイベント

などなど、実装することが予想以上に多い。

実装済み
しかし挙動が変。

Dropzone

実装済み、しかし
ファイルのアップロードが
動作しない

ImagePreview

実装ずみ、しかし動作があやしい

9/3 方針変更

最悪、styled-componentsを入れたまま
まずは本の完走を目指すことにしました。

①夏休みが終わって土日祝以外
 大して進められなくなったこと

②TailwindCSSで実装しようと思ったが、
 バグ直せず行き詰まってしまった

これまでぼくは、6章の実装は
本で紹介されているstyled-componentsではなく
Tailwind CSSを用いての実装をしてきました。

しかし、Atomsレベルならともかく
Moleculesレベルになると
うまく動作しないコンポーネントが多くなってきました。

TypeScriptの理解も
Reactの理解も
Styled-Componentsの理解も
Tailwind CSSの理解も

浅いと言わざるを得ないのですが、
ないものねだりしても仕方ないです、

一度、styled-componetsをnpm installして、
サンプルコードをコピペし、

1ブロックずつ Tailwindに
置き換えしておかしなところを
チェックできるようにしてみました。

例えば、以下Checkboxでは、
なんでもないようなCheckBoxElement
うまくTailwindに置き換えできてないことが判明。

src/components/molecules/CheckBox/index.tsx
//こっちは動く
const CheckBoxElement = styled.input`
  display: none;
`

//こちらに置き換えるとrefが効いてないのかonChangeイベントが発生しない
const CheckBoxElement2 = (props: React.ComponentPropsWithRef<'input'>) => {
  return <input className="hidden" {...props} />
}

Checkboxではそれ以外は
Tailwind置き換えして
うまく動作していることが確認できてます。

CheckBoxElementが
CheckBoxElement2に置き換えられない理由は
分からないのですが、(分かる人いたら教えて下さい)

→(9/4解決?)
多分refが動作してない、という推測があってる様子

普通のHTML要素ではref要素は
含まれないので、
props: React.ComponentPropsWithRef<'input'>
だけじゃ拾えてなかったようです。

軽く調べたところ、
forwardRef( (props, ref) => {} )
でラップするとうまくいくみたい?

// 非表示のチェックボックス要素
// eslint-disable-next-line react/display-name
const CheckBoxElement = React.forwardRef(
  (props: React.ComponentPropsWithRef<'input'>, ref) => {
    return <input ref={ref} className="hidden" {...props} />
  },
)

ただ、eslintの怒られ理由がよくわかってないです。

たぶん、自作要素に正しくrefを渡す作法があると思うんですけど・・・。

参考になりそうな記事:

https://www.white-space.work/using-react-ref-in-original-compoent/

https://chaika.hatenablog.com/?page=1633649400

https://zenn.dev/misuken/articles/d503c45c92aa70

各種サイト等活用しつつ、
最悪styled-componentsが混ざったままでも

まずは本の完走を目指していきます。

[9/12]

無事Molecules実装が終わり、

Organismsの実装に取り掛かります。

CartProduct

refが渡せなかった時と同様、
onClickが渡せていない?

Next.jsがイベントをいい感じに
してくれるのてソースでは
見えづらく、どこが悪いのかわかりません。

もうちょっと調べて
ダメならStyled-Componentで一度妥協予定。

GlobalSpinner

GlobalSpinnerContextが
何をやっているかサッパリです。

styled-componentはなかったので
問題なく動きました。


文章に書くと短いですが
今日はここまで。

kugyu10

GitHubで編集を提案

Discussion

うさぎうさぎ

WordPressは(MySQLへのアクセス含め)サーバーサイドで処理されるからSSRのはず?
クライアントからWordPress REST APIを叩いてたらCSRになるかも。

kugyu10kugyu10

なるほど、ここはぼくの理解が間違ってたようです。ご指摘ありがとうございます。

mizhiro3mizhiro3

とても読みやすくまとめてありますね。
Googleトレンドのリンクで、TypeScriptの後ろにスラッシュが入っているため、Dartが1位になっていますよ。