/movie_app_2020-2021

๐Ÿ’ป React, ์˜ํ™” ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ

Primary LanguageJavaScript

React JS Fundamental Course 2020 > 2021 ๋ฆฌ๋‰ด์–ผ ๐ŸŒŸ

gh-page๋กœ ๊ฒฐ๊ณผ๋ฌผ ํ™•์ธํ•˜๊ธฐ

result

changed ๋ฅผ ๋ˆ„๋ฅด๋ฉด redux-devTools๋กœ ํ•ด๋‹น ์•ก์…˜๋“ค์„ ๊ฐ์ง€ํ•˜๊ณ , ๋ณ€ํ™”๋œ state ๊ฐ’์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

2021.04.09 ์ธํ”ผ๋‹ˆํ‹ฐ ์Šคํฌ๋กค ๊ด€๋ จ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ โœ”๏ธ

infiniteResult


๐ŸŒŸ ๋ฆฌ๋‰ด์–ผ ๋ฐฉํ–ฅ

  • ํด๋ž˜์Šคํ˜• ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ๋กœ ๋ณ€๊ฒฝ โœ”๏ธ
  • ๋ฆฌ๋•์Šค์‚ฌ๊ฐ€๋ฅผ ํ†ตํ•œ ๋น„๋™๊ธฐ์ฒ˜๋ฆฌ โœ”๏ธ
  • ์Šคํ† ์–ด๋ฅผ ํ†ตํ•œ ์ƒํƒœ๊ด€๋ฆฌ โœ”๏ธ

๐ŸŒŸ ์ธํ”ผ๋‹ˆํ‹ฐ ์Šคํฌ๋กค ๊ธฐ๋Šฅ ์ถ”๊ฐ€ ์—ฌ๋ถ€ ํŒŒ์•… โœ”๏ธ

๐ŸŒŸ url์„ ํ†ตํ•ด ๋„˜๊ฒจ๋ฐ›๋Š” API๋ฅผ ์–ด๋–ป๊ฒŒ ๋‚˜๋ˆ ์ค„ ์ˆ˜ ์žˆ์„์ง€ โœ”

  • ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์ ์šฉ

๐ŸŒŸ ์™ธ๋ถ€ API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฆฌ์•กํŠธ๋กœ ๋ฐ์ดํ„ฐ ํ‘œํ˜„ํ•˜๊ธฐ

์™ธ๋ถ€ URL : https://yts.mx/api/v2/list_movies.json

ํ•„์š”ํ•œ ํฌ๋กฌ ํ™•์žฅ ํ”„๋กœ๊ทธ๋žจ: JSON VIEWR

๋ฆฌ๋•์Šค ๋ฐ๋ธŒํˆด์ฆˆ

์ถœ์ฒ˜: ๋‹ˆ์ฝ”์Œค์˜ ReactJS๋กœ ์˜ํ™” ์›น ์„œ๋น„์Šค ๋งŒ๋“ค๊ธฐ

๐ŸŒŸ OPEN API ๋ถˆ๋Ÿฌ์˜ค๊ธฐ

์šฐ๋ฆฌ๊ฐ€ ๋ถˆ๋Ÿฌ์˜ค๊ณ ์ž ํ•˜๋Š” ์˜คํ”ˆ API์˜ ๋ฐ์ดํ„ฐ ํ˜•์‹์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

moviesDataJson


์ฒซ ๋ฒˆ์งธ๋กœ ์˜คํ”ˆ ๋ฐ์ดํ„ฐ API๋ฅผ axios ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅด ์‚ฌ์šฉํ•˜์—ฌ ๋ถˆ๋Ÿฌ์˜ฌ ๋•Œ ์ค‘์š”ํ•œ ์ ์€ ๋ฐ์ดํ„ฐ๊ฐ€ ์–ด๋–ค ๊ตฌ์กฐ๋กœ ๋“ค์–ด ์žˆ๋Š” ์ง€๋ฅผ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

