なぽろぐ

気ままに感じたことを記事にまとめます。

input rangeのcssをカスタマイズしたときに進んだ量の色が消える問題

<input type="range" />css`-webkit-slider-runnable-trackで困った話

こいつの話です

こいつにcssを当てる際-webkit-slider-runnable-trackを使うと思うんですが(firefoxもありますよねmozなんとか)、backgroundの色変えたくなるじゃないですか・・・・・・・

こんなcss書くと

<style>
input[range]::-webkit-slider-runnable-track {
 background: red;
</style>
<input type="range" />

こうなるんす........

f:id:Naporitan:20201005012440p:plain

お前の青い部分の色だけ変わってくれればよかったんだけどな........ってなったんですが

webkitのprogressだけの色を変えるcssは非標準らしいんですね...... developer.mozilla.org

なので擬似的に再現する必要がありますと....僕はReactで次のように考えました

linear-gradient使ったら行けるんじゃねって

import React, { useState } from "react";
import styled from "styled-components";

export default function App() {
  const [value, setValue] = useState(0);
  return <Range value={value} onChange={(e) => setValue(parseInt(e.target.value, 10))} />;
}

const Range = styled.input.attrs({ type: "range", min: 0, max: 100 })<{ value: number }>`
  &::-webkit-slider-runnable-track {
    background: red;
    background: linear-gradient(to right, red ${(props) => props.value}%, white ${(props) => props.value}% 100%);
  }
