Home

React: ユーティリティ <img> コンポーネント(エラーハンドリング / alt 設定)

こんなのどうでしょう? #

const fallbackImage = '...';

type Props = {
  src: JSX.IntrinsicElements['img']['src'];
  alt?: JSX.IntrinsicElements['img']['alt'];
  srcOnError?: JSX.IntrinsicElements['img']['src'];
  className?: JSX.IntrinsicElements['img']['className'];
};

function Component(props: Props) {
  const { src, alt, srcOnError, className } = props;
  const srcFileName = src?.split('/').pop()?.split('.').shift();
  return (
    <img
      className={className}
      src={src}
      alt={alt || srcFileName}
      onError={(e) => {
        e.currentTarget.onerror = null;
        e.currentTarget.src = srcOnError ?? fallbackImage;
      }}
    />
  );
}

export const Image = Component;

嬉しい点1:エラー時のハンドリング #

ネットワークエラー等で画像が取得できなかったときは、自動でフォールバック用の画像を設定してくれます。

...

Localhost の Web アプリを iOS シミュレーター / Android エミュレーターで確認する

iOS シミュレータ #

シミュレータを起動する #

open -a Simulator

または

open /Applications/Xcode.app/Contents/Developer/Applications/Simulator.app

シミュレータからアクセスする #

シミュレータ上で Safari を開く。

例えば localhost:5500 で起動しているサーバにアクセスしたいなら Safari の URL 入力欄に localhost:5500 を入力すればアクセスできる。

...

TypeScript: filter で型が絞れない時は flatMap を使うと良いかも

こんな配列があるとして、

const items = ['hello', null, 'world'];

こうしても、残念ながら型はうまくフィルタリングされません。

const filtered = items.filter((item) => item !== null);
// filtered: (string | null)[]

ですので、Type Guard を使ってこうするのが一般的だと思うのですが、

...

カンマ演算子を使って JavaScript の if 文を Python のように書く

なんとなく MDN で JavaScript の式と演算子の一覧を眺めていたら目に入ったのが「カンマ演算子(,)」です。恥ずかしながら今回初めて知りました。

しかし実は知らずに使っていただけで、 for 文に出てくるのがそれでした。(for 文をこのように使うこともそれほど多くはないですが。)

...

JavaScript でイベント待ちする方法

例えば Window.alert() のように、ユーザがボタンをクリックするまでの間、処理を止めたい場面があったとします。

このとき、パッと思いつくのは、

  1. ビジーウェイトする方法
  2. Promise の解決を待つ方法

です。(間違いなく後者を採用すべきです。)

...

ソースコードの配置方法を Redux に学ぶ

Redux はもう使っていないが、ソースコード配置の考え方はどのプロジェクトでも役に立っているのでここにメモ。

Since Redux is just a data store library, it has no direct opinion on how your project should be structured. However, there are a few common patterns that most Redux developers tend to use:

...

JavaScript: クラスの Tips - メソッドチェーン / ファクトリーメソッド / クラス名でインスタンス化

this を返すようにするとメソッドチェーンで書けるので便利 #

class Calculator {
  #number;

  constructor(initial = 0) {
    this.#number = initial;
  }

