跳至主要内容

[react] React Redux Toolkit 教學

src
| App.js
| index.js
|
└─── components
│ │ Login.js
│ │ NotLogin.js
│ │ Profile.js

└─── reducers
│ userSlice.js

影片連結

這篇是要來教如何使用 Redux Toolkit ,和以往不同,Redux Toolkit 簡化了許多,在 Toolkit 還沒出來時,使用 Redux 來管理與操作 state 非常麻煩,今天的範例就簡單做個輸入資料然後登入的動作。

初始化

在開始之前先使用 npm 來安裝Redux Toolkit吧!

npm i react-redux @reduxjs/toolkit

使用 Redux Toolkit 之前,我們需要先做初始化的動作,我們先在 index.js 檔案做初始化,您也可選擇額外創建一個檔案,在裡面做初始化並 export

index.js
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就是我們管理 statefunction ,這部份待會再來做定義。

跟以往一樣,我們需要一個 reduxProvider 來將 store 傳遞到子組件。

建立 reducer

而現在可以先來創建我們的第一個 reducer ,在 src 目錄下新建一個資料夾,名為 reducers ,而繼續在該資料下創建新的檔案,名為userSlice.js

userSlice.js
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 內預設會帶入兩個參數,分別是 stateaction

  • state:state 為我們在 initialState 內定義的變數,我們可以直接透過物件訪問的方式來修改 state,像是 state.name="wei"

  • action:如果有外部參數傳進來的話,我們可以利用 action 來取得,在 action 物件裡面還會有個 payload,所以記得取得外部參數的時候要使用action.payload,也可以使用解構的方式。

建立完以後,在 index.js 的部分要改成以下:

index.js
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 中,先將 LoginNotLogin 引入,待會我們會利用 useSelector hook ,將原先定義的 state 內的 login 取出,來判斷現在使用者登入狀態還是未登入狀態。

Profile.js
import React from "react";import Login from "./Login";import NotLogin from "./NotLogin";const Profile = () => {  return (    <div>      <h1>Profile Info</h1>      <Login />      <NotLogin />    </div>  );};export default Profile;

Login 中,我們會將使用者輸入的資料顯示,並且有一個登出的按鈕,按出之後會再回到輸入資料的畫面。

Login.js
import React from "react";const Login = () => {  const handleLogout = () => {};  return (    <div>      <p>Name:</p>      <p>Age:</p>      <p>Email:</p>      <button onClick={handleLogout}>Logout</button>    </div>  );};export default Login;

NotLogin 中,會要求使用者輸入資料,按下登入後會將 state login 改為 true 並看到輸入的資料。

NotLogin.js
import React from "react";const NotLogin = () => {  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 NotLogin;

useSelector

當我們需要取出 store 內的 state 時,需要使用 useSelector 來幫助我們取出 state 的值,現在先來看怎麼取得我們最初定義的 state 吧!

現在來利用 userSelector ,將 state 取出,並判斷使用者是否為登入狀態。

Profile.js
import React from "react";import { useSelector } from "react-redux";import Login from "./Login";import NotLogin from "./NotLogin";const Profile = () => {  const state = useSelector((state) => state.user);  return (    <div>      <h1>Profile Info</h1>      {state.isLogin ? <Login /> : <NotLogin />}    </div>  );};export default Profile;

useDispatch

而當我們要使用 reducer 時,需要使用 useDispatch 來幫助我們呼叫 reducer 內的 function,在本次範例中只有定義兩個 function,分別是 setLoginsetLogout,現在就來使用看看吧~

因為我不想要在輸入 input 時不斷 Rerender,所以我會選擇使用 useRef 而不是 useState

這邊使用了 useDispatch 去改變 state ,所以React會幫我們 Rerender

NotLogin.js
import React, { useRef } from "react";import { useDispatch } from "react-redux";import { setLogin } from "../reducers/userSlice";const NotLogin = () => {  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 NotLogin;
import React from "react";
import { useDispatch } from "react-redux";
import { useSelector } from "react-redux";
import { setLogout } from "../reducers/userSlice";

const Login = () => {
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 Login;

Action Creator

因為在 reducer 內的 function 不能使用 async/await,也就是不能處理非同步事件,所以在這邊我們可以來自訂自己的 action。

我們修改一下上面的 userSlice.js ,透過 action creator 來取得外部 api 的資料

API 網址:https://randomuser.me/api/

userSlice.js
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
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
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;