해당 강의에서부터 클래스 컴포넌트와 함수컴포넌트(hooks)가 조금씩 달라진다.

Hooks에는 componentDidMount / componentDidUpdate / componentWillUnmount 이런 메서드가 존재하지 않는다.

1:1로 대응되진 않지만 useEffect로 비슷한 역할을 하게 할 순 있다.

 

useEffect

useEffect(()=>{
	//componentDidMount + componentDidUpdate 합쳐놓은 역할
    
    
    return ()=>{ //componentWillUnmount역할
    }

},[]);

 

내 코드는 강의 코드와 달라서 설명할 수 없지만 강의에서 외워두라고 한것!

  1. useEffect의 두 번째 파라미터는 어떤 값이 바뀔 때마다 useEffect를 실행하고 싶은지 설정하고 싶을 때 사용한다. 위의 예시처럼 빈 배열 [] 을 넣는다면 최초 렌더링 시 딱 한번 실행한다는 의미.
  2. 강의 코드
useEffect(()=>{
	console.log('다시 실행');
    interval.current = setInterval(()=>{setImageCoord(0);},100);
    
    return ()=>{
    	console.log('종료');
        clearInterval(interval.current);
    }
},[imgCoord]);

imgCoord가 바뀔 때마다 해당 useEffect가 실행된다.

그래서 해당 코드는 interval실행하고 return 부분에 있는 코드에 의해 clearInterval을 반복하기 때문에 setTimeout과 동일한 기능을 한다.

 

함수 컴포넌트의 특성 상 state/props가 바뀌면 함수 내부의 코드가 전체 재실행 된다. (클래스형 컴포넌트는 render함수만 재실행) 

 

useEffect에 대해서 외워야할 것 정리

■ 라이프사이클
◎ 해당 컴포넌트가 마운트될 때 실행되는 함수
◎ 해당 컴포넌트가 언마운트될 때 실행

useEffect(()=>{
    return ()=>{
    	//clean up 함수  --> 컴포넌트가 언마운트될 때 실행
    }
},[])


※ 언마운트 될 때는?(JSX라고 가정) showComponent가 false가 될 때 언마운트 되고, true가 될 때 마운트 된다.

{
	showComponent && <Todo/>
}



◎ 리렌더시 마다 실행 (보통 실수일 가능성이 높다.)

useEffect(()=>{
})



◎ todo가 바뀔 때마다 실행 (clean up과의 순서가 매우 중요함)

useEffect(()=>{
	//todo가 바뀔 때 마다 실행됨.
	return ()=>{
		//todo가 바뀌기 직전에 실행됨.
	}
},[todo])


※ 순서가 중요하다 (clean up은 바뀌기 전의 값이라고 생각하자!)
todo가 a->b로 바뀐다고 가정하면
1. a의 useEffect 실행 (최초 1회만 실행되는거 같다.)
2. a의 clean up  실행 
3. b의 useEffect 실행 

 

그 다음에 b -> c로  바뀐다면?

1. b의 clean up 실행

2. c의 useEffect실행 

만약에 a,b,c가 바뀔 때마다 실행되고 싶은데 각각 실행되는 코드가 다르다면? useEffect를 여러개 만들면 된다.
그리고 dependency 배열에 각각 넣어주면됨.

ComponentDidMount

  • render()가 최초 실행 후 실행되는 메서드. (state변경으로 인한 리렌더링이 일어나도 해당 메서드는 재실행되지 않는다.)
  • 해당 메서드에 비동기 요청을 많이 한다.

[비동기 함수 중 setInterval 주의 점]

  1. setInterval을 하고 취소를 하지 않으면 해당 컴포넌트가 사라져도 무한히 실행된다.
  2. setInterval을 하고 취소를 하지 않고, 해당 컴포넌트를 제거, 생성을 반복하면 취소되지 않은 setInterval은 중첩되어 문제가 생긴다.

 

 

복습 : 라이프사이클

클래스 컴포넌트 라이프사이클

  • 생성자(constructor) → render → ref설정 → componentDidMount
  • setState / props 바뀔 때 → shouldComponentUpdate (리렌더할지말지 결정하는 함수) → render → componentDidUpdate
  • 부모가 나를 없앴을 때 → componetWillUnmount → 소멸

 

라이프사이클 관련 메서드

componentDidMount : 렌더가 처음실행되고 componentDidMount가 실행된다. (state변경으로 인한 리렌더링이 일어나도 실행되지 않는다.)

componentDidUpdate  : 리렌더링 후에는 해당 메소드가 실행된다.

componentWillUnmount : 컴포넌트가 제거되기 직전

 

 

RSP 강의 안보고 짠 전체코드

import React, {Component} from "react";

