reduxで何度も書くやつをSublime Text 3のスニペットにした

React Native エンジニアのいとうです。

reduxでよく出てくる変数たちを手早く入力するためのスニペットを作りました。
自分がよく使う**storeのパターン**について示し、最後にSublime Text 3の**スニペット**を載せます。

# storeのパターン

storeのパターンとは、reducerの中の変数たちの中で、よく出てくるパターンのことを指します。
reduxとRESTで何か作っていると

- loadingフラグ
- errorフラグとその内容
- 取得したコンテンツ

の3種類が欲しくなりますね。

私は下記のように書きます。

```js
const initialState = {
  isGetContentLoading: false,
  isGetContentError: false,
  getContentError: null,
  content: {}
};
```

で、私はそのうち `loadingフラグ`と`errorフラグ`、`errorの内容`については、actionの種類の単位で書くことが多いです。

つまりContentに対して「作成」「取得」「更新」「削除」するときにreducerがこうなります。

```reducers/content.js
const initialState = {
  // get
  isGetContentLoading: false,
  isGetContentError: false,
  getContentError: null,
  // create
  isCreateContentLoading: false,
  isCreateContentError: false,
  createContentError: null,
  // update
  isUpdateContentLoading: false,
  isUpdateContentError: false,
  updateContentError: null,
  // delete
  isDeleteContentLoading: false,
  isDeleteContentError: false,
  deleteContentError: null,
  // リソース
  content: null
};
```

さらに続けますとreducerの本体はこんな感じになります。

```reducers/content.js
export default (state = initialState, action) => {
  switch (action.type) {
    // get content
    case types.REQUEST_GET_CONTENT:
      return {
        ...state,
        isGetContentLoading: true,
      };

    case types.RECEIVE_GET_CONTENT:
      return {
        ...state,
        content: action.state.content,
        isGetContentLoading: false
      };

    case types.FAIL_GET_CONTENT:
      return {
        ...state,
        isGetContentLoading: false,
        isGetContentFailed: true,
        getContentError: action.state.error
      };

    case types.RECOVER_GET_CONTENT:
      return {
        ...state,
        isGetContentFailed: false,
        getContentError: null
      };

    // create content
    case types.REQUEST_CREATE_CONTENT:
      return {
        ...state,
        isCreateContentLoading: true,
      };

    case types.RECEIVE_CREATE_CONTENT:
      return {
        ...state,
        content: action.state.content,
        isCreateContentLoading: false
      };

    case types.FAIL_CREATE_CONTENT:
      return {
        ...state,
        isCreateContentLoading: false,
        isCreateContentFailed: true,
        createContentError: action.state.error
      };

    case types.RECOVER_CREATE_CONTENT:
      return {
        ...state,
        isCreateContentFailed: false,
        createContentError: null
      };

    // update content
    case types.REQUEST_UPDATE_CONTENT:
      return {
        ...state,
        isUpdateContentLoading: true,
      };

    case types.RECEIVE_UPDATE_CONTENT:
      return {
        ...state,
        content: action.state.content,
        isUpdateContentLoading: false
      };

    case types.FAIL_UPDATE_CONTENT:
      return {
        ...state,
        isUpdateContentLoading: false,
        isUpdateContentFailed: true,
        updateContentError: action.state.error
      };

    case types.RECOVER_UPDATE_CONTENT:
      return {
        ...state,
        isUpdateContentFailed: false,
        updateContentError: null
      };

    // delete content
    case types.REQUEST_DELETE_CONTENT:
      return {
        ...state,
        isDeleteContentLoading: true,
      };

    case types.RECEIVE_DELETE_CONTENT:
      return {
        ...state,
        content: action.state.content,
        isDeleteContentLoading: false
      };

    case types.FAIL_DELETE_CONTENT:
      return {
        ...state,
        isDeleteContentLoading: false,
        isDeleteContentFailed: true,
        deleteContentError: action.state.error
      };

    case types.RECOVER_DELETE_CONTENT:
      return {
        ...state,
        isDeleteContentFailed: false,
        deleteContentError: null
      };

    default:
      return state;
  }
};
```

Action Typeはこんな感じですね。

```ActionTypes.js
// content
export const REQUEST_CREATE_CONTENT = 'REQUEST_CREATE_CONTENT';
export const RECEIVE_CREATE_CONTENT = 'RECEIVE_CREATE_CONTENT';
export const FAIL_CREATE_CONTENT = 'FAIL_CREATE_CONTENT';
export const RECOVER_CREATE_CONTENT = 'RECOVER_CREATE_CONTENT';

export const REQUEST_GET_CONTENT = 'REQUEST_GET_CONTENT';
export const RECEIVE_GET_CONTENT = 'RECEIVE_GET_CONTENT';
export const FAIL_GET_CONTENT = 'FAIL_GET_CONTENT';
export const RECOVER_GET_CONTENT = 'RECOVER_GET_CONTENT';

export const REQUEST_UPDATE_CONTENT = 'REQUEST_UPDATE_CONTENT';
export const RECEIVE_UPDATE_CONTENT = 'RECEIVE_UPDATE_CONTENT';
export const FAIL_UPDATE_CONTENT = 'FAIL_UPDATE_CONTENT';
export const RECOVER_UPDATE_CONTENT = 'RECOVER_UPDATE_CONTENT';

export const REQUEST_DELETE_CONTENT = 'REQUEST_DELETE_CONTENT';
export const RECEIVE_DELETE_CONTENT = 'RECEIVE_DELETE_CONTENT';
export const FAIL_DELETE_CONTENT = 'FAIL_DELETE_CONTENT';
export const RECOVER_DELETE_CONTENT = 'RECOVER_DELETE_CONTENT';
```

