はじめに
本記事は、「Redux Toolkit Tutorialを通してreduxを学ぶ #2」の続きです。
・Reduxを使用した場合
・Reduxを使用しなかった場合
の両方のコードを比較し、最後にreduxを活用するメリットについて感じたことを記述します。
目次
- #3でやりたいこと
- Reduxを使用した場合の実装
- Redux使用有無を比較し感じたメリット
やりたいこと
#2で実装したソースを、Reduxを使用した場合の実装に変更していく
Reduxを使用した場合の実装
以降、#2で作成したサンプルプロジェクトを別名でコピーして、
コピー先プロジェクトに編集を加えていく。
$cp -R sample_ts_project_noRedux sample_ts_project_withRedux
まず必要なパッケージをインストールする
$ npm install --sav-dev @reduxjs/toolkit react-redux
srcフォルダ配下にstore.tsxを作成する。
$ touch src/store.tsx
src/store.tsx
import { PayloadAction, configureStore, createSlice } from "@reduxjs/toolkit";
interface UserStateValue {
username: string;
}
interface UserState {
value: UserStateValue;
}
const initialState = { value: {username: ""} } as UserState;
const userSlice = createSlice({
name: "user",
initialState,
reducers: {
login: (state: UserState, action: PayloadAction<UserStateValue> ) => {
state.value = action.payload;
},
logout: (state: UserState) =>{
// state must be immutable so this won't work
// state = initialState;
// do this way
state.value = initialState.value;
},
},
});
export const { login, logout } = userSlice.actions;
export const store = configureStore({
reducer: {
user: userSlice.reducer,
},
});
App.txsからpropsで全ての子コンポーネントにステートを渡していた部分の記述が不要になる。
その代わりに、Providerタグで要素を囲む。
components/App.tsx
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
import Home from "./Home";
import Login from "./Login";
import Contact from "./Contact";
- import { useState } from "react";
+ import { Provider } from "react-redux";
+ import { store } from "../store";
function App() {
- const [newUsername, setNewUsername] = useState<string>("");
- function updateUsername(name: string){
- setNewUsername(name);
- }
return (
<div className="App">
+ <Provider store={store}>
<Router>
<Link to="/">Home </Link>
<Link to="/login">Login </Link>
<Link to="/contact">Contact </Link>
<Routes>
- <Route path="/" element={<Home username={newUsername} />} />
+ <Route path="/" element={<Home />} />
- <Route path="/login" element={<Login username={newUsername} onUpdate={updateUsername}/>} />
+ <Route path="/login" element={<Login />} />
- <Route path="/contact" element={<Contact username={newUsername} />} />
+ <Route path="/contact" element={<Contact />} />
</Routes>
</Router>
+ </Provider>
</div>
);
}
export default App;
以下メソッドを用いることで、グローバルなステートを管理できる。
useDispatch:ステートを更新する
useSelector:ステートを取得する
components/Login.tsx
import { useState } from "react";
+ import { login, logout } from "../store";
+ import { useDispatch, useSelector } from "react-redux";
import Header from "./Header";
import Footer from "./Footer";
- interface Props{
- username: string;
- onUpdate: (name:string) => void;
- }
- function Loign(props: Props) {
+ function Loign() {
const [inputUsername, setInputUsername] = useState<string>("");
+ const dispatch = useDispatch();
+ const username = useSelector((state: any) => state.user.value.username);
+ console.log("usename: " +username);
- console.log("usename: " +props.username);
function handleLogin(event:any) {
event.preventDefault();
- props.onUpdate(inputUsername);
+ dispatch(login({username: inputUsername}));
setInputUsername("");
}
function handleLogout(event:any){
event.preventDefault();
- props.onUpdate("");
+ dispatch(logout());
setInputUsername("");
}
return (
<div>
<Header />
<div className="login">
- {props.username}
+ {username}
<form className="login">
<input
name="loginV"
placeholder="loginPH"
value={inputUsername}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
setInputUsername(e.target.value);
}}
/>
<button onClick={handleLogin}> Submit login</button>
<button onClick={handleLogout}> Logout</button>
</form>
</div>
<Footer />
</div>
);
}
export default Loign;
components/Contact.tsx
import Header from "./Header";
import Footer from "./Footer";
+ import { useSelector } from "react-redux";
- interface Props{
- username: string;
- }
- function Contact(props: Props) {
+ function Contact() {
+ const username = useSelector((state: any) => state.user.value.username);
return (
<div>
<Header />
- <div className="contact">Contact {props.username}</div>
+ <div className="contact">Contact {username}</div>
<Footer />
</div>
);
}
export default Contact;
components/Home.tsx
import Header from "./Header";
import Footer from "./Footer";
+ import { useSelector } from "react-redux";
- interface Props{
- username: string;
- }
- function Home(props:Props) {
+ function Home() {
const username = useSelector((state: any) => state.user.value.username);
return (
<div>
<Header/>
- <div className="home"> Home route {props.username}</div>
+ <div className="home"> Home route {username}</div>
<Footer/>
</div>
);
}
export default Home;
Redux使用有無を比較し感じたメリット
- コンポーネント間のステートの受け渡しが容易になる
- 親子関係ではない場合など
- useStateで管理するステートがコンポーネント内で完結する
- コンポーネント内にステートを追加した時などに他のコンポーネントへの副作用がない - コードがシンプルになる
- 親子関係だが、親と通い孫コンポーネントだけでステートを使いたい場合など
(バケツリレーみたいにpropsを渡さなくていい)
簡単なサンプルプロジェクトで試してみたが、なんとなく便利なものなんだなということは理解できた。
今後はAPI呼び出しなど、より発展的な使い方も勉強していこうと思う。