커스텀 훅

  • 어떤 특정한 Hook 2개 이상이 반복된다면 커스텀 훅을 만들어낸다.
  • 커스텀 훅은 react의 기본 hooks의 조합으로 이루어진다. (기본 훅 여러 개가 세트로 움직인다면? 3개를 세트로 묶어서 커스텀 훅 1개로 퉁 쳐버리면 된다.)
import {useEffect, useRef} from "react";

// 기능
// useInterval(()=>{
//  console.log('hello');
// }, isRunning ? 1000 : null );
function useInterval(callback, delay){
    const savedCallback = useRef();

    useEffect(() => {
        savedCallback.current = callback;
    });

    useEffect(()=>{

        //왜 tick이라는 함수로 감싸서 실행했을까?
        function tick(){
            savedCallback.current();
        }

        if(delay !== null){
            let id = setInterval(tick, delay);
            return () => clearInterval(id);
        }
    },[delay]);

    return savedCallback.current;
}

export default useInterval;

강의에서 만들어 본 useInterval이라는 (useRef + setInterval) 커스텀 훅이다.

 

왜 tick이라는 내부함수가 필요한지에 대해서도 설명해주는데 이해하기 좀 난해하다.

 

따로 기억할것.

※useRef는 항상 최신의 객체를 참조할 수 있게 해준다 라는 것을 기억하자.

가상화 vs 컨테이너(도커)

가상머신과 도커는 리얼 머신이 아니라는 점에서 비슷하지만 작동 방식에서 차이를 보인다.
가상머신은 편하긴 하지만 성능이 좋지 못하다. (CPU에 가상화를 위한 기능들이 많이 들어갔지만 아직도 리얼머신에 비해 많이 느리다.)

 

전가상화와 반가상화

전가상화의 성능 개선을 위해 개발된 반가상화가 널리 쓰이고 있다.

 

가상화 계층도

가상 머신 자체는 완전한 컴퓨터이기 때문에 게스트 OS를 항상 설치해야한다. 그래서 이미지 용량이 커진다.

 

도커 계층도

가상화와 달리 도커는 게스트 OS를 설치하지 않고 OS자원은 호스트와 공유하는 방식이다. 이미지 용량이 크게 줄어들었으며 HW 가상화 계층이 없기 때문에 메모리 접근, 파일시스템, 네트워크 속도가 가상 머신에 비해 월등히 빠르다.

 

도커는 이미지 버전을 관리할 수 있고 저장소(docker hub)에 이미지를 업로드하거나 다른 이미지를 다운로드 받을 수 있는 등 배포에 특화된 기능을 제공해준다.

 

리눅스 컨테이너

LXC는 컴퓨터를 통째로 가상화하여 OS를 실행하는 것이 아닌 리눅스 커널 레벨에서 제공하는 일종의 격리된 가상 공간이다. 이 기상공간은 OS가 설치되지 않기 때문에 가상머신이 아닌 컨테이너라고 부른다.

 

리눅스 커널의 ControlGroups(cgroups)는 CPU, 메모리, 디스크, 네트워크 자원을 할당하여 완전한 형태의 가상공간을 제공한다. 또한 프로세스 트리, 사용자 계정, 파일시스템, IPC등을 격리시켜 호스트와 별개의 공간을 만든다. (chroot 명령어처럼) 이것을 Namespace isolation(namespaces) 라고 한다.

 

LXC는 리눅스 커널의 cgroups와 namespaces기능을 활영하여 가상 공간을 제공한다.

 

LXC의 구조

LXC는 격리된 공간만 제공할 뿐 개발 및 서버 운영에 필요한 부가 기능이 부족한데, docker는 리눅스 커널의 cgroups와 namespaces를 기반으로 하여 이미지, 컨테이너 생성 및 관리 기능과 부가기능을 제공한다.

 

따라서 컨테이너는 리눅스 커널을 기반으로 하기 때문에 윈도우에서 도커같은 컨테이너 기술을 사용하기 위해서는 리눅스 하위시스템이 필요하다. (wsl2나 따로 설정이 필요)

 

docker 명령어 전송방식

  1. Host OS에서 docker container에 명령어 전송
  2. docker daemon이 명령어를 받아서 container에게 전달해준다.

컨테이너 vs 이미지

이미지 

도커에서 실행되지 않고 있는 서버운영에 필요한 소스코드, 라이브러리 등이 셋팅된 상태의 일종의 프로그램 상태
(프로그램은 하드디스크에 저장되어있는 실행되고 있지 않은 상태를 의미하기도하니까)
(이미지는 특정 프로세스를 실행하기 위한 모든 파일과 설정, 의존성을 해결한 상태로 Ubuntu이미지는 Ubuntu를 실행하기 위한 모든 파일을 가지고있다.)

 