## スニペット

もうこれ書くの憂鬱で仕方なかったんですけどスニペットを作って少しでも早く書けるようにします。

```rdxrd.sublime-snippet
<snippet>
  <content><![CDATA[
import * as types from '../constants/ActionTypes';

const initialState = {
  // get
  isGet${1:Store}Loading: false,
  isGet${1:Store}Error: false,
  get${1:Store}Error: null,
  // create
  isCreate${1:Store}Loading: false,
  isCreate${1:Store}Error: false,
  create${1:Store}Error: null,
  // update
  isUpdate${1:Store}Loading: false,
  isUpdate${1:Store}Error: false,
  update${1:Store}Error: null,
  // delete
  isDelete${1:Store}Loading: false,
  isDelete${1:Store}Error: false,
  delete${1:Store}Error: null,
};

export default (state = initialState, action) => {
  switch (action.type) {
    // get ${2:Variable}
    case types.REQUEST_GET_${3:Const}:
      return {
        ...state,
        isGetContentLoading: true,
      };

    case types.RECEIVE_GET_${3:Const}:
      return {
        ...state,
        ${2:Variable}: action.state.${2:Variable},
        isGetContentLoading: false
      };

    case types.FAIL_GET_${3:Const}:
      return {
        ...state,
        isGetContentLoading: false,
        isGetContentFailed: true,
        getContentError: action.state.error
      };

    case types.RECOVER_GET_${3:Const}:
      return {
        ...state,
        isGetContentFailed: false,
        getContentError: null
      };

    // create ${2:Variable}
    case types.REQUEST_CREATE_${3:Const}:
      return {
        ...state,
        isCreateContentLoading: true,
      };

    case types.RECEIVE_CREATE_${3:Const}:
      return {
        ...state,
        ${2:Variable}: action.state.${2:Variable},
        isCreateContentLoading: false
      };

    case types.FAIL_CREATE_${3:Const}:
      return {
        ...state,
        isCreateContentLoading: false,
        isCreateContentFailed: true,
        createContentError: action.state.error
      };

    case types.RECOVER_CREATE_${3:Const}:
      return {
        ...state,
        isCreateContentFailed: false,
        createContentError: null
      };

    // update ${2:Variable}
    case types.REQUEST_UPDATE_${3:Const}:
      return {
        ...state,
        isUpdateContentLoading: true,
      };

    case types.RECEIVE_UPDATE_${3:Const}:
      return {
        ...state,
        ${2:Variable}: action.state.${2:Variable},
        isUpdateContentLoading: false
      };

    case types.FAIL_UPDATE_${3:Const}:
      return {
        ...state,
        isUpdateContentLoading: false,
        isUpdateContentFailed: true,
        updateContentError: action.state.error
      };

    case types.RECOVER_UPDATE_${3:Const}:
      return {
        ...state,
        isUpdateContentFailed: false,
        updateContentError: null
      };

    // delete ${2:Variable}
    case types.REQUEST_DELETE_${3:Const}:
      return {
        ...state,
        isDeleteContentLoading: true,
      };

    case types.RECEIVE_DELETE_${3:Const}:
      return {
        ...state,
        ${2:Variable}: action.state.${2:Variable},
        isDeleteContentLoading: false
      };

    case types.FAIL_DELETE_${3:Const}:
      return {
        ...state,
        isDeleteContentLoading: false,
        isDeleteContentFailed: true,
        deleteContentError: action.state.error
      };

    case types.RECOVER_DELETE_${3:Const}:
      return {
        ...state,
        isDeleteContentFailed: false,
        deleteContentError: null
      };

    default:
      return state;
  }
};
]]></content>
  <tabTrigger>rdxrd</tabTrigger>
  <scope>source.js</scope>
</snippet>
```

