monologue

クズエンジニアの独白

SWRをローカルの状態管理で使うときの注意

下記の記事でSWRでグローバルstateを管理する方法が気になった。

zenn.dev

上記を元にカスタマイズしつつ、検証しつつ遊んでみる。

まずは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と組み合わせた半永続化の術もあるかもだが、上記の状態ではページ読み込みすると値が破棄される。


似たような別記事があったので見てみる

zenn.dev

この記事ではuseStaticSWRのように、SRWの第2引数をnullに設定したカスタムのhooksを作っている。 これを使えばSWRでローカルでの状態管理を使える汎用的なものになっている。

https://github.com/webev-dev/webev-front/blob/0c7de95ab36e13a6c9a1851473fadabe3e817e9a/src/stores/use-static-swr.tsx

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'));
  }, []);

https://github.com/webev-dev/webev-front/blob/0c7de95ab36e13a6c9a1851473fadabe3e817e9a/src/components/domain/User/molecules/PersonalDropdown/PersonalDropdown.tsx#L32-L35

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していた。 多分これをしないと、全呼び出し元で初期データが同期されないはず。

まとめ

  • useSWRは第2引数にnullを設定することでローカル(API fetchではなく)での状態管理に利用できる
  • 上記を汎用的なhooksにしておくと便利
  • SPA遷移しても状態は保持される
  • 一番の注意点は、同keyのSWRを複数で使う場合、初期時は同期処理をしないと各コンポーネントにデータが同期されない(その後は更新があれば同期されるはず)

jsxファイルでのコンポーネント中外での処理タイミングの違い

例えば下記のようなファイル内の記述があった場合、

console.log("コンポーネント外");
const Text = () => {
  console.log("コンポーネント内");
  return (
    <div>test</div>
  );
}

なのでコンポーネント内で使う必要がないものは外で書いたほうが無駄がない気がする(その場合ファイルが分かれるべきかもだけど)

GitHubの認証をtokenに変更したときのメモ

GitHubのgit操作にトークンベースの認証が必要になったので対応したときのメモ

github.blog

HTTPSSSH での認証設定が必要で、推奨はHTTPSのよう

tl;dr

  • GitHubは基本認証をトークンベースにしていってて、2021年8月特定日からGit操作等ではトークンベースにしないと使えない
  • 個人トークンの発行と設定をすることで使えるようになる

やったこと

参考にしたのは下記

公式

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

2021-08-02

ローカルの開発環境をスマホタブレット実機で確認

localhostを実機で確認する方法。今回は Next.js で環境を作っている。

f:id:quki_0910:20210802155543p:plain

システム環境設定のネットワークの詳細設定で、「IPv4アドレス」のIPアドレスを使う。 ポート3000で開発環境を立ち上げているなら、実機で xxx.xxx.x.x:3000 でアクセスすればいいだけ。簡単。

このブログを今後は雑な個人用ログとして使う

色々なことで精神が安定しない日々が続いていたけど、少しだけ好転して前を向けるような気持ちになっているので、久しぶりにブログを書いてる。

何ヶ月か前にアウトプットの場を、オリジナルで構築しようと思っていたけど、最近の精神の不安定さを見ると、ある程度時間と労力を掛けたものを作りきることを完走できそうもないし、元々の自分の性格上、飽きて途中でやめるとか起きそうだなと気付いたのでやめた。

代わりにこのブログを再活用して、カジュアルに勉強のログを書く場にする。

というだけの記事。

リーダブル・コードを読んで

エンジニアにとっては必読書となっている「リーダブル・コード」を読み、自分なりに要点をまとめておく。

とにかくコードは「読みやすい必要がある」ということがキーワード。 これは他の人、未来の自分含めて、読み手にとって分かりやすいコードであることが重要であるということが大前提として主張されている。

第Ⅰ部 表面上の改善

本部ではコードのロジックではなく見た目の部分にフォーカスして書かれている。

2章 名前に情報を詰め込む

  • 明確で正確な名前を付ける
  • 単位(_secs 等)や重要属性(unescaped_ 等)を付ける
  • 名前の長さはスコープの範囲に比例して許容

3章 誤解されない名前

  • fillter より selectexcludeを使う(fillterだと対象を選ぶのか除外するのかの2通りの取り方が出来てしまう)
  • clip より removetruncate を使う
  • 限界値を含めるなら 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

quki.hatenablog.com

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にアクセスするためのものだが、意外といろいろ使い方がありそう。 ここはもう少し調べて使ってみて理解が必要なので割愛。