useReducer 왜 필요한가?

state의 갯수가 너무 많아지면 state와 setState함수들의 관리가 매우 어려워 지고, props로 넘겨줄 때 매우 많은 것을 넘겨줘야하는 경우가 왕창 생긴다.

그럴 때 state자체의 갯수를 줄이기 위해 필요하다고 함. (아직 감이 안오긴한다.)

 

TicTacToe.jsx의 구조는 아래와 같다.

TicTacToe > Table > Tr > Td

state들은 모두 TicTacToe.jsx에 있고 우리가 실제로 누르는 것은 Td컴포넌트 인데.. 이렇게 되면 state들과 setState함수들을 Table, Tr을 거쳐 Td까지 전달해주어야 한다.

 

이런 번거로운 경우를 해결하기 위해 ContextAPI를 많이 사용하지만 ContextAPI는 다음시간에 다뤄볼것이고, 이번에는 state 자체의 개수를 줄이는 useReducer를 다뤄본다고 함.

 

현재 winner, turn, tableData라는 state가 있는데 useReducer를 사용하면 딱 하나의 state와 하나의 setState함수로 통일할 수 있다고 한다.

    //아래의 세가지 스테이트를 한개로 통일 가능.
    const [winner, setWinner] = useState('');
    const [turn, setTurn] = useState('O');
    const [tableData, setTableData] = useState([['','',''],['','',''],['','','']]);
    
    //아래와 같이 통일이 가능하다.
    const [state, dispatch] = useReducer(reducer, initialState);

복잡한 state들이 한개로 통일되니까 가독성이 훨씬 좋아지는 것 같다.

 

1. 이를 위해선 당연히 useReducer의 import가 필요하다.

import {useReducer} from 'react';

 

2. initialState(초기 상태값들)

//state 초기값
const initialState = {
    winner : '',
    turn: 'O',
    tableData:[['','',''],['','',''],['','','']],
}

 

3. reducer (배열의 리듀서 함수처럼 어떤것을 줄이기 위한 함수!)

//리듀서 == 함수
const reducer = (state, action) => {

}

 

※ ContextAPI + useReducer = 소규모 앱에서 리덕스를 대체할 순 있지만 규모가 큰 앱이면 결국엔 리덕스가 필요하다고함.

 

 

현재까지의 코드

TicTacToe.jsx

import React, {useReducer, useState} from "react";
import Table from "./Table";

//state 초기값
const initialState = {
    winner : '',
    turn: 'O',
    tableData:[['','',''],['','',''],['','','']],
}
//리듀서 == 함수
const reducer = (state, action) => {

}

const TicTacToe = () => {
    const [winner, setWinner] = useState('');
    const [turn, setTurn] = useState('O');
    const [tableData, setTableData] = useState([['','',''],['','',''],['','','']]);

    const [state, dispatch] = useReducer(reducer, initialState);

    return(
        <>
            <Table />
            {
                winner && <div>{winner}님의 승리</div>
            }
        </>
    );
}

export default TicTacToe;

 

Table.jsx

import React from 'react';
import Tr from "./Tr";

const Table = () => {
    return (
        <Tr>
            
        </Tr>
    );
};

export default Table;

 

Tr.jsx

import React from 'react';
import Td from "./Td";

const Tr = () => {
    return (
        <Td>

        </Td>
    );
};

export default Tr;

 

Td.jsx

import React from 'react';

const Td = () => {
    return (
        <td>

        </td>
    );
};

export default Td;
  • Hooks를 조건문 안에 절대 넣으면 안되고, 함수나 반복문 안에도 웬만하면 넣지 말자.
    • 아래 코드와 같이 hooks는 조건문 안에 넣으면 안된다. 반복문의 참,거짓여부에 따라 실행되는 순서가 달라지면 안되기 때문.
useState([]);
useState(1);
if(~){
	useState(false);
}
useMemo
  • 다른 hooks안에 hooks를 넣지 말자. 
    • useEffect, useCallback, useMemo가 실행될 수도있고 안될수도 있는 상황이 있을 수도 있기 때문.
useEffect(()=>{
	useState(~);
},[]);

useCallback(()=>{
	useState(~);
},[]);

useMemo(()=>{
	useState(~);
},[]);

 

