React(생활코딩)_13일차_클래스 컴포넌트 - LifeCycle API 종류 & 함수 컴포넌트 - Hook useEffect API

Lina Hongbi Ko·2023년 9월 20일
0

React_생활코딩

목록 보기
14/23

저번 시간에는 state를 사용하기 위해서 Hook의 한 종류인 useState() API를 사용해 state를 지정하고 또 바꿔주는 실습을 했다.

이번 시간에는 클래스형 컴포넌트의 라이프 사이클 메소드에 대해 알아보고, 클래스형 컴포넌트와 비교하면서 함수형 컴포넌트의 훅인(라이프사이클 관련 API) useEffect() API에 대해 알아보자.

1. What's LifeCycle API ?

: 리액트 컴포넌트는 생명을 가지고 있는데, 이 생명의 주기는 페이지에 렌더링되기 전인 준비 과정에서 시작하여 페이지에서 사라질 때 끝난다.이러한 수명(생명 주기)을 간단하게 3부분으로 나누어서 각각 파트에서 불러오는 메서드들을 라이프사이클 메서드라고 부른다.

자세하게 말하자면, 컴포넌트가 DOM 위에 생성되기 전 후나 데이터가 변경되어 상태를 업데이트 하기 전 후로 실행되는 메소드들을 의미한다.

일전에 배운 shouldComponentUpdate() API 처럼 컴포넌트가 생성되고 소멸되고 업데이트되는 것에 관여하는 것이 LifeCycle API이다.

먼저, 클래스형 컴포넌트의 LifeCycle API부터 살펴보자.

📍 클래스형 Life Cycle API

✏️ Life Cycle 접두사

  • Will 접두사 : 어떤 작업을 작동하기 전에 실행되는 메소드
  • Did 접두사 : 어떤 작업을 작동 후에 실행되는 메소드

✏️ Life Cycle 카테고리 3가지

: Mount(생성), Update(변경), Unmount(제거)

  • Mount : DOM이 생성되고 웹 브라우저상에 나타나는 것
  • Update : 컴포넌트의 정보가 바뀌어서 리렌더링될 때
    조건 4가지 -> 1. props가 바뀔때, 2. state가 바뀔때, 3. 부모 컴포넌트가 리렌더링될때, 4. this.forceUpdate로 강제로 렌더링을 트리거할때
  • Unmount : DOM에서 컴포넌트를 제거하는 것

✏️ 컴포넌트안에서 메소드의 실행순서 - 1

  • 컴포넌트 생성시 : constructor -> componentWillMount -> render -> ComponentDidMount 순으로 진행

  • 컴포넌트 제거시 : componentWillUnmount 만 실행

  • 컴포넌트의 props 변경시 : componentWillReceiveProps -> should ComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate

  • 컴포넌트의 state 변경시 : shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate

✏️ 각 메소드의 역할

  • constructor
constructor(props) {
	super(props);
    console.log('constructor');
}

생성자 메소드로써 컴포넌트가 처음 만들어질때 실행된다. 이 메소드에서 기본 state를 정할 수 있다. (state : 이 메소드 안에서만 유효한 값)

  • componentWillMount
componentWillMount() {
	console.log('componentWillMount');
}

컴포넌트가 DOM 위에 만들어지기 전에 실행된다. 주의할 점으로 componentWillMount 내에서 props, state를 바꾸면 안된다. Mount 중이기 때문이다. 그리고 DOM에 접근할 수 없다. render함수가 작동되기 전이기 때문이다.

  • render
render(){
	...
}

컴포넌트 렌더링을 담당한다.

  • componentDidMount
componentDidMount(){
	console.log("componentDidMount");
}

컴포넌트가 만들어지고 첫 렌더링을 다 마친 후 실행되는 메소드.
다른 JavaScript 프레임워크를 연동하거나 setTimeout, setInterval 및 Ajax 처리 등을 넣는다. will과 다르게 DOM에도 접근할 수 있다. 주로 서버에 데이터를 요청한다. 개발 하면서 가장 많이 쓰이는 생명주기라고 한다.

  • componentWillReceiveProps
componentWillReceiveProps(nextProps) {
	console.log('componentWillReceiveProps: ' + JSON.stringify(nextProps));
}

