# 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; ```