const getMovies = async () => {
    const response = await axios.get('https://yts-proxy.now.sh/list_movies.json?limit=30&&sort_by=download_count');
    console.log(response);
  };

ํ•ด๋‹น api ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„ ์ฝ˜์†”๋กœ๊ทธ๋กœ ์ฐ์œผ๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฐ๊ณผ๊ฐ’์„ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค. Object ๊ฐ์ฒด ์•ˆ์— ํ”„๋กœํผํ‹ฐ ํ˜•์‹์œผ๋กœ ๋“ค์–ด์žˆ๋‹ค.

objectTree

์šฐ๋ฆฌ๊ฐ€ ์ ‘๊ทผํ•ด์•ผ ํ•˜๋Š” movies ํ”„๋กœํผํ‹ฐ๋Š” ๊ฒฐ๊ณผ์ ์œผ๋กœ ๋ฐ›์•„์˜ค๋Š” data.data.movies ์•ˆ์— ๋“ค์–ด์žˆ๋Š” ๋ฐฐ์—ด๋กœ ์ด๋ฃจ์–ด์ง„ ๊ฐ์ฒด๋“ค์ด๋‹ค.

[0] : {id : 8462 , title, genres, ...}

๊ทธ์— ๋งž์ถฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์œ„ํ•ด์„œ๋Š” data.data.movies.id, ... ์™€ ๊ฐ™์ด ๋ถˆ๋Ÿฌ์™€์•ผ ํ•˜๋ฏ€๋กœ ๊ตฌ์กฐ ๋ถ„ํ•ด ํ• ๋‹น ๋ฌธ๋ฒ•์„ ํ†ตํ•ด movies ํ”„๋กœํผํ‹ฐ ์•ˆ์— ๋“ค์–ด์žˆ๋Š” ๋‚ด์šฉ์„ movies ๋ณ€์ˆ˜์— ๋‹ด์•„ ๊ฐ€์ ธ์˜จ๋‹ค.

   const {data: {data: { movies },},} = await axios.get('https://yts-proxy.now.sh/list_movies.json?limit=30&&sort_by=download_count');

๊ตฌ์กฐ๋ถ„ํ•ดํ• ๋‹น์„ ํ†ตํ•ด ์–ป์–ด์˜จ movies ๊ฐ์ฒด๋ฅผ ์ฐ์œผ๋ฉด ์šฐ๋ฆฌ๊ฐ€ ์–ป๊ณ ์ž ํ•˜๋Š” ๋ฐฐ์—ด ํ˜•์‹์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๊ฒŒ๋œ๋‹ค.

[0] : {id : 8462 , title: ..., genres: ..., ...}

[1] : {id : 8463 , title: ..., genres: ..., ...}

[2] : {id : 8464 , title: ..., genres: ..., ...}

[3] : {id : 8465 , title: ..., genres: ..., ...}

[4] : {id : 8466 , title: ..., genres: ..., ...}

์ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ์กด reducer์—์„œ intialState๋กœ ๊ด€๋ฆฌํ•˜๋Š” movies : [] ์— concat ํ•ด์ฃผ๋ฉด ๋ฆฌ๋“€์„œ๋ฅผ ์‚ฌ์šฉํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๋Š” ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค.


๐ŸŒŸ data flow

  1. ์ฒซ ๋ฒˆ์งธ๋กœ, ํ™”๋ฉด์„ ๋ณด์—ฌ์ฃผ๋Š” view ๋‹จ์—์„œ useEffect()๋ฅผ ํ†ตํ•ด ์ƒํ™ฉ์„ ๊ฐ์ง€ํ•œ๋‹ค. ์•„๋ฌด๊ฒƒ๋„ ์—†๋Š” ์ƒํƒœ์ผ ๋•Œ, dispatch๋ฅผ ํ†ตํ•ด LOAD_MOVIES_REQUEST ์•ก์…˜์„ ์‹คํ–‰ํ•œ๋‹ค.
