reactを使ったアプリ開発
react.jsはviewの部分
データ管理にfluxフレームワークを導入が必要です
今回はreduxを利用したアプリ開発をベースに気をつける点などを説明します
componentとは
componentはUIを独立した再利用可能な部分に分割したものです
reactはcomponentを組み合わせてページを構成します
class ButtonComponent extends Component {
render() {
return (
<button>
ボタン
</button>
);
}
}
props
componentでどのようなデータ描画するかは呼び出し元からpropsというデータをもらい描画します
render() {
return (
<button
onClick={this.props.onClick}
>
{this.props.buttonName}
</button>
);
}
bind問題
ReactをES6で書いた場合に、イベントハンドラでthisがundefinedとなりメソッドが実行できないという問題
参考
http://qiita.com/konojunya/items/fc0cfa6a56821e709065
私のプロジェクトではstage-0でメソッド定義しています
onClick = (e) => {
this.props.onClick(e.target.dataset.key)
}
render() {
return (
<button
deta-key="test"
onClick={this.props.onClick}
>
{this.props.buttonName
</button>
);
}
arrow関数で書いていたこともありますがrenderをシンプルにしたい
renderの度にfunctionが生成されるのも無駄だよね
ということから移行しました
propsのobjectの一部を書き換えたい
reactではprops/stateはimmutableなobjectとして扱います
勝手に上書きすると他の部分のrenderなどが勝手に変わる、意図したタイミングでレンダーされない
などのも問題が発生します
駄目な例
static propTypes= {
item: React.PropTypes.shape({
name: React.PropTypes.string,
color: React.PropTypes.string,
})
};
render() {
this.props.item.color = '#000';
return (
<button
style={{color: this.props.item.color}}
deta-key="test"
>
{this.props.item.name}
</button>
);
}
immutableな変更の例
const assigned = _.assign({}. this.props.item, {color: '#000'}})
const updated = icepick.setIn(this.props.item, ['color'], '#000');
state
componentの状態に応じてrenderを変えたい場合、stateを使って管理できます
が、componentはstateを持たず、propsのデータを描画するviewに専念することで
componentとしての役割が明確になりテストもしやすくなります
redux
fluxフレームワークの一つ
Single source of truth
アプリの状態をもつStoreはただ1つ。
state is read-only
状態を変更するには必ずActionを発行しなければならない。
Changes are made with pure functions
Actionにより状態をどう変更させるかはReducerが行なう。
状態管理やレンダリングをきれいに分割できるのがメリット
presentation componenntとcontainer component
reduxでは描画に特化させたcomponentをpresentation componentとよび
presentation componenntとstoreを結びつけるためにcontainer componentという
概念を用いてデータの管理を区別します
以下 presentation component -> component
container component -> container
としています
container
componentにデータやコールバックを渡すcomponent
mapStateToPropsでstateから利用する値だけをとってpropsにセットします
onClickButton(key) {
this.context.store.dispatch(action());
},
render() {
return (
<PageComponent>
<ButtonComponent
name: this.props.item.name,
color: this.props.item.color,
onClick={this.onClickButton}
/>
</PageComponent>
);
}
const mapStateToProps = (state: Object, ownProps: Object) => {
return {
name: state.name,
color: state.color,
};
};
export default connect(
mapStateToProps
)(MainPageContainer);
action
actionはアプリケーションからの情報をstoreへ送る為のオブジェクト
actionはtypeを必ず持ちます
const SHOW_DIALOG = 'SHOW_DIALOG';
function showDialog() {
return {
type: SHOW_DIALOG,
dialogProps: {
show: true,
title: 'New Dialog',
},
};
}
reducer
actionが起こった結果、どのようにstateが変化するかを対応するのがreducer
const initialState = {};
function reducer(state = initialState, action) {
switch (action.type) {
case SHOW_DIALOG:
return _.assing({}, state, action.dialogProps);
default:
return state;
}
}
reducerは分割してconbineReducerで結合したりするとreducerの管理がしやすくなります
export function createRootReducer(): Function {
const reducers = {
dialog: reduceDialog,
button: reduceButton,
};
return reduceReducers(
combineReducers(reducers),
(state: Object = {}, action: Object) => {
return {
...state,
};
}
);
}
create-reducerを実装するとswitch文からも開放されて記述を減らせるのでおすすめ
export const reduceShowDialog = createReducer([], {
[ActionTypes.SHOW_DIALOG](state, action) {
return _.assing({}, state, action.dialogProps);
}
})
reduxの非同期処理
-
redux-thunk
- promiseを使った非同期処理 -
redux-sage
- generatorを使った非同期処理
redux-thunkではactionCreaterが肥大化していけどpromiseでゴリゴリっとかけるので楽ではある
一方、redux-sagaはgeneratorが必要でpolyfil入れたり多くの人に学習コストがかかったりするのけど
action周りはすっきりかけるとは思います
style
inline styleかcssか
inline styleは物理的にstyleとhtmlが近いので修正は楽だけど、デザイナーが用意したcss等を
ちょっと修正したりする必要がでてきます
最近はpostCSSを使いlocal使って名前の重複を防ぎ、componentの中で使うclass名をシンプルなものにし、cssをそのまま使えるようにしています
デバッグ
- react developer tools
chromeのdevtoolでcomponentがわかるようになります
- redux-devtools
こちらもchromeの拡張。stateの変化や状態をみることができます
テスト
componentをシンプルにしておくとテストが楽になります
react-addons-test-utilsを使い
componentに任意のpropsを渡し、期待したnodeが生成されているかを判定するというテストができます
const props = {
name: 'test',
color: '#000'
};
const component = TestUtils.renderIntoDocument(
<ButtonComponent
{...props}
/>
);
const node = ReactDOM.findDOMNode(component);
assert(node.children.length === 1);
actionはredux-mock-storeを使ったりします