『りあクト! TypeScriptで始めるつらくないReact開発 第3.1版【Ⅰ. 言語・環境編】』 の個人的まとめ
『りあクト! TypeScriptで始めるつらくないReact開発 第3.1版【Ⅰ. 言語・環境編】』を読んだ個人的まとめを書いていきます。
ほとんど引用で、大事・知らないかったところについてまとめます。
第1章
-
フロントエンド開発に Node が必要な理由
・JSをローカルのPCで使うため
・npm を使ってパッケージ管理するため
・パフォーマンス最適化のために JavaScript や CSS ファイルを少数のファイルにまとめる(バンド ル)
最新版の機能を使用している JavaScript のコードをブラウザ実行時に polyfill するのではなく 最初からコンパイルしておく
・開発中、ブラウザにローカルファイルを直接読み込ませるのではなく、ローカル環境で開発用の HTTP サーバを起動してそれ経由でアプリケーションを稼働させる
・テストツールを用いてユニットテストや E2E テストを記述する
・ソフトウェアテストやコードの構文解析をローカル環境で実行する
- package
nodenv を含めた『ほに ゃらら env』をまとめてアップデートをしてくれる anyenv プラグインの anyenv-update と、
npm イン ストール時にデフォルトでいっしょにインストールしておくパッケージを指定できる nodenv プラグ インの nodenv-default-packages
- CRA
create-react-app コマンドを実行すると、指定したテンプレートを用いてアプリケーションのスケル トンが生成されるのと同時に、次の 3 つのパッケージの最新バージョンがインストールされる
・react: React 本体のパッケージ
・react-dom: DOM を抽象化して React から操作できるように するレンダラーのパッケージ
・react-scripts: CRA の魔法を一手に引き受けてるパッケージ
- npx
npx というのは npm パッケージで提供されてるコマンドを実行するためのものなんだけど、該当 パッケージがマシンにインストールされていればそれを、なければ最新版をダウンロードしてきて それを実行する。
そしてダウンロード実行の場合は、実行後、そのコマンドのパッケージをきれい に削除してくれるの。1 回限りのコマンド実行にうってつけ。
- CRAの裏側
CRA で作成したプロジェクトでは、こうやってソースコードファイルはコンパイラである Babel によってコンパイルされて、それがバンドラである webpack によって適切な形にまとめられ、さら にそれらが相互に関連付けられる
- package.json と yarn-lock
・この package.json に各パッケージのインストールするべきバージョン番号が指定されてるわけだ けど、各パッケージが依存しているパッケージ群がどのバージョンでインストールされるかという のは、これだけでは固定されない。
それぞれのパッケージは日々開発が進んでバージョンが更新さ れているので、package.json の内容が同じであってもまっさらな状態から yarn install でインスト ールされた内容は、今日実行するのと 1 ヶ月後に実行するのとではおそらく異なってくる。
・同じアプリを開発してるはずのメンバーそれぞれで、インストールされているパッケージのバー ジョンがちがうと挙動が変わることもあるし、ひいては開発環境では動いてるのに本番環境では動 かないなんてことがザラに起こってくるよね。
だからこそ Yarn ではデフォルトで、パッケージの各依存関係のどのバージョンがインストールさ れたのかを、正確に記録し再現するために yarn.lock というファイルがプロジェクトのルートに作 られるようになってるの。
ちなみに npm コマンドを使ってる場合は package-lock.json というファイルになる。通常ではこ れらのロックファイルは Git のリポジトリに含まれるようになってるので、その内容が同じである 限りインストールされる npm パッケージ群のバージョンはすべて同じになるはず
- yarn eject について
これが前にちょっとだけふれた、react-scripts の庇護から抜け出すためのコマンド 庇護。実行すると隠蔽されてた 50 以上のパッケージが package.json の "dependencies" エントリに現 れる。
またプロジェクトルートには config/ ディレクトリが作られ、そこに webpack などの設定フ ァイルが置かれる。
これは 茨 の道だけど、その代償に webpack の設定をいじらないと使えないよ いばら うなプラグインが使えるようになったりとか、各種ライブラリが提供する独自の DSL を Babel に コンパイルさせることができたりといった自由が手に入る
第2章
- var を使ってはいけない理由
1. 再宣言および再代入が可能
2. 変数の参照が巻き上げられる
3. スコープ単位が関数
Web Speed Hackathon2021 に参加しました
Cyber Agentさんが主催されたWeb Speed Hackathon2021 に参加したので、やったことを殴り書きでまとめてみようと思います。
参加方法
Cyber Agentのサイトから参加登録をします。特に面接などはありませんでした。
ハッカソン当日
お題は「 めちゃくちゃ遅いSNSを早くする」でした。
どれくらい遅いかというと、lighthouseのスコアが0になるくらいには遅かったです(スコアを0にするのも大変だったそうです)。
二日間を通してlighthouseのperformanceをどれだけ高められるかという勝負でした。
それでは自分がやったことをまとめていきます。
1日目
mode: development
をmode: production
に変更。production モードにするだけでWebpackが諸々をある程度は最適化してくれます。- Webpackの
sourcemap
をfalse
もしくはprocess.env.NODE_ENV === 'production' ? false : true
のようにし、productionモードではJSのソースマップが展開されないように設定した - JSとCSSを最適化 する
- JSにはterser-webpack-pluginを、CSSにはoptimize-css-assets-webpack-plugin を使いました。
- tailwindcssの
purge
と言うプロパティを設定する。これを使うと tailwindcssで使われていないクラスを削除してくれます。tailswindcss自体触ったことなかったのですが、yarn build
した時にターミナルに警告文が出ていたので気がつきました。以下のように
module.exports = { darkMode: false, purge: { enabled: true, content: [ './src/**/*.jsx', ] } };
- 重たい
moment
を軽量のdayjs
に置き換える。 - 重たい
lodash
をlodash-es
に置き換える。
1日目は大体これくらいで終わりました。この時点でスコアが確か60後半とかまで出ていた気がします。(あれ、思ったよりも低いし僕の最終スコアって確か51とかだった気が…)
2日目
2日目は主に画像や音声、gifの最適化に費やしました。
.png
をより軽量のwebp
に変換するImageminWebpackPluginを使いました。CopyWebpackPluginも入れてます。
module.exports = { plugins: [ new CopyWebpackPlugin([{ from: "./public/images/*.png", // 変更元のディレクトリの場所と拡張子を指定 to: "./public/images/[name].webp" // webp化した画像を吐き出すディレクトリの場所を指定 }]), new ImageminWebpackPlugin({ plugins: [ ImageminWebP({ // quelity を設定。激しい画質落ちがなかったので1にした quality: 1 }) ] }) ] };
<img>
タグに<img loading="lazy">
にして遅延読み込みを行う(ブラウザがChromeなので作用します)。- 音声に関してはGUI上で最適化してくれるサイトがあったのでこれで済ませました。ファイルサイズは13MBくらいでした(重い…)
gifをwebmに変換まではしたのですが、なんかうまくいかなかったので、仕方なくgifを最適化するGUIツールを使いました。ファイルサイズは100MBでした。(重すぎるだろ…) ちなみに、競技終了後になってから
.gif
拡張子を正しく.webm
に変えられていなかった,<video>
タグへ変更していなかったと気がつきました。悔しい。JavaSctiptで並列処理していたところを並行化しました。
async function insertSeeds() { await ProfileImage.bulkCreate(profileImages, { logging: false }), await Image.bulkCreate(images, { logging: false }), await Movie.bulkCreate(movies, { logging: false }), await Sound.bulkCreate(sounds, { logging: false }), await User.bulkCreate(users, { logging: false }), await Post.bulkCreate(posts, { logging: false }), await PostsImagesRelation.bulkCreate(postsImagesRelation, { logging: false }), await Comment.bulkCreate(comments, { logging: false }) ]) }
これをこう↓
async function insertSeeds() { await Promise.all([ ProfileImage.bulkCreate(profileImages, { logging: false }), Image.bulkCreate(images, { logging: false }), Movie.bulkCreate(movies, { logging: false }), Sound.bulkCreate(sounds, { logging: false }), User.bulkCreate(users, { logging: false }), Post.bulkCreate(posts, { logging: false }), PostsImagesRelation.bulkCreate(postsImagesRelation, { logging: false }), Comment.bulkCreate(comments, { logging: false }) ]) }
<scirpt defer>
をつけることでJSを並行で取得しています。詳しいHTML仕様書はこちら
こんなことをしたあたりで時間切れになりました。
結果発表
気になる最終スコアですが、なんと…………………………
ど゛う゛し゛て゛な゛ん゛だ゛よ゛お゛お゛ぉ゛お゛!゛!゛!゛ん゛あ゛あ゛あ゛あ゛あ゛ぁ゛ぁ゛あ゛あ゛!゛!゛!゛!゛
なんか1日目よりも下がってる…ちゃんと画像系の最適化もしたはず何になぜ…
これも終わってから気づいたのですが、不要になったライブラリを削除していないと気がつきました。 しかも今回僕は色々なライブラリを試しまくっていました。不要になったライブラリを削除したところスコアが上がりました。悔しい…
競技終了後の解説で山ほど改善できた点があったことを知り、ずっと「すげぇ…」と言っていました。 ちなみに1位の人は600天超えでした。コードを拝見したのですが、擬似サーバーサイドレンダリング?などをしているようで「すげぇ…」となりました。
まとめ
初めてのパフォーマンスチューニングでしたが、めちゃくちゃ楽しかったです。 もっとパフォチュを知りたいと思ったし、ブラウザについての興味も湧いてきました。とりあえず『超速本』を読んで勉強します。 最後に Cyber Agentさん、今回はこのような素晴らしいハッカソンを開催していただきありがとうございました!
Vueの$attrsについて
Vueには$attrs
というAPIがあります。
使い方についてはここら辺を参考にしてください。
使い方は分かってもこれの使い所がよくわからなかったのですが、先日先輩に教えていただいたので書き記しておきます。
結論から書くと
親コンポーネントから子コンポーネントへたくさんprops
を渡す+子側でそのprops
をtemplate
部分で使わない時
です。
具体的なコードで見ていきましょう。
まずは$attrs
を使うのが有効な場合
parent.vue // 親側 <MyComponent foo="foo" bar="bar" hoge="hoge" fuga="fuga" />
Child.vue // 子側 <template> <div> これは子コンポーネントです。propsをtemplate部分で使っていません。 使うときは{{ $attrs.foo }} こうやって使います。 </div> </template>
このようにprops
を直接template部分で使わないときは全てまとめて渡すことに意味があります。
例えば、color
といったプロパティはそのまま渡すことで子コンポーネントの色を変えることができます。
しかし、template
で使おうと思ったら、いちいち$attrs.foo
のように書かなくてはいけません。これは浮上に面倒ですし可読性も良くありません。
続いて$attrs
を使うのが微妙な時(子側で明示的に宣言する形)
parent.vue // 親側 <MyComponent label="ラベル1 ラベル2 ラベル3" foo="foo" bar="bar" />
Child.vue // 子側 <template> <div> {{ label }} {{ foo }} {{ bar }} </div> </template> <script> ----------省略------------ props: { label: { type: string, requred: true }, ...... </script>
このようにtemplate
部分でガッツリ使うようなprops
を渡すときは子側でpropsを明示的に設定してあげることでtemplate
部分でも{{ foo }}
のように簡潔に書くことができます。また、必ず必要なprops
にはrequired: true
を設定することで、もしそのprops
がなければコンソールにエラーが出るようになります。
AWS tutorial(サーバーレスのウェブアプリケーションを構築)をやったので雑にまとめる
今回の目標
- AWS公式チュートリアルのサーバーレスのウェブアプリケーションを構築を素直に進めていきます。
- 手順は公式が書いてくれているので、ここでは主につまりポイントをまとめていきたいと思います。
開始時の筆者のステータス
- いつもTSとNuxtでフロント開発をしている。バックエンドはちょっとだけ遊んだことある。
- AWS?聞いたことはあります。当然環境構築なんかはしたことありません^^
詰まりポイント
モジュール1
概要: このモジュールでは静的なサイトをビルド、デプロイします。
つまりポイント1. 一般アクセスできる既存の S3 バケットからウェブサイトのコンテンツをコピーしようとしたら「Unable to locate credentials」というエラーが出た。
wildrydes-site % aws s3 cp s3://wildrydes-ap-northeast-1/WebApplication/1_StaticWebHosting/website ./ --recursive fatal error: Unable to locate credentials
原因と解決法
コンソールでaws configure list
を実行。すると以下のように情報を見ることができる。
wildrydes-site % aws configure list profile <not set> None None access_key <not set> None None secret_key <not set> None None region <not set> None None
aws configでaccess key IDとsecret access keyを設定していなかったのが理由らしい。以下を参考に設定した↓ https://dev.classmethod.jp/articles/aws-cli-credential-config/
以上でモジュール1終了。デプロイしたサイトがこれ
push したときに自動でbuildからdeployまで継続的統合?してくれる。便利!
モジュール2
cognito使って認証の実装。特につまりはなかった。
モジュール3
DynamoDBでユーザープールの作成を行った。操作するボタンがわかるづらかった以外は問題なかった。
モジュール4
API Gatewayを使ってlambda関数を実行(公開?)する。全体の流れを掴む必要がある。
まとめ
という感じで、最初に環境構築的なところを除けばドキュメントどうりに進めればよかったので、後半はスムーズに終わった。
いつもなんとなく聞き流ししていた単語やその意味がわかってよかったです(小並)。他のやつとかもやっていきたいと思います。
ログインと認証に関する質問への自分の回答
まずreq.userの中身が何なのかはわかりますでしょうか?ここでは簡単な確認方法を記してみます。
例えばroutes/login.jsファイル内で
router.get('/', (req, res, next) => {
res.render('login', { user: req.user });
console.log('req.userの値: ', req.user);
});
を追加して保存、サーバーを起動し、一番最初に表示される「ログイン」ボタンを押すとコンソールの出力(iTermとか)にreq.user の中身が表示されます。
しかしreq.userの値はGitHubによるログインの前だと「req.userの値: undefined」になると思います。これはログインしていない状態ではreq.userと言うものが存在しないからです。
ですが、GitHubでログインした後もう一度/loginのパスにアクセスすると、今度はブラウザ上に「現在〇〇(ログインしているユーザーネーム)でログイン中」、そしてコンソールの出力にはreq.user の中身が表示されます(id,username,emailsなどたくさんあります)。
つまり、ユーザーがGitHubでちゃんとログインしたら初めて「req.user」と言うものが生成されることになります。今回のコースではこの値を用いてユーザーのログイン状態を判断しています。
次に認証について確認します。そもそも「認証」、特に今回扱う「Authentication」とは何でしょうか。
http://e-words.jp/w/%E8%AA%8D%E8%A8%BC.html
このサイトによると、以下のように定義されています。
・認証
認証とは、対象の正当性や真正性を確かめること。ITの分野では、相手が名乗った通りの本人であると何らかの手段により確かめる本人確認(相手認証)のことを単に認証ということが多い。データが改竄されていないか確かめたり(メッセージ認証など)、データの属性が真正であることを確認すること(時刻認証など)を指す場合もある。
・二者間認証/相手認証 (authentication/オーセンティケーション)
二者が相対で相手方の真正性などを確かめることを「相手認証」あるいは「二者間認証」と呼び、英語では “authentication” (オーセンティケーション、動詞は “authenticate” )という。
例えば、ある利用者がコンピュータに自分のアカウント名を名乗り、そのアカウント名の正しいパスワードを入力できれば、確かにその利用者がアカウント名の本人であることが確認できる。日常的にも、金融機関のATM(現金自動預け払い機)などで、キャッシュカードを提示したのが本人であることを確認するために暗証番号を入力させる、といった場面で利用されている。
これを読むと「ログインしているだけ」の状態と、「認証されている」状態は異なることがチョットわかったのではないでしょうか。
今回の場合具体的に言うと、ログインしているユーザーが悪質な第三者などではなく本当にそのユーザー自身なのかを調べるのが「認証」です。ユーザーが行ったHttpリクエスト(req)に対してこの認証を行っているのがisAuthenticated()です。
なので、すごーーくざっくりとした説明をすると、
「ただ単にログインしていればOKみたいな処理には「req.user」、ちゃんとユーザーを認証しておきたい処理にはisAuthenticated()」
と言う理解で良いかと思います。一言に認証と言ってもいろいろあるので調べてみると面白いと思います。
子コンポーネントのイベントの値を親コンポーネントへ送る方法
イベントと値を送出する
イベントを特定の値付きで送出すると便利なことがあります。例えば、<blog-post> コンポーネントにテキストをどれだけ拡大するかを責務とさせたいかもしれません。そのような場合、$emit の2番目のパラメータを使ってこの値を提供することができます:
<button v-on:click="$emit('enlarge-text', 0.1)">
Enlarge text
</button>
親コンポーネントでイベントをリッスンすると、送出されたイベントの値に $event でアクセスできます:
<blog-post
...
v-on:enlarge-text="postFontSize += $event"
></blog-post>
または、イベントハンドラがメソッドの場合:
<blog-post
...
v-on:enlarge-text="onEnlargeText"
></blog-post>
値は、そのメソッドの最初のパラメータとして渡されます:
methods: {
onEnlargeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
参考文献
コンポーネントのpropsで静的/動的な値の受け渡しとその書き方の違い
button-mark.vue
<template> // {{ name }}を追加して表示させる
<button v-on:click="mark = mark + \'!\'">Hello {{ name }}{{ mark }}</button>
</template>
<script>
export default{
props: ['name'], // propsで’name’を列挙
data: function () {
return {
mark: '!'
}
},
}
</script>
続いて親コンポーネント
main.vue
<template>
<p>
<input v-model = "nameParent"> // inputからの動的なプロパティnameParent
</p>
<button-mark name="John"></button-mark> // 静的な文字列を渡す場合
<button-mark :name="nameParent"></button-mark> // 動的なプロパティを渡す場合
</template>
<script>
import button-mark from './button-mark.Vue'
export default{
components: {
button-mark
}, // inputで用いるプロパティを宣言
data: {
nameParent: ''
},
}
</script>
ただし、子コンポーネントでpropsに型などをつける場合は、渡すpropsが静的/動的にかかわらず、:valueなど動的なプロパティの渡し方の記述をする。これにハマった。
参考文献