※スマホ対応はしてません。

Reactで既存HTMLのプロパティを受け取るのにComponentPropsWithRefが使える?

カテゴリー: JavaScript

話題になっているのを全然みかけないんだけど、皆なんかもっと良いやり方やってんの? こういうことしないの?

あとコードハイライトなくてごめんね……。

先にまとめ

  • TypeScript の話
  • 既存 HTML ほとんどそのままのコンポーネントで、本来の props をそのまま受け取りたい場面
  • props: React.ComponentPropsWithRef<'span'> みたいにやれるっぽい
  • ドキュメントには載ってない
  • 定義を追っかけてみたけどたぶんきっと大丈夫そう
  • よくわかってないです

こんな風にして使っている。

export type HtmlProps<T extends ElementType> =
  React.ComponentPropsWithRef<T>;

export type HtmlComponent<T extends ElementType> =
  React.FC<HtmlProps<T>>;

後者は毎回書くのが面倒で追加したけど、ない方がコードは読みやすいかもしれない。

これを使って、特定のクラス名を付けただけのHTML要素を返すコンポーネントの例。

export const NiceButton: HtmlComponent<'button'> = (props) => (
  <button
    {...props}
    className={`NiceButton ${props.className || ''}`}
  />
);
<NiceButton>Niceですね~</NiceButton>

もうちょい複雑なコンポーネントの例。

interface NiceCheckboxProps extends HtmlProps<'input'> {
  label: string;
}

export const NiceCheckbox: React.FC<NiceCheckboxProps> = (props) => {
  const { className = '', label, ...inputProps } = props;
  return (
    <label className={`NiceCheckbox ${className}`}>
      <input {...inputProps} className="NiceCheckbox-input" type="checkbox" />
      <span className="NiceCheckbox-label">{label}</span>
    </label>
  );
};
<NiceCheckbox
  checked={agreed}
  label="ナイスであることに同意します"
  onClick={onAgreeClick}
/>

場面

ほぼ HTML

上に書いた、ああいう「ほとんどただの HTML 要素なんだけどちょっと追加とか組み合わせとかがあって、方々で使うから名前を付けて簡単に使いまわしたい」ような場面です。

CSS フレームワークみたいなノリだと思うんだけど、React でやるからには毎回クラス名書くんじゃなくて、そのクラス名を付けてくれるコンポーネントを使うようにしたいなと。

HTML同様の型が欲しい

例えば <input> だと value を持ってたりするじゃないですか。<span> にはないじゃないですか。そういうのを、補完で出したり型確認で弾いたりしたいなと思っておりました。

見つけた

React.prop まで入力したところの補完で出てきた候補の中からそれっぽいのを眺めてるときに見つけました。

React 公式サイトで検索しても出てこない、と思ったけど React プロジェクトじゃなくて @typed のだから当たり前か。ありがてえ。

定義を追ってみる

ここから先はTS初級者がうんうん唸ってるだけです。誰かに教えてほしい……。

なお @types/jest のバージョンは 24.0.12 でした。

ComponentPropsWithRef

type ComponentPropsWithRef<T extends ElementType> =
    T extends ComponentClass<infer P>
        ? PropsWithoutRef<P> & RefAttributes<InstanceType<T>>
        : PropsWithRef<ComponentProps<T>>;

へ、へえー……。

ElementType

まずジェネリクスの方。

type ElementType<P = any> =
    {
        [K in keyof JSX.IntrinsicElements]: P extends JSX.IntrinsicElements[K] ? K : never
    }[keyof JSX.IntrinsicElements] |
    ComponentType<P>;

これはあれだな、IntrinsicElements にあればその型('a' なら <a> 用の props)を使うぞってことだな。

IntrinsicElements

