an odd fellow

仕事のメモ

Reduxを使ってるときのAuth0の使い方

背景

ログイン処理にAuth0を使っている。Auth0はサンプルが豊富でReactを使ったサンプルも在るが、そのサンプルではReduxは使わずPure React構成のため、Reduxを使っている場合の使い方がイマイチ分からなかったので、考えて実装した結果をまとめておく。

どこで悩むか

基本的には Auth0 React SDK Quickstarts: Login を見てまず実装した上でRedux化していくのが良い。その上で悩みポイントは2つ。

  • Authオブジェクトで直接historyAPIを叩いているが、react-router-reduxを使っている場合それはできない
  • Authオブジェクトのインスタンス化のタイミング

AuthオブジェクトからhistoryAPIを叩く方法

react-router-reduxを使う場合historyAPIもActionを発行しReducerに処理させなければならない。そうなると store.dispatch をAuthオブジェクトは使わざるを得ない。

つまり公式のサンプルでは hitsoty.replace('/home') とやっているところを、 dispatch(push('/home')) とすれば良い。コードは以下のようになる。

Auth.js

import auth0 from 'auth0-js'
import { push } from 'react-router-redux'

export default class Auth {
  auth0 = new auth0.WebAuth({
    domain: AUTH0_DOMAIN
    clientID: AUTH0_CLIENT_ID,
    redirectUri: AUTH0_REDIRECT_URI
    responseType: 'token id_token',
    scope: 'openid'
  })

  constructor (dispatch) {
    this.login = this.login.bind(this)
    this.handleAuthentication = this.handleAuthentication.bind(this)
    this.isAuthenticated = this.isAuthenticated.bind(this)
    this.logout = this.logout.bind(this)
    this.dispatch = dispatch  // constructorの引数でdispatchをもらってthisに束縛する
  }

  login () {
    this.auth0.authorize()
  }

  handleAuthentication () {
    this.auth0.parseHash((err, authResult) => {
      if (authResult && authResult.accessToken && authResult.idToken) {
        this.setSession(authResult)
        this.dispatch(push('/home'))  // ここが公式のサンプルでは history.replace('/home') になってる
      } else if (err) {
        console.log(err)
        this.dispatch(push('/hoe'))
      }
    })
  }

  setSession (authResult) {
    let expiresAt = JSON.stringify(
      authResult.expiresIn * 1000 + new Date().getTime()
    )
    localStorage.setItem('access_token', authResult.accessToken)
    localStorage.setItem('id_token', authResult.idToken)
    localStorage.setItem('expires_at', expiresAt)
    this.dispatch(push('/home'))
  }

  isAuthenticated () {
    let expiresAt = JSON.parse(localStorage.getItem('expires_at'))
    return new Date().getTime() < expiresAt
  }

  logout () {
    // Clear Access Token and ID Token from local storage
    localStorage.removeItem('access_token')
    localStorage.removeItem('id_token')
    localStorage.removeItem('expires_at')
    this.dispatch(push('/home'))
  }
}

ディレクトリ構成

Authにstore.dispatchを渡さなければならないため、Authオブジェクトのインスタンス化とStoreのインスタンス化のタイミングは同時のタイミングのほうが都合が良い。よってディレクトリの構成は以下のようにした。

公式のサンプルだと routes.js というのがあって、ルーティングの設定を切り出しているが、 App.js で全てやってしまった。コードは以下のようになる。

index.js

import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { ConnectedRouter } from 'react-router-redux'
import createBrowserHistory from 'history/createBrowserHistory'
import App from './App'
import Auth from './auth'
import { createStore } from './store'

const history = createBrowserHistory()
const store = createStore(history)
const auth = new Auth(store.dispatch) // authのdispatchを渡す

ReactDOM.render(
  <Provider store={store}>
    <ConnectedRouter history={history}>
      <App auth={auth} />
    </ConnectedRouter>
  </Provider>,
  document.getElementById('root')
)

App.js

handleAuthenticationの引数にauthを追加しているのに注意。公式のサンプルだとauthがスコープにいたが、この構成だと明示的に渡してやる必要がある。

import React from 'react'
import { Route, Switch, Redirect } from 'react-router-dom'

const handleAuthentication = (nextState, replace, auth) => {
  if (/access_token|id_token|error/.test(nextState.location.hash)) {
    auth.handleAuthentication()
  }
}

export default ({ auth }) => (
  <Switch>
    <Route exact path='/login' render={() => <LoginPage auth={auth} />} />
    <Route
      exact
      path='/callback'
      render={props => {
        handleAuthentication(props, null, auth)
        return <p>ログイン中</p>
      }}
    />
  </Switch>
)

ログイン処理はこうなる

  1. handleAuthentication() の処理の完了を待たず "ログイン中" がレンダリングされる
  2. handleAuthentication() でログイン処理が終了すると push('/') がstoreにdispatchされ、react-router-reduxのRouterReducerがページ遷移を実行する

React入門 React・Reduxの導入からサーバサイドレンダリングによるUXの向上まで (NEXT ONE)

