Help us understand the problem. What is going on with this article?

Reactのアーキテクトを考える道のり - その2(Fluxとの出会い)

More than 1 year has passed since last update.

前回記事 - その1 (最小の構成)

肥大化するコンポーネント

ある程度、コンポーネント作成に慣れてくると色々な処理機能を持たせたくなってくると思います。
サンプルとして、非常に簡単ではありますが、入力された金額に税率をかけて表示する機能を作りました。

画面イメージ

sampleImage.gif

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というライブラリが使われています。
typescriptreduderを書くときに内容を簡略化するボイラーテンプレートですので、利用はしてもしなくても良いと思います。(私は単調な記述がかなり減ったと感じていますので、愛用しています:smile:)

次回は非同期処理について!

ここまでくると、いよいよAPIを使ったデータの取得などをやりたくなってきました。
ただ、ここにも壁があったのでどの様にして乗り越えたのかを次回、書いていきたいと思います。

motuo
千葉県松戸市発のスタートアップ企業に所属しています。 今まではPHP(Laravel)中心でしたが、今後はReactなどのフロントエンド技術にも多く携わっていきます。また、2019年に引き続き2020年ののIBM Championに認定いただきました! ※こちらの記事はあくまで個人の意見であり、所属する団体を代表するものではございません。
joolen
ECサイト構築から商材の提供、販売支援まで、ワンストップでサービスをご提供致します。 これからECを始めたいとお考えの方、すでに運営されている方、ECのことなら弊社にお任せください。 お客様のニーズにマッチしたサービスをご提供致します。
https://www.joolen.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした