肥大化するコンポーネント
ある程度、コンポーネント作成に慣れてくると色々な処理機能を持たせたくなってくると思います。
サンプルとして、非常に簡単ではありますが、入力された金額に税率をかけて表示する機能を作りました。
画面イメージ
Fluxを知る前のサンプル
コード
何も知らない状態の私は、下記の様に計算処理をコンポーネント内に組み込んでいます。
(金額はコンポーネント内のステートとして、また税率は変更できるようにプロパティとして持たせています。
)
import React, { useEffect } from 'react'
import { Typography, TextField } from '@material-ui/core';
type ownprops = {
taxRate: number
}
export const CalculateSample: React.FC<ownprops> = (props) => {
const [price, setPrice] = React.useState(0);
const [includeTax, setIncludeTax] = React.useState(0);
const setPrices = (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement | HTMLSelectElement>) => {
setPrice(parseInt(e.target.value))
}
//ここで税込金額を計算
useEffect(() => {
setIncludeTax(Math.round(price * props.taxRate))
}, [price])
return (
<div>
< Typography variant='h5' > this is a sample Page!</Typography >
<TextField
required
label='税抜き価格'
margin="normal"
variant="outlined"
onChange={(e) => setPrices(e)}
/>
<Typography variant='h5'>税抜き価格:{price}</Typography>
<Typography variant='h5'>税込み価格:{includeTax}</Typography>
</div>
)
}
実装後の懸念
やりたいことはできたのですが、コンポーネント内にロジックを組み込む事でコンポーネント自体が肥大化・複雑化するのではないか、という懸念が生まれました。例えば、この画面に「値引き」の概念を入れようとしたら?複数行を入力して、総計を出す様にするとしたら?
きっと、私はとても読みづらくバグを含んだコンポーネントを作ってしまうと思います。
Fluxという概念との出会い
そこで出会ったのが、Flux
という概念でした。
詳しい概念は、先達の方々が素晴らしい記事を書いてくださっていますのでGoogle先生にお任せですが、「データの流れを単一方向にする」事で、コンポーネントの状態が、「いつ、どこで、なぜ、どうやって」更新されたのかを分かりやすく管理できる様になります。
そして、この概念をReact
で実現する手助けになるのが、Reduxです。
このReduxを使ってコンポーネントを書き直してみました。
書き直したコード
コンポーネント
import React from 'react'
import { Typography, TextField } from '@material-ui/core';
import { sampleActions } from '../../containers/SampleContainer';
import { SampleState } from '../../states/SampleState';
type ownprops = {
taxRate: number
}
type props = ownprops & sampleActions & SampleState
export const CalculateSample: React.FC<props> = (props) => {
const setPrices = (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement | HTMLSelectElement>) => {
props.setPrice(parseInt(e.target.value))
props.setIncludePrice(props.taxRate)
}
return (
<div>
< Typography variant='h5' > this is a sample Page!</Typography >
<TextField
required
label='税抜き価格'
margin="normal"
variant="outlined"
onChange={(e) => setPrices(e)}
/>
<Typography variant='h5'>税抜き価格:{props.price}</Typography>
<Typography variant='h5'>税込み価格:{props.includeTax}</Typography>
</div>
)
}
コンポーネントからロジックが消えて、とてもスッキリした様に感じます。
おまけ(Reduxのコード)
この様なコードを追加しています。
reducer
import { reducerWithInitialState } from 'typescript-fsa-reducers';
import actionCreatorFactory from 'typescript-fsa';
const actionCreator = actionCreatorFactory();
export interface SampleState {
price: number;
includeTax: number;
}
const initialState: SampleState = {
price: 0,
includeTax: 0
};
export const sampleActions = {
setPrice: actionCreator<number>('ACTIONS_SET_PRICE'),
setIncludePrice: actionCreator<number>('ACTIONS_SET_INCLUDE_PRICE')
};
export const sampleReduecer = reducerWithInitialState(initialState)
.case(sampleActions.setPrice, (state, price) => {
const newState = Object.assign({}, state);
newState.price = price;
return newState;
})
.case(sampleActions.setIncludePrice, (state, tax) => {
const newState = Object.assign({}, state);
newState.includeTax = Math.round(state.price * tax);
return newState;
})
.default((state) => {
return state
}
);
container
import { Action } from 'typescript-fsa';
import { Dispatch } from 'redux';
import { connect } from 'react-redux';
import { State } from '../states/index';
import { sampleActions } from '../states/SampleState';
import { CalculateSample } from '../components/Organisms/Sample';
export interface sampleActions {
setPrice: (price: number) => Action<number>,
setIncludePrice: (price: number) => Action<number>,
}
function mapDispatchToProps(dispatch: Dispatch<Action<any>>) {
return {
setPrice: (price: number) => dispatch(sampleActions.setPrice(price)),
setIncludePrice: (tax: number) => dispatch(sampleActions.setIncludePrice(tax)),
};
}
function mapStateToProps(appState: State) {
return Object.assign({}, appState.sample);
}
export default connect(mapStateToProps, mapDispatchToProps)(CalculateSample);
このコードには、typescript-fsa
というライブラリが使われています。
typescript
でreduder
を書くときに内容を簡略化するボイラーテンプレートですので、利用はしてもしなくても良いと思います。(私は単調な記述がかなり減ったと感じていますので、愛用しています)
次回は非同期処理について!
ここまでくると、いよいよAPIを使ったデータの取得などをやりたくなってきました。
ただ、ここにも壁があったのでどの様にして乗り越えたのかを次回、書いていきたいと思います。