[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 。
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。
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 的部分要改成以下:
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 中,先將 Login 和 NotLogin 引入,待會我們會利用 useSelector hook ,將原先定義的 state 內的 login 取出,來判斷現在使用者登入狀態還是未登入狀態。
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 中,我們會將使用者輸入的資料顯示,並且有一個登出的按鈕,按出之後會再回到輸入資料的畫面。
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 並看到輸入的資料。
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 取出,並判斷使用者是否為登入狀態。
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,分別是 setLogin 和 setLogout,現在就來使用看看吧~
因為我不想要在輸入 input 時不斷 Rerender,所以我會選擇使用 useRef 而不是 useState。
這邊使用了 useDispatch 去改變 state ,所以React會幫我們 Rerender 。
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/
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 即可!
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;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;