SWRをローカルの状態管理で使うときの注意
下記の記事でSWRでグローバルstateを管理する方法が気になった。
上記を元にカスタマイズしつつ、検証しつつ遊んでみる。
まずはuseSWR
を使った特定のkey専用のロジックを定義。
export const useSWRSample = ( initailData: string ): { data: string | undefined; mutate: (updateData: string) => void } => { const { data, mutate } = useSWR("testKey", null, { initialData: initailData, }); return { data: data, mutate: mutate }; };
使いたいコンポーネントで呼び出し、初期化。
const { data, mutate } = useSWRSample("initaial data"); console.log(data); // initaial data
mutateで値を更新
<button onClick={() => { mutate("updated data"); }} >
console.log(data); // updated data
上記の値を別のコンポーネントでの読み込みは参考記事では、単純なuseSWR
を使っていたので真似する。
SWRの性質を利用してkeyさえあっていれば同様の値を読める。
const { data } = useSWR("testKey", null); console.log(data); // updated data
ただ、上記では問題があり、更新後のupdated data
は読み込めるが、初期値のinitial data
は読み込めずundefined
になる。
多分これは上記2つのコンポーネントが同ページで読み込まれたときに、初期化処理の前に別コンポーネントで読み込みをするため。
なので、その後mutateすると、同期されて2つのコンポーネントでupdated data
となる。
その証拠に、しれっと参考記事では下記のようにundefined
の際の対策がされている。
const SampleCount = () => { const { data: count } = useSWR('count', null); return <div>{count || 0}</div>; };
{count || 0}
の部分で、count
がfalsyだった場合は初期値の0をセットしている。あまり書き方として筋はよくなさそう。
ちなみに、このままSPA遷移でページを移動しても、値は保持され続ける。 いわゆるstoreのようにグローバルstateとして扱えるイメージ。 storageと組み合わせた半永続化の術もあるかもだが、上記の状態ではページ読み込みすると値が破棄される。
似たような別記事があったので見てみる
この記事ではuseStaticSWR
のように、SRWの第2引数をnullに設定したカスタムのhooksを作っている。
これを使えばSWRでローカルでの状態管理を使える汎用的なものになっている。
import useSWR, { Key, SWRResponse, mutate } from 'swr'; import { Fetcher } from 'swr/dist/types'; export const useStaticSWR = <Data, Error>(key: Key, updateData?: Data | Fetcher<Data>): SWRResponse<Data, Error> => { if (!updateData) { mutate(key, updateData); } return useSWR(key, null, { revalidateOnFocus: false, revalidateOnReconnect: false, }); };
そして各状態毎にuseStaticSWR
を利用した、専用のhooksを作っている構成。例えば下記のように。
export const useOgpCardLayout = (initialData?: OgpLayoutType): SWRResponse<OgpLayoutType | null, Error> => { return useStaticSWR<OgpLayoutType | null, Error>('ogpCardLayout', initialData); };
https://github.com/webev-dev/webev-front/blob/master/src/stores/contexts.tsx
前記事でも一番気になった、初期化後に各コンポーネントで状態を同期する方法を調査。
useStaticSWR
を利用した、専用のhooksは3種類あるので、それぞれ調査。
まずuseOgpCardLayout
は、下記のようにコンポーネント読み込み初期時にmutateを実行している。
この処理によって同期している気がする。
useEffect(() => { setIsEnableReadFromClipboard(retrieveValue('isEnableReadFromClipboard') === 'true'); mutateOgpCardLayout(retrieveValue<OgpLayoutType>('cardLayout')); }, []);
2つ目はuseUrlFromClipBoard
、
下記のreadClipboardText
中でmutateを実行。
windowが依存配列になっているが、useEffectが初期時も実行されるはずなので、結局初期時に同期してる気がする。
useEffect(() => { if (typeof window !== 'undefined') { window.addEventListener('focus', readClipboardText); } return () => { if (typeof window !== 'undefined') { window.removeEventListener('focus', readClipboardText); } }; }, [window]);
3つ目は、useSocketId
で、こちらも初期時にmutateしている。
useEffect(() => { socket.on('connect', () => { console.log('socket connected!!'); }); socket.on('disconnect', () => { console.log('socket disconnected!!'); }); socket.on('update-page', () => { console.log('Get Updated Data'); pageListMutate(); }); socket.on('issue-token', ({ socketId }: { socketId: string }) => { mutateSocketId(socketId); }); return () => { socket.close(); }; }, []);
結果、どれもコンポーネント初期読み込み時にmutateしていた。 多分これをしないと、全呼び出し元で初期データが同期されないはず。
まとめ
GitHubの認証をtokenに変更したときのメモ
GitHubのgit操作にトークンベースの認証が必要になったので対応したときのメモ
HTTPS か SSH での認証設定が必要で、推奨はHTTPSのよう
tl;dr
やったこと
参考にしたのは下記
公式
Token authentication requirements for Git operations | The GitHub Blog Creating a personal access token - GitHub Docs 個人アクセストークンを使用する - GitHub Docs macOS キーチェーンからの認証情報を更新する - GitHub Docs
Githubから届いたDeprecation Noticeメールに対応する - Qiita GitHubの認証方法の新しいビッグウェーブに乗り遅れるな! - Qiita
このブログを今後は雑な個人用ログとして使う
色々なことで精神が安定しない日々が続いていたけど、少しだけ好転して前を向けるような気持ちになっているので、久しぶりにブログを書いてる。
何ヶ月か前にアウトプットの場を、オリジナルで構築しようと思っていたけど、最近の精神の不安定さを見ると、ある程度時間と労力を掛けたものを作りきることを完走できそうもないし、元々の自分の性格上、飽きて途中でやめるとか起きそうだなと気付いたのでやめた。
代わりにこのブログを再活用して、カジュアルに勉強のログを書く場にする。
というだけの記事。
リーダブル・コードを読んで
エンジニアにとっては必読書となっている「リーダブル・コード」を読み、自分なりに要点をまとめておく。
とにかくコードは「読みやすい必要がある」ということがキーワード。 これは他の人、未来の自分含めて、読み手にとって分かりやすいコードであることが重要であるということが大前提として主張されている。
第Ⅰ部 表面上の改善
本部ではコードのロジックではなく見た目の部分にフォーカスして書かれている。
2章 名前に情報を詰め込む
- 明確で正確な名前を付ける
- 単位(
_secs
等)や重要属性(unescaped_
等)を付ける - 名前の長さはスコープの範囲に比例して許容
3章 誤解されない名前
fillter
よりselect
やexclude
を使う(fillterだと対象を選ぶのか除外するのかの2通りの取り方が出来てしまう)clip
よりremove
やtruncate
を使う- 限界値を含めるなら
max
min
を使う - 範囲指定なら
first
last
を使う - 包含・排他なら
begin
end
を使う - ブール値なら
is
has
can
should
を使う - 否定形より肯定形を使う
タイトル通り、誤解が生じたり、何通りかの取り方ができるような表現はしないようにして、なるべく取り方が万人共通になるように気をつけるということ。
4章 美しさ
- 一貫のあるレイアウト
- 似ているコードは見た目的にも似ているように見せる
- 関連するコードで段落を作る
コード全体でパッと見たときのわかりやすさや統一感を出そうという話。
5章
「コメントの目的は意図を伝えること」
逆説的に必要のないコメントは、 - コードからすぐ読み取れること - コメントのためのコメント
「ひどいコード + コメント」は「良いコード」に変えられるはず。
コメントすべきは下記のようなこと、
- 映画監督のコメンタリーの如く(自分の重要と思う考えを記録する)
- コードの欠陥を伝える
- 定数に対しての説明
- コードを見て疑問に思いそうなこと
- ファイルの最初に全体に対してのコメント(全体感を示すとか)
- 関数内部において、塊単位で要約するようなコメント
上記も非常に大切だが、結構今の自分に刺さったのが「コードを書いている過程で思ったことやメモ書きを一旦コメントとして残す → その後コメントを読み改善する」というフローがおすすめされていたこと。
これは結構迷いながらやることもあったので、背中を押された感覚で今後は積極的にやっていきたい。
6章
重要な考え「コメントは領域に対する情報比率が高くあるべき」
- コメントは完結に
- あいまいな代名詞は避ける
- 動作を正確に記述
- 入力値を実例で書くのも○
- コードの意図を書く
- 情報密度の高い言葉で
第Ⅱ部 ループとロジックの単純化
ここからはロジック等についての言及
7章 制御フローを読みやすくする
「制御フローはなるべく自然であるべき」
例えば、if (length > 10)
は、日本語的にも「lengthは10よりも大きい」とコードとの整合性があるので読みやすい。
なので、比較系では、左辺に比較対象(変化しやすい値)を持ってくると良い
- 条件文はなるべく肯定形を
- 単純な条件を先に書く
- 関心を引く条件や目立つ条件を先に書く
- 3項演算子は
if / else
より簡潔に書けそうなときだけ使う - 関数では早く return する(複数のreturnはNGとする説もあるみたいだが、本書では否定的)
- ネストは浅く保つ
8章 巨大な式を分割する
キーワード「巨大な式は飲み込みやすい大きさに分割する」
コードを分割して、適宜簡潔な名前を付けた変数「説明変数」にする。これにより、
- コードが文書化される
- コードの主要概念が素早く読み取れるようになる
第9章 変数と読みやすさ
- 邪悪な変数を削除(コードを分かりやすくしてるわけでないもの、計算の結果を一時保存しているだけのもの)
- 変数のスコープを極力小さく
const
で複数回の書き込みは禁止する(イミュータブルにする)
第Ⅲ部 コードの再構成
10章 無関係の下位問題を抽出する
「無関係の下位問題」とは、その文脈で高レベルの目標に対して効果がないもの。
例えば、日付をDBに保存するというのが目標のコードにおいて、「日付を表示用にフォーマットする」というのは直接高レベルの目標に対しての効果があるコードではない。
こうゆうコードは、可能なら抽出して別の関数にしたりするのが良い。
また、どんなプロジェクトでもよく使うような処理はユーティリティコードとして一括で管理したり、プロジェクト特有だが何度も使うようなコードは関数化して外部ファイル化しておく。
11章 一度に1つのことを
「コードは1つずつのタスクを行う」
読みにくいなと感じるコードがあれば、
- すべてのタスクを洗い出し列挙する
- 処理の塊でコードの段落を分ける
- 可能なものを関数化
12章 コードに思いを込める
プログラムを文章に起こして分かりやすく簡単な言葉で説明してみる。そしてそれをコードに反映する。
13章 短いコードを書く
「最も読みやすいコードは何も書かれてないコード」
コードは小さく保つことを心掛ける。そのために、
- 標準の機能やAPIを知って使う
- ライブラリ等使えるものは使う
- なるべく汎用コードをモジュール化
第Ⅳ部 選抜テーマ
14章 テストと読みやすさ
「安心してコードに変更を加えられるように、テストも分かりやすく書く」
- 本物のコードをテストしやすいように
- テストのトップレベルは極力簡潔に
- エラーメッセージは分かりやすく
- 単純な入力値を採用
- 何をテストしてるか分かりやすいテスト関数名にする(
Test_<関数名>_<状況>
等)
React 各hooksの覚え書き
Reactでは今やスタンダートになったhooksについて、公式APIとして提供されているものは色々あるが、今まではせいぜい useState
useEffect
程度しか使っていなかった。
最近徐々に他のhooksを使うシーンがあり、かなり便利だし、パフォーマンス観点でも使いこなせないとまずい気がして調べたのでまとめ。
公式のhooksは下記にまとまっている。 ja.reactjs.org
TL;DR
useState
はいわゆる state の保持・変更、複雑state管理なら、useReducer
を検討してもいいかもuseEffect
はレンダリング後に処理(副作用)したいときに利用、処理してからレンダリングしたいならuseLayoutEffect
※ただし使いすぎ注意useContext
はコンポーネント階層をまたぐバケツリレーの代替案、ただしコンポーネントが密結合になりかねないので設計に注意useCallback
useMemo
を利用することで不要な処理・レンダリングを抑制して高パフォーマンスを目指そうuseRef
はDOMアクセスの手段にとどまらず、インスタンス変数のイメージで書き換え可能な値の保持しておく使い方もできる
useState
多分最も基本的なhooksで特に使い方も難しくないので軽くメモ。
useState
はstateを保持するために使う。
const [count, setCount] = useState(0);
useStateの引数には初期値をセットして、戻り値として、stateとstateを変更する関数を得る。 state更新関数の引数に更新したい値をセットして利用。
ちなみに下記のように前のstateを元に更新もできる。
setCount(prevCount => prevCount - 1);
useState
は、Object.is
による比較ロジックが組み込まれていて、前回の値と同じ際はレンダリング等の実行を回避するようになっている
useEffect
useContext
主にバケツリレーの防止に約役立つ(バケツリレーは上位コンポーネントから下位コンポーネントへ値を渡していくこと、場合によっては何階層にも渡り辛みがある)
ただ、useContext
の利用は素直にpropsを受け取るような形で値を得ないので、上手くコンポーネント設計をしないと密結合になりやすいので乱用は避けた方が良さそう。
何となくだけど、アプリケーション全体に関連するstateを最上部で定義してどこからでもアクセスするように設計するとか、再利用の必要がなく、複雑、または多段でstateを管理するコンポーネント群に限定的に使う場合とかに使うとかの方が良い気がする。
また、useContext
を利用してグローバルstateライクに設計するパターンも見たことがある。
例として公式のコードを拝借
// 変数定義 const themes = { light: { foreground: "#000000", background: "#eeeeee" }, dark: { foreground: "#ffffff", background: "#222222" } }; // Contextを定義して、themes.lightを初期値に設定 const ThemeContext = React.createContext(themes.light); function App() { return ( {/* ThemeContext.Provider で子のコンポーネントでコンテクストを有効化, themes.dark をセット */} <ThemeContext.Provider value={themes.dark}> <Toolbar /> </ThemeContext.Provider> ); } function Toolbar(props) { return ( <div> <ThemedButton /> </div> ); } function ThemedButton() { // ThemeContext を読み込み const theme = useContext(ThemeContext); return ( {/* theme.backgound 等で値を読み込み */} <button style={{ background: theme.background, color: theme.foreground }}> I am styled by theme context! </button> ); }
useCallback
useMemo
詳細は別記事にまとめる予定なので軽く。
基本的にuseCallback
useMemo
はパフォーマンス改善のための hooks 。利用することで余計なレンダリングや再計算を抑制する。
Reactは何も気を付けずに構築すると、かなり再レンダリングや再計算が発生する。試しに自分のコンポーネントにlogを吐くようにしてみると、何回も同じlogが吐かれ、あれ?となるときがある。
自分の知っている限り理由はいくつかあるが、コンポーネントツリーの構築の仕方、stateの管理、propsでの値の受け渡し方等がある。
もちろんuseCallback
useMemo
で全てが解決できるわけではない。
useCallback
は、関数をメモ化して、依存配列が変更されたとき以外は再生成しない。
が、結構使い方に注意があり、どうやらReact.memo
と組み合わせて利用しないと効果を発揮しないらしい。
const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b], );
useMemo
は、値をメモ化する。
何か値を計算して変数に入れることはよくあるが、Reactではコンポーネントの再レンダリングによって、変数の値も無駄に再計算されることがあるので、これを防止するために利用する。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useRef
ぶっちゃけあまり分かってない(使ったこと無い)
パッと見はDOMにアクセスするためのものだが、意外といろいろ使い方がありそう。 ここはもう少し調べて使ってみて理解が必要なので割愛。