// useEffect์— ์กฐ๊ฑด๋ฌธ์„ ํ†ตํ•ด ํŠน์ •ํ•œ ์ƒํ™ฉ์„ ์ •ํ•ด์ฃผ์ง€ ์•Š์•˜์œผ๋ฏ€๋กœ, ํ•ด๋‹น ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋ Œ๋”๋ง๋  ๋•Œ ์‹คํ–‰์‹œํ‚จ๋‹ค.
useEffect(() => {
    dispatch({
      type: LOAD_MOVIES_REQUEST,
    });
  }, [dispatch]);
  1. sagas/movies ์—์„œ LOAD_MOVIES_REQUEST ์„ ๊ฐ์ง€ํ•˜๋Š” ํ•จ์ˆ˜ watchLoadMovies()๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ํ•จ์ˆ˜ loadMovies()๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.
function* watchLoadMovies() {
  yield takeLatest(LOAD_MOVIES_REQUEST, loadMovies);
}

export default function* movieSaga() {
  yield all([fork(watchLoadMovies)]);
}
  1. loadMovies()๋ฅผ ์‹คํ–‰ํ–ˆ์„ ๋•Œ ์‹ค์ œ ์ž‘์—…์„ ์ง„ํ–‰ํ•˜๋Š” loadMoviesAPI()๋ฅผ ์‹คํ–‰ํ•œ ํ›„ ๊ฒฐ๊ณผ๊ฐ’์„ result์— ๋‹ด๋Š”๋‹ค.
async function loadMoviesAPI() {
  try {
    const {
      data: {
        data: { movies },
      },
    } = await axios.get('https://yts-proxy.now.sh/list_movies.json?limit=10&&sort_by=download_count');
    // ์—ฌ๊ธฐ์„œ ํ™•์ธ
    console.log(`movies ๊ฐ€์ ธ์™€์„œ ๊ตฌ์กฐ๋ถ„ํ•ดํ• ๋‹น์œผ๋กœ ๋‹ด๊ธฐ`);
    console.log(movies);
    return movies;
  } catch (err) {
    console.error(err);
    return;
  }
}

