Closed MofuMofu2 closed 10 months ago
useStateとuseReducerの違いを説明してください。また、テストコードのサンプルがあれば知りたいです。
useState
と useReducer
は React の状態管理フックですが、それぞれ異なる使用シナリオと利点があります。
useState
useState
は単純な状態管理に使われます。これは値(文字列、数値、オブジェクトなど)を保持し、その値を更新する関数を提供します。const [value, setValue] = useState(initialValue);
useReducer
useReducer
はより複雑な状態ロジックを扱う際に使用されます。これは現在の状態とアクションを受け取り、新しい状態を返すリデューサー関数に基づいています。const [state, dispatch] = useReducer(reducer, initialState);
useState
のテストimport { render, fireEvent } from '@testing-library/react';
import MyComponent from './MyComponent';
test('increments counter', () => {
const { getByTestId } = render(<MyComponent />);
fireEvent.click(getByTestId('increment-button'));
expect(getByTestId('counter-value').textContent).toBe('1');
});
このテストでは、ボタンをクリックしてカウンターの値が増加するかを確認しています。
useReducer
のテストimport { render, fireEvent } from '@testing-library/react';
import MyReducerComponent from './MyReducerComponent';
test('increments counter through reducer', () => {
const { getByTestId } = render(<MyReducerComponent />);
fireEvent.click(getByTestId('increment-button'));
expect(getByTestId('counter-value').textContent).toBe('1');
});
こちらのテストでは、useReducer
を使用してカウンターの状態を管理し、同様にボタンクリックで状態が更新されることを確認しています。
これらのサンプルは基本的な例ですが、実際のコンポーネントやアプリケーションに合わせてテストをカスタマイズすることが重要です。
複数のアクションに基づいて状態が更新される場合の一般的な例は、ショッピングカートの管理です。ショッピングカートでは、商品の追加、削除、数量の更新、カートのクリアなど複数のアクションが考えられます。これらのアクションをuseReducer
を使って効率的に管理することができます。
const initialState = {
items: [], // カート内の商品
totalAmount: 0, // 総額
totalItems: 0, // 商品の総数
};
function cartReducer(state, action) {
switch (action.type) {
case 'ADD_ITEM':
// 新しいアイテムを追加するロジック
break;
case 'REMOVE_ITEM':
// アイテムを削除するロジック
break;
case 'UPDATE_QUANTITY':
// アイテムの数量を更新するロジック
break;
case 'CLEAR_CART':
// カートを空にするロジック
return initialState;
default:
return state;
}
}
const [cartState, dispatch] = useReducer(cartReducer, initialState);
// アイテムを追加する関数
const addItem = item => {
dispatch({ type: 'ADD_ITEM', payload: item });
};
// アイテムを削除する関数
const removeItem = itemId => {
dispatch({ type: 'REMOVE_ITEM', payload: itemId });
};
// 数量を更新する関数
const updateQuantity = (itemId, newQuantity) => {
dispatch({ type: 'UPDATE_QUANTITY', payload: { itemId, newQuantity } });
};
// カートをクリアする関数
const clearCart = () => {
dispatch({ type: 'CLEAR_CART' });
};
この例では、各アクション(ADD_ITEM
, REMOVE_ITEM
, UPDATE_QUANTITY
, CLEAR_CART
)が特定の操作を表し、これらのアクションに応じてカートの状態が更新されます。useReducer
を使用することで、これらの異なるアクションを一元管理し、状態の変更をより予測可能で管理しやすくなります。
同じページで複数のフォームを扱う場合はuseReducerを使う方がいい、みたいなイメージでしょうか。
はい、その通りです。複数のフォームを同じページで扱う場合、useReducer
を使用することで、フォームの状態管理をより簡潔に、かつ効率的に行うことができます。
useReducer
を使用する利点集中化された状態管理: 複数のフォームの状態を一つのリデューサー関数で管理できます。これにより、各フォームの状態が一箇所で更新され、状態の変更が追跡しやすくなります。
複雑な状態ロジックの簡素化: フォーム間での状態の共有や依存関係がある場合、useReducer
はこれらの複雑なロジックをより簡単に扱うことができます。
再利用性とテスタビリティ: リデューサー関数は純粋関数であり、同じ入力に対して常に同じ出力を返すため、テストが容易になります。また、リデューサーを他のコンポーネントで再利用することも可能です。
const formReducer = (state, action) => {
switch (action.type) {
case 'UPDATE_FIELD':
return {
...state,
[action.formName]: {
...state[action.formName],
[action.fieldName]: action.value
}
};
// 他のアクションハンドラ...
default:
return state;
}
};
const MyComponent = () => {
const [formState, dispatch] = useReducer(formReducer, {
form1: { field1: '', field2: '' },
form2: { field1: '', field2: '' },
// 他のフォームの初期状態...
});
const handleChange = (formName, fieldName) => event => {
dispatch({
type: 'UPDATE_FIELD',
formName,
fieldName,
value: event.target.value
});
};
// フォームのレンダリングロジック...
};
この例では、formReducer
は各フォームのフィールドを更新するためのアクション(UPDATE_FIELD
)を処理します。これにより、異なるフォームの状態更新を一つの場所で処理することができ、コードの可読性と保守性が向上します。
複数の項目で成り立っているフォームなんかと相性が良さそうだなと思った。
Reducer = State更新に利用する関数。Actionから更新する。この辺はVuexと似ている。 Actionはdispatch関数からでないと呼び出せない。
reducerの条件
テストの書き方を気を付ける必要はない。Stateとhandlerをくっつけて管理しているだけなので。その辺は楽。
https://react.dev/reference/react/useReducer