컴포넌트가 props를 새로 받았을때 실행된다. props의 변경에 따라 state를 업데이트 해야 할 때 사용하면 유리하다. this.setState()를 하더라도 추가적으로 렌더링 하지 않는다.

  • shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState){
	console.log('shouldComponentUpdate' + JSON.stringify(nextProps) + " " + JSON.stringify(nextState));
    return nextProps.id !== this.props.id;
}

props 혹은 state가 변경되었을때, 리렌더링을 할지말지 정하는 메소드이다. 기본값은 true를 반환하고, 실제로 사용할때는 필요한 비교를 하고 값을 얻어야 한다.

💡 JSON.stringify()를 쓰면 여러 field를 편하게 비교가능하다.

  • componentWilUpdate
componentWillUpdate(nextProps, nextState) {
	console.log('componentWillUpdate: ' + JSON.stringify(nextProps) + " " + JSON.stringify(nextState));
}

컴포넌트가 업데이트 되기 전에 실행.
this.setState()를 사용하면 무한루프에 빠지므로 주의! willMount처럼 state를 변경하면 안된다.

  • componentDidUpdate
componentDidUpdate(prevProps, pevState) {
	console.log('componentDidUpdate: ' + JSON.stringify(prevProps) + " " + JSON.stringify(prevState));
}

컴포넌트가 리렌더링을 마친 후 실행 된다. DOM에 접근도 가능하고, state를 변경해도 된다.

  • componentWillUnmount
componentWillUnmount() {
	console.log('componentWillUnmount');
}

컴포넌트가 DOM에서 사라진 후 실행되는 메소드이다. will만 있고 did는 없다. 이미 제거 되었기 때문에 :) 주로 addEventListener 등으로 추가한 리스너를 제거하거나, clear과 관련된 행동을 할때 실행한다.

  • componentDidCatch
componentDidCatch(error, info){
    this.setState({
        error : true,    
    });
    console.log({error, info});
}

서버 통신에 실패했을때의 에러페이지 등을 만들 때 사용. error은 어떤 오류가 발생했는지, info는 어느 코드에서 발생했는지에 대한 정보를 알려준다.


요로코럼 클래스형 컴포넌트의 라이프사이클 메소드들을 살펴보았다. 아주 많지만, 이들을 다 사용할 필요는 없다. v17부터는 componentWillMount, componentWillUpdate, componentWillReceiveProps가 비추천된다. 그러므로 사용하지 않는 것을 추천한다고 한다.

그래서 다시 찾아보니,

위처럼 업데이트 된 정보를 찾을 수 있었다.

자, 이제 최근에 업데이트된 클래스형 컴포넌트의 라이프사이클 메소드들에 대해서 알아보자.

✏️ 컴포넌트안에서 메소드의 실행순서 - 2

마운트 (Mount) 할 때 (위부터 아래 메소드 호출 순서)

  • constructor() : 컴포넌트를 새로 만들때마다 호출되는 클래스 생성자 메소드

  • static getDerivedStateFromProps() : props에 있는 값을 state에 넣을 때 사용하는 메소드

    static getDerivedStateFromProps(nextProps, prevState){
       if(nextProps.value !== prevState.value){
           return {value : nextProps.value}; 
       }
       return null; 
    }
    
  • render() : UI를 렌더링하는 메소드

  • componentDidMount() : 컴포넌트가 웹브라우저상에 나타난 후 호출하는 메소드

업데이트 (Update) 할 때 (위부터 아래 메소드 호출 순서)

  • getDerivedStateFromProps() : 마운트 과정에서도 호출되며 업데이트가 시작하기 전에도 호출된다. props의 변화에 따라 state 값에도 변화를 주고 싶을 때 사용.

  • shouldComponentUpdate() : 컴포넌트가 리렌더링 할지 말지 결정하는 메소드. 리턴 값에 따라 다름. this.forceUpdate() 함수를 호출한다면 이 과정을 생략하고 render 함수를 호출.

  • render() : 컴포넌트를 리렌더링

  • getSnapshotBeforeUpdate() : 컴포넌트 변화를 DOM에 반영하기 바로 직전에 호출하는 메소드. render에서 만들어진 결과물이 브라우저에 실제로 반영되기 직전에 호출되며 반환된 값은 componentDidUpdate에서 3번째 파라미터인 snapshot값으로 전달 받을 수 있다.

