javascriptでTabキーを無効にする方法

メモ。

window.onkeydown = function(e) {
  if (e.keyCode == 9)
    return false; // Disable Tab!
}

immutableJSのMapをredux-persistで保存できないときの対処法

以前説明したimmutableJSは簡単にimmutableオブジェクトを扱うことができるのでreduxのreducerで大活躍します。 normalizr風味のreducerを作る場合は、下記のようにimmutableJSのMapとListで簡潔に書く事ができます。

import { ADD_THOUGHT } from "../actions/thought";
import { Map, fromJS } from "immutable";

const initialState = fromJS({
  ids: [],
  entities: {}
});

export default function thought(state = initialState, action) {
  switch (action.type) {
    case ADD_THOUGHT:
      return state
        .setIn(["entities", action.draft.tid], action.draft)
        .set("ids", state.get("ids").push(action.draft.tid));
    default:
      return state;
  }
}

一つ問題があります。immutableJSはfromJSを利用するとMapオブジェクトでstateを保存します。これが、redux-persistを使うとMapで保存できないという問題です。redux-persistはtransformsという機構でserialize, deserializeを行うことができ、ここにモジュールを適用することで保存できるようになります。redux-persistのauthorであるr2tzz氏が提供するredux-persist-immutableがありますが、READMEの情報が少なくよくわかりません。他のソースを読むと、トップレベルでMapの置換を適用するようです。私の今回のreducerではnavというReact Navigatorのreducerがあるので、ここに適用する必要はないと思っていました。

Redux Persist Transform Immutable

Redux Persist Transform Immutableというものもr2tzz氏が提供しています。これは、reducerごとにシリアライズ・デシリアライズをできるものです。これはREADMEもしっかり書いてあります:)

では、実際のコードを見てみましょう。

import immutableTransform from "redux-persist-transform-immutable";

...

  componentWillMount() {
    persistStore(
      this.store,
      {
        storage: AsyncStorage,
        transforms: [
          immutableTransform({
            whitelist: ["user", "draft", "thought"],
            blacklist: ["nav"]
          })
        ]
      },
      async () => {
        await this.store.dispatch(initUser());
        await this._loadAsync();
      }
    ); //.purge();
  }

使い方は、transformsのvalueにimmutableTransformを入れます。whitelist/blacklistで追加します。どちらかを指定すれば片方がいらないかもしれませんが、わかりやすくするためにwhitelist/blacklist両方いれています。React Nativeでの利用なので、storage: AsyncStorageはそのままです。 ちなみにpersistStore(..).perge()でstateを消去できるので、開発初期にはよく使うことになると思います。

Redux + immutable.js + Reselectでredux reducerを安全かつ簡潔にする

日本語の技術ブログを読んでいるとImmutable.jsでmodelを作り、そこにロジックを入れる方法が書かれてるものが多いですが、英語の情報をまでみてみると、それはどちらかというと特殊な方法で多くのところでReduxのReducerに導入しています。

今回は、後者の方法を説明していきます。

Immutable.js

Reduxのstateはimmutableで操作すべきであり、そのためObject.assign()slice等を駆使してReducerを書いていきます。問題はその方法が冗長であり、人によってES6を使わないなど書き方も違うのでコードが見にくくなります。これを解決するのがImuutable.jsになります。ドキュメントを見ると(Typescript前提で書かれてるドキュメントなので、知らないと非常に読み難い…)、便利な関数が沢山生えてます。setInなどの関数を使えば、Nestされているオブジェクトに一発でデータを挿入できたりします。そして、そのオブジェクトが常にImmutableであることを保証してくれます。

Immutablejsのreducerへの組み込み方は簡単で、InitialStateをimmutableJSで作れるだけです。

import { ADD_TITLE, ADD_ELEMENT } from "../actions/draft";
import { Map, fromJS } from "immutable";

const initialState = fromJS({
  title: null,
  element: null,
  standards: {}, 
  answers: {}
});

export default function draft(state = initialState, action) {
  switch (action.type) {
    case ADD_TITLE:
      return state.set("title", action.title); // 直感的かつ簡潔!
    case ADD_ELEMENT:
      return state.set("element", action.element);
    default:
      return state;
  }
}

上記のstate.draft.standardsはImmutable.Mapオブジェクトとなります。問題はこれをtoJS,toArray, toObjectをコンポーネント内のRedux stateの呼び出しのmapStateToPropsで使うのがReduxのアンチパターンになっていることです。

This is a particular issue if you use toJS() in a wrapped component’s mapStateToProps function, as React-Redux shallowly compares each value in the returned props object. For example, the value referenced by the todos prop returned from mapStateToProps below will always be a different object, and so will fail a shallow equality check.

で、どうするか。ここで登場するのがReselectになります。

Reselect

Reselectは、キャッシュを持ち、変更があった場合だけ新しいオブジェクトを作成するような挙動を記述できます。これをmapStateToPropsに入れ込むことにより、アンチパターンを避けることができます。 具体的なコードは、

# app/selectors/draft.js
import { createSelectorCreator, defaultMemoize } from 'reselect';
import { isEqual } from 'lodash';

const createDeepEqualSelector = createSelectorCreator(
  defaultMemoize,
  isEqual
);

const draftStandardsSelector = (state) => state.draft.get('standards').toObject();

export const standardsInDraft = createDeepEqualSelector(
  [draftStandardsSelector], // InputSelector(s)
  (standards) => {
    return standards;
  }
);

詳しくはREADMEや他の技術ブログを参考にしていただければと思いますが、標準の比較関数は単純なオブジェクト比較(===)を利用しているため、オブジェクトのkey/valueのペアまで比較できません。ここでlodashisEqualを使って、deep比較をすることにしている(今回の場合、shallowな比較(ネストされたものは比較しない)で十分ではあるがREADMEがそうなっているので))。createDeepEqualSelectorの最初の引数が、Input Selectorと呼ばれるもので、ここが以前のもの(キャッシュ)との比較対象です。isEqualコードを読んでいないが直感的にあまりnestしすぎない方が比較自体の計算量が少なくなって良さそうですね。

これをcomponentのmapStateToPropsで直に呼び出すのは、単純です。

import { standardsInDraft } from '../selectors/draft';
...

function mapStateToProps(state, ownProps) {
  const title = state.draft.get("title");
  const element = state.draft.get("element");
  const standards = standardsInDraft(state); // directly calling
  return { title, element, standards };
}
export default connect(mapStateToProps)(Editor);

これでreducer内でimmutablejsで快適にstateを操作できるようになりました!

[読書] CRITICAL THINKING: A Beginner’s Guide To Critical Thinking, Better Decision Making, And Problem Solving !

CRITICAL THINKING: A Beginner’s Guide To Critical Thinking, Better Decision Making, And Problem Solving ! ( critical thinking, problem solving, strategic thinking, decision making)

という本を読んだ。一般的な概念の説明で終わってしまい実際どうやってCritical Thinkingを使うのかが言及されていない。Critical Thinking実行方法は、下記のようだが、

How to Carry Out Critical Thinking Step By Step

  1. Establish what the problem is
  2. Undertake to analyze the problem
  3. Think up manageable solutions
  4. Choose the best possible solution
  5. Wind up your process

これが全体の流れでどこがそこにあたるのかわからない。

  1. Observation
  2. Analysis
  3. Interpretation
  4. Reflection
  5. Evaluation
  6. Inference
  7. Explanation
  8. Problem solving
  9. Decision making

341円で知らない単語を覚えることができたと思えばよいが、内容はいまいちでした。