//클래스의 경우 -> constructor -> render -> ref설정 -> componentDidMount ->
// setState, props 바뀔 때 -> shouldComponentUpdate -> render -> componentDidUpdate
// 부모가 나를 없앴을 때 componentWillUnmount -> 소멸
class RSP extends Component{
    state = {
        result:'',
        score:0,
        imgCoord:0,
    }

    intervalId = null;
    position = 0;
    clickable = true;

    // 렌더가 처음실행되고 componentDidMount가 실행된다. (state변경으로 인한 리렌더링이 일어나도 실행되지 않는다.)
    componentDidMount() {
        //여기에 비동기 요청을 많이 한다.
        //만약에 setInterval을 여기다가 쓰고 취소하는 코드를 쓰지않으면 RSP 컴포넌트가 사라지더라도 계속 setInterval은 실행된다.
        //만약에 RSP 컴포넌트가 붙었다가 떼어지면 setInterval이 1번 중첩되어있지만 RSP컴포넌트가 한번 더 붙으면 setInterval이 2번 중첩되어 실행된다.
        this.startRsp();
    }

    //리렌더링 후에는 해당 메소드가 실행된다.
    componentDidUpdate(prevProps, prevState, snapshot) {
    }

    //컴포넌트가 제거되기 직전
    componentWillUnmount() {
        //componentDidMount, componentDidUpdate에서 비동기 작업을 했는데 그게 남아있으면 문제가 되기 떄문에 그런 애들을 정리해주는 것이 componentWillUnmount 이다.

        clearInterval(this.intervalId);
    }

    onClick = (e) => {
        if(!this.clickable)return; //연속클릭 방지
        this.clickable = false;
        const {target} = e;
        const id = target.id;
        clearInterval(this.intervalId);
        let userValue = 0;
        if(id === 'scissor'){
            userValue = 1; 
        }else if(id === 'paper'){
            userValue = 2;
        }

        if(userValue === this.position){
            console.log('무승부!');
            this.setState({result:'비겼습니다.'});
        } else if((userValue+1) % 3 === this.position){
            console.log('이겼어');
            this.setState((prev) => ({...prev, score:prev.score+1, result:'이겼습니다.'}));
        } else{
            console.log('졌어')
            this.setState((prev) => ({...prev, score:prev.score-1, result:'졌습니다.'}));
        }
        setTimeout(()=>{this.startRsp(); },1000)
        setTimeout(()=>this.clickable = true, 1500)
    }

    startRsp(){
        const positionArr = [0, 142, 284];
        let currentIndex = 0;
        if(this.intervalId)clearInterval(this.intervalId);
        this.intervalId = setInterval(()=>{
            this.position = currentIndex % positionArr.length;
            const pix = positionArr[this.position];
            this.setState({imgCoord:`-${pix}px`});
            //console.log(positionArr[index], this.state);
            currentIndex++;
        },100);
    }

    render() {
        const {result, score, imgCoord} = this.state;

        return(
            <>
                <div id="computer" style={{background:`url(https://en.pimg.jp/023/182/267/1/23182267.jpg) ${imgCoord} 0`}}></div>
                <button id="rock" className="btn" onClick={this.onClick}>바위</button>
                <button id="scissor" className="btn" onClick={this.onClick}>가위</button>
                <button id="paper" className="btn" onClick={this.onClick}>보</button>
                <div>{score}</div>
                <div>{result}</div>
            </>
        )
    }
}

export default RSP

도커의 역할

1. 컨테이너 생성

2. 컨테이너 관리 및 컨테이너 내부에 프로그램 설치 가능

3. 컨테이너 실행

 

도커 컴포즈

여러 컨테이너를 결합하여 실행할 수 있게해주는 도구

 

도커 허브

github와 비슷한 개념으로 개인이나 기관이 만든 컨테이너를 업로드하여 공유할 수 있는 클라우드 저장소 (레포지ㅌ리)

도커 등장배경

교역을 위한 항구와 배가 있다고 가정하자.
컨테이너가 발명되기 전에는 트럭으로 전달된 짐들을 모두 내린 후 다시 배에 적재 시키는 작업을 위해 엄청난 노력이 들었을 것이다.

(트럭에서 짐을 내린다 -> 내린 짐을 배에 싣는다.)


해당 작업에서 딜레이가 생기면 짐을 싣고 온 다른 트럭들은 모두 대기해야하는 상황이 생긴다.

이 병목현상을 해결하기 위해 컨테이너라는 것을 두면 각종 짐을 싣고있는 컨테이너만 옮기면 되기 때문에 작업이 굉장히 쉬워진다.

(크레인으로 컨테이너만 배로 옮겨버리면 되기 때문에 작업량 감소 효과!)

도커란?

컨테이너 관리 시스템 


도커 vs 현실 컨테이너

SW(jdk, python 등) 짐(컨테이너 안에있는)
OS 컨테이너
인프라(컴퓨터)