getSnapshotBeforeUpdate(prevProps, prevState){
    if(prevState.array !== this.state.array){
        const {scrollTop, scrollHeight} = this.list;
        return {scrollTop, scrollHeight};
    }
}
  • componentDidUpdate() : 컴포넌트의 업데이트 작업이 끝난 후 호출하는 메소드. 업데이트가 끝난 직후이므로 DOM 관련 처리를 해도 무방하고 prevProps, prevState를 이용하여 컴포넌트가 가졌던 데이터에 접근할 수 있으며 getSnapshotBeforeUpdate에서 반환한 값을 snapshot으로 전달 받을 수 있다.
componentDidUpdate (preProps, preState, snapShot){

}

언마운트 (Unmount) 할 때

  • componentWilUnmount : 컴포넌트가 웹 브라우저상에서 사라지기 전에 호출하는 메소드

이제 이 메소드들을 가지고 책에 나온 내용을 실습해보자.
전 시간에 실습했던 예제에 이어서 작성해보자.

// App.js 파일

... 생략 ...

const classStyle = 'color:red';

class ClassComp extends React.Component {
	state = {
    	number: this.props.initNumber,
        date: (new Date()).toString()
    }
    
    componentWillMount() {
    	console.log('%cclass => componentWillMount', classStyle);
    }
    componentDidMount() {
    	console.log('%cclass => componentDidMount', classStyle);
    }
    shouldComponentUpdate(nextProps, nexstState) {
    	console.log('%cclass => shouldComponentUpdate', classStyle);
    }
    componentWillUpdate(nextProps, nextState) {
    	console.log('%cclass => componentWillUpdate', classStyle)
    }
    componentDidUpdate(prevProps, prevState) {
    	console.log('%cclass => componentDidUpdate', classStyle);
    }
    render() {
    	console.log('%cclass => render', classStyle);
        return(
        	... 생략 ...
        );
    }
}

console.log()에서 %c는 css 스타일을 추가 하기 위해 붙인 것이다. 그래서 console창에 빨간색글씨가 적용된 것을 확인 할 수 있다.

자, 그럼 콘솔창을 확인하면

요런식으로 componentWillMount -> render -> componentDidMount 순으로 콘솔창에 찍힌 걸 확인할 수 있다.

+) 앞에서 언급했듯, componentWillMount, componentWillUpdate는 리액트 17버전 이상부터 사용하면 경고 메시지가 나오기 때문에 책에서는 'UNSAFE_'를 붙여야 한다고 언급했다. 그리고 componentWillMount 대신 componentDidMount를 사용하거나 state를 초기화할때는 constructor를 사용해야 한다고 한다. componentWillUpdate 대신 데이터를 조회하는 등의 작업은 componentDidUpdate에서 수행해야 한다.

정리하자면,마운트의 과정은 constructor -> componentWillMount -> render -> componentDidMount 의 단계를 거친다.

그럼, 업데이트를 실행시켜보자. random 버튼을 누르고 콘솔창을 다시 확인 하면,

shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate 순으로 메소드가 호출 된 것을 확인할 수 있다.

componentWillUnmount는 컴포넌트가 소멸될때 호출되므로, 이 예제에서는 컴포넌트가 소멸되지 않아 호출되는 것을 볼 수 없다.

자, 이렇게 클래스형 컴포넌트의 라이프사이클 메소드를 공부해봤으니 라이프사이클 메소드에 대해서 어느 정도 감이 왔을 것이다. 그리고 각 시기에 해당되는 메소드들을 이용해 여러가지를 적용할 수 있다는 것도 알 것이고 ^0^

그렇다면 함수형 컴포넌트에서는 라이프사이클을 어떻게 구현하는지 알아보자.

2. 함수에서 라이프 사이클 구현하기 - useEffect()

함수형 컴포넌트에서는 어떻게 라이프 사이클을 구현하는지 알아보기 위해서 일단 책에 나온 내용 처럼 실습환경을 만들어보자.

// App.js 파일

... 생략 ...

const funcStyle = 'color:blue';
const funcId = 0;

function FucComp(props) {
	... 생략 ...
    const [_date, setDate] = useState((new Date()).toString());
    
    console.log('%cfunc => render ' + (++funcId), funcStyle);
    return(
    	... 생략 ...
    );
}
... 생략 ...