지금까지의 내용 정리

  • useMemo는 값을 저장한다. (함수의 반환값), 의존배열 요소들이 바뀌기 전까지
  • useCallback은 함수 자체를 기억한다. 의존배열 요소들이 바뀌기 전까지
  • useEffect는 전달받은 콜백함수를 실행한다. 의존배열 요소들이 바뀔 때.
  • 두번째 배열들의 인자들이 바뀌면 앞에것들이 다시 실행된다!

useEffect심화

  • componentDidMount에서는 아무것도 안하고, componentDidUpdate에서만 ajax 요청을 하고싶다면? 아래의 패턴을 기억하자.
const mounted = useRef(false);
useEffect(()=>{
	if(!mounted.current){
   	 	mounted.current = true; //componentDidMount를 막을 순 없으니 이렇게 아무것도 안하도록 해두면 된다.
    } else {
    	//ajax 요청
    }
},[바뀌는값])
  • componentDidUpdate만 하고 싶다
useEffect(()=>{
	//ajax 요청하면됨.
},[])
componentDidUpdate(prevProps,prevState){
	if(prevState.winNumbers !== this.state.winNumbers){
    	console.log('로또 숫자를 생성');
    }
   	if(~){
    }
}

useState(()=>{
	console.log('로또 숫자를 생성');
},[winNumbers])

componentDidUpdate는 하나의 메서드에서 모든 state를 나열해서 사용한다. (가로)

hooks에서 useEffect는 따로따로 사용.. state들이 바뀌었을 때 각각 실행되어야하는 코드가 다르면 useEffect를 여러 개 만들면 된다. (세로)

componentDidUpdate는 한방에 해당 메서드에서 처리

hooks에서는 useEffect별로 따로따로.

docker run 명령어와 자주 쓰이는 옵션들 정리

우분투 컨테이너 다운로드 및 실행

docker run -d --name myubuntu ubuntu

run을 한 다음 docker ps 를 했을 때 실행 중인 우분투 컨테이너가 보일 줄 알았지만 해당 컨테이너는 실행과 동시에 종료되어 docker ps -a를 해야만 볼 수 있다.

 

우분투 컨테이너가 실행과 동시에 종료되는 이유

https://ilikecoding.tistory.com/78

 

도커 입문 6강. 컨테이너 실행하기

컨테이너 특징(생명주기 관련)컨테이너 내부에 프로그램이 일회성 이라면 컨테이너를 띄우자마자 종료되어버린다.즉, 컨테이너에서 실행될 프로그램이 데몬처럼 백그라운드에 떠있으며 무언

ilikecoding.tistory.com

6강에서도 설명하셨는데.. 컨테이너 내부 프로그램이 일회성이라면 컨테이너를 띄우자마자 종료되어버린다고함.

 

 

우분투 이미지는 내부에 실행될 프로그램이 없기 때문에 바로 종료.. httpd는 내부에 아파치 프로그램이 무한루프로 돌고 있기 때문에 종료되지 않는다.

 

[우분투 이미지 실행]

우분투 ▶ RUN ▶ 죽음(내부에 실행할 프로그램이 없다.)

[httpd 이미지 실행]

httpd ▶ RUN ▶ 내부에 무한루프로 Listener가 돌고있어서 종료되지 않는다.
httpd image = ubuntu + apache 인 것.

 

우분투 이미지 종료를 막는 방법

docker run -dit --name myubuntu ubuntu

바로 -it 옵션을 사용하는 것이다.

-i : interaction 상호작용을 하겠다.

-t : terminal 터미널 모드로 사용 (/bin/bash를 자동으로 실행시켜준다.)

--name : 도커 컴포즈를 할 때 컨테이너끼리 결합을 할 때 --link 옵션의 파라미터로 컨테이너 이름을 넘겨주기 위한 식별값.

(도커 컴포즈 뿐만 아니라 하나의 이미지로 여러 컨테이너를 띄울수도 있는데 각각 컨테이너 마다 붙일 수 있는 식별값으로 생각하면 될것 같다.)

 

 

우분투를 해당 옵션으로 다시 실행하기 위해서는 일단 전에 바로 종료되었던 컨테이너를 삭제해야한다.

docker rm $(docker ps -a -q --filter name=myubuntu)

 

실행중인 컨테이너에 접근하는 명령어

docker attach [CONTAINER ID]

-it 옵션으로 컨테이너를 실행하면 /bin/bash프로그램이 실행되는데 그때 우분투에 접속해서 bash 프로그램과 상호작용할 수 있는 명령어이다.

도커 이미지 삭제 프로세스

실행 중인 컨테이너 중지(stop) ▶ 중지된 컨테이너 삭제(rm) ▶ 이미지 삭제 (rmi)

 

