質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Redux

Reduxは、JavaScriptアプリケーションの状態を管理するためのオープンソースライブラリです。ReactやAngularで一般的にユーザーインターフェイスの構築に利用されます。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

Q&A

解決済

1回答

487閲覧

React / Reduxのカウンターコンポーネントを状態を分けて複製したい

ludient

総合スコア20

Redux

Reduxは、JavaScriptアプリケーションの状態を管理するためのオープンソースライブラリです。ReactやAngularで一般的にユーザーインターフェイスの構築に利用されます。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

0グッド

0クリップ

投稿2019/08/19 07:47

編集2019/08/19 07:59

経緯

ReactとReduxを使って状態管理できるカウンターコンポーネントを作りました。
このコンポーネントを複製して、カウンターを2つ表示し、それぞれカウント値を別々に保持したいと思ったのですが、単純にjsxにカウンターコンポーネントのを2つ記載しただけでは、2つのカウンターの数値が同期してしまい(Storeから同じ値を参照していると思うので)ただカウンターが2つ表示されただけ、という状態になってしまいました。

単純にコンポーネントやreducerなどをコピーして別の名前で定義すればできるのはわかるのですが、コンポーネントのマークアップや、カウンターのロジックは全く同じなのに、ファイルが倍になっていくそのやりかたは絶対にベストでないことは自明なのですが、こういう場合の最適な処理がわからず困っています。

知りたいこと

今回の目的としては、それぞれの独立した値を持つカウンターを2つ表示したいのです。
このようなことをしたい場合は、コードのどの部分を変更(ないしは複製?)するのが最適なのでしょうか?

また、そもそもこう書いてるのおかしいよ。みたいな箇所があれば指摘いただけると幸いです。

コード

js

1// src/index.js 2 3import React from 'react'; 4import { render } from 'react-dom'; 5import { Provider } from 'react-redux'; 6import { createStore } from 'redux'; 7import CounterContainer from './containers/Counter'; 8import reducer from './reducers' 9 10const store = createStore(reducer); 11 12render( 13 <Provider store={store}> 14 <CounterContainer /> 15 {/* ここにもう一つカウンターを設置したい */} 16 </Provider>, 17 document.getElementById('root') 18);

js

1// src/components/Counter.js 2 3import React from 'react'; 4 5const Counter = ({ count, increment, decrement }) => { 6 return ( 7 <div> 8 <span>{count}</span> 9 <button onClick={increment}>+1</button> 10 <button onClick={decrement}>-1</button> 11 </div> 12 ); 13}; 14 15export default Counter

js

1 2// src/containers/Counter.js 3 4import Counter from '../components/Counter' 5import { onIncrement, onDecrement } from '../actions' 6import { connect } from 'react-redux'; 7 8const mapStateToProps = (state) => ({ 9 count: state.counterReducer.count 10}) 11 12const mapDispatchToProps = (dispatch) => ({ 13 increment: () => dispatch(onIncrement()), 14 decrement: () => dispatch(onDecrement()) 15}) 16 17export default connect(mapStateToProps, mapDispatchToProps)(Counter)

js

1// src/action.index.js 2 3export const INCREMENT = 'INCREMENT' 4export const DECREMENT = 'DECREMENT' 5 6export const onIncrement = () => { 7 return { 8 type: INCREMENT 9 } 10} 11 12export const onDecrement = () => { 13 return { 14 type: DECREMENT 15 } 16}

js

1 2// src/reducers/counter.js 3 4import { INCREMENT, DECREMENT } from '../actions' 5 6export const counterReducer = (state = {count: 0}, action) => { 7 switch (action.type) { 8 case INCREMENT: 9 return { 10 ...state, 11 count: state.count + 1 12 } 13 case DECREMENT: 14 return { 15 ...state, 16 count: state.count - 1 17 } 18 default: 19 return state 20 } 21} 22export default counterReducer

js

1// src/reducers/index.js 2 3import { combineReducers } from 'redux' 4import { counterReducer } from './counter' 5 6const reducer = combineReducers({ 7 counterReducer, 8}) 9 10export default reducer

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

jun68ykt

2019/08/20 14:56

こちらのご質問に回答しておりますが、いかがでしょうか? 回答にご不明な点などあれば、コメントからお知らせください。
guest

回答1

0

ベストアンサー

こんにちは

今回の目的としては、それぞれの独立した値を持つカウンターを2つ表示したいのです。

このようなことをしたい場合は、コードのどの部分を変更(ないしは複製?)するのが最適なのでしょうか?

以下、最適な解かどうかは分かりませんが、一例として回答します。

1. カウンターを区別するためのprop追加

まず、個別のカウント値を参照、更新するカウンターを、コンポーネントではどのように区別するのかを考えます。この回答では、以下のように、CounterContainername というpropを追加することにします。

JSX

1<CounterContainer name="foo" /> 2<CounterContainer name="bar" />

2. 複数のカウンターを保持できるようにredux state を修正