컨테이너

이미지를 실행시켜 메모리에 로딩한 상태

비교

state가 바뀌었을 때 

  • class의 경우 componentDidMount나 componentDidUpdate에서 모든 state를 조건문으로 분기처리한다.
  • hooks의 경우 useEffect의 2번째 파라미터인 의존배열만 다르게 하여 useEffect를 여러 개 사용하면 된다. (배열에는 꼭 useEffect를 다시 실행할 값만 넣으세요)

state를 동시에 담당하는 법(?)

  • componentDidMount는 [result, imgCoords, score] 이러한 state들을 한번에 담당한다.  (하나의 componentDidMount 에서 state별로 조건분기를 하든 어떻게 하든 하나의 클래스에는 하나의 라이프사이클 메소드만 존재한다고 말하고 싶은거같다.) --> (componentDidUpdate,componentWillUnmount 도 마찬가지이다.)
  • hooks에서는 result용 useEffect, imgCoords용 useEffect 이렇게 useEffect를 state별로 하나씩 만들 수 있다. 하지만 하나의 useEffect에서 동시에 result, imgCoords 2개의 state를 담당할 수 도 있긴함.
  •  
//클래스에서는 하나의 라이프사이클 메서드에서 state여러 개를 한번에 처리
componentDidMount(){
	this.setState({
    	imgCoord: 3,
        score: 1,
        result: 2,
    })
}


//hooks의 useEffect에서는 각각 처리하거나, 묶어서 처리 가능
useEffect(()=>{
	setImgCoord(2);
	setScore(0);
},[imgCoord, score]);
useEffect(()=>{
	setResult('');
},[result]);

 

useEffect vs useLayoutEffect

  • useEffect는 렌더링이 실행되고 나서 실행된다.
  • useLayoutEffect는 렌더링 되기 전에 실행돼서 화면 바뀌는것을 감지하는 Effect이다. (자주 사용안한다고 하심)

 

복습

  • 부모 컴포넌트에서 자식 컴포넌트를 가지고 있을 때 부모 컴포넌트가 리렌더링 되면 자식 컴포넌트도 무조건 리렌더링 되는데 부모 컴포넌트가 리렌더링 될 때 굳이 자식 컴포넌트를 리렌더링 할 필요가 없을 때 React.memo를 사용하면 자식 컴포넌트는 리렌더링 되지 않는다.
  • useEffect 하나에서 componentDidUpdate,componentWillUnmount, componentDidMount 세 개를 동시에 처리할 수 있다고 생각하면 편리할 듯!

 

 

 

해당 강의에서부터 클래스 컴포넌트와 함수컴포넌트(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 -> 소멸

JSX에서 js의 for문과 if문을 사용하지 못하는 것은 아니지만 코드가 지져분해져 많이 사용되진 않지만 사용법을 알려주시니 기억은 하고 있자..

 

if문 사용하기

if문을 사용하는 방법은 함수를 사용하는 방법이다. jsx에서 { } 를 사용하면 js문법을 사용할 수 있는데 거기에 즉시실행함수를 실행하고 해당 함수 내부에 if문을 사용하면 된다.

    return(
        <>
            ...
            {(()=>{
                if(result.length === 1){
                    return null;
                }else{
                    return <>
                        <div>{result.length}</div>
                    </>
                }
            })()}
            
            ....
		</>
    )

 

 

for문 사용하기

for문도 if문과 동일하다. 함수안에 넣으면 사용하기 그나마 간편해진다. (함수는 즉시실행함수여야한다.)

return (			
		...
            {
                //반복문
                (()=>{
                    const array = [];
                    for(let i =0; i<5; i++){
                        array.push(<div>안녕하신가</div>);
                    }
                    return array;
                })()
            }
        ...
            
)

 

JSX에서 배열을 반환하는 것.

jsx에서 배열을 반환해도 된다. 하지만 실무나 연습할 때 좀 처럼 사용되진 않는다고 하셨다. apple coding 강의볼 때 탭구현할 때 본거 같기도하다.

return [
	<div key="apple">사과</div>
    <div key="AsianPear">배</div>
    <div key="banana">바나나</div>
    <div key="mandarin">귤</div>
]

 

 

 

전체코드

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>
            {(()=>{
                if(result.length === 1){
                    return null;
                }else{
                    return <>
                        <div>{result.length}</div>
                    </>
                }
            })()}

            {
                //반복문
                (()=>{
                    const array = [];
                    for(let i =0; i<5; i++){
                        array.push(<div>안녕하신가</div>);
                    }
                    return array;
                })()
            }
            {renderAverage()}
        </>
    )
}

export default ResponseHooks;

강의를 보지않고 혼자서 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 전체코드

+ Recent posts