function* loadMovies() {
  const result = yield call(loadMoviesAPI); // loadMoviesAPI ํ•จ์ˆ˜ ํ˜ธ์ถœ์— ์˜ํ•ด return ๋œ movies ๊ฐ์ฒด
  // ์—ฌ๊ธฐ์„œ ์ž˜ ๋ถˆ๋Ÿฌ์™”๋Š”์ง€ ํ™•์ธ
  console.log('๋ฆฌํ„ด๋œ result ์ถœ๋ ฅ :');
  console.log(result);
  1. ์–ป์–ด์˜ค๋Š” ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ์— ๋”ฐ๋ผ LOAD_MOVIES_SUCCESS ๋˜๋Š” LOAD_MOVIES_FAILURE๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๊ณ  ๊ทธ์™€ ํ•จ๊ป˜ data๋ฅผ ์ „๋‹ฌํ•œ๋‹ค. (์—๋Ÿฌ์ผ ๊ฒฝ์šฐ error๋ฅผ ์ „๋‹ฌ)
function* loadMovies() {
  const result = yield call(loadMoviesAPI); // loadMoviesAPI ํ•จ์ˆ˜ ํ˜ธ์ถœ์— ์˜ํ•ด return ๋œ movies ๊ฐ์ฒด
  // ์—ฌ๊ธฐ์„œ ์ž˜ ๋ถˆ๋Ÿฌ์™”๋Š”์ง€ ํ™•์ธ
  console.log('๋ฆฌํ„ด๋œ result ์ถœ๋ ฅ :');
  console.log(result);
  try {
    console.log('saga loadMovies start!');
    yield put({
      type: LOAD_MOVIES_SUCCESS,
      data: result,
    });
  } catch (err) {
    console.error(err);
    yield put({
      type: LOAD_MOVIES_FAILURE,
      error: err.response.data,
    });
  }
}
  1. ๋ฆฌ๋“€์„œ์—์„œ ์‚ฌ๊ฐ€์—์„œ ์ „๋‹ฌ๋ฐ›์€ ์•ก์…˜๊ณผ, ์•ก์…˜์— ๋‹ด๊ธด ๋ฐ์ดํ„ฐ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.
const movies = (state = initialState, action) =>
  produce(state, (draft) => {
    switch (action.type) {
      case LOAD_MOVIES_REQUEST: {
        draft.loadMovieLoading = true;
        draft.loadMovieDone = false;
        break;
      }
      case LOAD_MOVIES_SUCCESS: {
        draft.movies = draft.movies.concat(action.data);  
        // action.data ์—๋Š” ์‚ฌ๊ฐ€์—์„œ ๋„˜๊ฒจ์ค€ result ๊ฐ’์ด ๋‹ด๊ฒจ์žˆ๋‹ค.
        draft.isLoading = false;  
        // ๋ฐ์ดํ„ฐ๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ๋„˜๊ฒจ๋ฐ›์„ ์‹œ, initialStae์—์„œ ๊ด€๋ฆฌํ•˜๋Š” isLoading์„ false ๋กœ ๋ฐ”๊ฟ”์ค€๋‹ค. ์ด ์กฐ๊ฑด์„ ํ†ตํ•ด movies๋ฅผ ๋งคํ•‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.
        draft.loadMovieDone = true;
        break;
      }
      case LOAD_MOVIES_FAILURE: {
        draft.loadMovieError = action.error;
        break;
      }
      default:
        return state;
    }
  });
  1. virtual DOM์ด ๋ฐ”๋€ state๋ฅผ ๊ฐ์ง€ํ•˜์—ฌ ๋ฆฌ๋ Œ๋”๋งํ•œ๋‹ค.
return (
    <section className="container">
      {isLoading ? (
        <div>๋กœ๋”ฉ์ค‘..</div>
      ) : (
        <div className="movies">
          {movies.map((movie) => (
            <Movie
              key={movie.id}
              id={movie.id}
              year={movie.year}
              title={movie.title}
              summary={movie.summary}
              poster={movie.medium_cover_image}
              genres={movie.genres}
            />
          ))}
        </div>
        // <div>๋กœ๋”ฉ ์™„๋ฃŒ!</div>
      )}
    </section>
  );

๐ŸŒŸ ์ฃผ์˜ํ•  ์ 

  1. redux-saga๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ณผ์ •์—์„œ ๋„˜๊ฒจ๋ฐ›์€ ๊ฒฐ๊ณผ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ์ „๋‹ฌํ•ด์•ผ ํ•˜๋Š”๋ฐ, ์„œ๋ฒ„์—์„œ ๋ฐ›์€ ๊ฒƒ์ฒ˜๋Ÿผ result.data๋กœ ์ฒ˜๋ฆฌํ•˜๋ฉด ๋ฆฌ๋“€์„œ์—์„œ concat ํ•  ๋•Œ undefined ๊ฐ€ ๋œฐ ์ˆ˜ ์žˆ๋‹ค.
function* loadMovies() {
  const result = yield call(loadMoviesAPI); 
  try {
    yield put({
      type: LOAD_MOVIES_SUCCESS,
      data: result, // ์—ฌ๊ธฐ์„œ result.data๋กœ ์“ฐ์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•œ๋‹ค.
    });
  } catch (err) {
    console.error(err);
    yield put({
      type: LOAD_MOVIES_FAILURE,
      error: err.response.data,
    });
  }
}
  1. redux-saga๋ฅผ ํ†ตํ•ด ์ฒ˜๋ฆฌํ•˜๋ฏ€๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ฌ ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ธ๋‹ค๊ฐ€ ๋‹ค์Œ ์ž‘์—…์„ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก js์˜ generator์˜ ๊ฐœ๋…์„ ์ž˜ ์•Œ๊ณ  ์žˆ์–ด์•ผ ํ•œ๋‹ค.

์‚ฌ๊ฐ€์—์„œ data๋ฅผ ๋„˜๊ฒจ์ฃผ์ง€ ์•Š๊ณ , ์•ก์…˜ ํƒ€์ž…์ธ LOAD_MOVIES_SUCCESS๋งŒ ๋ณด๋‚ด์ฃผ๊ณ  ๋ฆฌ๋“€์„œ์—์„œ ์ž„์˜ ํ•จ์ˆ˜ getMovies()๋ฅผ ์ฒ˜๋ฆฌํ•  ๊ฒฝ์šฐ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๋Œ€๋กœ ๋ฐ›์•„์˜ค์ง€ ๋ชป ํ•œ ์ฑ„๋กœ ๋„˜๊ฒจ์ค„ ์ˆ˜ ์žˆ๋‹ค.

// getMovies()์˜ ๋ฐ”๋€ ํ˜•ํƒœ, ๋ฆฌ๋•์Šค-์‚ฌ๊ฐ€ ์ฒ˜๋ฆฌ๊ณผ์ •์„ ๊ฑฐ์น˜๊ธฐ ๋•Œ๋ฌธ์— call() ํ•จ์ˆ˜๋กœ loadMoviesAPI()๋ฅผ ์‹คํ–‰ํ•œ ํ›„ ๋ฆฌํ„ด๋ฐ›์€ movies๋ฅผ loadMovies() ํ•จ์ˆ˜์—์„œ result๋กœ ๋ฐ›์•„์คฌ๋‹ค.

async function loadMoviesAPI() {
  try {
    const {
      data: {
        data: { movies },
      },
    } = await axios.get('https://yts-proxy.now.sh/list_movies.json?limit=10&&sort_by=download_count');
    // ์—ฌ๊ธฐ์„œ ํ™•์ธ
    console.log(`movies ๊ฐ€์ ธ์™€์„œ ๊ตฌ์กฐ๋ถ„ํ•ดํ• ๋‹น์œผ๋กœ ๋‹ด๊ธฐ`);
    console.log(movies);
    return movies;
  } catch (err) {
    console.error(err);
    return;
  }
}

์ธํ”ผ๋‹ˆํ‹ฐ ์Šคํฌ๋กค ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ ๋ฌดํ•œ์œผ๋กœ ์ฆ๊ธฐ๊ธฐ ๐ŸŒŸ

์ธํ”ผ๋‹ˆํ‹ฐ ์Šคํฌ๋กค์„ ๊ตฌ๊ธ€๋งํ•˜๋ฉด ์ •๋ง ๋‹ค์–‘ํ•œ ์˜ˆ์ œ๋“ค์„ ์ฐพ์•„๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ธํ”ผ๋‹ˆํ‹ฐ ์Šคํฌ๋กค์ด๋ž€ ๊ฒฐ๊ตญ ์ดˆ๊ธฐ ๋ Œ๋”๋ง ์†๋„๋ฅผ ๋†’ํžˆ๊ธฐ ์œ„ํ•ด์„œ ์ฒ˜์Œ ํŽ˜์ด์ง€๋ฅผ ๋ Œ๋”๋งํ•  ๋•Œ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒŒ์‹œ๊ธ€ ๋˜๋Š” ์ด๋ฏธ์ง€์˜ ๊ฐœ์ˆ˜๋ฅผ ์ œํ•œํ•˜๊ณ , ํ›„์— ์‚ฌ์šฉ์ž์˜ ์š”๊ตฌ์— ๋”ฐ๋ผ ์„œ๋ฒ„์—์„œ ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€๋กœ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

์ด์ „์— ์ œ๊ฐ€ ํ•ด์™”๋˜ ๋ฐฉ๋ฒ•๋“ค ์ฒ˜๋Ÿผ ์‚ฌ์šฉ์ž๋“ค์— ์˜ํ•ด ์ž‘์„ฑ๋œ ๋ฐ์ดํ„ฐ๋ฅผ LOAD_POSTS_REQUEST ์™€ ๊ฐ™์ด ์„œ๋ฒ„์—์„œ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๊ฒƒ์€ ๋ณ„ ๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š์•˜์ง€๋งŒ, OPEN API์—์„œ ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๊ธฐ์ˆ ์„ ๊ตฌํ˜„ํ•˜๊ธฐ์—๋Š” ์ •๋ง ๋ง‰๋ง‰ํ–ˆ์Šต๋‹ˆ๋‹ค.

axios.get('https://yts-proxy.now.sh/list_movies.json?limit=10&&sort_by=download_count')

์™€ ๊ฐ™์ด https://...json? ๋’ค์— ๋ถ™๋Š” ๋ฌธ์ž์—ด์€, API ๋ฌธ์„œ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์ฟผ๋ฆฌ ์†์„ฑ ๋“ค์ž…๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” API๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

API

์ €์˜ ์ „๊ณต์ด DB ๊ด€๋ จ์ด๊ธฐ ๋•Œ๋ฌธ์—, limit ๋ผ๋Š” ์†์„ฑ์„ ๋ณด๊ณ  ์ฒ˜์Œ์—๋Š” limit=[1,10]์™€ ๊ฐ™์ด ์ฟผ๋ฆฌ๋ฅผ ์„ค์ •ํ•ด์•ผ๊ฒ ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค. limit์˜ ๊ฐ’์„ ์ดˆ๊ธฐ์— ์„ค์ •ํ•˜๊ณ , ํ›„์— state๋ฅผ ํ†ตํ•ด LOAD_MOVIES_POST ๋ฅผ ์‹คํ–‰ํ•  ๋•Œ๋งˆ๋‹ค ํ•ด๋‹น limit ์†์„ฑ๊ฐ’์„ ์—…๋ฐ์ดํŠธ ํ•˜๊ณ  ์‹ถ์—ˆ์ง€๋งŒ, ์ฟผ๋ฆฌ๋ฌธ์—์„œ ์œ ํšจํ•˜์ง€ ์•Š์€(?) API์—์„œ ์ œ๊ณตํ•˜์ง€ ์•Š๋Š” ๋ฐฉ๋ฒ•์ด๋ผ ์ดˆ๊นƒ ๊ฐ’์ธ limit=20 ์œผ๋กœ ๋ฐ˜๋ณต๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋˜ ์ค‘ page ๋ผ๋Š” ์†์„ฑ์„ ๋ฐœ๊ฒฌํ–ˆ๋Š”๋ฐ, limit๋กœ ์ œํ•œํ•œ ์˜ํ™”๋ฐ์ดํ„ฐ๋ฅผ ํŽ˜์ด์ง€ ๋ณ„๋กœ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ด ํŽ˜์ด์ง€ ๊ฐ’์„ useState๋กœ ๊ด€๋ฆฌํ•˜์—ฌ, ์ธํ”ผ๋‹ˆํ‹ฐ ์Šคํฌ๋กค์„ ํ†ตํ•ด LOAD_MOVIES_REQUEST๊ฐ€ ์‹คํ–‰๋  ๋•Œ๋งˆ๋‹ค ์ด `page` ์†์„ฑ ๊ฐ’์„ ์˜ฌ๋ ค์ค˜ ๋‹ค์Œ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

axios.get(`https://yts-proxy.now.sh/list_movies.json?limit=10&&sort_by=download_count&page=${data}`);

์ด์ฒ˜๋Ÿผ page=${data} ์ฟผ๋ฆฌ ๋ฌธ์„ ํ†ตํ•ด, dispatch ์‹œ์— action๊ณผ ํ•จ๊ป˜ data๋กœ ๋ฐ›์•„์™€ ์œ„ ๋ฐ์ดํ„ฐ๋ฅผ ๋Šฅ๋™์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์ถ”๊ฐ€์ ์œผ๋กœ action.data๋ฅผ loadMoviesAPI์— ๋ณด๋‚ด์คฌ๋Š”๋ฐ ์—ฌ๊ธฐ์„œ ๋‚˜์˜ค๋Š” action.data๊ฐ€ ์šฐ๋ฆฌ๊ฐ€ ์—…๋ฐ์ดํŠธํ•˜๊ณ  ์‹ถ์€ page์˜ state ๊ฐ’์ž…๋‹ˆ๋‹ค.

๐Ÿ“ sagas/movies.js

...

function* loadMovies(action) {
  const result = yield call(loadMoviesAPI, action.data); 
  // action { type:LOAD_MOVIES_REQUEST , data: pageNumber} ์ด๋ฏ€๋กœ action.data ๋ฅผ ํ†ตํ•ด
  // pageNumber์— ์ ‘๊ทผํ•˜๊ณ  ์ด data๋ฅผ loadMoviesAPI์— ๋„˜๊ฒจ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.
  try {
    yield put({
      type: LOAD_MOVIES_SUCCESS,
      data: result,
    });
  } catch (err) {
    console.error(err);
    yield put({
      type: LOAD_MOVIES_FAILURE,
      error: err.response.data,
    });
  }
}

useState๋กœ ๊ด€๋ฆฌํ•œ page๋ฅผ ํฌํ•จํ•œ ํŽ˜์ด์ง€๋‹จ์˜ ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๐Ÿ“ pages/HomeChanged
...

const [pageNumber, setPageNumber] = useState(1);

const dispatch = useDispatch();

useEffect(() => {
  dispatch({
    type: LOAD_MOVIES_REQUEST,
    data: pageNumber,
  });
  setPageNumber((pageNumber) => pageNumber + 1);
}, [dispatch]);

const handleScroll = () => {
  const scrollHeight = document.documentElement.scrollHeight;
  const scrollTop = document.documentElement.scrollTop;
  const clientHeight = document.documentElement.clientHeight;
  if (scrollTop + clientHeight >= scrollHeight) {
    console.log(`pageNumber ์—…๋ฐ์ดํŠธ  ${pageNumber}`);
    // ํŽ˜์ด์ง€ ๋์— ๋„๋‹ฌํ•˜๋ฉด ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜จ๋‹ค
    dispatch({
      type: LOAD_MOVIES_REQUEST,
      data: pageNumber,
    });
    setPageNumber((pageNumber) => pageNumber + 1);
  }
};

useEffect(() => {
  // scroll event listener ๋“ฑ๋ก
  window.addEventListener('scroll', handleScroll);
  return () => {
    // scroll event listener ํ•ด์ œ
    window.removeEventListener('scroll', handleScroll);
  };
}, [pageNumber, dispatch]);

pageNumber์˜ ์ดˆ๊ธฐ๊ฐ’์„ 1๋กœ ์ฃผ์—ˆ๊ณ , ์ฒ˜์Œ ์•„๋ฌด๊ฒƒ๋„ ์—†๋Š” ์ƒํƒœ์—์„œ LOAD_MOVIES_REQUESt๋ฅผ ์‹คํ–‰ํ•œ ํ›„, pageNumber๋ฅผ ์˜ฌ๋ ค์ค๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด axios ๋‹จ์—์„œ ๋‹ค์Œ ํŽ˜์ด์ง€์— limit=10 ์†์„ฑ๊ณผ ํ•จ๊ป˜ ์˜ํ™” ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€์ ์œผ๋กœ ๊ฐ€์ ธ์˜ฌ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๐Ÿ“/sagas/movies
...

async function loadMoviesAPI(data) {
  // 3. data ์•ˆ์—๋Š” action.data ์ฆ‰ useState๋กœ ๊ด€๋ฆฌํ•˜๋Š” pageNumber ๊ฐ€ ๋“ค์–ด์žˆ์Šต๋‹ˆ๋‹ค.
  // 4. ์ถ”๊ฐ€์ ์œผ๋กœ ์š”์ฒญ์ด ์ƒ๊ธธ ๋•Œ๋งˆ๋‹ค 1, 2, 3, ... ์ฆ๊ฐ€ํ•˜๋ฉฐ ๋ฐ์ดํ„ฐ๋ฅผ ํŽ˜์ด์ง€ ๋ณ„๋กœ ๋‚˜๋ˆ  ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  try {
    const {
      data: {
        data: { movies },
      },
    } = await axios.get(`https://yts-proxy.now.sh/list_movies.json?limit=10&&sort_by=download_count&page=${data}`);
    return movies;
  } catch (err) {
    console.error(err);
    return;
  }
}