도커 컴포즈

컨테이너끼리의 상호작용을 가능하게 하는 관리 도구
(여러 컨테이너를 하나의 어플리케이션으로 동작할 수 있도록 해준다.)

도커의 특징

WORA (Write Once Run Anywhere) : 컨테이너를 한번 잘 만들어놓으면 다른 컴퓨터에서도 동일하게 작동한다.

렌더함수가 실행되면 React가 JSX를 DOM에 딱 붙여주는데 그 순간을 Catch하여 무언가 할 수 있다.

 

React의 라이프사이클 소개

우선 class 기반으로 라이프사이클을 알아보자.

1. client.jsx에 의해 렌더링 되었을 때

constructor -> render -> ref설정 -> componentDidMount -> 화면에 표시된다.

 

2. setState / props 바뀌었을 때 

shouldComponentUpdate -> render -> componentDidUpdate

 

3. 부모가 나를 없앴을 때

componentWillUnmount -> 소멸

강의를 보지않고 혼자서 hooks로 전환했을 때는 버그가 있었다. setTimeout이 제대로 취소되지 않았고, startTime이 제대로 설정되지 않아서 평균 반응속도가 이상하게 나오는 현상이 발생했다. 

강의를 보고 이유를 알아보니 React Hooks에서 값이 바뀌는 부분은 그냥 사용하면 안되고 useRef를 사용해야한다는 것을 새롭게 알게 되었다.

useRef vs useState

useRef와 useState의 차이 : 렌더링의 차이가 있다.

useRef는 값이 바뀌어도 렌더링이 안되지만 useState는 값이 바뀌면 리렌더링이 발생한다.

 

useRef 사용처 2가지

1. useRef는 DOM을 넣는거 말고 다른 방식으로 사용되는게 값이 바뀌기는 하지만 화면에 영향을 끼치고 싶지는 않을 때 사용한다.

2. DOM을 넣어두고 DOM을 직접 제어하고 싶을 때 사용

 

※ useRef는 current써주는것에 주의해야한다.

 

import React, {useRef, useState} from "react";
import response from "./Response";

const ResponseHooks = () => {
    const [state,setState] = useState('waiting');
    const [message, setMessage] = useState('클릭해서 시작하세요.');
    const [result, setResult] = useState([]);
    const timer = useRef(null);
    const startTime = useRef(0);

    const onClickScreen = () => {
        if(state === 'waiting'){
            setState('ready');
            setMessage('색이 바뀌면 클릭해주세요');
            const max = 2000; //2초
            const min = 500 // 0.5초
            const randTime = Math.floor(Math.random() * (max - min + 1)) + min;
            timer.current = setTimeout(()=>{
                startTime.current = new Date().getTime();
                setState('now');
                setMessage('클릭해주세요!!');
            },randTime);
        }else if(state === 'ready'){
            alert('너무 성급합니다. 다시 시작하겠습니다.');
            setState('waiting');
            setMessage('클릭해서 시작하세요.');
            clearTimeout(timer.current);
        }else if(state === 'now'){
            clearTimeout(timer.current);
            const endTime = new Date().getTime();
            const responseTime = endTime - startTime.current;
            setMessage('클릭해서 시작하세요.');
            setState('waiting');
            setResult((prev) => {
                return [...prev, responseTime];
            });
        }

    }

    const onResetClick = () => {
        setResult([]);
    }

    const renderAverage = () => {
        return result.length === 0 ?
            null:
            <>
                <div>평균 시간{result.reduce((a,c)=>a+c) / result.length }ms</div>
                <button onClick={onResetClick}>리셋</button>
            </>
    }

    return(
        <>
            <div
                id="screen"
                className={state}
                onClick={onClickScreen}
            >
                {message}
            </div>
            {renderAverage()}
        </>
    )
}

export default ResponseHooks;

hooks로 변환된 ResponseHooks.jsx 전체코드

반응속도체크 코딩

전체코드

import React, {Component} from "react";

class Response extends Component{
    state = {
        state:'waiting',
        message:'클릭해서 시작하세요.',
        result:[],
    }
    timer = null;
    startTime = 0;

    onClickScreen = e => {
        const {state, message, result} = this.state;
        const {target} = e;
        if(state === 'waiting'){ //기달 -> 준비
            this.setState({state:'ready', message:'색이 바뀌면 클릭해주세요'});
            const max = 2000; //2초
            const min = 500 // 0.5초
            const randTime = Math.floor(Math.random() * (max - min + 1)) + min;
            this.timer = setTimeout(()=>{
                this.setState({state:'now', message:'클릭해주세요!'});
                this.startTime = new Date().getTime();
            },randTime);
        }else if(state === 'ready'){
            //너무 성급하다 다시 시작하겠다 멘트
            alert('너무 성급합니다. 다시 시작하겠습니다.');
            this.setState({state:'waiting', message:'클릭해서 시작하세요.'});
            clearTimeout(this.timer);
        }else if(state === 'now'){
            const endTime = new Date().getTime();
            clearTimeout(this.timer);
            const responseTime = endTime - this.startTime;
            this.setState((prevState) => {
                return {result:[...prevState.result, responseTime], state:'waiting', message: '클릭해서 시작하세요.'}
            });
        }
    }

