React 동작 원리의 간단한 예제

JGwon·2023년 9월 25일
0

React

목록 보기
1/1
post-thumbnail

🤚 들어가기

안녕하세요. 유튜브에 있는 "리액트 프로젝트 영상"을 보면서 코딩을 하던 중 정리도 하고, 정보를 나누고 싶어 글을 남기게 되었습니다.
여러분들은 리액트의 기본 동작원리에 대해서 잘 알고 계신가요?

잘 정리된 글 하나를 소개해드립니다!
juno 님이 정리해주신 리액트의 동작원리

일단 위의 글을 읽고 오시는 것을 추천드립니다‼️(강추)




자 읽고 오셨겠죠?🫡

리액트가 리렌더링이 되는 경우

React는 다음과 같은 경우에 리렌더링이 일어납니다.
1. Props가 변경되었을 때
2. State가 변경되었을 때
3. forceUpdate() 를 실행하였을 때.
4. 부모 컴포넌트가 렌더링되었을 때

라고 주노님께서 정리를 잘 해주셨습니다!! 이 네 가지 경우를 잘 기억해주세요.

프로젝트의 컴포넌트 경로 (Card.jsx, Pokeinfo.jsx -> Main.jsx -> App.js)

Card.jsx

import React from 'react';

const Card = ({ pokemon, loading, infoPokemon }) => {
  console.log(pokemon);
  return (
    <>
      {loading ? (
        <h1>Loading...</h1>
      ) : (
        pokemon.map((item) => {
          return (
            <>
              <div
                className='card'
                key={item.id}
                onClick={() => infoPokemon(item)}
              >
                <h2>{item.id}</h2>
                <img src={item.sprites.front_default} alt='' />
                <h2>{item.name}</h2>
              </div>
            </>
          );
        })
      )}
    </>
  );
};

export default Card;

Pokeinfo.jsx

import React from 'react';

