λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°

πŸ’¬/γ…γ……γ…Œγ…‹γ…γ…… μ±Œλ¦°μ§€

42일차

πŸ₯Έ

42일차

 

Part 11. Redux 둜 μƒνƒœκ΄€λ¦¬ν•˜κΈ°

Ch 2. Redux Advanced (1)

Ch 3. Redux Advanced (2)

 


 

 

Ch 2. Redux Advanced

 

 

 πŸ”— Async Action with Redux  (not use 미듀웨어)

 

API ν˜ΈμΆœμ€ 보톡 componentDidMount μ‹œμ μ— ν•΄μ•Ό ν•œλ‹€.

 

$ npm i axios

 

// ./redux/action.js

...

// users

// κΉƒν—™ API ν˜ΈμΆœμ„ μ‹œμž‘ν•˜λŠ” 것을 의미
export const GET_USERS_START = "GET_USERS_START";

// κΉƒν—™ API ν˜ΈμΆœμ— λŒ€ν•œ 응닡이 μ„±κ³΅μ μœΌλ‘œ λŒμ•„μ˜¨ 경우
export const GET_USERS_SUCCESS = "GET_USERS_SUCCESS";

// κΉƒν—™ API ν˜ΈμΆœμ— λŒ€ν•œ 응닡이 μ‹€νŒ¨ν•œ 경우
export const GET_USERS_FAIL = "GET_USERS_FAIL";

export function getUsersStart() {
  return {
    type: GET_USERS_START,
  };
}

export function getUsersSuccess(data) {
  return {
    type: GET_USERS_SUCCESS,
    data,
  };
}

export function getUsersFali(error) {
  return {
    type: GET_USERS_FAIL,
    error,
  };
}
// ./redux/reducers/users.js

import { GET_USERS_FAIL, GET_USERS_START, GET_USERS_SUCCESS } from "../actions";

const initialState = { loading: false, data: [], error: null };

export default function users(state = initialState, action) {
  if (action.type === GET_USERS_START) {
    return {
      ...state,
      loading: true,
      error: null,
    };
  }

  if (action.type === GET_USERS_SUCCESS) {
    return {
      ...state,
      loading: false,
      data: action.data,
    };
  }

  if (action.type === GET_USERS_FAIL) {
    return {
      ...state,
      loading: false,
      error: action.error,
    };
  }

  return state;
}
// ./redux/reducers/reducer.js

import { combineReducers } from "redux";
import todos from "./todos";
import filter from "./filter";
import users from "./users";

const reducer = combineReducers({
  todos,
  filter,
  users,
});

export default reducer;

 

// ./containers/UserListContainer.jsx

import { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import UserList from "../components/UserList";
import { getUsersFali, getUsersStart, getUsersSuccess } from "../redux/actions";
import axios from "axios";

export default function UserListContainer() {
  const users = useSelector((state) => state.users.data);
  const dispatch = useDispatch();

  const getUsers = useCallback(async () => {
    try {
      dispatch(getUsersStart());
      const res = await axios.get("https://api.github.com/users");
      dispatch(getUsersSuccess(res.data));
    } catch (error) {
      dispatch(getUsersFali(error));
    }
  }, [dispatch]);

  return <UserList users={users} getUsers={getUsers} />;
}
// ./components/UserList.jsx

import { useEffect } from "react";

export default function UserList({ users, getUsers }) {
  useEffect(() => {
    getUsers();
  }, [getUsers]);

  if (users.length === 0) {
    return <p>ν˜„μž¬ μœ μ € 정보 μ—†μŒ</p>;
  }

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.login}</li>
      ))}
    </ul>
  );
}

 

 

 πŸ”— λ¦¬λ•μŠ€ 미듀웨어 

 

https://redux.js.org/tutorials/fundamentals/part-4-store#middleware

 

Redux Fundamentals, Part 4: Store | Redux

The official Redux Fundamentals tutorial: learn how to create and use a Redux store

