7/25に発売されたばかりの、TypeScript/React/Next.js本読んでみた感想(随時更新)
7/25に発売されたばかりの、TypeScript/React/Next.js本読んでみた感想(随時更新)
- 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週間くらいかかる見込みです。。。
正誤表とサンプルコード
初版はけっこう正誤あるので、最初に(紙の本なら)書き込みしてしまうのがいいかも
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-dom
とjest-environment-jsdom
と
何やら4つもパッケージをインスコする
その後、jest.setup.js
とjest.config.js
とpackage.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導入します。
↑参考に
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
npx tailwindcss init -p
/** @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
インストールしすぎぃ!
(ログたどってエラーでてるパッケージないか確認する)
あとは各種設定ファイルを編集
{
"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
な、なおらない・・・
今回はスルーします。
直せました
storybookのセットアップ
mkdir .storybook/public
.storybook/main.jsラスト2行目に追加
"staticDirs": ['public'],
本の通りにstorybookの設定
npm run storybook
styled-componentsないよ、と怒られる。
styled-componentsに依存する場所はコメントアウト、
Tailwind CSSとstorybookを使う方法は
を参考に設定する。神。
@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'
が解決できないな、と思ったら
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",
]
これでできました。
参考:
Atoms/ShapeImage
コンポーネントの実装をどんどん進めていきます。
画像を四角や丸く表示するためのコンポーネントです。
Imageにshapeという引数を追加して拡張したようなモノを作るのですが
ts初心者のぼくには呪文に見える・・・
Input
また違った書き方・・・
TextArea
引数でextends使い始めたー
なんとかstyled-componentsを使わないで
実装とstoriesかけたけど、
これであっているかよくわからん・・・
Badge
これはシンプルに実装できた
Atoms残り
本書にないAtomsで、残り7つ、
- ロゴ
- パンくずリスト要素
- スケールイメージ
- スピナー
- セパレーター
- レクトローダー
- アイコンボタン
をGithubから写経・実装する必要がある
オー!ノーッ!めちゃくちゃ多い!!
おれの嫌いな言葉は一番が「努力」で二番目が「ガンバル」なんだぜーッ!
AppLogo
これはシンプルに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
なるものと出会ったので、それ読んで勉強してました。
脇道にそれるので別記事に記述してます。
あと、本書のサンプルコードの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を通す的な設定、見落とした?
でts.config.json
いじることで解決できそうなんだけど、
サンプルコードにはその記述ないんですよね・・・
あとで解決するとして、今回は相対パス指定でいきます。
→(9/3)時間たったらなぜだか解決してました・・・なぜ・・・?
問題2 Flexってlayout(この後実装するやつ)じゃね?
あれ?どこか読み落とした・・・?
→一度、 divで代用
実装してみたけど、ラベルがうまく動かない。
→普通に本書P226あたりを見落としてただけでした。
Dropdown
-
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に置き換えできてないことが判明。
//こっちは動く
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を渡す作法があると思うんですけど・・・。
参考になりそうな記事:
各種サイト等活用しつつ、
最悪styled-componentsが混ざったままでも
まずは本の完走を目指していきます。
[9/12]
無事Molecules実装が終わり、
Organismsの実装に取り掛かります。
CartProduct
refが渡せなかった時と同様、
onClickが渡せていない?
Next.jsがイベントをいい感じに
してくれるのてソースでは
見えづらく、どこが悪いのかわかりません。
もうちょっと調べて
ダメならStyled-Componentで一度妥協予定。
GlobalSpinner
GlobalSpinnerContextが
何をやっているかサッパリです。
styled-componentはなかったので
問題なく動きました。
文章に書くと短いですが
今日はここまで。
kugyu10
Discussion
WordPressは(MySQLへのアクセス含め)サーバーサイドで処理されるからSSRのはず?
クライアントからWordPress REST APIを叩いてたらCSRになるかも。
なるほど、ここはぼくの理解が間違ってたようです。ご指摘ありがとうございます。
とても読みやすくまとめてありますね。
Googleトレンドのリンクで、TypeScriptの後ろにスラッシュが入っているため、Dartが1位になっていますよ。
ご指摘ありがとうございます。修正しました!