`;

ぽいのができました。もっといいやり方あったら教えて下さい:pray:

Image from Gyazo

normalizrの型定義を書いた

Normalizrの型定義を(制約付きで)書いた

normalizrのhelper型です。こうなってくれているといいなぁと思って書きました。

読むのめんどくさいよ〜って人はサンプルを用意したのでcodesandboxか手元の環境でガチャガチャいじってみてください https://github.com/naporin0624/normalizr_ts

Normalizrというライブラリについて

normalizrというライブラリがあります。これはobjectをユーザが定義した構造(normalizrはこれをschemaとよんでいる)に従って分解できるライブラリでRedux公式ドキュメントに使うといいよって書かれてたりします。Normalizing State Shape | Redux

何ができるか

GitHubに大まかな挙動・APIドキュメントが存在しているので食わせるデータと結果だけを書きます。

食わせるデータ

{
  "id": "123",
  "author": {
    "id": "1",
    "name": "Paul"
  },
  "title": "My awesome blog post",
  "comments": [
    {
      "id": "324",
      "commenter": {
        "id": "2",
        "name": "Nicole"
      }
    }
  ]
}

ユーザがschemaを定義します。

import { normalize, schema } from 'normalizr';

// Define a users schema
const user = new schema.Entity('users');

// Define your comments schema
const comment = new schema.Entity('comments', {
  commenter: user
});

// Define your article
const article = new schema.Entity('articles', {
  author: user,
  comments: [comment]
});

const normalizedData = normalize(originalData, article);

normalizedDataの結果がこうなります。

{
  result: "123",
  entities: {
    "articles": {
      "123": {
        id: "123",
        author: "1",
        title: "My awesome blog post",
        comments: [ "324" ]
      }
    },
    "users": {
      "1": { "id": "1", "name": "Paul" },
      "2": { "id": "2", "name": "Nicole" }
    },
    "comments": {
      "324": { id: "324", "commenter": "2" }
    }
  }
}

やってくるjsonをユーザが定義したschemaごとに分解してentities配下に置かれます。resultnormalizrにあるdenormalizeに食わせることでもとのjsonを得ることができるというものです。ちなみにdenormalizeは次のように記述します。

import { denormalize } from 'normalizr';
const denormalizedData = denormalize(result, article, entities /* jsonのentitiesを入れる */ );

かなり柔軟にschemaは意義できます。normalizr/api.md at master · paularmstrong/normalizr · GitHub

TypeScriptの型定義を見てみる

normalizr/index.d.ts at master · paularmstrong/normalizr · GitHub

export function normalize<T = any, E = { [key:string]: { [key:string]: T } | undefined}, R = any>(
  data: any,
  schema: Schema<T>
): NormalizedSchema<E, R>;

export function denormalize(
  input: any,
  schema: Schema,
  entities: any
): any;

かなりanyだらけになっていてnormalizeするとき投入したdataに対してどのschemaがマッチするのかわからないです。またdenormalizeも同じで投入するinputに対してどのschemaを入れればよいのかわからない状態になっています。かなり柔軟にschemaを定義できるようにした代償なのかなと思っている。(僕も柔軟なschemaを維持したまま型定義を書こうとして辛くなってやめた過去もあります。)なので今回はschemaの作り方に制約をもたせて型定義を書いていきます。

目指すところ

  • entity型に含まれるentityを探し出してschema.Entityの第2引数に必要なschema.Entityの組が推論される
  • normalizeの第1引数に入れた型からほしいschema.Entityを推論する
  • denormalizeのReturnTypeを推論する

normalizrのschemaに制約をもたせる

A, B, Cはそれぞれの対応関係を型定義で表したものです。これからこの型定義のことをentityといいます。 Cの型を持つjsonが飛んできてnormalizeに食わせるとA, B, Cをentities配下に持ち, Cのidをresultに持つオブジェクトが返ってくるようなイメージです。

型定義を作るにあたってentityに制約をもたせます。 1. entityは必ずidを持つ 2. entityの持つプロパティにはentity同士のunion型を許容しない - C型のfのような形は許可しません - c, d, eのような形は問題ないです 3. entityの持つプロパティにはentityを含むPropertySignatureを許容しない - C型のgのような形は許可しません

type A = {
  id: string | number; // required
  hoge: string;
  huga: number;
}
type B = {
  id: string | number; // required
  cat: string[];
  dog: string[];
}
type C = {
  id: string | number; // required 
  a: number; // ok
  b: number | string; // ok
  c: A; // ok
  d: A[]; // ok
  e: A | null; // ok
  f: A | B; // ng
  g: { a: A } // ng
}

schemaに制約をもたせてnormalizrの型を書いてみる

実装するにあたりこの型はentityであるということを示すためにBaseEntityという型をつくりentityを作るときはこれをマージすることにしました。

type IUser = {
  name: string;
  address: string;
  phoneNumber: number;
} & BaseEntity;

構造を定義するschemaの型定義をかく

RelatedEntitiesでGenericで渡されてきたentityの中で使われているentityを探し出します。探し出されたrelatedEntitiesがそのままnew schema.Entityの第2引数に渡るのでentityの型からnormalizrに渡すschemaを決定することができます。

import { schema } from "normalizr";

export const createEntity = <T extends BaseEntity>(
  entityName: string,
  relatedEntities: RelatedEntities<T>,
): schema.Entity<T> => new schema.Entity<T>(entityName, relatedEntities);

RelatedEntities型

RelatedEntities型はGenericを受け入れます。Find型により、受け入れたTを1層だけmapped typeで走破してBaseEntityが持つkeyを両方持っているものをentityとみなして新たに型を作り上げていきます。BaseEntityは型情報としては持っているが値としては入れられることもないし、認識することもない_$entityというプロパティを持っています。これのおかげでidだけをプロパティとしてもっていてもentityとして検知されないようにしています。つまり、BaseEntityをマージしたものだけをentityとしてすく上げることができるようになります。

すくい上げられたentityだけを含む型に対してnormalizrschema.Entity型を付与していきます。

import { schema } from "normalizr";

type BaseEntity = { id: string; _$entity?: unknown; };

type NestArray<T> = T | Array<NestArray<T>>;
type Flatten<T extends NestArray<unknown>> = T extends NestArray<infer I> ? I : never;
type BaseType<T> = Exclude<Flatten<T>, null | undefined>;
type FindKeys<T> = { [K in keyof T]: keyof BaseEntity extends keyof BaseType<T[K]> ? K : never }[keyof T];
type Schema<T> = T extends Array<infer U> ? [Schema<U>] : schema.Entity<T>;
type DictSchema<T extends { [key: string]: unknown }> = { [K in keyof T]: Schema<Exclude<T[K], undefined | null>> };

type Find<T> = { [K in FindKeys<T>]: T[K] };

type RelatedEntities<T extends { [key: string]: unknown }> = DictSchema<Find<T>>;

normalizeの型を書く

normalizeの型は割と簡単です。受け入れるTに対応したschemaを待ち受ければよいだけです。 返り値はresultとentitiesがありますが、entitiesは今回かんたん化のためにユーザが定義したentityをimportしてEntitiesという型を作っています。(ここも推論できるのがより良いですが、実現できなかったのでこのようにしました:sob:)

EntitiesのなかにあるNormalizedEntity型はGenericで渡されるentityの中からentityを探し出してstringまたは, stringの配列にしてくれるものです。entityを含むentityをnormalizeするとentityがあった部分には他のentityへの参照であるidが格納されているからです。(normalizedDataを見るとわかると思います)

import { schema, normalize, NormalizedSchema } from "normalizr";

type Schema<T> = T extends Array<infer U> ? [Schema<U>] : schema.Entity<T>;
type ArrayToString<T> = T extends Array<infer U> ? Array<ArrayToString<U>> : string;
type NormalizedEntity<T extends { [key: string]: unknown }> = {
  [K in keyof T]: K extends keyof Find<T> ? ArrayToString<T[K]> : T[K];
};
type Entities = Partial<{
  // 定義したentity型をimportしてnormalizeされたときに排出されるentitiesの型とする
  users: { [key: string]: NormalizedEntity<IUser> };
  groups: { [key: string]: NormalizedEntity<IGroup> };
  addresses: { [key: string]: NormalizedEntity<IAddress> };
}>
type Result<T> = T extends Array<infer U> ? Array<Result<U>> : string;

export const normalizer = <T>(data: T, schema: Schema<T>): NormalizedSchema<Entities, Result<T>> =>
  normalize<T, Entities, Result<T>>(data, schema);

denormalizeの型を書く

denoramlizedata(単一のidもしくはidの配列)とschema(dataが分解される前の構造情報)とentitiesを渡せばよいです。

dataが単一ならschemaも単一で, dataが配列ならschemaも配列で与えるように型を書いていきます。dataからはdenormalizeのReturnTypeを推論することはできないので, 渡されたschemaからentityの型をGenericを用いて取り出します。Genericで取り出されたentityの型Sを渡されたdataの型に合わせて整形し直します。 denoamlizeしたときに第3引数のentitiesに必要なデータがないときdataが単一ならundefined, 配列なら[]が返されるのでDenormalized型を使ってそれを表現します。

import { schema, denormalize } from "normalizr";

type NestArray<T> = T | Array<NestArray<T>>;
type Entity<D, T> = D extends Array<infer U> ? [Entity<U, T>] : schema.Entity<T>;
type Denormalized<D, S> = D extends Array<infer U> ? Array<Denormalized<U, S>> : S | undefined;

export const denormalizer = <D extends NestArray<string>, S, T extends Entities>(
  data: D,
  schema: Entity<D, S>,
  entities: T,
): Denormalized<D, S> => denormalize(data, schema, entities);

使ってみる

こちらにサンプルコードをpushしたのでcheckoutすればすぐ使えます。GitHub - naporin0624/normalizr_ts codesandboxにも上げましたempty-glade-xxys6 - CodeSandbox

createEntity

src/entities/group.tsをみてください

import { IUser, userEntity } from "./user";
import { createEntity } from "../normalizer";
import { BaseEntity } from "../types";

export type IGroup = {
  users: IUser[];
} & BaseEntity;

export const groupEntity = createEntity<IGroup>("groups", { users: [userEntity] });

IUserがentityなのですがcreateEntityの第2引数からusersを消すと型エラーになります。 Image from Gyazo

配列になってないときも型エラーを吐いてくれます。 Image from Gyazo

またsrc/entities/user.tsはIAddressだけがentityでありcomplex, keysが持つ型にはcreateEntityが反応してないことがわかります。 BaseEntityをマージしてないとentityとして認識されないためこの型がentityなのかそうでないのかがしっかり分類できています。 f:id:Naporitan:20201005010616p:plain

normalizer

与えられたgroupオブジェクトからほしいschemaを導き出せています。resultの型も渡されたgroupオブジェクトは単一なのでstringが返ってきています f:id:Naporitan:20201005010605p:plain

denoramlzer

resultは単一のstring, schemaも単一のgroupEntityなので返り値はIGroup | undefined型になっています。 f:id:Naporitan:20201005010554p:plain

こちらは配列の場合です。同様にうまくいっています。 f:id:Naporitan:20201005010539p:plain

さいごに

僕はReduxを使う際にnormalizrを多用するのでこのようなヘルパー型を作って楽に開発できるようにしています。entityの作り方に制約をもたせていますが、今のところ特に苦しい場面に出会ってません。(複雑な構造に出会ってないだけかも) なので結構良さそうじゃないかな〜と思っています。できるならライブラリにしたいけど型定義だけのライブラリって作る意味あるんだろうか……。normalizrの機能もかなり制限しちゃってるし…

次はこれをどうReduxに使っているのかを紹介できればなと思います。最後まで見てくださった方ありがとうございました!!

もっとこうしたらいいよという提案はいつでも受け付けています!!

codesandboxでvim extensionを有効化して快適に使う方法

codesandboxでvimを有効化したい

File > Preferences > CodesandboxSettings

Enable VIM extensionのトグルボタンをonにすればとりあえずはvscode使用のvim extensionが効く。もしかしたらreloadしないといけないかも

codesandbox editor setting
editor setting

ESC入力するとエディターからのフォーカスが切れちゃうとき

  • Vimiumが悪さしてる可能性が高いです

github.com

このようにcodesandboxだけ除外しておきましょう. codesandbox開いた状態でAdd Rule -> Save Changesすれば効きます

連続入力できない(Mac)

こうなる時がある

こちらの記事を参考にGoogleIMEを入れることで解決した nakamura001.hatenablog.com

ABCになっていると連続入力できないのでGoogleIMEの英数を使用すると治ります

終わりに

codesandbox良すぎる。環境1クリックで作れるし、URL共有すればペアプロもできる。しかも作ったものをすぐSNSに投稿できるのでサクッとなにか作って見せたいときには便利だと思った〜

課金しました

声だけでツイートできるアプリvoitterを作りました

Voitter

ライブ中にキーボードを打ちたくない,でもツイートはしたい!というツイ廃向けかも知れません....

voitter.web.app

使い方

Twitterでログインして

Image from Gyazo

ハッシュタグをつけて

Image from Gyazo

Start押すだけ

Image from Gyazo

あとは喋ればツイートされます!

実際に使ってみると....

こんな感じになります.誤認識とかもあるのですが,それも含めて楽しんでいただけると!

Image from Gyazo

終わりに

MU2020最高すぎてキーボードを打つ時間すらもったいなく感じたので作りました.

ライブとかその辺向けに作ったつもりだったんですけど,誤認識が結構面白かったw

firebase + React + antdでさくっと作れたのでloginだけfirebaseに任せてtokenとるってやり方は結構良さそうだな〜って感じました

生配信をしたくなってきた

したいです生配信を.

 

covid-19の影響でほんとに引きこもりになってしまいました.

 

自分の気持ちで引きこもるのと,言われて引きこもるのだとやはり後者の方が辛いですね

 

なんか,勝手にやる勉強は好きだけど親からやれって言われる宿題は嫌い的なね

 

そんなわけで,もともと引きこもりの僕でも結構この状況はきつい(幸いなことに週末には話す相手は数人はいる)

 

ぼけーとお風呂に入りながら自分のこの状況を少しでもよくできないかなぁと考えていたところ,生配信,youtubeとかニコ生とかツイキャスとか,媒体はなんでもいいのですがやってみようという気になってきました.

 

なぜ生配信なのかというと,僕の働いている会社がストリーミング配信系の会社でもありドメイン知識を獲得しようかなぁという背景もあります.

 

なんか仕事のためみたいなことを書きましたが,本質はただただコミュニケーションを不特定多数の人間と顔を合わせずにしたいだけなんですよね.

 

僕はあまり顔をみて話すのが得意ではないため,ボイスチャットとか,テキストベースの会話は結構好きなんすよね.

 

だらだら書いてしまった・・・・

 

 

何をやりたいかっていうと.フロントエンジニアとして一応生きているしUI(ユーザーインターフェース)を作るのが好きなので,UIのリクエストを何かしらの媒体で受けてそれをひたすら喋りながら作っていくのがいいんじゃないかなぁと考えています.

 

媒体とぼかしたのは,開発者じゃない人たちも参加してくれたら嬉しいなぁと思ったからです.イラストでこんなのつくって.とか映像でも面白いかもしれませんね

 

普段はこんなのを作っています.

 

 

 

 

まずは今週末やれればいいなぁとか考えています.リクエストあればこちらに! 

marshmallow-qa.com

 

ボイチェンとかしてみようかなぁとひっそり考えています・・・・