redux.js.org

 

 

  • 미듀웨어가 "λ””μŠ€νŒ¨μΉ˜" μ˜ μ•žλ’€μ— μ½”λ“œλ₯Ό μΆ”κ°€ν• μˆ˜ 있게 ν•΄μ€λ‹ˆλ‹€.
  • 미듀웨어가 μ—¬λŸ¬κ°œλ©΄ 미듀웨어가 "순차적으둜" μ‹€ν–‰λ©λ‹ˆλ‹€.
  • 두 단계가 μžˆμŠ΅λ‹ˆλ‹€.
    • μŠ€ν† μ–΄λ₯Ό λ§Œλ“€λ•Œ, 미듀웨어λ₯Ό μ„€μ •ν•˜λŠ” λΆ€λΆ„
      • {createStore, applyMiddleware} from redux
    • λ””μŠ€νŒ¨μΉ˜κ°€ ν˜ΈμΆœλ λ•Œ μ‹€μ œλ‘œ 미듀웨어λ₯Ό ν†΅κ³Όν•˜λŠ” λΆ€λΆ„
  • ​dispatch λ©”μ†Œλ“œλ₯Ό 톡해 store둜 κ°€κ³  μžˆλŠ” μ•‘μ…˜μ„ κ°€λ‘œμ±„λŠ” μ½”λ“œ

 

import { applyMiddleware } from "redux";

 

// ./redux/stroe.js

import { applyMiddleware, createStore } from "redux";
import reducer from "./reducers/reducer";

function middleware1(store) {
  console.log("middleware1", 0);
  return (next) => {
    console.log("middleware1", 1, next);
    return (action) => {
      console.log("middleware1", 2);
      const returnValue = next(action);
      console.log("middleware1", 3);
      return returnValue;
    };
  };
}

function middleware2(store) {
  console.log("middleware2", 0);
  return (next) => {
    console.log("middleware2", 1, next);
    return (action) => {
      console.log("middleware2", 2);
      const returnValue = next(action);
      console.log("middleware2", 3);
      return returnValue;
    };
  };
}

const store = createStore(reducer, applyMiddleware(middleware1, middleware2));

export default store;

 

 

 

 πŸ”— redux-devtools 

 

$ npm i redux-devtools-extension -D
import { composeWithDevTools } from "redux-devtools-extension";

 

// ./redux/store.js

import { applyMiddleware, createStore } from "redux";
import reducer from "./reducers/reducer";
import { composeWithDevTools } from "redux-devtools-extension";

const store = createStore(reducer, composeWithDevTools(applyMiddleware()));

export default store;

 

 

 πŸ”— redux-thunk ⭐ 

 

https://github.com/reduxjs/redux-thunk

 

GitHub - reduxjs/redux-thunk: Thunk middleware for Redux

Thunk middleware for Redux. Contribute to reduxjs/redux-thunk development by creating an account on GitHub.

github.com

 

  • λ¦¬λ•μŠ€ 미듀웨어
  • λ¦¬λ•μŠ€λ₯Ό λ§Œλ“  μ‚¬λžŒμ΄ λ§Œλ“€μ—ˆμŒ. (Dan)
  • λ¦¬λ•μŠ€μ—μ„œ 비동기 처리λ₯Ό μœ„ν•œ 라이브러리
  • μ•‘μ…˜ μƒμ„±μžλ₯Ό ν™œμš©ν•˜μ—¬ 비동기 처리
  • μ•‘μ…˜ μƒμ„±μžκ°€ μ•‘μ…˜μ„ λ¦¬ν„΄ν•˜μ§€ μ•Šκ³ , ν•¨μˆ˜λ₯Ό 리턴함.

 

$ npm i redux-thunk
import thunk from "redux-thunk";

 

// ./redux/store.js

import { applyMiddleware, createStore } from "redux";
import reducer from "./reducers/reducer";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";

const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk)));

export default store;
// ./redux/action.js

import axios from "axios";

...

export function getUsersThunk() {
  return async (dispatch) => {
    try {
      dispatch(getUsersStart());
      const res = await axios.get("https://api.github.com/users");
      dispatch(getUsersSuccess(res.data));
    } catch (error) {
      dispatch(getUsersFali(error));
    }
  };
}
// ./container/UserListContainer.jsx

import { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import UserList from "../components/UserList";
import { getUsersThunk } from "../redux/actions";

export default function UserListContainer() {
  const users = useSelector((state) => state.users.data);
  const dispatch = useDispatch();

  // const getUsers = useCallback(async () => {
  //   try {
  //     dispatch(getUsersStart());
  //     const res = await axios.get("https://api.github.com/users");
  //     dispatch(getUsersSuccess(res.data));
  //   } catch (error) {
  //     dispatch(getUsersFali(error));
  //   }
  // }, [dispatch]);

  const getUsers = useCallback(() => {
    dispatch(getUsersThunk());
  }, [dispatch]);

  return <UserList users={users} getUsers={getUsers} />;
}

 

 

 πŸ”— redux-promise-middleware 

 

https://github.com/pburtchaell/redux-promise-middleware

 

GitHub - pburtchaell/redux-promise-middleware: Enables simple, yet robust handling of async action creators in Redux

Enables simple, yet robust handling of async action creators in Redux - GitHub - pburtchaell/redux-promise-middleware: Enables simple, yet robust handling of async action creators in Redux

github.com

 

$ npm i redux-promise-middleware
import promise from "redux-promise-middleware";

 

// ./redux/store.js

import { applyMiddleware, createStore } from "redux";
import reducer from "./reducers/reducer";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";
import promise from "redux-promise-middleware";

const store = createStore(
  reducer,
  composeWithDevTools(applyMiddleware(thunk, promise))
);

export default store;
// ./reudx/action.js

import axios from "axios";

...

const GET_USERS = "GET_USERS";

export const GET_USERS_PENDING = "GET_USERS_PENDING";
export const GET_USERS_FULFILLED = "GET_USERS_FULFILLED";
export const GET_USERS_REJECTED = "GET_USERS_REJECTED";

export function getUsersPromise() {
  return {
    type: GET_USERS,
    payload: async () => {
      const res = await axios.get("https://api.github.com/users");
      return res.data;
    },
  };
}
// ./redux/users.js

import {
  GET_USERS_FAIL,
  GET_USERS_FULFILLED,
  GET_USERS_PENDING,
  GET_USERS_REJECTED,
  GET_USERS_START,
  GET_USERS_SUCCESS,
} from "../actions";

const initialState = { loading: false, data: [], error: null };

export default function users(state = initialState, action) {
  if (action.type === GET_USERS_START || action.type === GET_USERS_PENDING) {
    return {
      ...state,
      loading: true,
      error: null,
    };
  }

  if (action.type === GET_USERS_SUCCESS) {
    return {
      ...state,
      loading: false,
      data: action.data,
    };
  }
  if (action.type === GET_USERS_FULFILLED) {
    return {
      ...state,
      loading: false,
      data: action.payload,
    };
  }

  if (action.type === GET_USERS_FAIL) {
    return {
      ...state,
      loading: false,
      error: action.error,
    };
  }
  if (action.type === GET_USERS_REJECTED) {
    return {
      ...state,
      loading: false,
      error: action.payload,
    };
  }

  return state;
}
// ./containers/UserListContainer.jsx

import { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import UserList from "../components/UserList";
import { getUsersPromise, getUsersThunk } from "../redux/actions";

export default function UserListContainer() {
  const users = useSelector((state) => state.users.data);
  const dispatch = useDispatch();

  // const getUsers = useCallback(async () => {
  //   try {
  //     dispatch(getUsersStart());
  //     const res = await axios.get("https://api.github.com/users");
  //     dispatch(getUsersSuccess(res.data));
  //   } catch (error) {
  //     dispatch(getUsersFali(error));
  //   }
  // }, [dispatch]);

  const getUsers = useCallback(() => {
    dispatch(getUsersPromise());
  }, [dispatch]);

  return <UserList users={users} getUsers={getUsers} />;
}

 

 

 πŸ”— Ducks Pattern  ⭐

 

https://github.com/erikras/ducks-modular-redux

 

GitHub - erikras/ducks-modular-redux: A proposal for bundling reducers, action types and actions when using Redux

A proposal for bundling reducers, action types and actions when using Redux - GitHub - erikras/ducks-modular-redux: A proposal for bundling reducers, action types and actions when using Redux

github.com

 

κ·œμΉ™

ν•˜λ‚˜μ˜ λͺ¨λ“ˆμ€...

  1. 항상 reducer()λž€ μ΄λ¦„μ˜ ν•¨μˆ˜λ₯Ό export default ν•΄μ•Όν•©λ‹ˆλ‹€.
  2. 항상 λͺ¨λ“ˆμ˜ action μƒμ„±μžλ“€μ„ ν•¨μˆ˜ν˜•νƒœλ‘œ export ν•΄μ•Όν•©λ‹ˆλ‹€.
  3. 항상 npm-module-or-app/reducer/ACTION_TYPE ν˜•νƒœμ˜ action νƒ€μž…μ„ κ°€μ Έμ•Όν•©λ‹ˆλ‹€.
  4. μ–΄μ©Œλ©΄ action νƒ€μž…λ“€μ„ UPPER_SNAKE_CASE둜 export ν•  수 μžˆμŠ΅λ‹ˆλ‹€. λ§Œμ•½, μ™ΈλΆ€ reducerκ°€ ν•΄λ‹Ή action듀이 λ°œμƒν•˜λŠ”μ§€ 계속 κΈ°λ‹€λ¦¬κ±°λ‚˜, μž¬μ‚¬μš©ν•  수 μžˆλŠ” 라이브러리둜 퍼블리싱할 κ²½μš°μ— 말이죠.

μž¬μ‚¬μš©κ°€λŠ₯ν•œ Redux 라이브러리 ν˜•νƒœλ‘œ κ³΅μœ ν•˜λŠ” {actionType, action, reducer} λ¬ΆμŒμ—λ„ μœ„ κ·œμΉ™μ„ μΆ”μ²œν•©λ‹ˆλ‹€.

 

// ./redux/modules/todos.js

// μ•‘μ…˜ νƒ€μž… μ •μ˜
export const ADD_TODO = "redux-start/todos/ADD_TODO";
export const COMPLETE_TODO = "redux-start/todos/COMPLETE_TODO";

// μ•‘μ…˜ 생성 ν•¨μˆ˜
// {type: ADD_TODO, text: '할일'}
export function addTodo(text) {
  return {
    type: ADD_TODO,
    text,
  };
}
// {type: COMPLETE_TODO, index: 3}
export function completoTodo(index) {
  return {
    type: COMPLETE_TODO,
    index,
  };
}

// μ΄ˆκΈ°κ°’
const initialState = [];

// λ¦¬λ“€μ„œ
export default function reducer(previousState = initialState, action) {
  ...
}
// ./redux/modules/filter.js

// μ•‘μ…˜ νƒ€μž… μ •μ˜
const SHOW_ALL = "redux-start/filter/SHOW_ALL";
const SHOW_COMPLETE = "redux-start/filter/SHOW_COMPLETE";

// μ•‘μ…˜ 생성 ν•¨μˆ˜
export function showAll() {
  return { type: SHOW_ALL };
}
export function showComplete() {
  return { type: SHOW_COMPLETE };
}

// μ΄ˆκΈ°κ°’
const initialState = "ALL";

// λ¦¬λ“€μ„œ
export default function reducer(previousState = initialState, action) {
  ...
}
// ./modules/users.js

import axios from "axios";

// μ•‘μ…˜ νƒ€μž… μ •μ˜

// κΉƒν—™ API ν˜ΈμΆœμ„ μ‹œμž‘ν•˜λŠ” 것을 의미
export const GET_USERS_START = "redux-start/users/GET_USERS_START";

// κΉƒν—™ API ν˜ΈμΆœμ— λŒ€ν•œ 응닡이 μ„±κ³΅μ μœΌλ‘œ λŒμ•„μ˜¨ 경우
export const GET_USERS_SUCCESS = "redux-start/users/GET_USERS_SUCCESS";

// κΉƒν—™ API ν˜ΈμΆœμ— λŒ€ν•œ 응닡이 μ‹€νŒ¨ν•œ 경우
export const GET_USERS_FAIL = "redux-start/users/GET_USERS_FAIL";

// redux-promise-middleware
const GET_USERS = "redux-start/users/GET_USERS";
export const GET_USERS_PENDING = "redux-start/users/GET_USERS_PENDING";
export const GET_USERS_FULFILLED = "redux-start/users/GET_USERS_FULFILLED";
export const GET_USERS_REJECTED = "redux-start/users/GET_USERS_REJECTED";

// μ•‘μ…˜ 생성 ν•¨μˆ˜
export function getUsersStart() {
  return {
    type: GET_USERS_START,
  };
}
export function getUsersSuccess(data) {
  return {
    type: GET_USERS_SUCCESS,
    data,
  };
}
export function getUsersFali(error) {
  return {
    type: GET_USERS_FAIL,
    error,
  };
}

// μ΄ˆκΈ°κ°’
const initialState = { loading: false, data: [], error: null };

// λ¦¬λ“€μ„œ
export default function reducer(state = initialState, action) {
  ...
}

// react-thunk
export function getUsersThunk() {
  return async (dispatch) => {
    try {
      dispatch(getUsersStart());
      const res = await axios.get("https://api.github.com/users");
      dispatch(getUsersSuccess(res.data));
    } catch (error) {
      dispatch(getUsersFali(error));
    }
  };
}

// redux-promise-middleware
export function getUsersPromise() {
  return {
    type: GET_USERS,
    payload: async () => {
      const res = await axios.get("https://api.github.com/users");
      return res.data;
    },
  };
}
// ./redux/modules/reducer.js

import { combineReducers } from "redux";
import todos from "./todos";
import filter from "./filter";
import users from "./users";

const reducer = combineReducers({
  todos,
  filter,
  users,
});

export default reducer;

 

 

 πŸ”— react-router-dom κ³Ό redux ν•¨κ»˜ μ“°κΈ° 

 

https://github.com/supasate/connected-react-router

 

GitHub - supasate/connected-react-router: A Redux binding for React Router v4

A Redux binding for React Router v4. Contribute to supasate/connected-react-router development by creating an account on GitHub.

github.com

 

$ npm i react-router-dom

 

// ./App.js

import "./App.css";
import { BrowserRouter, Route, Router, Routes } from "react-router-dom";
import Home from "./pages/Home";
import Todos from "./pages/Todos";
import Users from "./pages/Users";
import history from "./history";

function App() {
  return (
    <Router history={history}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/todos" element={<Todos />} />
        <Route path="/users" element={<Users />} />
      </Routes>
    </Router>
  );
}

export default App;
// ./pages/Home.jsx

import { Link } from "react-router-dom";

export default function Home() {
  return (
    <div>
      <h1>Home</h1>
      <ul>
        <li>
          <Link to="/todos">Todos</Link>
        </li>
        <li>
          <Link to="/users">Users</Link>
        </li>
      </ul>
    </div>
  );
}
// ./pages/Todos.jsx

import TodoListContainer from "../containers/TodoListContainer";
import TodoFormContainer from "../containers/TodoFormContainer";

export default function Todos() {
  return (
    <div>
      <TodoListContainer />
      <TodoFormContainer />
    </div>
  );
}
// ./pages/Users.jsx

import UserListContainer from "../containers/UserListContainer";

export default function Users() {
  return (
    <div>
      <UserListContainer />
    </div>
  );
}

 

// history.js

import { createBrowserHistory } from "history";

const history = createBrowserHistory();

export default history;

 

// ./redux/store.js

import { applyMiddleware, createStore } from "redux";
import reducer from "./modules/reducer";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";
import promise from "redux-promise-middleware";
import history from "../history";

const store = createStore(
  reducer,
  composeWithDevTools(
    applyMiddleware(thunk.withExtraArgument({ history }), promise)
  )
);

export default store;
// ./modules/users.js

...

function sleep(ms) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, ms);
  });
}

