an odd fellow

仕事のメモ

関数の引数にスプレッド構文を使った場合

背景

React書いていると以下のような...propsのような記述がたまに見られるが、どういう振る舞いになるのか少し混乱したから整理する。

const DeleteButton = ({className, ...props}) => (
  <span className={[style.root, className].join(' ')} {...props}>
    <TrashCanIcon />
    <Baloon>削除する</Baloon>
  </span>
);

スプレッド構文

ドットを3つつなげたこれはスプレッド構文と呼ばれている。Iterableなオブジェクトの中身を展開してくれるようだ。

スプレッド構文 - JavaScript | MDN

これらは以下の3つの場面で使うことができる。 1. 関数呼び出し 2. Arrayリテラル 3. Objectリテラル

2,3は想像が付くんだが関数呼び出しのときの振る舞いがうまく想像できない。

実験

とりあえず使ってみる。

const hage = ({...props}) => {console.log(props)};
hage({a:1});
// => {a: 1}

const hage=(props) => {console.log(props)};と変わらないっぽい? propsの中身を一度展開し、再度Objectにしたものをpropsという名前で束縛しているのか? そもそも({a, b}) => {}って引数に渡されたObjectからabのプロパティだけを抽出する構文だよね?なんかおかしくね?

const hige = ({...props}) => {console.log(a)};
hige({a: 1});
// => ReferenceError: a is not defined

これでa1が束縛されるんじゃないかと思ったんだけどな…。

propsの中身を一度展開し、再度Objectにしたものをpropsという名前で束縛している?

これが正解っぽい

const huge = ({a, ...props}) => {console.log(a, props)};
huge({a: 1, b: 2, c: 3});
/// => (1, {b: 2, c: 3})

なるほど?...を付けたら問答無用で残りは引き渡ってくれるのかー。

結論

というわけで最初の例はなんでもごちゃごちゃに渡されてくるpropsからclassNameだけをチュ出し、残りの要素は再度propsに束縛するという感じか。納得はしてないが振る舞いはわかった。

再掲

const DeleteButton = ({className, ...props}) => (
  <span className={[style.root, className].join(' ')} {...props}>
    <TrashCanIcon />
    <Baloon>削除する</Baloon>
  </span>
);

storybookに階層構造を追加する

背景

デフォルトでは1階層しかパスが切れない。

例えばAtomsのButton、MoleculesのFormがあったとして、理想的には以下のように整理したい。

  • Atoms
    • Button
      • with text
      • with some emoji
  • Molecules
    • Form
      • ContactForm
      • MailAddressForm

しかしデフォルトでは以下のようにするしかない。

  • Button
    • with text
    • with some emoji
  • Form
    • ContactForm
    • MailAddressForm

@storybook/addon-chaptersを使うと実現できるらしい。おれは以下の本を読んでこのアドオンの存在を知った。

Atomic Design ~堅牢で使いやすいUIを効率良く設計する | 五藤 佑典 | コンピュータ・IT | Kindleストア | Amazon

公式のaddon galleryでも紹介されている。

https://storybook.js.org/addons/addon-gallery/#chapters

階層構造と読んでいたがChapterと言うようだ。

@storybook/addon-chapters

install

npmを見る。

@storybook/addon-chapters - npm

storiesOf('Atoms', module)
  .addChapter('Button', chapter=>chapter
    .add('with text', () => <Button>with text</Button>)
    .add('with som emoji', () => <Button>with some emoji</Button>)
  );