```rdxtp.sublime-snippet
<snippet>
  <content><![CDATA[
export const REQUEST_GET_${1:Const} = 'REQUEST_GET_${1:Const}';
export const RECEIVE_GET_${1:Const} = 'RECEIVE_GET_${1:Const}';
export const FAIL_GET_${1:Const} = 'FAIL_GET_${1:Const}';
export const RECOVER_GET_${1:Const} = 'RECOVER_GET_${1:Const}';

export const REQUEST_CREATE_${1:Const} = 'REQUEST_CREATE_${1:Const}';
export const RECEIVE_CREATE_${1:Const} = 'RECEIVE_CREATE_${1:Const}';
export const FAIL_CREATE_${1:Const} = 'FAIL_CREATE_${1:Const}';
export const RECOVER_CREATE_${1:Const} = 'RECOVER_CREATE_${1:Const}';

export const REQUEST_UPDATE_${1:Const} = 'REQUEST_UPDATE_${1:Const}';
export const RECEIVE_UPDATE_${1:Const} = 'RECEIVE_UPDATE_${1:Const}';
export const FAIL_UPDATE_${1:Const} = 'FAIL_UPDATE_${1:Const}';
export const RECOVER_UPDATE_${1:Const} = 'RECOVER_UPDATE_${1:Const}';

export const REQUEST_DELETE_${1:Const} = 'REQUEST_DELETE_${1:Const}';
export const RECEIVE_DELETE_${1:Const} = 'RECEIVE_DELETE_${1:Const}';
export const FAIL_DELETE_${1:Const} = 'FAIL_DELETE_${1:Const}';
export const RECOVER_DELETE_${1:Const} = 'RECOVER_DELETE_${1:Const}';
]]></content>
  <tabTrigger>rdxtp</tabTrigger>
  <scope>source.js</scope>
</snippet>
```

```rdxac.sublime-snippet
<snippet>
  <content><![CDATA[
import * as types from '../constants/ActionTypes';

// get
function requestGet${1:Store}() {
  return {
    type: types.REQUEST_GET_${2:Constant}
  };
}

function receiveGet${1:Store}(${3:Variable}) {
  return {
    type: types.RECEIVE_GET_${2:Constant},
    state: {
      ${3:Variable}
    },
  };
}

function failGet${1:Store}(error) {
  return {
    type: types.FAIL_GET_${2:Constant},
    state: {
      error
    }
  };
}

export function recoverGet${1:Store}() {
  return {
    type: types.RECOVER_GET_${2:Constant}
  };
}

export function get${1:Store}() {
  return (dispatch, getState) => {
    dispatch(requestGet${1:Store}());

    fetch(/* TODO */).then(response => {
      dispatch(receiveGet${1:Store}(response));
    }).catch(error => {
      dispatch(failGet${1:Store}(error));
    });
  };
}

// create
function requestCreate${1:Store}() {
  return {
    type: types.REQUEST_CREATE_${2:Constant}
  };
}

function receiveCreate${1:Store}(${3:Variable}) {
  return {
    type: types.RECEIVE_CREATE_${2:Constant},
    state: {
      ${3:Variable}
    },
  };
}

function failCreate${1:Store}(error) {
  return {
    type: types.FAIL_CREATE_${2:Constant},
    state: {
      error
    }
  };
}

export function recoverCreate${1:Store}() {
  return {
    type: types.RECOVER_CREATE_${2:Constant}
  };
}

export function create${1:Store}() {
  return (dispatch, getState) => {
    dispatch(requestCreate${1:Store}());

    fetch(/* TODO */).then(response => {
      dispatch(receiveCreate${1:Store}(response));
    }).catch(error => {
      dispatch(failCreate${1:Store}(error));
    });
  };
}

// update
function requestUpdate${1:Store}() {
  return {
    type: types.REQUEST_UPDATE_${2:Constant}
  };
}

function receiveUpdate${1:Store}(${3:Variable}) {
  return {
    type: types.RECEIVE_UPDATE_${2:Constant},
    state: {
      ${3:Variable}
    },
  };
}

function failUpdate${1:Store}(error) {
  return {
    type: types.FAIL_UPDATE_${2:Constant},
    state: {
      error
    }
  };
}

export function recoverUpdate${1:Store}() {
  return {
    type: types.RECOVER_UPDATE_${2:Constant}
  };
}

export function update${1:Store}() {
  return (dispatch, getState) => {
    dispatch(requestUpdate${1:Store}());

    fetch(/* TODO */).then(response => {
      dispatch(receiveUpdate${1:Store}(response));
    }).catch(error => {
      dispatch(failUpdate${1:Store}(error));
    });
  };
}

// delete
function requestDelete${1:Store}() {
  return {
    type: types.REQUEST_DELETE_${2:Constant}
  };
}

function receiveDelete${1:Store}(${3:Variable}) {
  return {
    type: types.RECEIVE_DELETE_${2:Constant},
    state: {
      ${3:Variable}
    },
  };
}

function failDelete${1:Store}(error) {
  return {
    type: types.FAIL_DELETE_${2:Constant},
    state: {
      error
    }
  };
}

export function recoverDelete${1:Store}() {
  return {
    type: types.RECOVER_DELETE_${2:Constant}
  };
}

export function delete${1:Store}() {
  return (dispatch, getState) => {
    dispatch(requestDelete${1:Store}());

    fetch(/* TODO */).then(response => {
      dispatch(receiveDelete${1:Store}(response));
    }).catch(error => {
      dispatch(failDelete${1:Store}(error));
    });
  };
}

]]></content>
  <tabTrigger>rdxac</tabTrigger>
  <scope>source.js</scope>
</snippet>
```

以上、私がよく使うreduxのstoreパターンでした。

なんかもっとうまい方法とかあったら教えてください。。

ちなみに趣味ではreducer書きたくないので、Apollo積極的に使っています。