const Pokeinfo = ({ data }) => {
  console.log(data);
  return (
    <>
      {!data ? (
        ''
      ) : (
        <>
          <h1>{data.name}</h1>
          <img
            src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/dream-world/${data.id}.svg`}
            alt=''
          />
          <div className='abilities'>
            {data.abilities.map((poke) => {
              return (
                <>
                  <div className='group'>
                    <h2>{poke.ability.name}</h2>
                  </div>
                </>
              );
            })}
          </div>
          <div className='base-stat'>
            {data.stats.map((poke) => {
              return (
                <>
                  <h3>
                    {poke.stat.name}:{poke.base_stat}
                  </h3>
                </>
              );
            })}
          </div>
        </>
      )}
    </>
  );
};

export default Pokeinfo;

Main.jsx

import React, { useEffect, useState } from 'react';
import axios from 'axios';

import Card from './Card';
import Pokeinfo from './Pokeinfo';

const Main = () => {
  const [pokeData, setPokeData] = useState([]);
  const [loading, setLoading] = useState(true);
  const [url, setUrl] = useState('https://pokeapi.co/api/v2/pokemon/');
  const [nextUrl, setNextUrl] = useState();
  const [prevUrl, setPrevUrl] = useState();
  const [pokeDex, setPokeDex] = useState();

  const pokeFunc = async () => {
    setLoading(true);
    const res = await axios.get(url);
    setNextUrl(res.data.next);
    setPrevUrl(res.data.previous);
    getPokemon(res.data.results);
    setLoading(false);
    // console.log(pokeData);
  };
  const getPokemon = async (res) => {
    res.map(async (item) => {
      const result = await axios.get(item.url);
      setPokeData((state) => {
        state = [...state, result.data];
        state.sort((a, b) => a.id - b.id);
        return state;
      });
    });
  };
  useEffect(() => {
    pokeFunc();
  }, [url]);

  return (
    <>
      <div className='container'>
        <div className='left-content'>
          <Card
            pokemon={pokeData}
            loading={loading}
            infoPokemon={(poke) => setPokeDex(poke)}
          />
          <div className='btn-group'>
            {prevUrl && (
              <button
                onClick={() => {
                  setPokeData([]);
                  setUrl(prevUrl);
                }}
              >
                Previous
              </button>
            )}
            <button
              onClick={() => {
                setPokeData([]);
                setUrl(nextUrl);
              }}
            >
              Next
            </button>
          </div>
        </div>
        <div className='right-content'>
          <Pokeinfo data={pokeDex} />
        </div>
      </div>
    </>
  );
};

export default Main;

자 정말 복잡하고 어지러우시죠..? 저도 그렇습니다만 천천히 얘기해보자구요.🫡

  • Main.jsx를 봅시다.
    일단 App.js가 실행되면 Main.jsx 파일을 불러오고, Main컴포넌트를 불러옵니다.

아 ! 잠깐 컴포넌트가 무엇일까요❓❗

  • 여러 개의 프로그램 함수들을 모아 하나의 특정한 기능을 수행할 수 있도록 구성한 작은 기능적 단위

이해하기 쉽게 하나의 기능을 전문적으로 하는 코드 뭉치라고 생각하세요!

여기서 Main컴포넌트는 전체적인 코드를 불러오고 배치해주는 역할을 하고 있어요! 마치 전장에서 지휘하는 지휘관처럼 말이죠!


Main컴포넌트에 useEffect는 왜 있을까요?

  • 다양한 경우에 사용되지만, 이 코드에서는 마운트 됐을 때(화면에 보여졌을때), url이 변경되었을 때 effect안에 있는 pokefunc()이 실행되겠죠?
  • "useEffect의 동작원리" 구글링을 하고 오시면 무슨 말인지 아실거에요!

pokefunc()이 실행되면 무슨 일이 발생할까요?

 const [pokeData, setPokeData] = useState([]);
  const [loading, setLoading] = useState(true);
  const [url, setUrl] = useState('https://pokeapi.co/api/v2/pokemon/');
  const [nextUrl, setNextUrl] = useState();
  const [prevUrl, setPrevUrl] = useState();
  const [pokeDex, setPokeDex] = useState();

const pokeFunc = async () => {
    setLoading(true);  // <--- (4)⭐
    const res = await axios.get(url);
    setNextUrl(res.data.next);
    setPrevUrl(res.data.previous);
    getPokemon(res.data.results);
    setLoading(false);
    // console.log(pokeData);
  };

 const getPokemon = async (res) => {
    res.map(async (item) => {
      const result = await axios.get(item.url);
      setPokeData((state) => { // <-- (3)⭐
        state = [...state, result.data];
        state.sort((a, b) => a.id - b.id);
        return state;
      });
    });
  };
  • 자 useEffect의 pokefunc()이 실행되는데, pokefunc()의 내부를 보면 set... 함수가 많습니다.
    이 set...함수는 useState의 기본 동작 원리에 대해서 공부해보시면 될거 같습니다.

간단하게 말씀드리면 set...함수에 값을 넣으면 set...함수 옆에 있는(loading, url, nextUrl, prevUrl..)등등의 값이 변하게 됩니다. 즉 state가 변경된 겁니다.

아까 말씀 드렸던 리랜더링이 발생하는 경우중에 2번 state가 변경되었을 때를 기억하시겠죠? 이 경우가 2번 케이스에 해당합니다.

비동기 pokeFunc안에서 state가 바뀌게 됩니다. 즉 리렌더링이 발생하게 됩니다.


getPokemon()이 실행되면?

pokeFunc()이 실행되면 getPokemon()함수가 실행됩니다. 인자로는 api의 객체를 가진 배열을 전달합니다(res.data.results). 배열 안에는 20개의 포켓몬 객체 정보가 있습니다.

자 이건 별로 중요하지 않고, getPokemon()안에 setPokeData(3)참조☝️ 있습니다. 아까도 말씀드렸듯이 state의 변화가 일어났습니다!

Main 컴포넌트

 return (
    <>
      <div className='container'>
        <div className='left-content'>
          <Card
            pokemon={pokeData} // <--- 이 부분⭐
            loading={loading}
            infoPokemon={(poke) => setPokeDex(poke)}
          />
 //... 생략

변화된 pokeData를 Card.jsx의 prop로 전달합니다. 잘 보시면 setLoading함수에 의해 loading은 true인 상태이고, (4) 참조☝️


Card.jsx


const Card = ({ pokemon, loading, infoPokemon }) => {
  console.log(pokemon);
  return (
    <>
      {loading ? (
        <h1>Loading...</h1>
      ) : (
        pokemon.map((item) => {
          return (
            <>
    //... 생략

loading이 true 상태일땐 변화된 pokeData가 보여지지 않습니다.

pokeFunc()안에 있는 getPokemon()이 끝나고 나서 setLoading(false);가 실행될 때(state가 변화될 때) 변화된 부분(pokeData, loading)을 화면에 빠르게 랜더링 해줍니다.
주노님의 realDOM 과 virtualDOM의 diffing알고리즘 부분을 참조하세용.

Card.jsx의 props중 pokemon은 방금 전 main.jsx에서 변화 된 pokeData의 값입니다. 즉 20개의 키를 가진 array를 가져오는 것이지요.



자 여기까지 이해하셨으면 제가 작성한 예제코드를 다 이해하실 수 있을거라고 생각됩니다.

💭 자 여기서 문제!!

Card.jsx 안에 있는 div태그의 onClick이 발생했을 때 브라우저에 어떤 변화가 생길까요??





정답🔥
1. onClick이 발생하면 infoPokemon의 콜백함수가 실행된다.
2. item은 cliked당한 해당 객체를 전달한다.
3. callback에 의해 pokeDex 즉 state가 변경이 된다.
4. Pokeinfo.jsx에게 바뀐 props를 전달한다.
5. 브라우저에 빠르게 표시해준다.



잘못된 정보가 있다면 편하게 말씀해주세요! 저도 배울 수 있는 기회라고 생각합니다. 언제든 환영🤗

feat. velog 첫 글인데 네이버보다 UI가 너무
맘에 드는거 실화입니까!?

profile
하루에 한 가지의 변화를 만들자.

0개의 댓글