前回useStateをアウトプットしたので今日はuseReducerをアウトプットしていきます。
const [state, dispatch] = useReducer(reducer, initialArg, init);
上記はuseReducerの基本的な構文です。
useStateと見比べてみます。
// useState
const [state, setState] = useState(initialState);
// useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init);
どちらも分割代入で第1引数にはstateを受け取っています。
第2引数は違いますがどちらもstateを更新させるための関数です。
ちなみに
const [state, setState] = useState(initialState);
の書き方はこの記事を読むとよくわかります。
const [state, dispatch] = useReducer(reducer, initialArg, init);
useReducerの方には第1引数に以下のようなstateの値を更新させる関数を渡し、
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
第2引数に以下のような初期値を渡します。
const initialState = {count: 0};
全体を見ると以下のようになります。
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
個人的にuseReducerの第一引数と第二引数を逆にしてくれた方が見やすいと思っています。
// 以下は誤った構文だがこちらの方が分割代入のstate(値),dispatch(関数)に
// useReducerの引数の並びを合わせているので見やすい
const [state, dispatch] = useReducer(initialArg,reducer);
半年くらい前に読んだ本でuseReducerを使ってuseStateでsetStateするのではなく、useReucerを使用するという方法があったので紹介します。
以下はチェックボックスをuseStateで管理しているあるあるなコードなのですが、ここでuseReducerを使えるそうです。
あるあるuseState管理
export function CheckBox() {
const [checked, setChecked] = useState(false);
return (
<>
<input
type="checkbox"
checked={checked}
onChange={() => setChecked((checked) => !checked)}
/>
{checked ? "checked" : "not checked"}
</>
);
}
useReducerを使って書き換える
export function CheckBox() {
const [checked, toggle] = useReducer(checked => !checked,false);
return (
<>
<input
type="checkbox"
checked={checked}
onChange={() => toggle()}
/>
{checked ? "checked" : "not checked"}
</>
);
}
今までuseStateを使用してコンポーネント部分からstateを参照していたのがなくなりかなりスッキリしました。
何をやっているかというと
const [checked, toggle] = useReducer(checked => !checked,false);
useReducer()
の第1引数が関数でこれはtoggle関数になります。そして第二引数が初期値のfalseでありこれがcheckedの値になります。
またtrue,falseのtoggleだけでなく数値にも使用できます。
export function Numbers() {
const [number, setNumber] = useReducer(
(number, newNumber) => number + newNumber,
0
);
return <h1 onClick={() => setNumber(3)}>{number}</h1>;
}
さらにオブジェクトの場合、もっと光ります。
以下もオブジェクトをuseStateで管理しているあるあるなコードなのです
export function User() {
const firstUser = {
id: "1234567",
firstName: "cawa",
lastName: "uchi",
city: "nasu",
state: "tochigi",
email: "hogehoge@gmail.com",
admin: false
};
const [user, setUser] = useState(firstUser);
return (
<div>
<h1>
{user.firstName} {user.lastName} - {user.admin ? "Admin" : "Usser"}
</h1>
<p>Email: {user.email}</p>
<p>
Location: {user.city}, {user.state}
</p>
<button
onClick={() => {
setUser({ ...user, admin: true });
}}
>
Make Admin
</button>
</div>
);
}
問題は以下の部分でsetUserの中でuserというオブジェクトを参照してスプレッド構文でマージしてそれを返しているのですが、...user
を書き忘れるとuserの情報はadmin: true
のみになってしまいます。
<button
onClick={() => {
setUser({ ...user, admin: true });
}}
>
もし以下のようにすると
<button
onClick={() => {
setUser({ admin: true });
}}
>
以下のようにadmin以外の情報がなくなってしまう
user = {
admin: true
}
ここでuseReducerを使用すると
export function User() {
const firstUser = {
id: "1234567",
firstName: "cawa",
lastName: "uchi",
city: "nasu",
state: "tochigi",
email: "hogehoge@gmail.com",
admin: false
};
const [user, setUser] = useReducer(
(user, newDetails) => ({ ...user, ...newDetails }),
firstUser
);
return (
<div>
<h1>
{user.firstName} {user.lastName} - {user.admin ? "Admin" : "Usser"}
</h1>
<p>Email: {user.email}</p>
<p>
Location: {user.city}, {user.state}
</p>
<button
onClick={() => {
setUser({ admin: true });
}}
>
Make Admin
</button>
</div>
);
}
setUserからスプレッド構文をなくすことができました
<button
onClick={() => {
setUser({ admin: true });
}}
>
実務でもどんどんuseReducerを使っていきたいと思いました!
参考
強くなりたい!!!!!