Closed20

React×React-Query×TSの公開日報

kodekode
  • Reactのバージョンは18系です。
  • 使っている技術は、React(18x), TypeScript

環境構築でコケる

環境構築にてこづってしまった。
原因はかなり単純で、react cliを入れた後にtypescriptをインストールして、インストール後に必要なセットアップを一切していなかったからだ。
(セットアップ = tsc --init的なコマンドで、tsconfig.jsonを作る)

→ そもそもreactと一緒にtsの環境を構築してくれる以下のコマンド

npx create-react-app {プロジェクト名} --template typescript


があるらしいので、みんなはぜひこっちのコマンドを使ってみてくれ。

18xだと、コンポーネントの型にVFCは非推奨らしい

なぜなら、18系のFCの挙動がVFCと同じみたい。

17系までのVFCとFCの違い

  • VFC: 子コンポーネントの型が与えられていないと、エラーが発生する
  • FC: 子コンポーネントの型が指定されていなくても、FCの中で定義されているので、エラーは発生しない = 意図しない子コンポーネントが存在する場合にエラーが出ない、。
kodekode

propsを使うときは、propsの型を書く必要がある

親から渡されたpropsを子コンポーネント内で使う際は、propsの型を以下のように読み込まなければならない。

// Parent.tsx 念のため親コンポーネントも貼っておく

export const TodoList: React.FC = () => {
  const todosData = [
    {
      title: 'Eat'
    },
    {
      title: 'Sleep'
    },
    {
      title: 'Repeat'
    },
  ];

  return (
    <ul>
      {
        todosData.map((todoData) =>
          <TodoItem title={todoData.title} />
        )
      }
    </ul>
  );
};
// Child.tsx

// ★ React.FC<ここにpropsの型をいれる>
export const TodoItem: React.FC<Todo> = (props) => {
  return (
    <li>
      <label>
        <input type="checkbox" />
        {props.title}
      </label>
      <div className="editTodoButtonWrapper">
        <button></button>
        <button></button>
      </div>
    </li>
  );
}
kodekode

ファイル構成は難しい

現在のファイル構成

src
|-common
|  |-type
|    |-Todo.ts
|-components
  |-TodoList
    |-TodoItem
      |- index.tsx
    |-index.tsx
    |-index.css
  |-CreateTodo

課題点

  • 共通cssをどこに置くか。。
  • 書いてて気づいたが、共通のcssにすべきなのではなく、共通のコンポーネントにすべきだ!
  • 共通cssは、src配下をさらにjs, cssに分けてcss側に入れておけば良さそう?(未検証)
kodekode

6/22

React 16系から組み込まれた「Suspense, Lazy」とは何か?メリデメは何か?

Lazy

コンポーネントを動的に読み込む機能

メリット

  • jsファイルの初回読み込みサイズが少なくなり、画面の読み込み速度・パフォーマンス向上を見込める

デメリット

  • lazyloadするコンポーネントを個別のjsファイルに分割してビルドする際、ファイル名がランダムに生成される
    • そのため、jsファイルをデプロイした際、ユーザーがデプロイ前の画面にアクセスしているとすると、ChunkLoadErrorが発生してしまう

Suspense

コンポーネントのレンダリングを待機できる機能

メリット

  • 非同期でデータやコンポーネントを取得する際、画面表示の準備ができるまで別の要素を表示させることができる

デメリット

  • ?
kodekode

6/24

学び

  • セレクトボックスのselectタグには、background-colorが効かない(今更、、)

  • react でselectboxを使う際、optionタグにselected属性を使うのは非推奨
     → 代わりに、selectタグのvalue属性に対し、selectedにしたい値を設定する必要あり

  • コンポーネントスタイルの上書き方法(子コンポーネントにスタイルを受け取るpropsを設定する必要がある -> Vueはそんなことしなくてよかったなー🤔)

        // 子コンポーネント
        export const Child = (props: any) => {
          return (
            <div className={props.className}></div>
          )
        };
    
        // 親コンポーネント
        export const Parent = () => {
          return (
            <Child className="childStyle">  
          )
        };
    
  • ある子コンポーネントをループ処理する場合、親コンポーネント側で一意なkeyを設定する必要がある

  • hasというcss擬似要素が存在する

    • MDNを見たところ、iOS safariでしか対応していないため、プロダクトでは使えなさそう、、
    • 以下は使用例
