# Redux Toolkit
<style>
.abstract{
letter-spacing : 2px;
line-height : 28px;
text-align : justify;
}
.firstLine{
margin-left : 30px;
}
</style>
:::success
:bookmark: 書籤
[TOC]
:::
這篇是要來教如何使用**Redux Toolkit**,和以往不同,**Redux Toolkit**簡化了許多,在Toolkit還沒出來時,使用**Redux**來管理與操作**State**非常麻煩,今天的範例就簡單做個輸入資料然後登入的動作。
## 初始化
在開始之前先使用npm來安裝**Redux Toolkit**吧!
```javascript=
npm i react-redux @reduxjs/toolkit
```
使用**Redux Toolkit**之前,我們需要先做初始化的動作,我們先在***index.js***檔案做初始化,您也可選擇額外創建一個檔案,在裡面做初始化並**Export**。
***index.js***
```javascript=
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import { configureStore } from "@reduxjs/toolkit";
import { Provider } from "react-redux";
const store = configureStore({
reducer: {},
});
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById("root")
);
```
我們首先利用**configureStore**來建立**Store**,而**Store**內的**reducer**就是我們管理**State**的**Function**,這部份待會再來做定義。
跟以往一樣,我們需要一個**redux**的**Provider**來將**store**傳到子組件。
## 建立reducer
而現在可以先來創建我們的第一個**reducer**,在**src**目錄下新建一個資料夾,名為**reducers**,而繼續在該資料下創建新的檔案,名為**userSlice.js**。
***userSlice.js***
```javascript=
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
profile: {
name: "",
age: 0,
email: "",
login : false
},
};
const userSlice = createSlice({
name: "user",
initialState: initialState,
reducers: {
setLogin(state, action) {
const { name, age, email } = action.payload;
state.value = {
name,
age,
email,
isLogin: true,
};
},
setLogout(state, action) {
state.value = initialState;
},
},
});
export const { setLogin, setLogout } = userSlice.actions;
export default userSlice.reducer;
```
這邊有幾個部分要介紹
==name==:這個Slice的名稱,建議取跟檔名有關係的。
==initialState==:初始化的State,在該部分需要定義初始的State,如程式碼3~9行。
==reducers==:最重要的部分,在裡面我們可以定義改變State的function,而function內預設會帶入兩個參數,分別是**state**和**action**。
==state==:state為我們在initialState內定義的變數,我們可以直接透過物件訪問的方式來修改state,像是**state.name="wei"**。
==action==:如果有外部參數傳進來的話,我們可以利用action來取得,在action物件裡面還會有個payload,所以記得取得**外部參數**的時候要使用**action.payload**,也可以使用**解構**的方式。
建立完以後,在**index.js**的部分要改成以下:
```javascript=
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import { configureStore } from "@reduxjs/toolkit";
import { Provider } from "react-redux";
import userSlice from "./reducers/userSlice";
const store = configureStore({
reducer: {
user : userSlice
},
});
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById("root")
);
```
程式碼第十行的部分,要將剛剛定義的Slice加入到reducer內,而前面定義的名稱很重要,後續要將State取出必須先訪問該名稱才能取得資料。
## Components
我們先來建立等等會用到的component,會有3個。
在Profile中,先將**LoginComponent**和**NotLoginComponent**引入,待會我們會利用**useSelector hook**,將原先定義的**State**內的**login**取出,來判斷現在使用者登入狀態還是未登入狀態。
***Profile.js***
```javascript=
import React from "react";
import LoginComponent from "./LoginComponent";
import NotLoginComponent from "./NotLoginComponent";
const Profile = () => {
return (
<div>
<h1>Profile Info</h1>
<LoginComponent />
<NotLoginComponent />
</div>
);
};
export default Profile;
```
**LoginComponent**中,我們會將使用者輸入的資料顯示,並且有一個登出的按鈕,按出之後會再回到輸入資料的畫面。
***LoginComponent.js***
```javascript=
import React from "react";
const LoginComponent = () => {
const handleLogout = () => {};
return (
<div>
<p>Name:</p>
<p>Age:</p>
<p>Email:</p>
<button onClick={handleLogout}>Logout</button>
</div>
);
};
export default LoginComponent;
```
**NotLoginComponent**中,會要求使用者輸入資料,按下登入後會將**State login**改為**true**並看到輸入的資料。
***NotLoginComponent.js***
```javascript=
import React from "react";
const NotLoginComponent = () => {
const handleLogin = () => {};
return (
<div>
<label htmlFor="name">Name:</label>
<input type="text" placeholder="name" name="name" id="name"/>
<br />
<label htmlFor="age">Age:</label>
<input type="text" placeholder="age" name="age" id="age" />
<br />
<label htmlFor="email">Email:</label>
<input type="text" placeholder="email" name="email" id="email" />
<br />
<button onClick={handleLogin}>Login</button>
</div>
);
};
export default NotLoginComponent;
```
## useSelector
當我們需要取出**store**內的**State**時,需要使用**useSelector**來幫助我們取出**State**的值,現在先來看怎麼取得我們最初定義的**State**吧!
現在來利用**userSelector**,將**State**取出,並判斷使用者是否為登入狀態。
***Profile.js***
```javascript=
import React from "react";
import { useSelector } from "react-redux";
import LoginComponent from "./LoginComponent";
import NotLoginComponent from "./NotLoginComponent";
const Profile = () => {
const state = useSelector((state) => state.user);
return (
<div>
<h1>Profile Info</h1>
{state.isLogin ? <LoginComponent /> : <NotLoginComponent />}
</div>
);
};
export default Profile;
```
## useDispatch
而當我們要使用reducer時,需要使用useDispatch來幫助我們呼叫reducer內的function,在本次範例中只有定義兩個function,分別是 **setLogin** 和 **setLogout**,現在就來使用看看吧~
因為我不想要在輸入input時不斷**Rerender**,所以我會選擇使用**useRef**而不是**useState**。
這邊使用了**useDispatch**去改變**State**,所以**React**會幫我們**Rerender**。
***NotLoginComponent.js***
```javascript=
import React, { useRef } from "react";
import { useDispatch } from "react-redux";
import { setLogin } from "../reducers/userSlice";
const NotLoginComponent = () => {
const dispatch = useDispatch();
const nameRef = useRef();
const ageRef = useRef();
const emailRef = useRef();
const handleLogin = () => {
const nameValue = nameRef.current.value;
const ageValue = ageRef.current.value;
const emailValue = emailRef.current.value;
dispatch(setLogin({name: nameValue,age: ageValue,email: emailValue}));
};
return (
<div>
<label htmlFor="name">Name:</label>
<input type="text" placeholder="name" name="name" id="name" ref={nameRef}/>
<br />
<label htmlFor="age">Age:</label>
<input type="text" placeholder="age" name="age" id="age" ref={ageRef}/>
<br />
<label htmlFor="email">Email:</label>
<input type="text" placeholder="email" name="email" id="email" ref={emailRef}/>
<br />
<button onClick={handleLogin}>Login</button>
</div>
);
};
export default NotLoginComponent;
```
***LoginComponent.js***
```javascript=
import React from "react";
import { useDispatch } from "react-redux";
import { useSelector } from "react-redux";
import { setLogout } from "../reducers/userSlice";
const LoginComponent = () => {
const dispatch = useDispatch();
const state = useSelector((state) => state.user);
const handleLogout = () => {
dispatch(setLogout());
};
return (
<div>
<p>Name:{state.name}</p>
<p>Age:{state.age}</p>
<p>Email:{state.email}</p>
<button onClick={handleLogout}>Logout</button>
</div>
);
};
export default LoginComponent;
```
## Action Creator
因為在reducer內的function不能使用async/await,也就是不能處理非同步事件,所以在這邊我們可以來自訂自己的Action。
我們修改一下上面的**userSlice.js**,透過**action creator**來取得**外部api的資料**。
API 網址:https://randomuser.me/api/
***userSlice.js***
```javascript=
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
user: {},
};
const userSlice = createSlice({
name: "user",
initialState,
reducers: {
setUserProfile(state, action) {
state.user = action.payload.results[0];
},
},
});
//因為reducers內不能使用非同步 所以必須建立自己的action並dispatch資料進去
export const fetchUserProfile = () => {
return async (dispatch) => {
const fetchData = async () => {
const response = await fetch("https://randomuser.me/api/");
if (!response.ok) {
throw new Error("Fetch Fail!!!");
}
const data = await response.json();
return data;
};
try {
const userData = await fetchData();
dispatch(userAction.setUserProfile(userData));
} catch (error) {}
};
};
export const userAction = userSlice.actions;
export default userSlice.reducer;
```
而在**App.js**內只要**dispatch**我們的**action creator**即可!
***App.js***
```javascript=
import { useEffect } from "react";
import { useDispatch } from "react-redux";
import "./App.css";
import Profile from "./component/Profile";
import { fetchUserProfile } from "./store/user/userSlice";
function App() {
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchUserProfile());
}, [dispatch]);
return (
<div className="App">
<Profile />
</div>
);
}
export default App;
```
***Profile.js***
```javascript=
import React from "react";
import { useSelector } from "react-redux";
const Profile = () => {
const user = useSelector((state) => state.user.user);
const UserProfile = () => (
<>
<p>name : {user.name.first}</p>
<p>gender : {user.gender}</p>
<p>phone : {user.phone}</p>
</>
);
return (
<div>
<h1>User Profile</h1>
{user?.name ? <UserProfile /> : null}
</div>
);
};
export default Profile;
```