    renderAverage = () => {
        const {result} = this.state;
        return result.length === 0 ?
            null:
            <div>평균 시간{this.state.result.reduce((a,c)=>a+c) / this.state.result.length }ms</div>
    }

    render() {
        const {state, message, result} = this.state;
        return (
            <>
                <div
                    id="screen"
                    className={state}
                    onClick={this.onClickScreen}
                >
                    {message}
                </div>
                {this.renderAverage()}
                {
                    result.map(v => <div>{v}ms</div>)
                }
            </>
        );
    }
}

export default Response;

강의 보기 전에 미리 코딩해봄. 그래서 강의랑 좀 다름.

 

※ setTimeout은 call stack으로 넘어가서 실행이 되는데 콜스택으로 넘어가더라도 clearTimeout으로 취소 가능함.

jsx안에서 for문과 if문을 사용하는것이 매우 힘들다.

그래서 map, reduce같은 배열의 메서드를 사용하는데 이번에는 반응속도 게임을 만들어보면서 평균 시간을 구할 때 reduce를 사용해보자.

 

renderAverage = () => {
        const {result} = this.state;
        return result.length === 0 ?
            null:
            <div>평균 시간{this.state.result.reduce((a,c)=>a+c) / this.state.result.length }ms</div>
}

render() {
        const {state, message, result} = this.state;
        return (
            <>
                <div
                    id="screen"
                    className={state}
                    onClick={this.onClickScreen}
                >
                    {message}
                </div>
                {this.renderAverage()}
            </>
        );
    }

보통 JSX에서 조건문을 사용할 떄는 삼항연사자를 많이 사용하거나 값A && 값B 이러한 평가식을 많이 사용한다.

해당 예시에서는 삼항연사자를 사용했다.

 

result.length === 0 ? null : <div>{~}</div>

이렇게 하면 result.length가 0이 아닐 경우에만 <div>{~}</div>를 렌더링한다.

React.createRef는 클래스 컴포넌트에서도 함수 컴포넌트의 useRef hooks처럼 사용 할 수 있는 방법이다.

useRef처럼 사용하기 때문에 current도 붙여줘야한다.

 

react에서 dom reference를 가져오는 방법

1. class에서 가져오기

input;
onRefInput = c =>{
    this.input = c;
}

render(){
	...
    <input ref={this.onRefInput} />
    ...
}

 

2. hooks에서 가져오기

import {useRef} = from "react";

const inputRef = useRef(null);

//ref 사용하기
const use = e => {
	inputRef.current.focus();
}

return (
	...
    <input type="text" maxLength={4} ref={inputRef}
    ...
)

 

 

근데 클래스 컴포넌트에서도 함수 컴포넌트처럼 가져오는 방법이 있다.

import React, {Component, createRef} from "react";

createRef를 import한다.

 

/*
onRefInput = c => {
    this.input = c;
}
*/

//위의 코드를 대체한다.
input = createRef();

 

render(){
	return (
    	<input ref={this.input} />
    )
}

이렇게 하면 된다.

 

근데 사용할 때도 hooks와 같이 current를 사용해줘야한다.

this.input.current.focus(); 이렇게. current를 더 써줘야하니까 더 안좋은게 아니냐고 반문할 수도 있지만 hooks와 통일성이 있기 때문에 외울 것이 하나 줄어드는 장점을 가진다.

 

 

1. Props를 자주 사용하다 보면 생기는 문제

렌더링이 자주 일어나서 성능이 안좋아지는 문제가 있다.

 

해당 문제를 해결하기 위해서 크롬 확장 프로그램 중에 React Devtools를 설치해서 props나 state부분을 살펴볼 수 있다는 내용이었음..

 

리렌더가 되는 타이밍 : state나 props가 바뀌었을 때 (props가 state였을 때 변경된다.. 일반 변수 넘긴 후 변경해봤는데 안됨..)

 

강의 내용에서 클래스 컴포넌트로 테스를 해봤는데 리렌더가 되는 타이밍은 사실 this.setState가 호출되는 시점이었다. (state가 바뀌지 않은 상황이더라도 호출은 되었으니 리렌더가 일어남.)

 

이것을 방지하려면

shouldComponentUpdate

을 구현해서 이전값과 다를 때만 렌더링되도록 true를 반환하면 되는데 클래스컴포넌트 문법이라서 따로 따라하지는 않았다.

+ Recent posts