실행중인 컨테이너 확인 후 종료 및 삭제

docker ps
docker stop [CONTAINER ID]
docker rm [CONTAINER ID]

중지된 컨테이너 확인 및 삭제

docker ps -a
docker rm [CONTAINER ID]

이미지 확인 및 삭제

docker images
docker rmi [IMAGE ID]

 

(이미지를 삭제하기 위해선 해당 이미지로 실행중 이거나 중지된 컨테이너가 없어야 한다.)

 

위와 같은 프로세스는 많은 이미지를 지울 때 너무 많은 것을 반복해야한다.

#실행중인 모든 컨테이너 종료
docker stop $(docker ps -q)
#중지된 컨테이너 모두 삭제
docker rm $(docker ps -a -q)
#모든 이미지 삭제
docker rmi -f $(docker images -q)

※ $(docker ps -a -q)

$()로 감싸면 변수의 값으로 취급한다. docker ps -a -q 결과값을 docker stop의 입력값으로 취급한다는 이야기이다.

-q옵션 : IMAGE나 CONTAINER의 ID만을 반환하는 옵션

--filter name 옵션 --filter name=jenkins 처럼 --filter name 옵션을 활용하면 내가 원하는 값만 필터링할 수 있으니 잘 사용해보자.

useMemo : 1번째 인자로 받은 값을 캐싱 하고 있는 Hooks, 2번째 인자가 바뀌지 않는 한 다시 실행되지 않음.

Hooks로 바뀌면서 리렌더 될 떄마다 로또 숫자를 뽑는 함수인 getWinNumber() 가 계속 호출되는 문제가 있어서 useMemo를 사용해야 했다.

const lottoNumbers = useMemo(()=> getWinNumbers(), []); //2번째 인자로 넘겨준 배열안의 값이 바뀌지 않는 이상 재실행되지 않는다.
const [winNumbers, setWinNumbers] = useState(lottoNumbers);

 

하지만 아래 링크의 강의내용에서도 같은 내용이 나오는데 useState에서 두 번째 파라미터로 함수자체를 넣어주면 알아서 lazy init을 해준다고 했는데 이번에는 다른 방식으로 해결하는 것이 참 인상깊었다.

( section2. 3-8. 숫자야구 Hooks로 전환하기(+useState lazy init))

 

section2. 3-8. 숫자야구 Hooks로 전환하기(+useState lazy init)