// react-thunk
export function getUsersThunk() {
  return async (dispatch, getState, { history }) => {
    try {
      console.log(history);
      dispatch(getUsersStart());
      const res = await axios.get("https://api.github.com/users");
      dispatch(getUsersSuccess(res.data));
      // sleep
      await sleep(2000);
      history.push("/");
    } catch (error) {
      dispatch(getUsersFali(error));
    }
  };
}

// redux-promise-middleware
...
}

 

  • κ°•ν•˜κ²Œ μ—°κ²°
$ npm i connected-react-router

 

// ./App.js

import "./App.css";
import { BrowserRouter, Route, Router, Routes } from "react-router-dom";
import Home from "./pages/Home";
import Todos from "./pages/Todos";
import Users from "./pages/Users";
import history from "./history";
import { ConnetedRouter } from "connected-react-router";

function App() {
  return (
    <ConnetedRouter history={history}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/todos" element={<Todos />} />
        <Route path="/users" element={<Users />} />
      </Routes>
    </ConnetedRouter>
  );
}

export default App;
// ./redux/reducer.js

import { combineReducers } from "redux";
import todos from "./todos";
import filter from "./filter";
import users from "./users";
import { connectRouter } from "conneted-react-router";
import history from "../../history";

const reducer = combineReducers({
  todos,
  filter,
  users,
  router: connectRouter(history),
});

