HYUDORO

勉強したことや日記など

YupのSchemaオブジェクトで動的なバリデーションルールを設定する

例えば、最大値をユーザーごとに変えたいケースなど、APIから取得した値を使ってバリデーションしたいことがある。その際に、Schemaのあるファイルとデータを取得するファイルが同じなら、ただ変数を使えばいいのだけど、ファイルが別の場合の対応で悩んだ。

関数を使って渡す

YupのAPIに適した機能がないかを調べたが、Schemaオブジェクト自体はイミュータブルということでざっと見た感じなさそうだった。フォームの値自体をSchemaに渡すことはできるが、それ以外の値はわからん...というのをチームで相談した結果、Schemaオブジェクトをラップする関数を作って、引数をオブジェクトに渡す案が浮かんだ。こんな感じ。

export const getSchema = (
  maxNum: number
): AnyObjectSchema => { // 適切な型がわからなかった...
  return yup.object({
    point: yup
      .number()
      .max(maxNum, `${maxNum}より小さい数を入力してください`)
  });
};

TypeScriptを使ってるので、SchemaオブジェクトからFormDataの型を生成する。返り値のオブジェクトを使うのでReturnTypeを使った。

export type FormData = yup.Asserts<
  ReturnType<typeof getSchema>
>;

react-hook-formを使ってるので、resolverで先ほどの関数を実行する。

  const methods = useForm<FormData>({
    resolver: yupResolver(getSchema(100)),
  });

初期値を設定しておいて、API実行後に値を置き換えたい場合は、引数側でgetSchema(data ?? 100) などとするか、yupのschemaでdefault(0)を使うと良い。

はじめてのペアプロ with Live Share

この前仕事中に詰まった時に、「ペアプロしませんか?」とお誘いをもらい、おかげでスピーディに問題が解決&勉強になった。お互いにリモート勤務でもできる方法があったので、次に自分が他の人とやれるようメモっておく。

ツール

  • VS Code
  • Live Share
  • Live Share Extension Pack (音声通話だけならLive Share Audioでも可)

marketplace.visualstudio.com

marketplace.visualstudio.com

marketplace.visualstudio.com

導入方法

qiita.com

ほぼ上記を参考にセットアップして、問題なく始めることができた。ライブラリ自体があんまりオンボーディングをちゃんとしてくれないので、始め方がわかりづらかったので参考資料があって助かった。

Live Share ライブラリだけでもコードをお互いに書き換えあえたり、エディタ内でチャットをすることができるが、音声通話もしたい場合は、Zoomではなく Live Share Extension Pack or Live Share Audio をインストールすることで実現できる。

音声通話の使い方としては、他の機能と同じくサイドバーから「Audio Call Participants」から通話したい人を選択するだけ。


以上でペアプロ環境構築完成。先人に感謝。

Google Data Analytics Certificateを始めてみた

フロントエンドエンジニアではあるけど、データをうまく活用していくことを求められており、興味が沸いたので勉強のためにはじめてみた。

自分はフロントエンドだけ極めたい、というエンジニアではない。フロントエンドに限らず、プロダクトを開発してビジネスを進めていくために必要な知識・スキルを習得していきたい派。データを使って、現在の状況を見える化したり、未来のためにインサイトを得ることは、少なからずプロダクトの開発には必要だという認識。

ただ、バックエンドの方もやりたいので、最終的にプロフェッショナルになってたいかというと、結局微妙なところ。データに関しては自分が手を動かしたいというよりも、設計と評価がちゃんとできるようになりたい、というニーズの方が強い。

月額¥5,000もしないし、証明書も発行できるようなので、マイペースにやってみる。

grow.google

Homebrewインストールがzshのパーミッション起因で失敗した

オフィシャルサイトのコマンドでHomebrewをインストールしようとしたときのこと。OSは10.15.7。

% /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
...
...
...
==> Pouring portable-ruby-2.6.3_2.yosemite.bottle.tar.gz
Error: Failed to link all completions, docs and manpages:
  Permission denied @ rb_file_s_symlink - (../../../Homebrew/completions/zsh/_brew, /usr/local/share/zsh/site-functions/_brew)
Failed during: /usr/local/bin/brew update --force --quiet

zsh周りのパーミッションを変更しないといけなさそうだった。 brew doctorsしてみた。