.parent {
  color: blue;
}

.child {
  color: red;
}

.parent:has( > child:hover) {
  color: white;
}
kodekode

6/27

学んだこと

textareaの属性の当て方

  <textarea id="" cols={30} rows={3}></textarea>

画像の読み込み方

結論: importを使う

  import img from '../../../../images/img.jpg';

  return (
    <img src={img} />
  )

hooksとは

  • 関数コンポーネント内で、プロパティやライフサイクルの機能を使えるようにするメソッド
  • Reactのhooksで作成したオブジェクト型の変数は、直接触ってはいけない
    • 参照先が変わってしまい、イミュータブルな特性が失われるため

React suspenseとは

  • データをロード中のコンポーネントがレンダリングを放棄し(= サスペンドし)、この状態をReact側が検知してサスペンド状態のUI描画を行う機能のこと
    • 従来は、サスペンド時のUIの描画はコンポーネント1つ1つの責務であったが、suspenseが登場してからは、複数コンポーネントのサスペンド時のUIを取得することができるようになった

開発環境でコンポーネントが2回呼び出している、?

  • おかしいなと思っていたが、原因はStrictModeでした(公式リファレンスにも書かれていましたが、Reactの意図しない動作を防ぐために開発環境では2回呼び出しているみたいです)

非同期処理の中でHooksを使う際の注意

  • todoのデータを非同期で取得した時のコールバック関数内で、useStatteを使うと無限に非同期処理が実行されてしまいました。
    • 原因は、useStateを使うと(正確には、内部状態やプロパティが更新されると)、再描画されるため、その度に非同期処理が実行され、、、というループに陥っていた
    • 解決策としてuseEffectを使うらしい(これから学ぶ💪)
kodekode

6/28

学び

useEffectの第二引数

  • 必ず配列
    • 値を監視したい場合は、配列の中に値を記載する
        useEffect(() => {
          ...etc
        }, [value])
    

react-router-domSwitchRoutesに変わっている

  • Linkは、クライアントサイドでレンダリングを行うため、画面間でデータを持ち回すのが容易である
  • aは、サーバーサイドでレンダリングを行うため、アプリ全体にリロードが走ってしまう
kodekode

6/29

学び

react-router-domのv6系からの変更点

  • Switchではなく、Routes
  • useHistoryではなく、useNavigate参考ページ