  print() {
    console.log(this.#number);
  }

  add(num) {
    this.#number += num;
    return this;
  }

  subtract(num) {
    this.#number -= num;
    return this;
  }

  multiply(num) {
    this.#number *= num;
    return this;
  }

  divide(num) {
    this.#number /= num;
    return this;
  }
}
new Calculator(9).add(5).subtract(4).multiply(3).divide(2).print(); // 15

new を書きたくないならファクトリーメソッドを用意すると便利 #

以下では of というメソッド名にしています。

...

TypeScript: ジェネリックな引数をとる関数の ReturnType を取得する

ジェネリック型を引数にとる関数の戻り値の型を取得すると、結果は以下のようになります。

function foo<T>(arg: T) {
  return arg;
}

function bar<T extends object>(arg: T) {
  return arg;
}

type FooReturn = ReturnType<typeof foo>; // unknown

type BarReturn = ReturnType<typeof bar>; // object

実際には渡される引数によってさらに具体的な型になるわけですが、このように戻り値の型を定義しておこうとすると、ジェネリックな部分についてはどうしても型に反映できなくなります。

...

TypeScript: レコードの Optional なプロパティの型のみ(あるいはその逆)を抽出する

こんな型があるとします。

type Person = {
  name: string;
  age: number;
  isAdult: boolean;
  siblings: string[];
  birthplace?: string;
  height?: number;
  hasPartner?: boolean;
  friends?: string[];
};

こうすれば Optional なプロパティのみを抽出できます。

type OptionalPropertyKeys<T> = {
  [K in keyof T]-?: undefined extends T[K] ? K : never;
}[keyof T];

type OptionalPersonPropertyKeys = OptionalPropertyKeys<Person>;
// 👉 "birthplace" | "height" | "hasPartner" | "friends"

type OptionalPersonProperty = Pick<Person, OptionalPersonPropertyKeys>;
// 👉 {
//      birthplace?: string;
//      height?: number;
//      hasPartner?: boolean;
//      friends?: string[];
//    }

/**
 * ということでこうして使いましょう。
 */
type OptionalProperty<T> = Pick<
  T,
  {
    [K in keyof T]: undefined extends T[K] ? K : never;
  }[keyof T]
>;

逆に、Required なプロパティのみを抽出するならばこうです。

...

React: 1つの state に対して複数箇所で useEffect / setState すると最終的に有効なのはどの値か

試行その1 #

import { useState, useEffect } from 'react';

export default function Component() {
  const [int, setInt] = useState<number>();

  useEffect(() => setInt(1), []);
  useEffect(() => setInt(3), []);
  useEffect(() => setInt(2), []);

  console.log(int);

  return <></>;
}

結果その1 #

コンソールには以下の順番で出力されます。

undefined
2

初回レンダリングは undefined となっているとして、次のレンダリングでは最後に指定された setState の結果が有効となるようです。

...

React の useState に関数をセットするときに注意すべきこと

関数の実行結果として別の関数が return されるようなケースがあります。

例えばイベントリスナーに処理を登録して、その結果としてリスナーを解除するための関数が return されるといった感じですね。外部のライブラリを使用していると遭遇することも多いと思います。

...

React で DOM を操作する4つのパターン(useRef / useCallback)

コールバック Ref パターンがあまり使われていない(知られていない?)ように思われたので布教も兼ねて。

1. DOM のアタッチ時だけ処理が必要な場合 #

1-1. useRef + useEffect パターン #

import { useRef, useEffect } from 'react';

function Component() {
  const ref = useRef();

  useEffect(() => {
    ref.current.innerHTML = 'Hello, world.';
  }, []);

  return <div ref={ref} />;
}

1-2. useCallback パターン #

import { useCallback } from 'react';

function Component() {
  const setRef = useCallback((node) => {
    node.innerHTML = 'Hello, world.';
  }, []);

  return <div ref={setRef} />;
}

2. DOM のアタッチ後にも処理が必要な場合 #

2-1. useRef + useEffect パターン #

import { useRef, useEffect } from 'react';

function Component() {
  const ref = useRef();

  useEffect(() => {
    ref.current.innerHTML = 'Hello, world.';
  }, []);

  const handleOnClick = () => {
    ref.current.innerHTML = 'Goodbye, world.';
  };

  return <div ref={ref} onClick={handleOnClick} />;
}

2-2. useRef + useCallback パターン #

import { useRef, useCallback } from 'react';

function Component() {
  const ref = useRef();

  const setRef = useCallback((node) => {
    node.innerHTML = 'Hello, world.';
    ref.current = node;
  }, []);

  const handleOnClick = () => {
    ref.current.innerHTML = 'Goodbye, world.';
  };

  return <div ref={setRef} onClick={handleOnClick} />;
}

FYI:コールバック Ref #

function Component() {
  return <div ref={(ref) => console.log(ref)} />; // <div></div>
}

コールバック Ref

...

ページをリロードしたとき React のアンマウント処理は行われない

useEffectreturn など、コンポーネントのアンマウント時に実行される処理ですが、これはページを再読み込みした時には実行されません。

リロード(だったり、アドレスバーに現在と同じ URL を指定して開いたり)はつまり、一度ページを閉じて新しく開くようなものです。

...

Reactのコンポーネントの外側の処理の実行タイミング

Parent コンポーネントが ChildA および ChildB をインポートとしているとして、それぞれのコンポーネントで以下のように console.log() しているとどの順番に出力されるのか。

Parent.jsx

import { ChildB } from './ChildB';
import { ChildA } from './ChildA';

console.log('Parent | Outer Top');

export function Parent() {
  console.log('Parent | Inner');
  return (
    <>
      <ChildA />
      <ChildB />
    </>
  );
}

console.log('Parent | Outer Bottom');

ChildA.jsx

...

ユーザの Chrome ブラウザの 301 Redirect cache をクリアする方法

ハマった経緯 #

最初に #