% brew doctor
Please note that these warnings are just used to help the Homebrew maintainers
with debugging if you file an issue. If everything you use Homebrew for is
working fine: please don't worry or file an issue; just ignore this. Thanks!

Warning: The following directories are not writable by your user:
/usr/local/share/zsh
/usr/local/share/zsh/site-functions

You should change the ownership of these directories to your user.
  sudo chown -R $(whoami) /usr/local/share/zsh /usr/local/share/zsh/site-functions

And make sure that your user has write permission.
  chmod u+w /usr/local/share/zsh /usr/local/share/zsh/site-functions

sudo chown -R $(whoami) /usr/local/share/zsh /usr/local/share/zsh/site-functions を実行、解決しました。

JavaScriptで画像と動画を描画せずにプロパティの値を取得する

ブラウザ描画せずに、画像または動画のサイズなどのデータを取得したいときのTipsです。

まずは画像と動画をロードして返す関数をつくります。引数にファイルのパスを文字列として渡します。

ポイントはPromiseを使ってメディアファイルを取得する時にundefinedになってしまうのを防ぐこと。 画像の場合はImageオブジェクトを作成して、DOMを作成せずにロードします。

loadImageFromPath(src) {
  return new Promise((resolve, reject) => {
    const img = new Image()
    img.onload = () => resolve(img)
    img.onerror = (e) => reject(e)
    img.src = src
  })
}

動画の場合はこちら。 動画はvideoエレメントを作る必要があります。

loadVideoFromPath(src) {
  return new Promise((resolve, reject) => {
    const video = document.createElement('video')
    video.onloadedmetadata = () => resolve(video)
    video.onerror = (e) => reject(e)
    video.src = src
    video.load()
  })
}

上記の関数をasync/awaitで扱う例です。Promise.then()でもいいです。

画像と動画で実行する関数と参照するプロパティは異なりますが、ほぼ一緒です。

今回は幅と高さをオブジェクトとして返す関数を作ってます。こちらも引数はファイルのパス。ファイルがなかったりエラーが生じた時のためにハンドリングしてます。

async getMediaSize(src) {
  let size = { width: 0, height: 0 }
  if (!src) {
    return size
  }
  const media = await loadImageFromPath(src).catch() // 画像の場合
  const media = await loadVideoFromPath(src).catch() // 動画の場合
  if (media !== undefined) {
    size = { width: media.naturalWidth, height: media.naturalHeight } // 画像の場合
    size = { width: media.videoWidth, height: media.videoHeight } // 動画の場合
  }
  return size
}

最後に、先程のサイズを取得する関数を実行します。

async printSize() {
  const src = "パスが入ります"
  const size = await getMediaSize(src)
  console.log(size.width, size.height)
}

以上、すでに画像と動画が描画されている場合に値を取得することは容易ですが、描画されていない場合は上記のようなプロセスで取得できます。

参考リンク

qiita.com

lab.syncer.jp

lab.syncer.jp

findまたはfilterを使って配列の一番最後に該当する要素だけを取得する

配列の中から.find().filter()を使って条件に該当する要素を見つけるが、 findの場合は条件に該当した最初の要素を返し、filterの場合は複数の要素が帰ってくる場合がある。

条件に該当する配列の最後の要素だけ取得したい場合は、下記のように.reverse()を使って、 配列の要素を最後から調べていくとよい。

const users = [
    {id: 1, name: "taro"},
    {id: 2, name: "taro"},
    {id: 3, name: "taro"}
];

let result;

result = users.slice().reverse().find(elem => {
    return elem.name === "taro";
});

console.log(result);

result = users.slice().reverse().filter(elem => {
    return elem.name === "taro";
});

console.log(result[0]);

どちらも{id: 3, name: "taro"}を返す。 ポイントはsliceで配列をコピーして非破壊的に行うこと。

参考はこちら

stackoverflow.com

SVGファイルを外部読み込みして色を変える

<svg fill="currentColor">
  <use href="/img/foo.svg#id=bar" />
</svg>
svg {
  color: red;
}

ポイントは下記

  • svgタグにfill="currentColor"またはfill: currentColorを設定する
  • useタグのhref属性にidをつけ、svgファイル側のsvgタグにid属性を設定する
  • useタグの代わりにimageタグを使っても画像を外部読み込みできるが、その場合色が変えられないので注意

参考:

tsuchippo.com