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" />
こうなるんす........
お前の青い部分の色だけ変わってくれればよかったんだけどな........ってなったんですが
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:
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
配下に置かれます。result
をnormalizr
にある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だけを含む型に対してnormalizr
のschema.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の型を書く
denoramlize
はdata
(単一の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を消すと型エラーになります。
またsrc/entities/user.tsはIAddress
だけがentityでありcomplex, keysが持つ型にはcreateEntity
が反応してないことがわかります。
BaseEntityをマージしてないとentityとして認識されないためこの型がentityなのかそうでないのかがしっかり分類できています。
normalizer
与えられたgroup
オブジェクトからほしいschema
を導き出せています。result
の型も渡されたgroup
オブジェクトは単一なのでstringが返ってきています
denoramlzer
result
は単一のstring, schemaも単一のgroupEntityなので返り値はIGroup | undefined
型になっています。
こちらは配列の場合です。同様にうまくいっています。
さいごに
僕はReduxを使う際にnormalizrを多用するのでこのようなヘルパー型を作って楽に開発できるようにしています。entityの作り方に制約をもたせていますが、今のところ特に苦しい場面に出会ってません。(複雑な構造に出会ってないだけかも) なので結構良さそうじゃないかな〜と思っています。できるならライブラリにしたいけど型定義だけのライブラリって作る意味あるんだろうか……。normalizrの機能もかなり制限しちゃってるし…
次はこれをどうReduxに使っているのかを紹介できればなと思います。最後まで見てくださった方ありがとうございました!!
もっとこうしたらいいよという提案はいつでも受け付けています!!
codesandboxでvim extensionを有効化して快適に使う方法
codesandboxでvimを有効化したい
File > Preferences > CodesandboxSettings
Enable VIM extensionのトグルボタンをonにすればとりあえずはvscode使用のvim extensionが効く。もしかしたらreloadしないといけないかも
ESC入力するとエディターからのフォーカスが切れちゃうとき
- Vimiumが悪さしてる可能性が高いです
このようにcodesandboxだけ除外しておきましょう. codesandbox開いた状態でAdd Rule -> Save Changesすれば効きます
連続入力できない(Mac)
こうなる時がある
Codesandboxで一生これがでて困ってるんですが,どうしたら治るんだろう・・・・
— Naporitan🍆🍆 (@naporin24690) 2020年5月16日
キーボードの連続入力するとこうなる感じ・・・ pic.twitter.com/Tr0kvfP6Mp
こちらの記事を参考にGoogleIMEを入れることで解決した nakamura001.hatenablog.com
ABCになっていると連続入力できないのでGoogleIMEの英数を使用すると治ります
終わりに
codesandbox良すぎる。環境1クリックで作れるし、URL共有すればペアプロもできる。しかも作ったものをすぐSNSに投稿できるのでサクッとなにか作って見せたいときには便利だと思った〜
課金しました
声だけでツイートできるアプリvoitterを作りました
Voitter
ライブ中にキーボードを打ちたくない,でもツイートはしたい!というツイ廃向けかも知れません....
使い方
Twitterでログインして
ハッシュタグをつけて
Start押すだけ
あとは喋ればツイートされます!
— Naporitan🍆🍆 (@naporin24690) 2020年4月18日
実際に使ってみると....
こんな感じになります.誤認識とかもあるのですが,それも含めて楽しんでいただけると!
終わりに
MU2020最高すぎてキーボードを打つ時間すらもったいなく感じたので作りました.
ライブとかその辺向けに作ったつもりだったんですけど,誤認識が結構面白かったw
firebase + React + antdでさくっと作れたのでloginだけfirebaseに任せてtokenとるってやり方は結構良さそうだな〜って感じました
202004170125 PC電源CoolerMasterの750Wが死にました
お前は5年くらいかなよく頑張ってくれたよ・・・・・
ありがとう
12Vが安定供給できなくなったみたいです
生配信をしたくなってきた
したいです生配信を.
covid-19の影響でほんとに引きこもりになってしまいました.
自分の気持ちで引きこもるのと,言われて引きこもるのだとやはり後者の方が辛いですね
なんか,勝手にやる勉強は好きだけど親からやれって言われる宿題は嫌い的なね
そんなわけで,もともと引きこもりの僕でも結構この状況はきつい(幸いなことに週末には話す相手は数人はいる)
ぼけーとお風呂に入りながら自分のこの状況を少しでもよくできないかなぁと考えていたところ,生配信,youtubeとかニコ生とかツイキャスとか,媒体はなんでもいいのですがやってみようという気になってきました.
なぜ生配信なのかというと,僕の働いている会社がストリーミング配信系の会社でもありドメイン知識を獲得しようかなぁという背景もあります.
なんか仕事のためみたいなことを書きましたが,本質はただただコミュニケーションを不特定多数の人間と顔を合わせずにしたいだけなんですよね.
僕はあまり顔をみて話すのが得意ではないため,ボイスチャットとか,テキストベースの会話は結構好きなんすよね.
だらだら書いてしまった・・・・
何をやりたいかっていうと.フロントエンジニアとして一応生きているしUI(ユーザーインターフェース)を作るのが好きなので,UIのリクエストを何かしらの媒体で受けてそれをひたすら喋りながら作っていくのがいいんじゃないかなぁと考えています.
媒体とぼかしたのは,開発者じゃない人たちも参加してくれたら嬉しいなぁと思ったからです.イラストでこんなのつくって.とか映像でも面白いかもしれませんね
普段はこんなのを作っています.
クッソカッコよくなって爆笑してる pic.twitter.com/8mYyePb4Lv
— naporitan🍆🍆 (@naporin24690) 2020年4月11日
👀: Blog Assets: https://t.co/AqS82Zb63N
— naporitan🍆🍆 (@naporin24690) 2020年4月8日
chromeのtab作ろうとして挫折しかけてるやつ pic.twitter.com/7U2RwKk2bn
できた〜 pic.twitter.com/kQRBlRFQov
— naporitan🍆🍆 (@naporin24690) 2020年4月8日
まずは今週末やれればいいなぁとか考えています.リクエストあればこちらに!
ボイチェンとかしてみようかなぁとひっそり考えています・・・・