Hooks 변경한 코드import React, {Component, useState, useRef} from "react";import TryHooks from "./TryHooks";function getNumbers(){ const numbers = new Array(9).fill(0).map((v,i)=>v+i+1); const shuffle = []; while(numbers.length > 0){ const rand = Math

ilikecoding.tistory.com

 

useMemo vs useRef

useMemo : 복잡한 함수 결과값을 기억

useRef : 일반 값을 기억

 

useMemo vs useCallback

useMemo : 함수 결과값을 기억한다.

useCallback : 함수 자체를 기억한다.

const onClickRedo = useCallback(()=>{
  setWinNumbers(getWinNumbers());
  setWinBalls([]);
  ...
},[]);

이렇게 useCallback을 사용하면 함수 컴포넌트가 리렌더 되면서 onClickRedo가 재생성 되는 것을 방지할 수 있다.

만약에 함수 생성하는 것 자체가 너무 오래걸려서 함수 생성 자체가 비용이 크다면 함수 자체를 기억해둘 수 있다.

(useCallback을 사용하지 않으면 함수 컴포넌트는 리렌더 되면 해당 함수 컴포넌트 자체가 재실행 되기 때문에 원래라면 onClickRedo같은 함수도 재생성된다고함.)

 

그렇다면 useCallback으로 모든 함수를 감싸면 이득인가?

반은 맞고 반은 틀리다고 함.

useCallback안에서 쓰는 state들은 항상 2번째 파라미터에도 값을 적어주어야 한다.

const onClickRedo = useCallback(()=>{
  console.log(winNumbers);
  setWinNumbers(getWinNumbers());
  setWinBalls([]);
  ...
},[]);

이렇게 state를 썼는데 2번째 파라미터에 값을 넘겨주지 않으면??? winNumbers가 최신화가 되지 않아서 해당 함수는 예전 state를 사용하게 된다.... winNumbers는 바뀌었는데 기억을 너무 잘해서 예전꺼를 그대로 사용하는 문제가 발생한다.

(강의 09:00 참고)

 

const onClickRedo = useCallback(()=>{
  console.log(winNumbers);
  setWinNumbers(getWinNumbers());
  setWinBalls([]);
  ...
},[winNumbers]);

이렇게 useMemo나 useCallback은 둘 다 기억을 하는데 너무 기억을 잘하면 이전값을 사용해버리는 문제가 생기기 때문에 잊어버릴 필요도 있는데.. 언제 잊어버려야하는가? 이 두 번째 배열의 요소들이 바뀌었을 때이다.

 

useCallback 필수로 적용해야할 때

자식 컴포넌트에 함수를 넘길 때는 useCallback을 꼭 해줘야 한다.

useCallback이 없다면 매번 새로운 함수가 생성된다. 그럼 자식 컴포넌트는 어? 부모로 받은 프롭스가 바뀌었네? 하면서 매번 새로 렌더링을 해버린다고 한다.

useEffect : 2번째 파람이 빈 배열 == componentDidMout와 동일

배열에 요소가 있다면 ? componentDidMout, componentDidUpdate 둘 다 수행

 

timerRef (강의 내용에서의 timeouts.current) 가 바뀌는 시점을 헷갈렸다.

왜 강의에서 timeouts.current를 useEffect의 2번째 파라미터로 넣었는지 헷갈렸는데... 다시 생각해보니 내가 헷갈렸던 것...

 

timerRef.current는 배열이기 때문에 push, shift 또는 배열 인덱스로 직접 접근하는 것은 배열 참조(배열의 주소)가 바뀌는 것이 아니기 때문에 바뀌는 것으로 간주하지 않는다.

React에서 배열이나 객체로 된 state나 ref, prorps가 바뀌는 것은 배열의 참조가 바뀔 때 이므로

timerRef.current = []

이렇게나

 tirmerRef.current = [...prev, 0];

이렇게 spread 연산자를 사용하여 새로운 참조를 만들어 줄 때 바뀌는 것이다.

 

 tirmerRef.current[7] = 100; //이건 변경된것으로 간주하지 못한다. 그냥 같은 배열주소에 하나 더 추가된것일뿐
 //변경된 것으로 간주되려면 배열참조가 바뀌어야함!

 

포트포워딩하기

docker run -d #detach의 약자로 백그라운드에서 작동하도록 하는 옵션 (daemon으로 외워도 될듯하다)
docker run -p #port를 지정해주는 옵션으로 "HOST:CONTAINER" 형식으로 나열
docker run -d -p 8080:80 httpd #호스트의 8080으로 접근하면 컨테이너 80포트로 전달해준다

 

현재 나의 구성도

[인터넷] - KT공유기 - DMZ - IPTIME공유기 - H2서버

 

Apache 컨테이너 실행하기

docker run -d -p 9096:80 httpd

이제 호스트OS로 9096으로 요청하면 아파치 컨테이너의 80포트로 연결해주게 된다.

원격지에서 실험해보니 정말 잘동작한다.

원격지:9096 ▶ 인터넷 ▶ KT공유기 ▶ DMZ ▶ IPTIME공유기 ▶ 9096포트를 H2:9096으로포트포워딩 
▶ H2서버9096 ▶ httpd컨테이너

이렇게 되어있는 건데 iptime공유기에서 외부의 9096을 내부의 9096으로 설정해놔서 가능하다.

KT공유기와 iptime공유기는 DMZ설정이 되어있어서 모든 요청을 IPTIME공유기로 통과하도록 설정해놨다.

 

Apache 컨테이너 실행하기

docker run -d -p 9096:80 httpd

이제 호스트OS로 9096으로 요청하면 아파치 컨테이너의 80포트로 연결해주게 된다.

 

Nginx 컨테이너 실행하기

docker run -d -p 9097:80 nginx

apache나 nginx나 같은 80포트를 사용하지만 HostOS에서 포트포워딩을 해주니까 상관없다는 점이 정말 좋은거 같다.

9096으로 접근하면 httpd 80

9097로 접근하면 nginx 80

 

 

※ docker run을 할 때 로컬에 저장되지 않은 이미지를 지정하면 도커허브에서 자동으로 pull해서 run을 해준다.
즉, docker run nginx를 하였을 때, 로컬에 nginx이미지가 있으면 있는것을 사용하고 없으면 자동으로 도커허브에서 다운로드받아서 run해준다.

 

 

 

 

 

 

 

 

 

컨테이너 특징(생명주기 관련)

컨테이너 내부에 프로그램이 일회성 이라면 컨테이너를 띄우자마자 종료되어버린다.

즉, 컨테이너에서 실행될 프로그램이 데몬처럼 백그라운드에 떠있으며 무언가를 처리하는 종류가 아니라 어떤 일을 끝마치고 종료되는 프로그램이라면 컨테이너도 그 프로그램이 종료되면 같이 종료되어버린다고 한다.

 

명령어

이미지 다운로드 (테스트를 위해 tomcat)

docker pull tomcat

컨테이너 실행하기

docker run tomcat # 포그라운드로 컨테이너가 실행된다.

 

컨테이너 백그라운드로 실행하기

일반적으로 docker run tomcat같은 명령어로 실행하면 컨테이너가 포그라운드로 실행되어 해당 터미널에서는 어떤한 작업도 할 수 없게 된다. (또한 해당 터미널 접속이 끊어지면 실행중이던 컨테이너도 중지되므로 매우 불편하다.) 그래서 대부분 컨테이너를 실행할 때는 백그라운드 모드로 실행한다.

docker run -d [IMAGE ID]

 

컨테이너가 정상적으로 실행되고 있는지 확인하기 (실행중인 컨테이너, 중지된 컨테이너 두 개다 확인하는 방법)

docker ps #현재 실행중인 컨테이너 확인
docker ps -a #실행중이거나 중지된 컨테이너까지 모두 확인

컨테이너를 중지시키는 방법

docker stop [CONTAINER ID]

도커에서 실행중인 톰캣으로 접근이 안되는 이유

도커에 톰캣을 띄워놓고 호스트OS나 다른 곳에서 localhost:8080 을 해도 도커에 실행중인 톰캣에 접근이 안되는 이유는 도커와 호스트OS는 다른 네트워크 대역을 사용하고 있기 때문이다.

만약에 도커에 있는 톰캣으로 접근하고 싶다면 호스트OS에서 도커로 접근하는 요청에 대해 포트포워딩을 해줘야 한다.

 

가위바위보에서 굳이 useEffect나 componentDidMount 에서만 setInterval을 사용하는건 아니고, 필요할 때 setInterval을 쓰고 componentWillUnMount에서 정리만 잘하면 된다.

 

Hooks는 함수 컴포넌트를 말하는 것이 아니라 useEffect, useState 같은 것들을 말하는 것이다.

 

Lotto.jsx (내가 만든 코드 + 강의코드)

import React, {Component} from "react";
import Ball from "./Ball";

const winBall = () => {
    const balls = Array(45).fill(0).map((v,i) => i+1);
    const result = [];
    while(balls.length > 0){
        const randIndex = Math.floor(Math.random() * balls.length); // <= 0 x < length
        result.push(balls.splice(randIndex, 1)[0]);
    }
    const bonus = result[result.length - 1]; //맨 마지막
    const wins = result.slice(0,6).sort((a,b)=>a-b); // 0~5
    console.log(wins, bonus);
    return [...wins, bonus];
}

class Lotto extends Component{
    state = {
        winNumbers: winBall(),
        winBalls:[],
        bonus:null,
        redo:false,
    }
    timeouts=[];

    componentDidMount() {
        console.log('componentDidMount')
        this.startLotto();
    }

    onClickRedo = () => {
        this.setState({
            winNumbers: winBall(),
            winBalls:[],
            bonus:null,
            redo:false
        });
        this.timeouts = [];
    }

    componentDidUpdate(prevProps, prevState, snapshot) {
        if(this.state.winBalls.length === 0){
            this.startLotto();
        }
    }

    componentWillUnmount() {
        this.timeouts.forEach(v => clearTimeout(v));
    }

    startLotto = () => {
        const length = this.state.winNumbers.length - 1;
        for(let i=0; i<length; i++){
            const timerId = setTimeout(()=>{
                this.setState((prev)=>{
                    const winBall = prev.winNumbers.slice(i,i+1)[0];

                    return {
                        winNumbers:[...prev.winNumbers],
                        winBalls:[...prev.winBalls, winBall],
                        bonus:null,
                        redo:false
                    }
                });
            },1000 * (i+1));
            this.timeouts.push(timerId);
        }

        this.timeouts[6] = setTimeout(()=>{
            this.setState({bonus: this.state.winNumbers[length],redo:true});
        }, 1000 * this.state.winNumbers.length);
    }

    render() {
        const {winNumbers, winBalls, bonus, redo} = this.state;
        return (
            <>
                {winBalls.map(v=><Ball number={v} key={v+1}/>)}

                {
                    bonus && <><div className="bonus__comment">보너스!</div><Ball number={bonus} /></>
                }
                {
                    redo && <button onClick={this.onClickRedo}>한번더!</button>
                }
            </>
        )
    }
}

export default Lotto;

 

 

Ball.jsx

import React, {memo} from 'react';
const Ball = memo(({number})=>{
    let background, color = null;
    if(number >= 40){
        background = 'red';
    }else if(number >= 30){
        background = 'green';
    }else if(number >= 20){
        background = 'yellow';
    }else if(number >= 10){
        background = 'blue';
        color = 'white';
    }else{
        background = 'aqua';
    }

    return (
        <>
            <div className="ball" style={{background, color}}>
                {number}
            </div>
        </>
    )
});

export default Ball;

 

 

LottoHooks.jsx

import React, {useEffect, useRef, useState} from "react";
import Ball from "./Ball";

const LottoHooks = () => {
    const winBall = () => {
        const balls = Array(45).fill(0).map((v,i) => i+1);
        const result = [];
        while(balls.length > 0){
            const randIndex = Math.floor(Math.random() * balls.length); // <= 0 x < length
            result.push(balls.splice(randIndex, 1)[0]);
        }
        const bonus = result[result.length - 1]; //맨 마지막
        const wins = result.slice(0,6).sort((a,b)=>a-b); // 0~5
        console.log(wins, bonus);
        return [...wins, bonus];
    }

    const [winNumbers, setWinNumbers] = useState(winBall);
    const [winBalls, setWinBalls] = useState([]);
    const [bonus, setBonus] = useState(null);
    const [redo,setRedo] = useState(false);
    const timerRef = useRef([]);


    useEffect(()=>{
        for(let i=0; i<6; i++){
            const timerId = setTimeout(()=>{
                setWinBalls((prev)=>{
                    return [...prev, winNumbers[i]];
                });
            },(i+1) * 1000);
            timerRef.current.push(timerId);
        }
        timerRef.current[6] = setTimeout(()=>{
            setBonus(winNumbers[6]);
            setRedo(true);
        },7000);
    },[winNumbers]);

    const onClickRedo = (param) => (e) => {
        console.log(param);
        console.log(e.target);
        setWinBalls([]);
        setWinNumbers(winBall);
        setBonus(null);
        setRedo(false);
        timerRef.current = [];
    }

    return (
        <>
            {
                winBalls.map((v,i) => <Ball number={v} key={v+i}/>)
            }
            {
                bonus && <><div>보너스!!</div><Ball number={bonus} /></>
            }
            {
                redo && <button onClick={onClickRedo(1)}>한번더</button>
            }
        </>
    );
}

export default LottoHooks;

이미지 다운로드

docker pull [이미지:태그]

(보통 태그는 버전을 명시하는 것 같다. 태그를 입력하지 않으면 최신버전을 다운로드한다.)

 

이미지 확인

docker images

 

현재 실행중인 컨테이너 확인

docker ps // 중지된 것은 보이지 않고, 실제로 실행중 인것만 보인다.

docker ps -a // 중지된 컨테이너도 모두 보인다.

 

이미지 삭제하기 

현재 실행중인 컨테이너 혹은 중지된 컨테이너인 경우 삭제할 수 없다.

docker rmi [IMAGE ID] 

docker rmi [REPOSITORY]

 

이미지 실행시키기 (컨테이너로 띄운다)

docker run 

컨테이너 중지하기 (실행중인 것을 중지)

docker stop [CONTAINER ID]

컨테이너 삭제하기 (종료된 것을 삭제)

docker rm [CONTAINER ID]

 

 

기억할 것

  • 컨테이너의 상태는 2가지가 있다. 
    • 실행중 : docker ps 로 확인 가능
    • 중지중 : docker ps -a 를 해야 비로소 확인 가능
  • 컨테이너라는 의미는 메모리에 할당된 프로세스 상태라는 상태인데 docker stop 명령어로 중지 했는데 계속 프로세스 상태인것일까? 라는 의문에는 나중에 설명해주신다고 한다.
  • 이미지를 삭제할 때는 컨테이너로 띄워져 있을 때는 삭제할 수 없다. (중지된 컨테이너라도 docker rm 으로 아예 컨테이너에서 지워줘야 이미지를 삭제할 수 있다.)

 

+ Recent posts