export default reducer;
// ./redux/store.js

import { applyMiddleware, createStore } from "redux";
import reducer from "./modules/reducer";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";
import promise from "redux-promise-middleware";
import history from "../history";
import { routerMiddleware } from "conneted-react-router";

const store = createStore(
  reducer,
  composeWithDevTools(
    applyMiddleware(
      thunk.withExtraArgument({ history }),
      promise,
      routerMiddleware(history)
    )
  )
);

export default store;

 

// ./pages/Home.jsx

import { useDispatch } from "react-redux";
import { Link } from "react-router-dom";
import { push } from "connected-react-router";

export default function Home() {
  const dispatch = useDispatch();

  return (
    <div>
      <h1>Home</h1>
      <ul>
        <li>
          <Link to="/todos">Todos</Link>
        </li>
        <li>
          <Link to="/users">Users</Link>
        </li>
      </ul>
      <button onClick={click}>todo 둜 이동</button>
    </div>
  );

  function click() {
    dispatch(push("/todos"));
  }
}

 

 

 πŸ”— redux-saga : μ‚¬μ΄λ“œ μ΄νŽ™νŠΈ(비동기 둜직)λ₯Ό 컨트둀 ν•  수 μžˆλ‹€.

 

 

  • 미듀웨어 μž…λ‹ˆλ‹€.
  • μ œλ„ˆλ ˆμ΄ν„° 객체λ₯Ό λ§Œλ“€μ–΄ λ‚΄λŠ” μ œλ„€λ ˆμ΄ν„° 생성 ν•¨μˆ˜λ₯Ό μ΄μš©ν•©λ‹ˆλ‹€.​
  • λ¦¬λ•μŠ€ 사가 미듀웨어λ₯Ό μ„€μ •ν•˜κ³ ,
  • λ‚΄κ°€ λ§Œλ“  사가 ν•¨μˆ˜λ₯Ό λ“±λ‘ν•œ ν›„
  • 사가 미듀웨어λ₯Ό μ‹€ν–‰ν•©λ‹ˆλ‹€.
  • 그리고 λ“±λ‘λœ 사가 ν•¨μˆ˜λ₯Ό μ‹€ν–‰ν•  μ•‘μ…˜μ„ λ””μŠ€νŒ¨μΉ˜ν•˜λ©΄ λ©λ‹ˆλ‹€.

 

 

