fishシェルでrbenvのPATHを通す

zshからfishに移行していて、rbenvの移行で手こずったのでメモ

結論

fisher rbenv は使わない

このプラグインメンテ止まってるっぽい

ので、公式のreadmeに従って

~/.config/fish/config.fish

# rbenv
set -x PATH $HOME/.rbenv/bin $PATH
status --is-interactive; and source (rbenv init -|psub)

と書く。 config.fishないなら作りましょう。

なんなのか

そもそもzshでは.zshrcにこう書いていた

export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init -)"

eval "$(rbenv init -)" の部分がfishでは勝手が違うのでNGで上記のようになっている。

参考

github.com

参考になった。

fishシェル上で rbenv init すると、結論のところで書いたとおりのコマンド status --is-~~~~config.fishに書けよ、ってメッセージが出る。

react-virtualizedでTwitter/FBみたいな無限スクロールのタイムラインをつくる知見

を書いたんですよ

qiita.com

DEMO

Edit react-virtualized-demo-likes-twitter-timeline

そういうわけでなんとなくこっちにもリンクを貼っている次第です。

余談

モバイルアプリによくある引っ張って更新するPulltoRefreshという文化があるんですが、アレ実装しようとしたらRVの仕様に足元を取られてとにかく厳しかったので一旦実装していない。

github.com

pull-to-refresh系のライブラリを一通り試したところこれがまだマシだったけどRVだとリサイズ処理とかリストのコンテナの取得とかがめちゃくちゃややこしくて、バグが多く出てしまった。あと型定義が微妙。このライブラリにRV対応のPR出すか、これをforkして自分で作ったほうが早そうという気はしている。

github.com

PullToRefresh系で一番スター数が多いBoxFacturaのこっちは非react向けなのでラッパーを書いてみて試したところうまく動いた。いい感じに動いたけど、いわゆるローディングインジケーターとかがstringのみでJSX受け付けていないのでよくなかった…。UI再現上Reactコンポーネント渡せないときつい…。

とはいえせっかく書いたのでここにひっそりpulltorefreshjsのラッパーを貼っておく。

TypeScriptでReact.createRef()に型定義をわたす

React Refsとは

https://reactjs.org/docs/refs-and-the-dom.html

ReactでDOMを参照するアレ.

この記事ではv16.3から実装されたReact.createRef()を使うrefsのことを指している。

そんなプロパティないとか怒られる

Image from Gyazo

型がない

例えばこういう風にinputタグにrefを張りたいときにTSにプロパティ型がないと怒られる。

結論

型定義をわたす

React.createRef()はこうジェネリクスを受け付けているので

function React.createRef<{}>(): React.RefObject<{}>
class InputComponent extends React.Component {
  private textInput = React.createRef<HTMLInputElement>()

  public render() {
    return (
      <div>
        <input
          type="text"
          ref={this.textInput} />
      </div>
    )
  }
}

こうHTMLInputElementをわたすといい感じになれる。

Image from Gyazo

わたしてからvscodeの注釈みるとこういう感じ


よかったですね

.envファイルで配列ライクな環境変数を持ってJSでArray展開する

3行

  • .envファイルで

  • 配列ライクに環境変数を持って

  • JSで配列として展開したい

.env書く

CLIENT_ORIGIN='http://localhost:3000 http://192.168.0.99:3000'

localhostIPアドレスの2種類を持った環境変数を例に

文字列で半角スペース空けでもつ。

これをJSで string[] 形式で展開したい

JSファイル内

const originList = process.env.CLIENT_ORIGIN.split(' ')

.spritで半角スペースでスプリットしてやると目的通り string[] で展開できる


もっとなんかいい方法がありそうだけどとりあえずこんな感じで。

参考

javascript - Defining an array as an environment variable in node.js - Stack Overflow

TypeScriptのmapped typesでネストされた型もすべてオプショナルにする

partial<T>

TSのmapped typesを利用して提供されている便利な型partial<T>がある