storiesOf('Molecules', module)
  .addChapter('Form', chapter=>chapter
    .add(
    ...

というような感じで階層構造を追加出来た。

storybook-directory-chapters

components以下のdirectory構造をそのままstorybookにできるnpmパッケージもあったので試してみたが動かないようだ。 storybook-directory-chapters - npm

設定で module.exports を使っているがimport文も使っておりEJSとCJSの仕様がごちゃまぜになってる。module.exportsexports defaultに書き換えたが今度はcontext(...) is not a functionというエラーが出てしまい解消できなかった。

formikを使ってみる

前提

$ node --version
v.9.11.1

React, ReduxでSPAを作っている。

背景

connectで渡されたpropsをComponentで加工してFormを作っている場合、Formを触って変更された値をStoreまで持っていっても、その値はあくまでもFormのみで有効な値であるから、Storeに反映させる変更は無いことに気付いた。

例えば、read・write・ execの3つの権限をチェックボックスで付けたり外したりできるFormを考えるとわかりやすい。connectでは3つの権限のうち、既に付いている権限だけが ["read", "exec"] のように配列で渡されてくる。Componentではこれを {read: true, write: false, exec: true} のような辞書に変換してチェックボックスを作る。ここからが重要で、チェックボックスをクリックすると onChange に登録されたイベントハンドラには {read: false} のような辞書が引数として渡されることになる。しかし、これをStoreまで引きずっていってもどうしようもないのである。Storeが欲しいものはread・write・execの全ての状態がどうなったかという、Formで最終的に生成された状態であって、「readがfalseになりました」というような断片的な情報には興味が無い。もちろん {read: false} を受け取って ["read", exec"]["exec"] に変更することはできる。しかし、今回はたまたま簡単にハンドリングできるだけであって、より複雑なFormでは現実的でないだろう。

解消方法としては2つ思いついた

  1. StoreにFormごとのstateを持たせる
  2. reactのstateを使う

1は論外だろう。あり得ない。というわけで2番を採用する方向でReduxを利用しているときのReactのstateの利用有無について調べていたら、どうやらこれがReact界隈でいうところの Global State vs Local State という議論らしい。

ReduxにおけるGlobal stateとLocal stateの共存 - LIVESENSE ENGINEER BLOG

まあ現実的にはLocal Stateを解禁するしかないだろうと思ったが、一応社内のフロントエンジニアに聞きに行ったら今は「Ephemeral Stateという概念があるよ」と言ってFromikを教えてくれた。

GitHub - jaredpalmer/formik: Build forms in React, without the tears 😭

3行でどういうものか説明して、と頼んだらこういうものだと言う。

  • formのような一時的なstate管理はreduxで管理しなくていいよ
  • 代わりにpropsをformのstateにmappingしてくれる機能を用意したよ
  • formを送信するタイミングでformのstateをreduxに流し込むよ

つまり、ComponentのstateでもなくStoreのstateでもない中間層を用意してそこにFormの中間状態を保持してsubmitのタイミングでStoreに渡してくれるようだ。

欲しかったものっぽいので試してみる。

BasicForm

公式のデモのBasicがシンプルなフォームを作っていてわかりやすかったので、適当に不要な所を省いて写経した。

formik-tutorial/index.js at master · roronya/formik-tutorial · GitHub

使ってみた感じは彼が教えてくれた3行そのままだった。その他に気付いたことは、handleChangehandleResetなど良く使うハンドラは既に定義されていて一般的な振る舞いをやってくれる。また、特殊な動きがしたければ簡単に上書きも可能というもののようだった。中間状態はFormikの定義しているvaluesが担うようだ。

背景で説明したread・write・execのFormを実装するならば、wirhFormikの mapPropsToValues で配列から辞書に変換して、handleSubmitvaluesから辞書を取り出して配列に戻してActionCreatorを呼び出すようになるかなと思った。

PermissionSelectorForm

背景に書いた、read・write・execのFormにFormikを適用してみたい。このFormをPermissionSelectorFormと呼称することにする。

Reactだけで実装

多分こんな感じになる。

formik-tutorial/index.js at master · roronya/formik-tutorial · GitHub

Formikを適用する

多分こうなる。

formik-tutorial/index.js at master · roronya/formik-tutorial · GitHub

ハマりどころ

handleChangeかsetFieldValueか

最初はinputのonChangehandleChangeを渡していたが動かなかった。handleChange<input type=text>のような逐次変わる値を追従させるときに使うようだ。<input type=checkbox>みたいなやつはsetFieldValueを使う。 また、setFieldValueの第一引数は厄介だった。withFormicmapPropsToValuesで作っているpropsの構造を文字列で書く必要がある。propsの構造を複雑にしていると大変だ。

setFieldValueの使い方を良くよむこと。

GitHubのissueにcheckbox使う場合の例がcodesandboxで置いてくれているのが参考になった。 How to populate array with checkboxes? · Issue #360 · jaredpalmer/formik · GitHub

上書きできるのはhandleSubmitだけ?

↑でハマっていたときにhandleChangewithFormicで上書きしてしまえばいいのでは?と思ってためしたができなかった。上書きできるのはhandleSubmitだけらしい。

というわけでFormikでやりたいことができていい感じだった。おすすめ。

prettierがいい感じ

背景

WebStormのフォーマッターがいけてない。例えば

import React from 'react'

としたら

import React from 'react';

セミコロン付けて欲しいが付けてくれない。 ほかにもダブルクオーテーションとクォーテーションを統一してくれない、とか、細かい不満がいっぱいあった。

Prettier

適当にググったらこれが出てきた。

https://qiita.com/furuk4wa/items/5f5a28e81a15653af0c8

名前だけ分かれば後は公式を見る。

https://prettier.io

install

$ yarn add prettier --dev --exact

https://prettier.io/docs/en/install.html

WebStormとの連携

公式に説明がある。

https://prettier.io/docs/en/webstorm.html

WebStormのバージョンが2018.1以上なら Alt-Shift-Cmd-P でフォーマットがかかるらしい。確かにかかった。上の ; の補完もやってくれる。クォーテーションの統一もしてくれる。いい感じ。

1028.07.12 追記

Alt-Shift-Cmd-Pを押しても何もおこらないとき

prettierの実行ファイルへのパスを指定しないとダメみたい。↓にやり方が書いてある。

blog.jetbrains.com

JavascriptのExportについて

前提

$ node --version
v.9.11.1

背景

import React from 'react';
import {render} from 'react-dom';

前者のように{}無しで書く場合と後者のように{}有りで書く場合があって、よくわからなかったから調べる。

名前付きエクスポートとデフォルトエクスポート

エクスポートの方法によってインポートの方法も変わるようだ。

名前を付けてエクスポートする場合はインポート側もその名前で束縛しなければならない。デフォルトエクスポートの場合はインポート側で束縛する名前を指定できる。

以下はlib.jsでaとbを定義してindex.jsでaをxとして、bをbとしてインポートしている。

lib.js

const a = 'this is a';
const b = 'this is b';
export default a;
export {b};

index.js

import x from './lib';
import {b} from './lib';

console.log(x);
console.log(b);

参考

名前付きエクスポートとデフォルトエクスポートについてはMDNを良く読む。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/export

nodeでimport/exportを使う方法

ついでにnodeでimport/export文を使うには .js でなく .mjs として保存し、 node --experimental-modules index.mjs と実行する。

参考

適当にググってこのQiitaが出てきた。

https://qiita.com/ryohji/items/93f5050b9af6fc15693c

公式はこのへん。

https://nodejs.org/api/esm.html#esm_enabling

dockerの-vオプション

なんかいつも忘れるからメモ

書き方

docker -v ホストのディレクトリ:コンテナのディレクトリ image名

ホストのディレクトリは絶対パスで記述する。

カレントディレクトリをマウントしたいとき

docker -v $(pwd):/path/to/directory image

覚え方

前と後ろのどっちがホストのディレクトリだっけ?っていつも悩んでしまう。

Dockerは基本的に基本的にホスト:コンテナの順で書く。だから大抵source => destinationの順になってる。

-v $(pwd):/path/to/directory だったらホストのカレントディレクトリをコンテナの /path/to/directory にマウントする。

portもそう。-p 80:8080 だったらホストの80番をコンテナの8080にフォワードする。

展開のされかた

以下のようなディレクトリ構造がホストに構築されているとする

  • /app/build/
    • index.html
    • style.css

で、-v /app/build:/usr/share/nginx/html と書くとコンテナでは以下のように展開される。

  • /usr/share/nginx/html/
    • index.html
    • style.css

以下のようにはならない。

  • /usr/share/nginx/html/
    • build/
      • index.html
      • style.css

逆に↑のようにしたければ以下のようにコンテナのパスの方に書くしか無い。

-v /app/build:/usr/share/nginx/html/build

またコンテナのパスのほうに存在しないディレクトリを書いても勝手に作ってくれる。

TAMRON 28-75mm F/2.8 Di III RXD

おれのα7にはオートフォーカスするレンズが着いたことが一度もない。初代α7が発売されてすぐに買ったから、もう4年間も使っていることになるが、全てフルマニュアルで撮ってきた。カメラマンだったらしい祖父の遺した古いNikkorCanon FDレンズだけがレンズ資産だった。レンズがそんなだから、ピントも絞りもレンズを直接いじくって決めるし、「どうせなら」と思って測光もマニュアルでやっていた。撮影には手間と時間がかかるけど、それくらい時間をかけられるような余裕を持って被写体を眺めた方が発見があるんじゃないかなと思っている。

f:id:roronya:20180527202934j:plain

この桜の写真は AI Nikkor 50mm f/1.4Sで撮った。風が吹くとすごい量の花びらが舞うので、その様子を撮りたくて、1時間くらい待った。それくらいの時間感覚で写真とは向き合うのがおれは楽しいと思っている。

しかし、この時間のかけ方は趣味で一人でやるにはいいけど、人と旅行に行くときはそうもいかないので、やはりオートフォーカスするレンズが欲しいなあと去年頃から実は思っていた。それで、今年のCP+でTAMRONがEマウントのフルサイズ向けに標準ズームを出すと発表していたから、発売したら買おうと思っていた。

www.tamron.jp

それがこれ。今日はこれを持って旧古河庭園に行った。

f:id:roronya:20180527123914j:plain f:id:roronya:20180527120216j:plain f:id:roronya:20180527122231j:plain

解像度が高い。おれは今までα7の本気を知らなかったんだなあと思った。洋館の壁面の写真なんか、構図も意識せずに適当にパシャッとやったんだけど、それなりの写真に見えてしまう。被写体の質感を正確に伝えるだけで表現になるというのは知らなかった。1枚目も3枚目も木々の葉の質感だけで訴えられる。

この下の桜の写真はさっきの桜の写真と同じレンズで撮ってるんだけど、上に比べるとまったく解像してなかったんだとわかる。撮ったときは結構撮れてるじゃんと思ったんだけどな。

f:id:roronya:20180527194243j:plain

とにかく解像度に驚いた。しかしなんでも解像すれば良いというものでもないだろうとも思う。表現の幅としてどれくらい解像させるか?という軸が自分の中に加わった。