$ npm i redux-saga

 

// ./redux/modules/users.js

import axios from "axios";
import { call, delay, put, takeEvery } from "redux-saga/effects";
import { push } from "connected-react-router";

...

// redux-saga

const GET_USERS_SAGA_START = "GET_USERS_SAGA_START";

// ⭐ λ¦¬λ•μŠ€ 사가 미듀웨어λ₯Ό μ„€μ •ν•˜κ³ ,
function* getUsersSaga(action) {
  try {
    yield put(getUsersStart);
    const res = yield call(axios.get, "https://api.github.com/users");
    yield put(getUsersSuccess(res.data));
    // sleep
    yield delay(2000);
    // history.push("/");
    yield put(push("/"));
  } catch (error) {
    yield put(getUsersFali(error));
  }
}

export function getUsersSagaStart() {
  return {
    type: GET_USERS_SAGA_START,
  };
}

// ⭐ λ‚΄κ°€ λ§Œλ“  사가 ν•¨μˆ˜λ₯Ό λ“±λ‘ν•œ ν›„
export function* usersSaga() {
  yield takeEvery(GET_USERS_SAGA_START, getUsersSaga);
}
// ./redux/modules/rootSaga.js

import { all } from "redux-saga/effects";
import { usersSaga } from "./users";

export default function* rootSaga() {
  yield all([usersSaga()]);
}
// ./redux/store.js