Link, useNavigateの使い方

  • useNavigate<Route />で囲まれているコンポーネント内でしか使えない
  • 遷移先のページに値を渡すことが可能(参考ページ
    • ※以下は<Link />の例
        <Link
          to={`/detail?todoId=${props.todo.id}`}
          state={props.todo}
          className="link"
        >
    
kodekode

7/3

学び

useContextの使い方

  1. 親コンポーネント側でcreateContextを使う
  2. データを渡したい子コンポーネントが存在するコンポーネントを囲む
  3. データを使いたい子コンポーネント側でuseContextを使う

コンポーネントの型付け

  • 以下のComponentTypepropsの型を付与する
  export const Componet: React.FC<ComponentType> = (props) => {

配列の型の付け方

  • Array<T>のように、配列の要素の型まで指定する必要あり
kodekode

7/9(土)

react-query

  • データの取得、取得したデータのキャッシュ、保存先のデータと同期するためのライブラリ
  • useQueryは、第一引数にkey、第二引数にクエリ関数(= promiseを返すメソッド)
    • クエリ関数でfetchを使う際、fetchの仕様上エラーを返さないので、自分でthrow Errorする必要がある
  • 一意なkeyには、単純な文字列型だけでなく、配列型・オブジェクト型も指定することができる
    useQuery('todos', ...) // queryKey === ['todos'] 文字列をkeyに指定しても、内部的には配列として保持される
    
  • クエリキーは単にキーとして使うだけでなく、クエリ関数の引数として使うこともできる
kodekode

7/12(火)

userId

  • 一意のユニークIDを生成するHooks
  • 配列処理のkeyに使うと、パフォーマンスが落ちる

useQueryとuseMutations

  • useQueryはデータ取得に使える
  • useMutationsはデータの保存、更新、削除時に使える
    • 引数にクエリ関数を設定することができる
      • 定義済みの関数を設定しようとすると、「この呼び出しに一致するオーバーロードは存在しません」と怒られてしまう
    • 返り値にmutateというメソッドが存在し、そのメソッドがクエリ関数と同じ働きをする

オーバーロード

  • 同じ名前で複数の実装を持つこと。「関数のオーバーロード」といった使い方をする。(この概念はJavaScriptでは使えない)
    • メリット:入力する値によって、出力が変わること

hooks

  • 必ず関数コンポーネント or hooks内で呼び出す必要がある
kodekode

7/13(水) -> この日までで、約40h

react-queryは何が嬉しいのか

  • キャッシュ機能があることで、サーバーへアクセスする回数を減らせること
  • キャッシュを使って状態管理をすることで、クライアントの状態管理ができる
    • 今までクライアントの状態管理をする際は、useContextreduxを使っていた
  • コード量がかなり減る
    • 例えば、従来のstore管理では、dispatchしてデータを取得し、stateを更新する処理も自分で書いて実行する必要があったが、react-queryではuseQueryを使ってデータを取得するだけで、全て完了する。

stale while revalidation

  • 初回のマウント時はキャッシュがないため、サーバーから取得する
  • 2回目以降は、キャッシュを表示させつつ、裏側でサーバーへアクセスし、最新のデータを表示させることができる

useContextの問題

  • 再レンダリングが多く実行されてしまう

getQueryData or useQuery

  • getQueryDataでキャッシュからデータを取得した後、キャッシュの値を更新しても取得したデータは古いまま
  • useQueryで取得したデータは、キャッシュのデータを更新すると、更新後のデータが即座に反映されるため、画面にすぐ反映させたい場合はuseQueryでデータを取得する

stale while revalidate

  • アプリケーションへのデータの反映を早くするために、最初はキャッシュから使うが、裏側で最新のデータをフェッチするという概念

suspenseをネストさせた場合

以下のようなコードがあるとする。(FetchViewAxiosViewは、内部に非同期処理を持つコンポーネントとする)

const sample () => {
  return (
    <suspense fallback={<Spinner />}>
            <FetchView />
            <suspense fallback={<Spinner />}>
                    <AxiosView />
            </suspense>
    </suspense>
  )
}

この時、ネストしているAxiosViewは、親であるFetchViewが表示された後から実行されるため、AxiosView単体での非同期処理より表示が遅くなる

改めて、Reactのライフサイクルについてまとめる

  • componentWillMount => コンポーネントがマウントされる直前に呼び出される
  • componentDidMount => コンポーネントがマウントした直後に呼び出される
  • componentWillReceiveProps => コンポーネントのpropsの値が変更された時に呼び出される
  • shouldComponentUpdate => コンポーネントのpropsstateが変更され、再描画される前に呼び出される。このメソッドでtrueを返した場合は再描画され、falseを返した際は再描画しない。
  • componentWillUpdate => コンポーネントが再描画される直前に呼び出される。shouldComponentUpdateがfalseの場合は、呼び出されない
  • componentDidUpdate => コンポーネントが再描画された後に呼び出される
  • componentWillUnmount => コンポーネントが棄却される前に呼び出される

useMutate

  • useQueryと違い、自動でキャッシュとサーバーのデータを同期してくれることはない
  • そのため、こちらで処理を細かく記載する必要がある
  • データを同期する方法は2つある
    • invalidation: Queryに、サーバーのデータを更新したことを知らせる。
    • Direct Updates: `直接キャッシュの値を変更する
    • ここを見る限り、キャッシュの更新にはinvalidationを使うことを優先した方がいいとのこと。
      • 理由は、バックエンドとフロントエンドのデータを同じ状態に同期する必要があり、直接更新すると、本来サーバーで行うべき処理を、フロント側でも行う必要があるためである。
      • 例えば、リストからデータを削除する際、サーバーにリストのidを送れば済む話だが、直接更新する場合は、フロント側で削除するデータを探し、削除する機能を実装する必要が出てきたりする。

useMutationはsuspenseをサポートしていない?

  • https://github.com/TanStack/query/discussions/967
  • 自分の見解:useMutationuseQueryと違い、キャッシュとサーバーのデータの整合性を担保できる処理を記載する必要があるため、宣言的に使えるのではなく、こちら側で処理を記載できるようにし、自由度を上げた仕様にしている。
    • よって、useMutationにはsuspenseもサポートされておらず、こちら側でローディング中かどうかを記載する必要がある
    • ただし、ErrorBoudaryに関してはサポートしているよう
kodekode

7/14(木) -> 計50h

useStateに渡す型

  • ステートの型を渡す

useEffectの第二引数に入れるもの

useEffect(() => {
  // 処理
}, ?) // ${?}に何を入れるか
  • 結論:useEffectを実行したいタイミングで変更される値
    • 第二引数の値の変化によって、useEffectは実行されるため、watchして欲しい値を入れる

Reactのレンダリングパフォーマンスチューニングについて

基礎知識

  • Reactコンポーネントは以下の3パターンで再レンダリングが行われる
    • 自身のstateが変更された時
    • propsのstateが更新された時
    • 親コンポーネントが再レンダリングされた時に子コンポーネントも一緒にレンダリングされる
  • コンポーネントが再レンダリングされた時、以前持っていたデータのメモリは空き、新しいメモリに再び保存される(ガベージコレクション)

useCallback

  • useCallbackはメモ化されたコールバックを返すhook

メモ化とは・・・メモ化またはメモライゼーションとは、コンピュータプログラムの最適化の手法で、高コストな関数呼び出し結果を保存し、同じ入力が再び発生した際にキャッシュから値を返すというもの(Wikipediaより

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

メモ化されたコールバックを返します。

インラインのコールバックとそれが依存している値の配列を渡してください。useCallback はそのコールバックをメモ化したものを返し、その関数は依存配列の要素のいずれかが変化した場合にのみ変化します。これは、不必要なレンダーを避けるために(例えば shouldComponentUpdate などを使って)参照の同一性を見るよう最適化されたコンポーネントにコールバックを渡す場合に便利です。

上記は公式より。
これを読むと、useCallbackの目的は「不必要なレンダーを避けるため」であることが分かる。

パフォーマンスが悪い例

const Child = ({ onClick, count }) => {
  return <button onClick={onClick}>{count}</button>;
};

const Parent = () => {
  const [count1, setCount1] = React.useState(0);
  const increment1 = () => setCount1(c => c + 1);
  const [count2, setCount2] = React.useState(0);
  const increment2 = () => setCount2(c => c + 1);
  return (
    <>
      <Child count={count1} onClick={increment1} />
      <Child count={count2} onClick={increment2} />
    </>
  );
}

上記で、片方のChildボタンをクリックすると、親のParentコンポーネントのステートを更新するため、親は再レンダリングされ、親が再レンダリングされると、Childも再レンダリングされてしまう。

解決策

  • setCount1, setCount2useCallbackで囲む
  • 子コンポーネントをReact.memo化する

React.memo

  • React.memo は高階コンポーネントです。

高階コンポーネントとは、あるコンポーネントを受け取って新規のコンポーネントを返すような関数です。(公式より

React.memoを使う目的は、親コンポーネントの再レンダリングによって子コンポーネントが再レンダリングされてしまうのを防ぐためです。

React.memoは、propsのみチェックするため、propsが変更された場合に限り、子コンポーネントを再レンダリングします。

備忘録

  • おバカな私は、以下のようにデータを更新する処理に対して、useCallbackを使ってみました←
  const updateData = useCallback(() => {
    update(data); // dataは関数外で定義されているuseStateとします
  }, []);

するとどうでしょう。データの更新が全く行われなくなりました。
useCallbackは、中の処理をキャッシュするため、キャッシュさせたくない更新処理には使っちゃだめなんですね。。。

kodekode

7/15(9h)

列挙型(別名enum型

  • 関連する値の集合を扱うことができる
  • TypeScriptの列挙型はnumberベースとなっている(値を入れない限り、デフォルトで数値が0~1ずつ増えていく)
enum = {
  apple,  // 0
  banana,  // 1
  meron // 2
}

Tree-shaking

コンピューティングでは、ツリーシェイクは、コードを最適化するときに適用されるデッドコード除去手法です。minifierに共通する従来の単一ライブラリのデッドコード除去技術とは対照的に、ツリーシェイクは、エントリポイントから開始し、実行可能な関数のみを含めることで、バンドル全体から未使用の関数を除去します。簡潔に「ライブコードインクルージョン」と表現されます。(wikipediaより

JavaScriptでは、import/exportが静的なため、Tree-shakingによって使われていない関数を除却しバンドルサイズを小さくすることが容易となっている。

Reactのcss適用手法について

Pure CSS

  • create-react-appした際に用意されているやり方
  • スコープが広く、クラス名が衝突しやすいため、オススメはされない(←今回やってしまった)

メリット

  • 学習コストが低い

デメリット

  • cssクラスのスコープが広く、命名が衝突しやすい

style props

<button style={
  color: red;
  font-weight: bold;
} />

CSS Modules

  • CSS Modulesとは、cssの適用範囲をコンポーネントに閉じさせる手法です
  • 記法がPure CSSに近いため、学習コストが低い
  • 懸念点としては、現在ライブラリがメンテナンス状態であり、将来的に非推奨

メリット

  • 学習コストが低い
  • スコープがローカルなため、cssクラス名の競合が発生しづらい

デメリット

  • 将来的にサポートが終了し、非推奨になるリスクがある
  • css, jsの二つのファイルをメンテナンスする必要があるし、開発する際に行き来しないといけない

CSS in JS

  • jsファイル内にcssを記載する
  • ファイルをcss, jsで分ける必要がない

メリット

  • jsファイル内にcssを記載でき、開発体験が上がる

デメリット

  • 若干のパフォーマンス低下

選択肢

  • styled-components:以前から存在し、スターがemotionに比べ2倍以上ある。パフォーマンス面の課題が大きい
  • emotion:リリースされてから人気が上がってきている(npmより)
  • 最近はvanilla-extractも人気が上がってきている(npmより
    • ビルド時に静的なcssを生成するため、runtimeがゼロでパフォーマンスが良い
  • stiches/react:これはnear runtime zero。runtimeゼロ系で一番人気(npm-trend)。記法はstyled-componentsに似ており、型安全に書けて、パフォーマンスもいいことからstyled-componentsの強化版のようなもの。
  • linaria:こちらはruntimeがゼロ(github, npm)。
    • 以下の理由により、個人的にはこれが一番いいのでは?という感想
      • 記法がstyled-componentsに似ている
      • 特殊な書き方が少なくて学習コストが低そう
      • React用のライブラリ(linaria/react)が用意されており、サポートされている
      • runtimeゼロ系のライブラリで2番目に人気

👉linariaを使ってみます
👉linariaは、beta版しか上手く動かなかったためなしとする

postcss

  • 特殊な構文でcssを読み取って処理し、通常のcssを返すJavaScript製のツール
  • プラグインを使うことで、sassのような記法やlinter、その他cssを書く上で助けとなるツールを使うことができる

performance~runtime~

  • プログラム実行時のパフォーマンス
    • ex.) CSS in JS は CSS modulesに比べて、runtimeが劣る

performance~loadtime~

  • ページ読み込み時のパフォーマンス
    • ex.) CSS in JS は CSS modulesに比べて、loadtimeが劣る

冪等性

  • ある処理を何回実行しても同じ結果になる性質のこと

HMR

  • Hot Module Replacementの略:ビルドツールがtsの変更を検知した際、画面の再描画を行うことなく際レンダリングしてくれる機能
kodekode

7/17

propsの型変数の命名は、xxxPropsが一般的

kodekode

7/18

Reactを使う上での命名(絶対的な決まりではないが、一般的に使われやすいもの)

  • propsの型変数の命名:xxxProps
  • useStateの関数:setXxx
  • イベントをハンドリングする関数:onXxx or handleXxx
  • 下位コンポーネントに処理を渡す際:onXxx

ステートフル、ステートレス

  • ステートフル:以前のリクエスト・レスポンスの文脈を汲み取り、その文脈を踏まえた上でレスポンスしてくれるもの
  • ステートレス:以前のリクエスト・レスポンスの文脈とは関係なく、あるリクエストをしたら同じ結果が返ってくるもの
  • ステートフルなプログラムでは、状態のパターンが複雑化しやすく、不具合が混入しやすくなる

ボタンのUXについて

  • 内部で早期リターンしてしまう場合は、そもそも押下できない作りの方が、ユーザーにとって分かりやすい

contextの使い所

  • 主に、複数のネストした様々なコンポーネントで、一部のデータへアクセスしたい場合に使われる
  • デメリットとしては、コンポーネント同士が依存するため、再利用性が低くなること

TypeScript

  • never型:値を持たない型のこと。プログラミング言語の設計にはbottom型の概念があり、TypeScriptではそれを表現するためにnever型として設定しているそう

Flow

  • JavaScriptのtypeチェッカー
  • 一昔前のOSS。フェイスブック社が開発

useRef

  • 値の参照を保持するためのhook
  • 主にDOMの参照に使われる
const element = useRef<HTMLInputElement>(null);

<input ref={element}>

こうすることで、element.current.focus()でフォーカスさせられる。

内部の設計としては、Refオブジェクト({ current: null })を生成し、メモ化しているだけ。

他の使い方としては

const ref = useRef(initialValue);

// 設定した値を取り出す
const value = ref.current;

// 値を設定し直す
const newValue = 10;
ref.current = newValue;

useRefで作成した値を更新しても、参照先を更新するだけのため、再レンダリングが発生しない。

kodekode

7/20

クエリパラメータとパスパラメータ

どちらで実装すべきか迷った時

  • パスパラメータ:一意の画面を表示させたい場合(学校ごとのページとか、各todoの詳細ページとか)
  • クエリパラメータ:フィルタ・ソート・検索などの特定の条件を追加したいとき

react-router

  • <Route />コンポーネントをネストさせると、親の中に子を表示する、というレンダリングになる
<Route
  path={`parent`}
  element={<Parent />}
>
  <Route
    path={`child`}
    element={<Child />}
  />
</Route>

↑の例では、Parentコンポーネントを描画した上で、Childコンポーネントを表示させる、という作りになる。

さらに、その特性を利用し、Parent側で<Outlet />というコンポーネントが使える

const Parent = () => {
  return (
    <>
      <div>parent</div>
      <Outlet />
    </>
  )
};

こうすることで、<Outlet />部分に、ネストした子コンポーネントが表示される。

Map

  • 同じ配列やオブジェクト等の値からデータを取り出すケースが多い場合、Mapを使った方が効率がいい
  • JSのMapは順序性を持っている(Rustは持ってなさそう)
kodekode

7/21(75h)

Map

  • 既存のkeyに対し、set()で新たな値をセットすると、値が更新される
  • Array.from(new Map([...hoge]))で、エントリーの配列を取得できる
  • new Map([...hoge]).valuesで、配列のうち値の部分を挿入順に持つ配列を取得できる

react-router

  • パスを:todoIdのように設定すると、コンポーネント内で、useParams().todoIdのように取得可能
kodekode

7/22(77h)

React公式

useEffect

  • 外部のデータとReact内部の状態をつなげる処理を書くところ
  • 外部データへアクセスする記述がなければ、冗長なコードになっている可能性が高い
このスクラップは21日前にクローズされました