type Partial<T> = {
    [P in keyof T]?: T[P];
};

これが提供されているので

例えば以下のようなfoo型のプロパティをすべてオプショナル(?:の形式)にしたかったらこうすればよい。

interface foo {
  foo: string
  bar: string
  baz: number
}

type optionalFoo = partial<foo>

とすると

interface optionalFoo {
  foo?: string | undefined
  bar?: string | undefined
  baz?: number | undefined
}

と同じことをしていることになる。

便利。

ネストがある場合問題あり

partial<T>便利なのだけど、ネストした型がある場合、ネストされたやつまではoptionalな形式に変換してくれない。ネストされたものもすべてオプショナルに変換したいときこれでは困る。

interface foo {
  foo: string
  bar: string
  baz: {
    hoge: string
    piyo: string
    fuga: number
  }
}

こういう状態のやつ

これをpartial<T>すると

interface optionalFoo {
  foo?: string | undefined
  bar?: string | undefined
  baz?: {
    hoge: string
    piyo: string
    fuga: number
  }
}

となってしまう。

解決方法

俺はネストされた型もすべてオプショナルにしたいんだよねという場合はpartial<T>がrecursiveな挙動をするようなmapped types型を自分で書いてやればOKらしい

stackoverflow.com

type NestedPartial<T> = {
    [K in keyof T]?: T[K] extends Array<infer R> ? Array<NestedPartial<R>> : NestedPartial<T[K]>
}

TS2.8以降はこうすればArrayがあっても大丈夫とのことらしい。

というわけで

type NestedPartial<T> = {
    [K in keyof T]?: T[K] extends Array<infer R> ? Array<NestedPartial<R>> : NestedPartial<T[K]>
};

interface foo {
  foo: string
  bar: string
  baz: {
    hoge: string
    piyo: string
    fuga: number
  }
}

type optionalFoo = NestedPartial<foo>

こうすればネストされた型も全部オプショナルに変換された。

ありがとうstackoverflow 🙏🙏🙏

axiosのparamsに階層の深いネストしたオブジェクトを渡したい

表題どおり

paramsにネストの深いオブジェクトを設定してGETメソッドでサーバーとやりとりしたかった。

普通にやるとJSONにされるのでサーバーで受け取ったとき string になってしまってオブジェクトとして取り扱えない。

qsを使ってparamsSerializerに設定する

github.com

qsはこれ

github.com

  // `params` are the URL parameters to be sent with the request
  // Must be a plain object or a URLSearchParams object
  params: {
    ID: 12345
  },

  // `paramsSerializer` is an optional function in charge of serializing `params`
  // (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
  paramsSerializer: function(params) {
    return Qs.stringify(params, {arrayFormat: 'brackets'})
  }

axiosのリポジトリから引用

てな具合なので、実際の感じはこう

const config = {
        method: 'get',
        params: { deepObject, tooDeepObject, deeeeeeeeeepObject },
        paramsSerializer(params) {
          return qs.stringify(params)
        },
        responseType: 'json'
      }

      const fetch = await axios('/hogehoge', config)
      return fetch

POSTメソッドの場合は『application/x-www-form-urlencoded』

axiosのREADMEの"Using application/x-www-form-urlencoded format"の項を参照して同じくqsを使えばOK

Node.js Expressのエラーハンドリング&next()の知見

主にExpress v4でのはなし。

Expressでのエラー処理全然わからなくて困っていたけど、ちょっとわかってきた気がするのでメモついでに書く。

くらいの人向けを想定して書いている。

便利なモジュール

boomはエラーオブジェクトユーティリティライブラリ。

github.com

hapijsのモジュールだけどExpressでも使えるので使っていく。

boomはようは『適切なエラーオブジェクト生成君』でエラーを出したいときに何のエラーかを区別する必要があったりするけど、boomを通すとラクになる。

例えば