次に、上記の 1. で追加した prop nameごとに個別のカウント値を保持するように redux state を修正します。たとえば、name"foo" のカウンターが 2、 "bar" のカウンターが 5 であるときの state をどのような形にすればよいかという問題もいろいろ案がありそうですが、ここでは単純に、以下の形式のオブジェクトで持つことにします。

javascript

1{ foo: 2, bar: 5 }

この場合、初期状態は以下の2つが考えられます。

(1) foo と bar をプロパティとして持たせ、初期値を明示的に記載する。

javascript

1const initialState = { foo: 0, bar: 0 }

または、
(2) 空オブジェクト

javascript

1const initialState = {}

考え方の違いでいうと、(1) は、 redux state のプロパティに無いような name (たとえば name="bazz" )のカウンターは無効なものとする(画面上では、ボタンを押せないようにする等)という考え方の案です。(2) は、redux state からは、カウンターの name prop に与える値に特に制限を設けないという考え方の案です。

どうちらもありな方向性ですが、ここでは (2) で進めます。

3. アクションクリエータの修正

アクションクリエータは onIncrement 、 onDecrementともに、引数として name を取り、これをアクションの中に持つように修正します。

javascript

1export const onIncrement = name => { 2 return { 3 type: INCREMENT, 4 name 5 } 6} 7 8export const onDecrement = name => { 9 return { 10 type: DECREMENT, 11 name 12 } 13}

4. リデューサーの修正

state に含まれる action.name の値を変更するように修正します。

javascript

1 case INCREMENT: 2 return { 3 ...state, 4 [action.name]: (state[action.name] || 0) + 1 5 } 6 case DECREMENT: 7 return { 8 ...state, 9 [action.name]: (state[action.name] || 0) - 1 10 }

上記で、 state[action.name] || 0 としているのは、初めは state[action.name]undefined になり、 undefined に1を足したり、1を引いたりすると NaN になってしまうための対応です。言い換えると、 state[action.name] || 0 は、どんな name に対しても、カウンターの初期値は 0 であることを表しています。

5. コンテナの修正

次に React 側の修正です。まず、コンテナを作るときの mapStateToProps で、 name prop に対応したカウント値が返されるようにします。

javascript

1const mapStateToProps = (state, ownProps) => ({ 2 count: state.counterReducer[ownProps.name] 3})

mapDispatchToProps のほうは、incrementdecrement を、引数 name を受け取ってアクションクリエータに渡すように修正します。

javascript

1const mapDispatchToProps = (dispatch) => ({ 2 increment: name => dispatch(onIncrement(name)), 3 decrement: name => dispatch(onDecrement(name)) 4})

6. カウンターコンポーネントの修正

受け取るpropsに name を追加し、 初期表示のときは mapStateToProps によって count には undefined が渡されてくるので、初期値の指定を追加します。

jsx

1const Counter = ({ name, count=0, increment, decrement }) => {

+1ボタンと -1 ボタンのクリックハンドラで、 incrementdecrement に引数として name を渡すように修正します。

jsx

1<button onClick={() => { increment(name) }}>+1</button> 2<button onClick={() => { decrement(name) }}>-1</button>

以上の修正で、とりあえず意図どおり、個別のカウント値を持つように CounterContainerを書けるようになったので、以下で試します。

jsx

1render( 2 <Provider store={store}> 3 <CounterContainer name="foo" /> 4 <CounterContainer name="bar" /> 5 </Provider>, 6 document.getElementById('root') 7);

上記の修正手順 1.〜 6. の中での考えどころは、1.と 2. です。1.と2. が決まればその後のコード修正は(多くのファイルに手を入れなければならないものの)、やることの見えている作業レベルという感じですが、3.以降の作業のどこかで、redux state の設計がまずかったために、それが後々の作業を面倒にしていることに、作り始めてから気がつくことは、ままあります。その場合は 2. または 2.をやろうとした発端になった 1. にまで戻って見直します。

動作確認用のコード

上記の修正後のものを動作確認するため、作成したものを以下に上げました。

以下の手順で動かすことができると思います。

上記のレポジトリで、最初のコミットでは、ご質問に掲載のコードをコピペして各ファイルを作成して、その後の数回のコミットで、カウンターの複数対応の修正をしています。

以上、参考になれば幸いです。

投稿2019/08/19 13:11

編集2019/08/20 03:38
jun68ykt

総合スコア9058

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ludient

2019/08/26 03:00

完璧な回答をいただいていたのにレスが遅くなってしまい大変申し訳ありませんでした。 知りたかったことがすべて書かれていて本当に目から鱗です。 nameなどのpropsで場合分けするというところまでは何となくそんなそういうことをするべきなのかなという考えはあったのですが、実際にコードを見てみるとなるほどと思うことばかりです。 特に、手順2の考え方に関する記述や、4のundefinedへの対策は本当に参考になりました。 サンプルコードまで添付いただいて本当にありがとうございました!
jun68ykt

2019/08/26 08:07

どういたしまして! お役に立てたようで、よかったです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問