React入門 React・Reduxの導入からサーバサイドレンダリングによるUXの向上まで (NEXT ONE)

JavascriptのPromiseとasync/awaitについて調べる

背景

ReactでSPAを作っている。APIからデータを取得するときredux thunkとaxiosを使っているが、このときの処理がexampleを見て見よう見まねでやっていてよくわかっていない。

例えば投稿を取得するソースコードはこんな感じになっているが、return async とか axios.get のあとの then とかがわかっていない。

export const fetchPosts = () => {
  return async (dispatch, getState) => {
    const posts = getState().posts
    dispatch(actions.startRequest(posts))
    axios
      .get("http://example.com/posts")
      .then(response => {
        dispatch(
          actions.receiveData(
            posts,
            null,
            normalize(response.data, [schemas.post])
          )
        )
      })
      .catch(error => {
        dispatch(actions.receiveData(posts, error))
      })
      .finally(() => {
        dispatch(actions.finishRequest(posts))
      })
  }
}

これを理解するためにいくつかドキュメントにあたったり実験した結果をまとめる。

そもそも同期/非同期とは

この記事がわかりやすかった。

JavaScriptの同期、非同期、コールバック、プロミス辺りを整理してみる

console.log(1);
setTimeout(function(){console.log(2)}, 0);
console.log(3);

待ち時間がゼロなので、console.log(1)、console.log(2)、console.log(3)の順に実行されると勘違いしやすいです。 しかしあくまでlog(1)、setTimeout(〜略〜)、log(3)が順に、つまり同期的にキューに登録された後、setTimeout(〜略〜)が実行されたタイミングで、つまり非同期でlog(2)がキューに登録される

だから、出力は以下のようになる。

1
3
2

Promiseやasync/awaitを使うと

1
2
3

のように書いた順番通りに実行できるような仕組みのようだ。これを同期的と呼んでいる。

async/awaitの前にPromiseを理解するべきっぽい

適当にググっていると以下の記事を見つけた。そこにはこうある。

Promiseの使い方、それに代わるasync/awaitの使い方

async/awaitを理解するには、Promiseも知る必要がある。

なのでまずPromiseから調べる。

Promiseの基本的な使い方

コマンドライン引数がaかどうかでresolve、rejectを切り替える。

promise-basic.js

const promise = new Promise((resolve, reject) => {
  if (process.argv[2] === 'a') {
    resolve("GOOD")
  } else {
    reject("BAD")
  }
})

promise.then(value=>console.log(value)).catch(value=>console.log(value))

実行結果

$ node promise-basic.js a
GOOD
$ node promise-basic.js b
BAD

Promise.resolveでPromiseのインスタンスを作れる。

promise-basic2.js

promise = Promise.resolve("GOOD")

promise.then(value=>console.log(value)).catch(value=>console.log(value))

実行結果

$ node promise-basic2.js
GOOD

resolveとかrejectの結果を変数に束縛できるか?

こんなような感じで書いて変数hogeに"GOOD"とか"BAD"とか入って欲しい。

promise-bind.js

const promise = new Promise((resolve, reject) => {
  if (process.argv[2] === 'a') {
    resolve("GOOD")
  } else {
    reject("BAD")
  }
})

const hoge = promise.then(value=>value).catch(value=>value)
console.log(hoge)

実行結果

$ node promise-bind.js a
Promise { <pending> }

thencatch の中でごちゃごちゃと書くしか無いのかなあ…。

thenの中でreturnすれば外に出せるのでは説

thenreturn したら外に出せるのでは?と思ったけどダメだった。 promise-bind2.js

const promise = new Promise((resolve, reject) => {
  if (process.argv[2] === 'a') {
    resolve("GOOD")
  } else {
    reject("BAD")
  }
})

const hoge = (
  () => {promise.then(value=>return value).catch(value=> return value)}
  )()
console.log(hoge)

実行結果

$ node promise-bind2.js a   
/Users/roronya/src/github.com/roronya/async-await/promise-bind2.js:10
  () => {promise.then(value=>return value).catch(value=> return value)}
                             ^^^^^^

SyntaxError: Unexpected token return
    at new Script (vm.js:51:7)
    at createScript (vm.js:136:10)
    at Object.runInThisContext (vm.js:197:10)
    at Module._compile (internal/modules/cjs/loader.js:618:28)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:665:10)
    at Module.load (internal/modules/cjs/loader.js:566:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:506:12)
    at Function.Module._load (internal/modules/cjs/loader.js:498:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:695:10)
    at startup (internal/bootstrap/node.js:201:19)

resolveとかrejectとか書かなかったらどうなるのか?

何も起こらなかった。

promise-not-resolve.js

const promise = new Promise((resolve, reject) => "GOOD")

promise.then(value=>console.log(value)).catch(value=>console.log(value))

実行結果

$ node promise-not-resolve.js
# 何も出ない