interface IntrinsicElements {
    // HTML
    a: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>;
    abbr: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
    address: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
    area: React.DetailedHTMLProps<React.AreaHTMLAttributes<HTMLAreaElement>, HTMLAreaElement>;
    article: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
…

究極的にはここの右側 React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement> を毎回コピペして使えば使えるんだな。したくないけど。

JSX で書いた <span> とかを VS Code で Ctrl + クリックすると出てくるのもこいつ。

ComponentClass

こっちがわからない。

interface ComponentClass<P = {}, S = ComponentState> extends StaticLifecycle<P, S> {
    new (props: P, context?: any): Component<P, S>;
    propTypes?: WeakValidationMap<P>;
    contextType?: Context<any>;
    contextTypes?: ValidationMap<any>;
    childContextTypes?: ValidationMap<any>;
    defaultProps?: Partial<P>;
    displayName?: string;
}

<infer P> との組み合わせで、React.Component 継承のクラスを与えると良い感じに props の型を抽出してくれる系かなと思ったんだけどそんなことなかった。

ちなみに infer ってここ(上の ComponentPropsWithRef のとこ)で初めて見ました。

PropsWithRef

Indent Hadouken みがある。

/** Ensures that the props do not include string ref, which cannot be forwarded */
type PropsWithRef<P> =
    // Just "P extends { ref?: infer R }" looks sufficient, but R will infer as {} if P is {}.
    'ref' extends keyof P
        ? P extends { ref?: infer R }
            ? string extends R
                ? PropsWithoutRef<P> & { ref?: Exclude<R, string> }
                : P
            : P
        : P;

たぶん大丈夫でしょう

知らんけど。

とりあえず動いてはいる。うーんちゃんと理解して使いたい……。

おしまい

VS Code で F12 を駆使して追っかけてみました。ほんとべんり。

よくわからないけどとりあえず動いているっぽいのでこのまま使おう。趣味プロジェクトだし、まあーそのうちTS力が上がれば完全理解できるでしょう。

あとそういえばこのブログテーマも更新して、TypeScript のハイライトも入れたいなあ。

Edgeでiframe内でウェブフォントが使えないかもしれない問題を踏んだ。

カテゴリー: CSS

最近踏んだ問題です。

  • Edge
  • 複数の <iframe>
  • フレームは同時に生成
  • 同じウェブフォントを読み込む
  • 一部のフレームでしかウェブフォントが出てこない

デモとコードはこちら。

3つのフレームのうちひとつでしかウェブフォントが有効化されてない様子。

EdgeのDevToolsでネットワーク見てると、CSSファイルは読み込まれているけどフォントファイルは読み込まれていない、という状況までは観測できた。

解決策

どうやらCSSファイルの読み込み完了を待ってから、そのウェブフォントを利用する部分を出力すると問題なくなる様子。

つまり onload で待つべしと。

const elLink = document.createElement('link');
elLink.href = `https://fonts.googleapis.com/css?family=${family}`;
elLink.rel = 'stylesheet';

elLink.onload = () => render(); // <---

document.body.appendChild(elLink);

原因はっきりわかってないけどこれでうまく行っているように見える。

処理の想像。