function* loadMovies(action) {
  const result = yield call(loadMoviesAPI, action.data); 
  // 1. action.data์—๋Š” dispatch ์‹œ์— ์•ก์…˜ ํƒ€์ž…๊ณผ ๊ฐ™์ด ๋ณด๋‚ธ data, ์ฆ‰ pageNumber ๊ฐ€ ๋“ค์–ด์žˆ์Šต๋‹ˆ๋‹ค.
  // 2. ์ด๋ฅผ call loadMoviesAPI๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด์„œ ๊ฒฐ๊ณผ๊ฐ’์„ result์— ๋‹ด์Šต๋‹ˆ๋‹ค.
  try {
    yield put({
      type: LOAD_MOVIES_SUCCESS,
      data: result,
    });
  } catch (err) {
    console.error(err);
    yield put({
      type: LOAD_MOVIES_FAILURE,
      error: err.response.data,
    });
  }
}

๋Š๋‚€์ 

์ƒˆ๋กœ์šด ๊ธฐ์ˆ  ๊ณต๋ถ€ํ•˜๊ธฐ ๐ŸŒŸ

ํ•ญ์ƒ ๊ฐ•์˜๋งŒ ๋”ฐ๋ผํ•˜๋ฉด์„œ ๊ณต๋ถ€๋ฅผ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์•Œ๋ ค์ฃผ์ง€ ์•Š๋Š” ๊ธฐ์ˆ ์— ๋Œ€ํ•ด ๋„์ „ํ•˜๋Š” ๊ฒƒ์ด ๋‘๋ ต๊ณ  ํž˜๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ตฌ๊ธ€๋ง๊ณผ ํ•จ๊ป˜ ์•‰์€ ์ž๋ฆฌ์—์„œ๋„, ์ž๊ณ  ์ผ์–ด๋‚˜์„œ๋„, ์šด๋™ํ•˜๋ฉด์„œ๋„ ๊ณ ๋ฏผ์„ ๊ณ„์†ํ•˜๋ฉด์„œ '์ด๋ ‡๊ฒŒ ํ•ด๋ณด๋ฉด ์–ด๋–จ๊นŒ', '์ €๋ ‡๊ฒŒ ํ•ด๋ณด๋ฉด ์–ด๋–จ๊นŒ' ๋ฅผ ๋ฐ˜๋ณตํ–ˆ๋Š”๋ฐ, ๊ฒฐ๊ณผ์ ์œผ๋กœ ๋ฐฐ์› ๋˜ ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ๋ฐ์ดํ„ฐ ๋ Œ๋”๋ง์— ์„ฑ๊ณตํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

api ์•Œ๋งž๊ฒŒ ์š”๋ฆฌํ•˜์—ฌ ๊ฐ€์ ธ์˜ค๊ธฐ ๐ŸŒŸ

axios๋ฅผ ํ†ตํ•ด api์—์„œ ๋ถˆ๋Ÿฌ์˜จ ๋ฐ์ดํ„ฐ๋ฅผ ๊ตฌ์กฐ๋ถ„ํ•ด ํ• ๋‹น์„ ํ†ตํ•ด ํ•„์š”ํ•œ ๋ชจ์Šต์œผ๋กœ ๊ฐ€์ ธ์™€ ๋ Œ๋”๋งํ•˜๋Š” ๋ถ€๋ถ„์ด ๋งค์šฐ ์žฌ๋ฐŒ์—ˆ๊ณ , ๋˜ํ•œ api์— ์ฟผ๋ฆฌ๋ฌธ์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์›ํ•˜๋Š” ํ˜•์‹์œผ๋กœ ๋ฐ”๊ฟ” ๊ฐ€์ ธ์˜ค๋Š” ๊ณผ์ •์ด ํฅ๋ฏธ๋กœ์› ์Šต๋‹ˆ๋‹ค.