  • これまで example.com でサイトを運営していた。

そして #

  • これまで運営していた example.comother.example.com としてドメインを変更して引き続きサイトを運営しつつ、
  • example.com には新たなサイトを割り当てることにした。

そのため #

  • まずは example.comother.example.com にリダイレクトさせるようにしておいた。
  • 一定期間たったあとでそのリダイレクトを解除し example.com を新たなサイトに割り当てた。

すると起きた問題が #

  • すでにリダイレクト設定を解除しているにも関わらず example.com にアクセスすると other.example.com にリダイレクトされてしまう。

原因は #

  • リダイレクトでは 301 リダイレクトしていた。
  • Google Chrome がこれをリダイレクトキャッシュとしてブラウザにキャッシュしているのが原因。
  • このキャッシュ期限は永久で、意図的に削除しない限り残り続ける。
  • (Google Chrome 以外に Firefox も同様に永久キャッシュするようです)

【注意喚起】Google Chrome は 301 リダイレクトを永久キャッシュするので要注意

...

サブディレクトリから import する npm のパッケージを作る

import の from にある /(スラッシュ) の意味 #

npm のパッケージを import する時に from の中でスラッシュを記述するものがある。例えば Firebase SDK はこのパターン。

import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
import { getFirestore } from 'firebase/firestore';
import { getFunctions } from 'firebase/functions';

これはサブディレクトリから import している。

...

もう SPA だからといって SEO の心配をしなくていいのでは?

SPA の優位性(ポエム) #

Angular や React をはじめとした CSR ライブラリが登場して以来、SSR が登場し、SSG が登場し、ISR が登場しました。

技術の登場順に従って、一番初めに登場した CSR よりもその後登場した技術の方が上位だと考える人もいるかもしれません。

...

JavaScript:Dateオブジェクトへ TryParse する関数

日付を表す文字列があるとして、可能ならばこれを Date オブジェクトに変換したい、というような時に使える関数です。

JavaScript 版 #

/** 引数を Date オブジェクトへ変換する。Date として解釈できなかった場合は引数をそのまま返す。 */
const tryParseToDate = (arg) => {
  if (arg instanceof Date) {
    return arg;
  }
  if (typeof arg !== 'string' && typeof arg !== 'number') {
    return arg;
  }
  const maybeDate = new Date(arg);
  const invalidDate = Number.isNaN(maybeDate.getTime());
  return invalidDate ? arg : maybeDate;
};

TypeScript 版 #

引数の型から Date 変換できないと判断できるものは先に弾くのか、それとも JavaScript 版のようにとりあえず全ての引数を受け付けるのか、どのように使いたいかにより、2パターンに分けられます。

...