Boom.forbidden('try again some time'); という感じにboomのforbiddenメソッドを呼ぶと

{
    "statusCode": 403,
    "error": "Forbidden",
    "message": "try again some time"
}

こういうエラーオブジェクトを作ってくれる。こっちでステータスコードをどうこう考えなくてよくなって便利。

エラー処理用ミドルウェアを作る

というわけでboomを用いたエラー処理ミドルウェア君を作っておく。

./middleware/error.js のようなファイルを作って

const errorHandler = (err, req, res, next) => {
  console.log('エラーハンドリング')
  if (res.headersSent) return next(err)
  if (!err.statusCode) err = boom.boomify(err)

  if (err.isServer) {
    // boom通した500番台のエラーはisServerでtrueが返るので
    // 500番台のみsentryみたいなエラー監視saasに送信したりできる
  }
  return err.isBoom
    ? res.status(err.output.statusCode).json(err.output.payload)
    : res.status(err.statusCode).json(err)
}

export default errorHandler

こういう感じのエラーハンドラーを作る。

boomを通したエラーオブジェクトはisServerisBoomなどのキーを持つので、これらによって「boomを介したエラーか」「500番台エラーか」とかの切り分けができる。

三項演算子でfalse側を用意しているのはboomを介していないエラーを投げたい時もあるはずなので、そういうときの処理のために書いている。 あとここではブログ的にエラーハンドラーの処理がなされたことがわかるようにconsole.logしているけど実際はいらない。

index.js 的なexpressの設定を書いているこういうファイルで、作ったエラーハンドラーを食わせるようにしておく

import express from 'express'
import errorHandler from '../middlewares/error'

const app = express()

// エラー処理ミドルウェア
app.use(errorHandler)

これで最低限のエラー処理機構ができた。エラーが想定されるところでエラーを投げると、エラー処理ミドルウェアがキャッチして処理してくれるようになった。

それではエラーを投げてみましょう。

そもそもどう書けばいいのか

エラーハンドラーのミドルウェアを書いたはいいけど、じゃあこのミドルウェアへ行くようにどう書けばいいのか。

エラーハンドリングのガイドは特に読みにくく、つらい。

『Expressでエラーを投げてエラー処理に行くには??』

それは

next(error)

です。

…と日本語の公式ガイドには"明確に"書かれていない…。

nextメソッドの第一引数になにかを入れると、エラー処理のミドルウェアにいきます…。

なお

if (err) {
    next(err); // Pass errors to Express.
}

なお英語版の公式ガイドにはこう書いてあるのでひと目で察しがつく

next(), next(error), next('route')について

公式ガイドに

next() 関数に (ストリング 'route' を除く) 何らかを渡すと、Express は、現在の要求でエラーが発生したと想定して、エラーが発生していない残りのすべての処理のルーティングとミドルウェア関数をスキップします。

next() および next(err) の呼び出しは、現在のハンドラーが完了したことと、その状態を示します。next(err) は、上記のようにエラーを処理するようにセットアップされたハンドラーを除き、チェーン内の残りのハンドラーをすべてスキップします。

こう書いてあるけど「?」ってなる

ようは

ということなのだけどわかりが悪い。

引数なしのnext()はわかるけど、引数ありはどういうことなのか。

next(error)

next(error)は

だった。

例としてこういうルーティングとミドルウェアを想定してみる。例なので isSuccess は強制的にfalseにしている。

returnなしパターン
router.get('/hello', (req, res, next) => {
    let isSuccess = false
    if (!isSuccess) next(Boom.badRequest('invalid query'))

    // returnしてないので表示される
    console.log('Logging: A')
  },
  // 後続のミドルウェア
  (req, res, next) => {
    // next(error)したので表示されない
    console.log('Logging: B')
  }
)

// 後続のルーティング処理
router.get('/hello', (req, res, next) => {
  // next(error)したので表示されない
  console.log('後続のルーティング処理だよ〜')
  next()
})

コンソールの表示はこう