두 개의 변수를 만들고 클래스형 컴포넌트와 비교하기 위해 글씨를 파란색으로 출력되도록 콘솔을 작성했다. funcId는 호출할때마다 증가하는 변수 값이다.

콘솔창을 확인해보면,

파란색으로 쓴 글씨를 확인할 수 있다. render가 한번 호출된 것을 알 수 있다.

그렇다면 렌더링 관련 작업을 처리하는 FuncComp 함수형 컴포넌트가 실행된 후에 추가로 필요한 작업을 어떻게 처리할 수 있을까?

그것은 바로

useEffect()

를 이용 하는 것이다.

💡 useEffect()

: useEffect는 리액트의 기본 내장 함수로써, useEffect 함수가 포함된 컴포넌트가 처음 마운트되거나, 컴포넌트가 리렌더링 될 때, 또는 선언된 변수의 값이 변경되거나 redux store의 값이 변경될 때 실행할 구문들을 정의해놓은 함수 라고 한다. 즉, 리액트의 useEffect 훅을 사용하면 함수 컴포넌트에서도 side effect를 사용할 수 있다.
그리고 이 메소드는 componentDidMount, componentDidUpdate, componentWillUnmount를 합친 것과 같은 기능을 가진다.

이 함수를 사용하기 위해서

  1. useEffect를 import 한다.
import React, { useEffect } from 'react'; 
  1. 해당 컴포넌트에 useEffect API를 사용한다. 첫번째 인자(effect)는 함수, 두번째 인자는 배열(deps)이 들어간다.
funcComp () {
	useEffect(() => {
  	... // 실행할 내용들
    	// - 렌더링 이후 실행할 함수
        	(리액트는 이 함수를 기억 했다가 DOM 업데이트 후 불러낸다.)
	}, [, deps]);
}

사용하기 위한 준비들을 알았으니, 실습했던 함수형 컴포넌트에 useEffect를 사용해보자.

// App.js 파일

import React, { useState, useEffect } from 'react';
import './App.css';

... 생략 ...

const funcStyle = 'color:blue';
let funcId = 0;