import { applyMiddleware, createStore } from "redux";
import reducer from "./modules/reducer";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";
import promise from "redux-promise-middleware";
import history from "../history";
import { routerMiddleware } from "conneted-react-router";
import createSagaMiddleware from "redux-saga";
import rootSaga from "./modules/rootSaga";

const sagaMiddleware = createSagaMiddleware();

const store = createStore(
  reducer,
  composeWithDevTools(
    applyMiddleware(
      thunk.withExtraArgument({ history }),
      promise,
      routerMiddleware(history),
      sagaMiddleware
    )
  )
);

// ⭐ 사가 미듀웨어λ₯Ό μ‹€ν–‰ν•©λ‹ˆλ‹€.
sagaMiddleware.run(rootSaga);

export default store;
// ./containers/UserListContainer.jsx

import { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import UserList from "../components/UserList";
import { getUsersSagaStart, getUsersThunk } from "../redux/modules/users";

export default function UserListContainer() {
  const users = useSelector((state) => state.users.data);
  const dispatch = useDispatch();

  // const getUsers = useCallback(async () => {
  //   try {
  //     dispatch(getUsersStart());
  //     const res = await axios.get("https://api.github.com/users");
  //     dispatch(getUsersSuccess(res.data));
  //   } catch (error) {
  //     dispatch(getUsersFali(error));
  //   }
  // }, [dispatch]);

  // ⭐ 그리고 λ“±λ‘λœ 사가 ν•¨μˆ˜λ₯Ό μ‹€ν–‰ν•  μ•‘μ…˜μ„ λ””μŠ€νŒ¨μΉ˜ν•˜λ©΄ λ©λ‹ˆλ‹€.
  const getUsers = useCallback(() => {
    dispatch(getUsersSagaStart());
  }, [dispatch]);

  return <UserList users={users} getUsers={getUsers} />;
}

 

 

 πŸ”— redux-actions : Ducks Pattern 을 μ‰½κ²Œ κ΅¬ν˜„ν•  수 μžˆλ„λ‘ λ„μ™€μ£ΌλŠ” 라이브러리

 

https://github.com/redux-utilities/redux-actions

 

GitHub - redux-utilities/redux-actions: Flux Standard Action utilities for Redux.

Flux Standard Action utilities for Redux. Contribute to redux-utilities/redux-actions development by creating an account on GitHub.

github.com

https://redux-actions.js.org/api

 

API Reference - redux-actions

 

redux-actions.js.org

 

$ npm i redux-actions

 

import { createActions, handleActions } from "redux-actions";

// μ•‘μ…˜ νƒ€μž… μ •μ˜ + μ•‘μ…˜ 생성 ν•¨μˆ˜
export const { showAll, showComplete } = createActions(
  "SHOW_ALL",
  "SHOW_COMPLETE",
  {
    prefix: "redux-start/filter",
  }
);

// μ΄ˆκΈ°κ°’
const initialState = "ALL";

// λ¦¬λ“€μ„œ
const reducer = handleActions(
  {
    SHOW_ALL: (state, actions) => "ALL",
    SHOW_COMPLETE: (state, actions) => "COMPLETE",
  },
  initialState,
  { prefix: "redux-start-filter" }
);

export default reducer;

 

'πŸ’¬ > γ…γ……γ…Œγ…‹γ…γ…… μ±Œλ¦°μ§€' μΉ΄ν…Œκ³ λ¦¬μ˜ λ‹€λ₯Έ κΈ€

45일차  (0) 2022.03.28
43, 44일차  (0) 2022.03.26
41일차  (0) 2022.03.25
useMemo 와 useCallback 은 무엇이 λ‹€λ₯Έ 것인가......  (0) 2022.03.25
40일차  (0) 2022.03.24