Logging: A
エラーハンドリング

最初に紹介したBoomモジュールを使ってnextの第一引数にエラーを渡している。のでnext(error)したかたち。

エラー処理ミドルウェアの処理に移ったので「エラーハンドリング」が表示されている。

気をつけたほうがいいのはnext(error)した箇所でreturnしないとミドルウェア内の後続の処理は走ってしまう。だからLogging: Aが表示されている。

next(error)すると後続のミドルウェア・ルーティング処理はスキップされるので、てっきりthrow new Error()的な挙動をするのかと思うけど実際はそうではない。

なので、後続の処理をガードしたいならこう。

returnありパターン
router.get('/hello', (req, res, next) => {
  let isSuccess = false
  if (!isSuccess) return next(Boom.badRequest('invalid query')) // ここでreturn

  // returnしたので表示されない
  console.log('Logging: A')
  },
  // 後続のミドルウェア
  (req, res, next) => {
    // next(error)したので表示されない
    console.log('Logging: B')
  }
)

// 後続のルーティング処理
router.get('/hello', (req, res, next) => {
  // next(error)したので表示されない
  console.log('後続のルーティング処理だよ〜')
  next()
})

コンソールの表示はこう

エラーハンドリング

「Logging: A」が消えた。ミドルウェア内の後続処理をガードしたいならこのように適切にreturnしてあげないといけない。

next('route')

next('route')は

だった。なので以下のようになる

router.get('/hello', (req, res, next) => {
  let isSuccess = false
  if (!isSuccess) return next('route')

  // returnしたので表示されない
  console.log('Logging: A')
  },
  // 後続のミドルウェア
  (req, res, next) => {
    // next(error)したので表示されない
    console.log('Logging: B')
  }
)

// 後続のルーティング処理
router.get('/hello', (req, res, next) => {
  // next(error)はしたけどnext('route')なのでこれは表示される
  console.log('後続のルーティング処理だよ〜')
  next()
})

コンソールの表示はこう

後続のルーティング処理だよ〜

nextの第一引数になにかを渡しているのでnext(error)形式ではあるので後続のミドルウェアは実行されない。なので「Logging: B」は出ていない。

またnext(error)形式ではあるものの例外的にエラー処理には移らないので「エラーハンドリング」も表示されていない。後続のルーティング処理に移るので「後続のルーティング処理だよ〜」は実行されている。


next(), next(error), next('route')の挙動は難しい。


実際の処理の様子

そういうわけで実際には基本的には上記をふまえてこのように書いていくと思う。あくまでパターンの一例ですが例として書いておきます。

Expressアプリケーション側

import boom from 'boom'

router.use('/v1/hello', (req, res, next)=> {
  try {
    // なんかサーバー処理実行する
    next()
  } catch(error) {
    next(boom.clientTimeout('timed out'))

    //// boom通したくないならエラーオブジェクトをそのまま放り込む     
    // next(error)
  }
})

クライアント側

import axios from 'axios'

const awesomeFetch = async() => {
  try {
    const {data} = await axios(`/v1/hello`, {
          method: 'get',
          responseType: 'json'
        })
  } catch (error) {
    console.log(error.response)
  }
}

awesomeFetch()

axiosでフェッチ処理してエラーが発生(Express側でエラー処理がなされたら)、catch節にてこういう感じにエラーオブジェクトをcatchできます。

ここではboom.clientTimeout('timed out')タイムアウトエラーを発行しているので、

{
    "statusCode": 408,
    "error": "Request Time-out",
    "message": "timed out"
}

てな感じにエラー内容が取得できます。


とりあえず長いので今回はこの辺までで。

async/awaitを使ったPromiseベースのエラーハンドリングについてはもうちょいあるのでまたこんど書くかもしれない。(下記参考URLに載っていることがほとんどではあるけれど)

なんか間違ってるところがあるかもしれないのでなんかあったら逆に教えてほしいです。


【参考URL】