function FuncComp(props) {
	... 생략 ...
    
    const [_date, setDate] = useState((new Date()).toString());
    
    // side Effect
    useEffect(function(){
    	console.log('%cfunc => useEffect' + (++funcId), funcStyle);
        document.title = number + ' : ' + _date;
    });
    
    console.log('%cfunc => render' + (++funcId), funcStyle);
    ... 생략 ...

콘솔창을 확인해보면,

요로코럼 render 작업이 끝난 다음에 호출된 것을 볼 수 있다. 그렇다면 이 상태에서 함수 컴포넌트의 date 버튼을 클릭했을때는 어떻게 될까?

render 함수가 또 호출되고 useEffect의 인자로 전달한 함수도 다시 호출된 것을 확인할 수 있다. 이것을 통해 useEffect라는 훅은 처음으로 렌더링됐을때(Mount) 실행되고, 그 다음에 render가 실행될때마다 호출되는 것을 알 수 있다. 이것은 클래스형 컴포넌트의 componentDidMount나 componentDidUpdate와 효과가 같다고 볼 수 있다.

📍 그렇다면, 왜 이 훅의 이름은 effect가 있는 것일까?
: 이것은 부수효과(side effect)의 'effect'를 표현한 것이다. 앞에서 useEffect에 대해 설명할 때 이 API를 사용하면 side Effect를 사용할 수 있다고 했는데 이 말은 무엇이냐면,

함수형 컴포넌트가 호출됐을때 반환되는 것을 화면에 그려주는 작업을 리액트 컴포넌트의 'main effect'라고 볼 수 있다. 이 main effect는 반횐된 것을 그리는 것이 주임무인데, 여기서 벗어는 작업(컴포넌트가 렌더링된 후에 컴포넌트의 어떤 정보를 다른 곳에서 가져와 내용을 변경하거나 네트워크 통신 등의 작업)을 하는 것을 'side effect'라고 한다.

function Example() {
	const [count, setCount] = useState(0);
    
    useEffect(()=>{
    	document.title = "You clicked" + count + "times";
    });
}

이 코드는 문서의 title을 바꾸는 작업을 한다. 이 코드가 실행되면 문서의 타이틀이 변경된다. 이것은 사실 컴포넌트가 렌더링되는 작업과는 상관 없는 작업이다. 이러한 것을 side effect라고 하고이를 적당한 타이밍에 동작하게 하는 것이 기존 클래스 방식의 컴포넌트에서 componentDidMount나 componentDidMount같은 라이프사이클 API가 수행했던 것이다.

자 그럼 더 자세히 이 useEffect API에 대해 살펴보기 위해 파일 하나를 작성해서 import 해보자.

✏️ 조건에 맞춰 useEffect 사용하기

- 렌더링이 완료될 때마다 실행

: useEffect() 안에 함수만 써주고, 두번째 인자를 쓰지 않으면 => componentDidMount와 componentDidUpdate를 합친것과 같이 실행된다.

// UseEffectTest.js 파일

import React, { useState, useEffect } from "react";

function UseEffectTest () {
  const [count, setCount] = useState(0);
  const counting = () => setCount(count + 1);

  useEffect(() => {
    console.log("useEffect!!", count);
  });
  
  return (
    <div>
      <p>{count}번 클릭!</p>
      <button onClick={counting}>Click Me</button>
    </div>
  );
};

export default UseEffectTest;

콘솔창을 보면, 처음 마운트 됐을 때 (처음 렌더링됐을 때)

이 콘솔메시지를 바로 확인할 수있다. (componentDidMount)

그리고나서 버튼을 두번 클릭하고 콘솔창을 확인하면,

업데이트마다 useEffect가 호출되는 것을 확인할 수 있다.(componentDidUpdate)

정리하면,

useEffect의 첫번째인자인 함수만 쓰면 componentDidMount와 componentDidUpdate를 둘 다 쓰는 것과 같은 기능을 한다.

- 최초 렌더링시에만 실행 (처음으로 마운트되었을 때만 실행)

: 빈 배열을 넣어주면 된다. 초기에 한 번 실행할 작업들이 필요할 때 사용한다.

// UseEffectTest.js 파일

... 생략 ...

  useEffect(() => {
    console.log("useEffect!!", count);
  }, []);
  // 빈배열 추가
  
  return (
  ... 생략 ...

콘솔창을 확인하고 버튼을 8번 클릭해도 useEffect가 한번만 실행되고 더이상 실행되지 않는 것을 알 수 있다.

- 특정 값이 변경될 때에만 실행

: 배열에 해당값을 넣어준다.
useEffect는 두번째인자로 전달받은 배열의 요소내의 값의 상태가 바뀌었을때만 첫번째 인자인 콜백함수가 호출되도록 약속되어 있다.

// UseEffectTest.js 파일

import React, { useState, useEffect } from "react";

function UseEffectTest() {
  const [count, setCount] = useState(0);
  let counting = () => setCount(count + 1);
  const [name, setName] = useState("CheolSu");
  let handleChangeName = (e) => setName(e.target.value);

  useEffect(() => {
    console.log("useEffect!!", count);
  }, [count]);

  return (
    <div>
      <p>안녕하세요. {name} 입니다.</p>
      <input onChange={handleChangeName}></input>
      <p>{count}번 클릭</p>
      <button onClick={counting}>Click me</button>
    </div>
  );
}

export default UseEffectTest;

name의 값이 변경될 때에는 console.log가 동작하지 않지만,


count의 값이 변경될 때에는 찍히는 것을 볼 수 있다.

정리하자면,

배열안 해당 변수의 값이 변경될 때, useEffect 함수가 실행된다.

- cleanup 기능

: useEffect에는 해당 컴포넌트가 언마운트될 때 실행되는 cleanup함수라는 기능이 있다. useEffect 안에서 return 할 때 실행 된다. (useEffcet의 뒷정리를 한다.)

만약 컴포넌트가 마운트 될 때 이벤트 리스너를 통해 이벤트를 추가하였다면 컴포넌트가 언마운트 될 때 이벤트를 삭제 해주어야 한다.
그렇지 않으면 컴포넌트가 리렌더링 될 때마다 새로운 이벤트 리스너가 핸들러에 바인딩 될 것이다. 이는 자주 리렌더링 될 경우 메모리 누수가 발생할 수 있다.
cleanup 함수는 컴포넌트가 사라질 때 호출되는 부분으로, 메모리 누수를 방지하여 메모리 관리를 하거나 컴포넌트가 사라질 때 수행할 작업들을 추가하기 위해 사용한다.

// UseEffectTest.js 파일

... 생략 ...
  useEffect(() => {
    console.log("useEffect!!", count);
    return() => {
    	console.log("cleanup!", count);
    }
  }, [count]);

  return (
  ... 생략 ...

클릭시 cleanup 함수에서는 이전 count값, 그리고 렌더링 이후에는 변경된 count값이 찍히는 것을 볼 수 있다.

useEffect가 다시 실행되기 전에 뭔가 정리하는 작업이 필요한경우, 반환값으로 함수를 전달하면 그 함수를 실행한다. 그래서 정리정돈을 한다는 의미로 useEffect의 return 안의 함수를 cleanup 함수라고 한다.

즉, return 함수를 추가하여 componentWillUnmount 역할을 할 수 있다.

또 다른 예시를 보자.

// App.js 파일
... 생략 ...

function FuncComp(props) {
	... 생략 ...
    
    const [_date, setDate] = useState((new Date()).toString());
    
    useEffect(function(){
    	console.log('%cfunc => useEffect (componentDidMount)' + (++funId), funcStyle);
        document.title = number;
        return function() {
        	console.log('%func => useEffect return (componentWillUnMout)' + (++funcId), funcStyle);
    }, []);
    
    // side effect
    
    useEffect(function(){
    	console.log('%cfunc => useEffect number (componentDidMount & componentDidUpdate) ' + (++funcId), funcStyle);
        document.title = number;
        return(function(){
        	console.log('%cfunc => useEffect number return (componentDidMount & componentDidUpdate)' + (++funcId), funcStyle);
        }
    }, [number]);
    ... 생략 ...

useEffect의 두번째 인자로 빈 배열을 전달하면, 위에서 언급한대로 컴포넌트가 생성될때 (마운트할때)만 한번 호출 되고 이후에는 state값이 변경되더라도 호출되지 않는 것을 알 수 있다.

그러므로 이 useEffect의 cleanup함수는 componentWillUnmount와 같은 효과를 낸다.

이어서, 책에서는 App이라는 이름의 컴포넌트를 개조해서 버튼을 만들고 클릭했을때 아래에 만들어둔 컴포넌트가 사라지게 했다.

// App.js 파일

import React, {useState, useEffect} from 'react';
import './App.css';

function App() {
	const [funcShow, setFuncShow] = useState(true);
    const [classShow, setClassShow] = useState(true);
    
    return(
    	<div className="container">
        	<h1>Hello World</h1>
            <input type="button" value="remove func" onClick={
            function(){
            	setFuncShow(false)
            }}></input>
            <input type="button" value="remove class" onClick={function(){
            	setClassShow(false)
            }}></input>
            {funcShow ? <FuncComp initNumber={2}></FuncComp> : null}
            {classShow? <ClassComp initNumber={2}></ClassComp> : null}
        </div>
    );
}

... 생략 ...

removefunc 버튼을 누르면 함수형 컴포넌트는 사라지고 console창에 컴포넌트가 생성됐을때 한번만 호출되게 구현한 useEffect의 cleanup에 해당하는 함수가 호출되는 것을 볼 수 있다. componentWillUnmount의 역할을 수행했다.

자, 여기까지 길고 긴 정리가 끝났다...

책에서는 리액트의 훅에 대한 내용이 더 많고, 우리가 알고 싶으면 더 알아보라고 권장한다. 나중에 또 다른 것들을 공부하거나 실습하면서 정리하려고 한다.

요번 시간에는 라이프사이클메서드와 훅에 대한 개념, 그리고 useEffect에 대해 자세히 알고 가는 것 같아서 뿌듯하다:)


출처

https://velog.io/@ejchaid/React%EC%9D%98-%ED%95%84%EC%88%98%EC%9A%94%EA%B1%B4-LifeCycle-API-g8k2lj2la9

https://velog.io/@sik2/React-%EB%9D%BC%EC%9D%B4%ED%94%84-%EC%82%AC%EC%9D%B4%ED%81%B4-API

https://hyogeun-android.tistory.com/entry/4-React-%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0

https://code-anthropoid.tistory.com/110

https://phsun102.tistory.com/75

https://goddaehee.tistory.com/308

생활코딩! 리액트 프로그래밍 책

profile
프론트엔드개발자가 되고 싶어서 열심히 땅굴 파는 자

0개의 댓글