  1. フレーム1出力開始
    1. CSSファイル読み込み開始
    2. ウェブフォント利用のテキスト出力
    3. 該当フォントなしとして記憶 (A-1)
  2. フレーム2出力開始
    1. CSSファイル読み込み開始
    2. ウェブフォント利用のテキスト出力
    3. 該当フォントなしとして記憶 (A-2)
  3. いずれかのフレームで
    1. CSSファイル読み込み完了 (B)
    2. 該当なしのフォントに合致、フォントファイル読み込み開始 (C)
    3. フォントファイル読み込み完了
    4. テキスト再描画

このB前にAが来るとCがちゃんと動いてない気がします。知らんけど。

Edge…

老い先短いEdgeHTMLさんのためにどれだけコストかけるべきか悩む。

おまけ

おまけ2

Google Chromeで音声認識する例。

カテゴリー: JavaScript

意識しないとすぐTwitterに書いて終わりにしてしまうので、気負わず軽い気持ちで書きます。

諸事情により音声認識APIを試しているんだけど、ウェブ標準でもそういうAPIが準備中であるのに気づきました。現状のGoogle Chromeでもそこそこ動くようです。せっかくなので(Chromeで)動くやつを用意してみました。

ginpei/speech-recognition-example

まだ実験的な機能だし、実際動きも微妙なところなので、やっぱりGoogleなりMicrosoft AzureなりのAPI使う方が良さそう。無料枠あるし。

基本的なコード

// 作成
const sr = new window.SpeechRecognition();

// 完了時に結果を表示
sr.addEventListener('result', (event) => {
  const text = event.results[0][0].transcript;
  console.log(text);
});

// 開始
sr.start();

音が止まると音声認識も自動で終了する様子。

各ブラウザー

Chrome

接頭辞付きの webkitSpeechRecognition で動く。まだ完璧ではない様子。

あとそういえば昔 <input x-webkit-speech> みたいなのあったなあ、みたいな記憶。

Firefox

まだ対応していない。コードは mozSpeechRecognition に対応しておいたので、実装されたらきっと動くことでしょう。知らんけど。

Edge

ちょうど最近開発版が出た、中身がChromiumの新Edgeはまだ動きが微妙な感じでした。

同じ webkit 接頭辞なのに動いたり動かなかったりは困るなあ。いや今は開発版だから良いんだけど。

Safari

試してないけど未対応らしい。

JPEG/Exif画像の回転情報を取れるやつ作った。

カテゴリー: JavaScript

新年明けましておめでとうございます。作ったよ。

インストール。

$ npm install @ginpei/exif-orientation

利用例。

import * as exif from '@ginpei/exif-orientation';
 
const orientation = await exif.getOrientation(fileOrBuffer);
console.log(
  `${orientation.rotation} degree,`,
  orientation.flipped ? 'flipped' : 'not flipped',
);

上記のものではないが実行例。

$ node from-node.js sample-images/090-flipped-5.jpg
Read image from: sample-images/090-flipped-5.jpg
Rotation: 90 degree, Flipped: true.

検索するといかにもな名前のやつとかが複数出てくるんだけど、先行事例に対する利点は特にないです、たぶん。

作りたいので作った感じのやつです。ちゃんと動くと思うけど。

Scoped package

名前が取られてたけど作っちゃったし変えるのもなあと思って、自分の名前 “@ginpei” 付きの形での公開としました。

READMEにも書いたけど、TypeScriptの場合は --moduleResolution node の設定が必要です。へえそうなんだ、知らなかった。

エラー例。

src/index.ts:1:23 - error TS2307: Cannot find module '@ginpei/exif-orientation'.

1 import * as exif from '@ginpei/exif-orientation';
                        ~~~~~~~~~~~~~~~~~~~~~~~~~~


Found 1 error.

Exif仕様

こちらのPDFを大いに参考にしています。感謝感謝。

ところでExifて日本発祥の規格なんだね。

富士フイルムが開発し、当時の日本電子工業振興協会 (JEIDA)で規格化

画像の回転

とかそういうのは、一切しないです。調査するまで。

やるならCanvasが良いのかな。いやJPEGの画素情報の位置を回転的に動かす方が再圧縮しないから綺麗そう。

一時的ならCSSでやるのも手。よし transform: matrix() 使おうぜ!

JPEG以外

Exifを他の規格の画像にも使えるらしいんだけど、JPEGのみの対応です。見ないし。

おしまい

本年もよろしくお願い致します。

2018年を振り返る。

カテゴリー: 日記

日記です。

まとめ

ぼちぼち作ったなー。

GitHub

GitHubプロフィールページのContributionsのキャプチャ。ぼちぼち草生えてる。
GitHubの1年分の活動結果。

自分のGitHubプロフィールページで2018年を開いて、これする。

(() => {
  const year = 2018;
  const days = [...document.querySelectorAll('rect.day')]
    .filter(e=>e.getAttribute('data-date').startsWith(year));
  const counts = days.map(e=>e.getAttribute('data-count')|0);
  const sum = counts.reduce((s,c)=>s+c);
  console.log(`${year}年${days.length}日中活動したのが${counts.filter(v=>v!=0).length}日、合計${sum}回、平均${(sum/days.length).toFixed(2)}回毎日。`);
})();

2018年365日中活動したのが196日、合計1466回、平均4.02回毎日。

作ったもの

Clock

1月。アナログ時計 + ポモドーロタイマー。

手頃に使えるアナログ時計が欲しいなと思って、PCの脇にスマホを立ててそっちに映す用に。

どうせならブラウザーじゃなくても動くようにしようかなと思って、後述のQuick NoteでPWAを試して、その後導入した。結果的にNuxt.jsがよしなにやってくれるのであまり気にする必要はなかったけど。

そう、Nuxt.js使ったけどあんまり便利機能使ってなかった。History APIを使うSPAもGitHub Pagesだと使えないし。

Quick Note

2月。オフラインでも動く簡単ノート。入力内容はLocalStoragに保存される。

PWAを作ってみたい!と思って作ったやつです。依存パッケージも一切なし。( devDependencies はあります。ESLintとか。)

なんならもうちょっとこうノートにタイトルを付けて保存とかするようにしても良かったかもしれない。でもまあそういう目的ではないし、作っても使わないだろうからよし。

Stop SNS

4月。Twitterとかの閲覧を阻害するChrome拡張、Firefoxアドオン。

ちょっと思うところあってブラウザー拡張作ってみよう、と思って。簡単に作れて良いね。自分で使ってません。

本編とは関係ないんだけど、SVGの図形に影を付けるってのを学んだ。これおもしろいな。

お前を消す方法 for GitHub

6月。どこか懐かしいマスコットを表示するChrome拡張、Firefoxアドオン。ジョークアプリ。

MicrosoftがGitHubを買収という報道を受けて。

まさかの窓の杜、やじうまの杜に掲載。

最近☆1評価付けられてるのに気付いた。何を求めてたってんだ……。

DevTools z-index

6月。 z-index を利用している要素とその値を開発者ツールから一覧できるChrome拡張、Firefoxアドオン。

仕事中の息抜きで書いたコードスニペットをもうちょっとこねてブラウザー拡張へ転用したもの。気軽に作ったんだけど、「便利だ」と割とウケが良かく嬉しかった。ただ実は自分で使ってないので、何がそんなに便利なのかよくわかってない……。

Pretty Letters

7月。 。Unicodeで定義されている特殊な見た目の文字を表示する。

変わった見た目の文字を使ってる人をたまにTwitterで見かけて、それを調べてたのが始まり。あー逆さ文字とかもやりたいな。

デザインがひどい。

Potoshop (v0-1)

8月。クリップボードから画像を貼り付け、サイズ変更や枠の付加を行うだけ。Photoshopではない。

ある晩眠れずにいて、諦めて起きてぽちぽちしてるうちにできあがったもの。これくらいの編集ができるやつが欲しかった。

その後もっとあれこれしたいなと思って一から作り直していて、当時の面影は残っていない。今でもだらだらいじってる……。一度終わらせた方が良いんだろな。

画像アップロード前に小さくして転送量を減らすRailsアプリの例

8月。Canvasを使って画像を小さくし、転送量ひいては転送時間を減らす例。Railsの体だが主となるのはもちろんJavaScript。

知人から相談されて「アップロードを早くする」コードを書いたついでにまとめたもの。

その後Exifのorientation情報(回転、反転)に対応してないことに気付いた。他のところで対応したけど、こいつに反映してないや。

RailsをDockerで動かす例

8月。Dockerを使い、開発環境自体にRubyをインストールしないで始めるやり方。

前述の例を作った、さらにそのついで。

書いてみたは良いけどどうなんだこれ。

Understanding transform:matrix()

11月。CSSの transform で使える関数 matrix() の動きを視覚的に試せるもの。

行列式を分かりやすく解説する記事を読んで感動したのがきっかけ。読みながら「あーそうか matrix() のやつはこういうことだったのか!」となって、「あれそうだっけ? 動かしてみるかー」みたいな流れから。

Code Chat Cat

未完成。リアルタイムにMarkdown文書を共有する。ワークショップでの利用を想定。

電子黒板みたいな感じで、講師が書いて生徒がそれを見るってのが欲しかった。HTMLとして出力された結果が生徒の手元にあるような状態になる。パワポ的なやつと比べた利点は、視力とかに依らない点、任意に(5分前とか)過去のものを見られる点、サンプルコードをコピペできる点、リンクが活きる点、そして生徒の反応(質問)をすぐ反映して全員に届け、残せる点。

他にも画像他のファイル添付だ控えめなコミュニケーションだ何だとかんだと考えていたら、UIどうしようと悩んで手が止まってしまった。欲しい機能を出して再設計して出直そう。

名前が気に入ってる。なお犬派。

やったこと

JavaScript勉強ついでにブラウザー拡張(アドオン)を作ってみようぜ会

6月。JavaScript自体の初級者向けのワークショップ。

やり方わかってきたので近くの人にお裾分けしたやつ。ちゃんと初級者の人も来てくれて、やった甲斐があった。メッセージとかで通信して「境界をまたぐ」というのは、やっぱり難しい概念なのだと再確認。どうやったらうまく伝えられるだろうか。

資料作成は、せっかくなのでVuePressに挑戦。

配列とかおれおれAdvent Calendar2018

12月。JavaScriptの配列と繰り返しにまつわる記事を1日から24日まで毎日投稿。

毎年恒例のひとりアドベントカレンダー。今年は始まる前に完成原稿を15日分程用意できたので、毎日締め切りに追われることはなかった……と言いたかったんだけど、結局毎日見直して大幅に加筆修正していたので、忙しかった。新規に書きたくなってしまったものもあり、実はいくつか記事が余った。

最初は毎日メソッドをひとつずつ紹介する程度のつもりだったんだけど、気が付いたら仕様書を読み漁っていた……。楽しかったからいいけど。

読者の想定レベルも謎な感じになってしまった。いやおれは楽しかったからいいけど!

来年は12月に入る前に完成原稿を24日分用意したい。

Lightning Talks

いろんな人の話を聞きたいなと思って、ここ数年個人的にLT会を主催している。

気が向いたときにやる感じなんだけど、2018年は5回開催できた。月1くらいを目指したい。参加人数が全然読めないんだよなー。3人だったり13人だったり……。

仕事

改善してこ。

おしまい

全体的に気持ちが向上してきた一年だった。やっぱり何か作るのは良いなあ。

Vue.jsとReactのアプリをいくつか作ってみて、ようやくわかってきた気がする。あとTypeScriptにもようやく手を出せて、こいつはなかなかのgame changerじゃねえかと。

本当は「毎月ひとつ何か作って出す」が目標だったんだけど、あんまり達成できなかった。結果的にそれくらいの数はやれたからヨシとするか。

2019å¹´

作りかけのものは完成させたいし、他にも作りたいものはまだまだあるので、どんどん作ってゆく所存。

あともっとワークショップやりたいでござる。