Promiseを使った感想

  • Promiseの内部で使った値を呼び出し側に出せないのが使いづらい
    • もし値を使いたいなら関数を呼んでその引数にするしか無さそう
  • Promise.resolveは使い所無い気がする
  • Promiseが返ってくる関数は、resolveやrejectに何が渡されるのか実装によって調べる必要があるっぽい

async/awaitの使い方

asyncだけで使う

関数にasyncと付けると Promise.resolve(関数の返り値) と同じ結果になるっぽい。 async-basic.js

const f = async () => "GOOD"

console.log(f)
f().then(value => console.log(value))

実行結果

$ node async-basic.js
[AsyncFunction: f]
GOOD

asyncの中でだけawaitが使える

await-basic.js

const f = async () => "GOOD"
const g = async () => {
  const y = await f()
  console.log('inner', y)
  return y
}

g().then(value => console.log('outer', value))

実行結果

$ node await-basic.js
inner GOOD
outer GOOD

classメソッドでもasync使える?

使える

async-class.js

class C {
  async f() {
    return "GOOD"
  }
}

const c = new C()
console.log(c.f)
c.f().then(v => console.log(v))

実行結果

[AsyncFunction: f]
GOOD

async/await使った感想

  • 受け取る側でPromiseを処理すればasyncは気軽に付けてよい
  • await付けるとPromiseの中の値を外に持ってこれる
    • が、結局asyncによってPromiseに包まれるから、もともとの呼び出し側はPromise.then.catchは書かないとダメっぽい

Prettierとstandardを併用する

背景

今のプロダクトにlinterを入れたかった。今までは雑にググってヒットしたPrettierのみ使っていた。

Linterも入れたほうがいいらしい

PrettierはFormatterであってLinterではない。だからPrettierはコードの「この部分が良くないよ!」とは教えてくれず問答無用で整形だけしてくれる。「この部分が良くないよ!」を教えてもらうにはLinterが必要。JavascriptならESLintがデファクトスタンダード

eslint --fix で良くない?

おれもそう思ったが、2つの理由でPrettierも一緒に使ってもよいかなと思う。

まず、WebStormがPrettierに公式で対応していて使うのが簡単だから。Prettierがインストールされていれば何の設定も無しに Alt-Shift-Cmd-P を押せば整形してくれる。これは以前記事にも書いた。

prettierがいい感じ - an odd fellow

ふたつめが、横長過ぎるコードをESLintの --fix では整形できないが、Prettierはこれができる。なぜかというと、Prettierは書かれているコードをパースしてASTにして整形するから、らしい(What is Prettier? · Prettier)。だから横長過ぎるコードも妥当な位置で改行したりインデントを入れることができるようだ。

というわけでESLintとPrettierを併用することにした。

standard

ESLintには、Javascriptのコーディング規約に則っているかチェックしてもらいたい。Javascriptのコーディング規約は世の中にいくつかあるが、Javascript Standard Styleに従うことにした。

JavaScript Standard Style

Standard Style用にESLintの設定を書くのは骨が折れれるが、Standard StyleはGitHub - standard/standard: 🌟 JavaScript Style Guide, with linter & automatic code fixerというツールを提供している。これはESLintの設定をまるっとやって standard コマンドにラップしている。だからインストールして $ standard --fix と叩けばStandard Styleに則ったコードに整形される。素敵。

prettier-standardでPrettierとstandardを併用する

standardを使うと、今度困るのがPrettierで、PrettierのコードフォーマットもJavascript Standard Styleに従ってもらう必要がある。しかし、Prettierの設定でStandard Styleになるよう書くのもやはり骨が折れる。そこで調べているとprettier-standardというのがあった。

GitHub - sheerun/prettier-standard: (✿◠‿◠) Prettier and standard brought together!

これは $ prettier-standard ''/path/to/file' でstandardとPrettierを両方適用してくれる。素敵!!!

WebStormからprettier-standard呼べるの?

残念ながらWebStormから呼べるのはPrettier単体のみ。ただし、あとで説明するようにgit commit時にprettier-standardが走るように設定しておけば、書いている最中はどんな風に整形しても問題はない。だからPrettier単体で呼び出して使っています。ただし、セミコロンの設定だけは気になったから .prettierrc を作って ; は補完されないようにしている。

{
  "semi": false
}

git commit時にprettier-standardを走らせるようにする

commit hookはcloneしてから各自で設定する必要があり微妙に使いづらいが、huskyというライブラリを使うと、インストール時にcommit hookの設定を書き込んでくれる。git commit時に具体的にどのようなコマンドを実行するかは package.json に書けるようになるという素晴らしいライブラリ。これとlint-stagedというライブラリを併用すると、git commit時にコードフォーマットを当ててからgit commitするということが可能になる。この記事が詳しい。

コミット前に Lint を強制するなら lint-staged が便利

結局どうなったの

package.jsonに以下のように書き足すことでgit commit時にprettier-standardが走って全JSファイルをフォーマットしてくれる。

{
  "scripts": {
    "format": "prettier-standard 'src/**/*.js'"
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.js": [
      "yarn format",
      "git add"
    ]
  },
}

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

背景

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