• 힙은 우선순위 큐를 위해 만들어진 자료구조

우선순위 큐

  • 우선순위 큐란?
    • 큐 : FIFO 형식의 자료구조
    • 우선순위 큐는 큐에 우선순위라는 개념을 접목시킨 자료구조이다.
    • 따라서 우선순위 큐는 먼저 들어오는 데이터가 아니라 우선순위가 높은 데이터가 먼저 나가는 형태의 자료구조.

  • 힙이란?
    • 우선순위 큐를 위해 고안된 완전 이진 트리 형태의 자료구조
    • 여러 값 중 최대값과 최소값을 빠르게 찾아내도록 만들어진 자료구조
    • 반정렬 상태를 유지
    • 이진 탐색 트리와 달리 중복된 값이 허용된다.

힙의 종류

  • 최대 힙
    • 부모 노드의 키 값이 자식 노드의 키 값보다 크거나 같은 완전 이진 트리
  • 최소 힙
    • 부모 노드의 키 값이 자식 노드의 키 값보다 작거나 같은 완전 이진 트리

 

 

힙의 데이터 저장 / 삭제 개념

  • 최소 힙에 저장할 때
    • 들어올 새 노드를 우선 순위가 가장 낮다는 가정을 하고 맨 끝 위치에 저장
    • 부모 노드와 우선 순위를 비교해서 위치가 바뀌어야 하면 바꾼다
    • 올바르게 위치할 때까지 B 반복
  • 최소 힙에서 삭제할 때
    • 우선순위 큐의 pop == 가장 우선순위가 높은 데이터를 빼낸다.
      1. 루트 노드를 반환하고 마지막 노드를 루트 노드 자리에 옮긴다.
      2. n의 왼쪽, 오른쪽 자식노드 중 더 우선순위가 높은 것과 비료를 진행한다.
      3. 최소 힙의 구조를 유지할 때까지 B 반복
      ⇒ 이렇게 힙의 구조를 유지하는 과정을 heapify라고 한다.

파이썬의 우선순위 큐

  • heapq 모듈
import heapq
# 파이썬의 heapq 모듈은 최소 힙을 기본으로 구현되어 있다.

pq = []

heapq.heappush(pq, (1, '작업1')) # (우선순위, 값)
heapq.heappush(pq, (4, '작업4'))
heapq.heappush(pq, (3, '작업3'))
heapq.heappush(pq, (2, '작업2'))

print(heapq.heappop(pq)) 
print(heapq.heappop(pq))
  • PriorityQueue 클래스
from queue import PriorityQueue

pq = PriorityQueue()

pq.put((1, '작업1'))
pq.put((3, '작업3'))
pq.put((2, '작업2'))

print(pq.get())
print(pq.get())

⇒ PriorityQueue는 스레드 안전을 제공하므로 멀티스레딩 환경에서 유용하지만, 단일 스레드 환경에서는 heapq가 더 간편하다.

문제 풀이

JS로도 풀어보고 싶었지만 JS에는 heap 모듈이 따로 없고 코테 외에서는 라이브러리를 사용할 수 있다.

 

더 맵게


  • 파이썬 풀이
  • # 스코빌 섞을 수 없는 경우: 길이가 2보다 짧을 시 -1
    
    import heapq
    
    def solution(scoville, K):  
        heapq.heapify(scoville)
        
        mix_count = 0
        
        while scoville[0] < K:
            if len(scoville) < 2:
                return -1
            
            first = heapq.heappop(scoville)
            second = heapq.heappop(scoville)
            new_food = (first + second * 2)
            heapq.heappush(scoville, new_food)
            mix_count += 1
                
        return mix_count
    ​
  • 자바스크립트 풀이⇒ 이 코드는 모든 테스트 케이스를 통과하지만 효율성에서 걸린 코드이다.
  • function solution(scoville, K) {
        scoville.sort((a, b) => b - a);
        let mix_count = 0;
        while (scoville.at(-1) < K) {
            if (scoville.length < 2) return -1;
            
            let first = scoville.pop();
            let second = scoville.pop();
            let new_food = first + second * 2;
            
            scoville.push(new_food);
            scoville.sort((a, b) => b - a);
            
            mix_count++;
        }
        return mix_count;
    }
    

 

디스크 컨트롤러


  • 파이썬 풀이

이중우선순위큐


  • 파이썬 풀이

스택

스택의 개념

  • 스택
    • 쌓아 올린다는 것을 의미
    • 스택 자료구조는 책을 쌓는 것처럼 쌓아 올린 형태의 자료구조

스택의 특징

  • 스택은 같은 구조의 크기와 자료를 정해진 방향으로만 쌓을 수 있고, top으로 정한 곳을 통해서만 접근할 수 있다.
  • top에 있는 가장 위에 있는 자료는 가장 최근에 들어온 자료를 가리키고 있으며, 삽입되는 새 자료는 top이 가리키는 자료의 위에 쌓이게 된다.
  • 스택에서 자료를 삭제할 때도 top을 통해서만 가능하다.
  • 삽입하는 연산은 push, 삭제하는 연산은 pop
  • 스택은 시간 순서에 따라 자료가 쌓여서 가장 마지막에 삽입된 자료가 가장 먼저 삭제된다는 구조적 특징이 있다.
    • 비어있는 스택에서 원소를 추출하려고 하는 것 → stack underflow
    • 스택이 넘치는 경우 → stack overflow
  • ⇒ 후입선출 구조

스택의 활용 예시

  • 스택의 특징인 후입선출을 활용하여 여러 분야에서 활용 가능하다.
  • 웹 브라우저 방문 기록
    • 가장 나중에 열린 페이지부터 다시 보여준다.
  • 역순 문자열 만들기
    • 가장 나중에 입력된 문자부터 출력한다.
  • 실행 취소
    • 가장 나중에 실행된 것부터 실행을 취소한다.
  • 후위 표기법 계싼
  • 수식의 괄호 검사

큐의 개념

  • 말 그대로 줄
  • 선입선출 방식의 자료구조

큐의 특징

  • 정해진 한 곳을 통해서 삽입, 삭제가 이루어지는 스택과 달리 큐는 한쪽 끝에서 삽입 작업, 다른 끝에서 삭제 작업이 이루어진다.
  • 삭제 연산만 수행되는 곳을 프론트, 삽입연산만 이루어지는 곳을 리어로 정한다.
  • 큐의 리어에서 이루어지는 삽입 연산을 인큐
  • 큐의 프론트에서 이루어지는 삭제 연산을 디큐라고 한다.
  • 즉, 큐에서 프론트 원소는 가장 먼저 큐에 들어왔던 첫번째 원소
  • 리어 원소는 가장 늦게 큐에 들어온 마지막 원소

활용 예시

  • 큐는 주로 데이터가 입력된 시간 순서대로 처리해야 할 필요가 있는 상황에 이용
  • 우선순위가 같은 작업 예약
  • 은행 업무
  • 콜센터 고객 대기시간
  • 프로세스 관리
  • 너비 우선 탐색 구현
  • 캐시 구현

문제 풀이

같은 숫자는 싫어

 

자바스크립트

function solution(arr) {
    let answer = [];
    for (let i = 0; i < arr.length; i++) {
        if (arr[i] !== arr[i - 1]) {
            answer.push(arr[i]);
        }
    }
    return answer;
}

 

파이썬

def solution(arr):
    answer = []
    for e in arr:
        if answer and answer[-1] == e:
            continue
        answer.append(e)
    return answer

 

 

=> 로직은 같다. 현재 가리키고 있는 요소와 요소 -1의 인덱스를 가진 요소를 비교해서 다른 경우에만 answer에 push한다.

 

기능 개발

 

자바스크립트

function solution(progresses, speeds) {
    const answer = [];
    let days = progresses.map((progress, i) => Math.ceil((100 - progress) / speeds[i]));
    
    let maxDay = days[0];
    let count = 1;
    
    for (let i = 1; i < days.length; i++) {
        if (days[i] > maxDay) {
            answer.push(count);
            maxDay = days[i];
            count = 1;
        } else {
            count++;
        }
    }
    
    answer.push(count);
    return answer;
}

 

 

 

올바른 괄호

 

자바스크립트

function solution(s){
    let paren = 0;
    for (let i = 0; i < s.length; i++) {
        s[i] === "(" ? paren++ : paren--;
        if (paren < 0) {
            return false;
        }
    }
    return s.at(-1) == ")" && paren == 0;
}

 

프로세스

추후 수정

다리를 지나는 트럭

추후 수정

주식가격

 

추후 수정

해시의 개념

  • 해시는 해시 함수를 사용해서 변환한 값을 인덱스로 삼아 키와 값을 저장해서 빠른 데이터 탐색을 제공하는 자료구조이다.
  • 보통은 인덱스를 활용해서 탐색을 빠르게 만들지만 해시는 키를 사용해 데이터 탐색을 빠르게 한다.
  • 해시는 키와 데이터를 일대일 대응하여 저장하므로 키를 통해 데이터에 바로 접근할 수 있다. 사람에게는 숫자로 데이터를 관리하는 배열보다 조금 더 접근성이 좋은 자료구조이다.

키워드

    • 최종적으로 얻고자 하는 정보
    • 값을 검색하기 위해 활용하는 정보
  • 해시 함수
    • 키를 이용해 해시값 또는 인덱스로 변환하는 함수

해시의 특징

  • 해시는 단방향으로 동작한다.
    • 키를 통해 값을 찾을 수 있지만 값을 통해 키를 찾을 수는 없다.
    • 단방향으로만 동작하는 해시의 특성은 외부에 정보를 안전하게 제공한다는 특징이 있어 네트워크 보안에서 많이 활용된다.
  • 찾고자 하는 값을 O(1)에서 바로 찾을 수 있다.
    • 키 자체가 해시 함수에 의해 값이 있는 인덱스가 되므로 값을 찾기 위한 탐색 과정이 필요 없다.
  • 값을 인덱스로 활용하려면 적절한 변환 과정을 거쳐야 한다.

해시 분야

  • 해시는 단방향으로만 검색할 수 있는 대신 빠르게 원하는 값을 검색할 수 있다.
  • 그래서 해시는 데이터를 저장하고 검색하거나 보안이 필요할 때 활용된다.
  • 비밀번호 관리
    • 해시 함수를 활용해 해싱한 비밀번호를 저장한다. 비밀번호가 맞는지 확인할 때도 마찬가지이다.
  • 데이터베이스 인덱싱
    • 데이터베이스에 저장된 데이터를 효율적으로 검색할 때 해시를 활용한다.
  • 블록체인
    • 블록체인에서 해시 함수는 핵심 역할을 한다. 각 블록은 이전 블록의 해시값을 포함하고 있으며, 이를 통해 데이터 무결성을 확인할 수 있다.

해시 구현

  • 해시 함수가 변환한 값은 인덱스로 활용해야 하므로 해시 테이블의 크기를 넘으면 안 된다.
  • 해시 함수가 변환한 값의 충돌은 최대한 적게 발생해야 한다.
    • 충돌 : 서로 다른 두 키에 대해 해싱 함수를 적용한 결과과 동일한 것을 의미

자주 사용하는 해시 함수

  • 나눗셈법
    • 가장 떠올리기 쉬운 단순한 해시 함수
    • 나눗셈법 : 키를 소수로 나눈 나머지를 활용
      • h(x) = x mod k
    • 위와 같이 계산하는 경우 자연스레 나눗셈법의 해시 테이블 크기는 K가 된다. 왜냐하면 K에 대해 모듈러 연산을 했을 때 나올수 있는 값은 0 ~ 9(K - 1)이기 때문이다.
  • 곱셈법
    • 나눗셈법과 비슷하게 모듈러 연산을 활용하지만 소수는 활용하지 않는다.
    • 황금비를 사용한다.
      • h(x) = (((x * A) mod 1) * m
      • m은 최대 버킷의 개수, A는 황금비이다.
  • 문자열 해싱
    • 키의 자료형이 문자열일 때도 사용할 수 있는 해시 함수인 문자열 해싱
    • 문자열 해싱은 문자열의 문자를 숫자로 변환하고 이 숫자들을 다항식의 값으로 변환해서 해싱한다.
      • hash(s) = (s[0] + s[1] * p + s[2] * p^2 + … + s[n-1] * p^n-1) mod m
    • p는 31이고, m은 해시 테이블의 최대 크기이다.
      • p를 31로 정한 이유는 홀수이면서 메르센 소수이기 때문이다.
        • 메르센 소수는 해시에서 충돌을 줄이는데 효과적이라는 연구 결과가 있다.

해시 함수 적용시 중요한 점

  • 해시 함수를 적용한 값이 해시테이블 크기에 비해 너무 클 수 있다는 점.
  • 그래서 해시 함수가 내는 결과의 크기를 해시 테이블 크기에 맞도록 하는 작업이 필요하다.
    • hash(s) = (s[0] % m + s[1] * p % m + s[2] * p^2 % m + … + s[n-1] * p^n-1 % m) mod m
    • 위처럼 중간 중간 모듈러 연산을 해 더한 값을 모듈러 연산하면 오버플로우를 최대한 방지할 수 있다.

충돌처리

  • 서로 다른 키에 대해서 해시 함수의 결괏값이 같으면 충돌이라고 한다. 하나의 버킷에 2개의 값을 넣을 수는 없으므로 해시 테이블을 관리할 때는 반드시 충돌 처리를 해야 한다.
  • 체이닝으로 처리하기
    • 해싱한 값이 같은 경우 충돌을 해결하는 간단한 방법
    • 체이닝은 충돌 발생 시 해당 버킷에 연결 리스트로 같은 해시값을 가지는 데이터를 연결한다.
      • 충돌이 많아지면 그만큼 연결 리스트의 길이가 길어지고 다른 해시 테이블의 공간은 덜 사용하므로 공간 활용성이 떨어진다.
      • 충돌이 많으면 연결 리스트 자체의 한계 때문에 검색 성능이 떨어진다. 연결리스트는 맨 앞의 데이터부터 검색해야 하기 때문
  • 개방 주소법으로 처리하기
    • 체이닝에서 연결 리스트로 충돌값을 연결한 것과 다르게 빈 버킷을 찾아 충돌값을 삽입한다.해시 테이블을 최대한 활용하므로 체이닝보다 메모리를 더 효율적으로 사용할 수 있다.
    • 선형 탐사 방식
      • 선형 탐사 방식은 충돌이 발생하면 다른 빈 버킷을 찾을 때까지 일정한 간격으로 이동한다.
      • h(k, i) = (h(k) + i) mod m
        • m은 수용할 수 있는 최대 버킷. 선형 탐사 시 테이블의 범위를 넘으면 안 되므로 모듈러 연산을 적용한 것.
    • 이중 해싱 방식
      • 말 그대로 해시 함수를 2개 사용하는 방식
      • 해시 함수를 N개로 늘리기도 한다.
      • 두번째 해시 함수의 역할은 첫번째 해시 함수로 충돌이 발생하면 해당 위치를 기준으로 어떻게 위치를 정할 지 결정하는 역할을 한다.

JS에서의 해시

  • 자바스크립트에서는 주로 Map을 사용하여 객체를 생성해 해시 테이블을 만든다.
  • // 맵 객체 생성 let hash = new Map(); // 객체에 키:값을 삽입 hash.set("key1", 1234); hash.set("key2", "hi"); // 객체에 삽입된 값을 가져오는 법 console.log(hash.get("key1")); //1234 console.log(hash.keys()); //{"key1", "key2"} console.log(hash.values()); //{1234, "hi"} // 객체의 값을 모두 삭제 hash.clear(); // 객체의 특정 값 삭제 hash.delete("key1");

해시 문제 풀이

1. 폰켓몬


  1. 해시 카테고리에 있는 문제임은 알았으나 아직 자바스크립트로 해시를 구현한다는 것이 잘 감이 잡히지 않아 지금까지 풀어왔던 것처럼 단순하게 풀어봤다. 문제를 읽어보니 결론적으로 필요한 것은 N/2의 값인 것 같아 num으로 값을 미리 구한 후, 중복 된 값을 모두 없애버린 answer에서 if문으로 상황에 따른 처리를 해주었다. 풀이 후에 Set()과 해시 테이블 코드가 결국 비슷하게 돌아간다고 생각해서 찾아보니 둘은 기본적으로 다른 개념이지만 실제 구현에서는 해시 테이블을 기반으로 한 Set을 많이 사용하게 되므로 이를 Hast Set이라고 부르기도 한다는 것을 알았다.
function solution(nums) {
    var answer = [...new Set(nums)];
    let num = nums.length / 2;
    if (num > answer.length) {
        return answer.length;
    } else {
        return num;
    }
  return answer;
}

2.  해시 알고리즘을 이해하고 싶어서 다른 분의 풀이를 참고했다. 그리고 이 코드를 이해하면서 대충 이해했다. nums에 들어온 각 값들이 해시테이블에 존재하지 않는다면 우선 넣고, 존재할 때마다 해당 포켓몬의 수를 하나씩 늘리는 것.

function solution(nums) {
    let answer = 0;
    let bag = new Map();
    nums.forEach((monster) => {
        if(bag.get(monster)) bag.set(monster, bag.get(monster) + 1);
        else bag.set(monster, 1);
    });
    
    if (bag.size < nums.length / 2) answer = bag.size;
    else answer = nums.length / 2;
    
    return answer;
}

 

 

 

3. 파이썬도 어느 정도 함께 이해하고 싶어서 파이썬 코드로도 풀이해보았다. 파이썬도 set 으로 중복을 없앨 수 있고, 다시 배열 형태로 사용하고 싶다면 list()안에 넣어서 사용해야 한다.

def solution(nums):
    num = len(nums) // 2
    answer = list(set(nums))
    return len(answer) if num > len(answer) else num

2. 완주하지 못한 선수


  1. 해시 코드
    function solution(participant, completion) {
        var answer = new Map();
    
        for (let i = 0; i < participant.length; i++) {
            if (answer.get(participant[i])) {
                answer.set(participant[i], answer.get(participant[i]) + 1);
            } else {
                answer.set(participant[i], 1);
            }
        }
    
        for (let i = 0; i < completion.length; i++) {
            if (answer.get(completion[i])) {
                answer.set(completion[i], answer.get(completion[i]) - 1);
            }
        }
    
        for (let [key, value] of answer) {
            if (value > 0) {
                return key;
            }
        }
    }
    
    해시 쓴다고 쓴건데 다른 사람들 코드 보면서 빨리 이 길을 접어야 하나 진지하게 고민할 정도의 코드가 나온 것 같다 ^_^ 

2. 둘러보던 중 가장 마음에 들었던 코드

  • 내가 작성한 첫번째 for문을 forEach로 바꿔서 각 배열에 쉽게 접근하고 if 문 대신 or의 경우 왼쪽의 값이 false이면 오른쪽 값이 된다는 것을 이용해 이름이 있는 경우 기존 값 + 1, 없는 경우 0 + 1이 되어 1로 세팅된다.
  • 두번째 for문 대신 find() 메소드를 사용해 completion 배열 안에 이름이 있다면 값을 줄이고 없는 경우 해당 인덱스의 사람 이름을 return 한다.
var solution = (participant, completion) => {
    let completionCount = {};
    completion.forEach(name => {
        completionCount[name] = (completionCount[name] || 0) + 1;
    });

    return participant.find(name => {
        if (!completionCount[name]) {
            return true;
        }
        completionCount[name]--;
    });
};

 

3. 파이썬으로 작성한 코드

def solution(participant, completion):
    par = sorted(participant)
    com = sorted(completion)
    for i in range(len(com)):
        if par[i] != com[i]:
            return par[i]
        
    return par[-1]

js 답변들 둘러보다보니 정렬 후 다르거나 마지막 사람이 미완주자가 되는 것을 이용해서 풀이한 코드가 많아서 그대로 파이썬에 적용했다.

3. 전화번호 목록


  1. 해시를 사용하지 않고 정렬과 startsWith 메소드를 사용해서 풀이한 방법.
    • 예시로 나온 phone_book의 element들이 ["119", "97674223", "1195524421"] 라고 가정하는 경우 접두사가 되는 요소는 접두사가 포함되는 요소보다 짧을 것이고, sort로 정렬하면 비슷한 요소들이 가깝게 정렬이 될 테니 정렬 후에 근처에 있는 요소를 확인하면 된다.
    • function solution(phone_book) { phone_book.sort(); for (let i = 0; i < phone_book.length - 1; i++) { if (phone_book[i + 1].startsWith(phone_book[i])) { return false; } } return true; }
  2. 해시를 사용해 풀이하는 방법
    • 해시 사용 방법을 통한 풀이를 제대로 이해 못해서 추후 수정 예정
  3. 파이썬 코드
    • 파이썬에도 startswith 메소드가 있다는 점!
      def solution(phone_book):
          sorted_arr = sorted(phone_book)
          for i in range(len(sorted_arr) - 1):
              if sorted_arr[i + 1].startswith(sorted_arr[i]):
                  return False
          return True
      
    • 1번 풀이를 파이썬으로 똑같은 방식을 사용해 풀이한 과정이다.
    • 이건 파이썬 해시를 사용한 다른 분의 코드이다.
      def solution(phone_book):
          answer = True
          hash_map = {}
          for phone_number in phone_book:
              hash_map[phone_number] = 1
          for phone_number in phone_book:
              temp = ""
              for number in phone_number:
                  temp += number
                  if temp in hash_map and temp != phone_number:
                      answer = False
          return answer
      
    • 우선 hash 사용을 위해 hash 객체 하나를 생성한다. phone_book에 주어진 번호를 도는 동안 hash_map에 키로 저장하고 값을 1로 할당한다. 그리고 안에 있는 값을 바로 하나씩 꺼내어서 사용할 것이다. 우선 temp 변수를 초기화한다. 각 번호 묶음의 번호 한 자리씩을 꺼내어 temp에 저장한다. 만약 temp가 hash_map에 존재하면서 temp가 phone_number은 아니라면 전체 전화번호와는 동일하지 않다는 뜻이므로 다른 번호의 접두사가 된다는 의미이다.

 

4. 의상

추후 수정 예정

5. 베스트 앨범

추후 수정 예정

 

AMP

  • Accelerated Mobile Pages
  • AMP는 구글이 주도하는 오픈 소스 프로젝트이다.
  • 웹 페이지가 모바일 장치에서 더 빠르고 효율적으로 로드되도록 설계되었다.
  • AMP는 특히 모바일 환경에서 페이지 로딩 속도를 높여 사용자 경험을 개선하고 검색 엔진 순위를 향상시키는 데 중점을 둔다.

AMP의 주요 요소

AMP HTML

  • AMP HTML은 기존 HTML을 기반으로 하지만, 성능을 향상시키기 위해 제한된 HTML 태그와 기능을 사용한다.
  • 예를 들어, 일부 JS 기능을 사용할 수 없으며, 대신 AMP 프로젝트에서 제공하는 맞춤 태그를 사용한다.

AMP JS 라이브러리

  • AMP JS 라이브러리는 AMP HTML 페이지의 빠른 렌더링을 보장한다. 이 라이브러리는 모든 외부 리소스가 페이지 로딩을 방해하지 않도록 비동기적으로 로드되며, 사용자 정의 요소와 애니메이션의 성능을 최적하한다.

AMP 캐시

  • AMP 페이지는 자동으로 AMP 캐시라는 Google의 CDN에 캐시될 수 있다. 이 캐시는 AMP 페이지를 사전 렌더링하고 최적화 된 상태로 제공하여 로딩 속도를 더욱 향상시킨다.
  • Google 검색 결과에서 AMP 페이지는 번개 아이콘과 함께 표시되며, 즉시 로드된다.

AMP의 원리

  • 리소스 우선순위 지정
  • 광고 및 분석 최적화
  • 레이아웃 제

AMP의 장점

빠른 로딩 속도

  • 모바일 장치에서 웹 페이지의 로딩 속도가 빠르기 때문에 사용자 이탈률이 줄어든다.

SEO 개선

  • Google은 빠르게 로드되는 페이지를 선호하며 이는 검색 엔진 순위에 긍정적인 영향을 끼친다.

브랜딩과 사용자 경험

  • AMP 페이지는 기본 페이지와 스타일이 다를 수 있어, 브랜드 정체성을 유지하기 어려울 수 있다.

AMP의 대안

  • AMP 외의 다양한 웹 성능 최적화 방법 사용하기
    • PWA (Progressive Web App)
      • 오프라인 기능을 제공하면서 성능을 향상시키는 반면, 일반적인 성능 최적화 기법도 AMP의 대안으로 고려될 수 있다.

AMP 리소스

'TIL' 카테고리의 다른 글

WAS ; Web Application Server  (0) 2024.11.13
React (Redux , Context API, 클래스형과 함수형)  (0) 2024.08.22
자바스크립트의 ES6  (0) 2024.08.21
자바스크립트의 this  (0) 2024.08.20
자바스크립트의 타입  (0) 2024.08.19

 

useReducer

  • useReducer()을 는 상태와 그 상태를 업데이트하는 함수를 반환하는 React 훅입니다. 이 훅은 상태를 업데이트하는 로직을 함수 형태로 분리해 관리한다.
  • useReducer은 기본적으로 Redux에서 사용하는 패턴과 유사하게 동작하며, 상태 변경 로직을 컴포넌트 외부로 분리할 수 있어 상태 관리가 복잡해질 때 매우 유용하다.
const [state, dispatch] = useReducer(reducer, initialState);
  • reducer
    • 현재 상태와 액션을 기반으로 새로운 상태를 반환하는 함수
  • initialState
    • 초기 상태 값
  • state
    • 현재 상태를 나타낸다.
  • dispatch
    • 상태를 변경하기 위한 액션을 발생시키는 함수

useReducer의 역할

  • 복잡한 상태 관리
    • useState를 사용하면 여러 상태 변수를 개별적으로 관리해야 하는 경우가 많은데, useReducer을 사용하면 상태와 그 변화를 하나의 함수에서 관리할 수 있어 복잡한 상태 로직을 쉽게 다룰 수 있다.
  • 코드 가독성 향상
    • 상태 변화를 관리하는 로직을 reducer 함수로 분리하기 때문에 코드가 더 깔끔하고 가독성이 높아진다.
  • 유지보수성 개선
    • 상태 변경 로직이 한 곳에 모이므로, 유지보수와 테스트가 용이하다.

useState ⇒ useReducer

useState

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
}

export default Counter;

useReducer

import React, { useReducer } from 'react';

// reducer 함수 정의
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function Counter() {
  const initialState = { count: 0 };
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
    </div>
  );
}

export default Counter;
  • reducer 함수는 현재 상태와 액션을 인자로 받아, 새로운 상태를 반환한다.
  • dispatch 함수를 사용해 액션을 발생시키고, 이를 통해 상태를 변경한다.

useReducer 사용

  • 복잡한 상태 로직이 필요한 경우
    • 상태 업데이트 로직이 여러 가지 경우를 처리해야 할 때 useState가 더 적합하다.
    • 여러 상태 변수나 다양한 액션 타입이 필요한 경우
  • 상태 관리의 일관성
    • 상태 변경 로직을 하나의 reducer 함수에 통합하면, 코드의 일관성을 유지하고 버그를 줄일 수 있다.

useReducer 객체 관리

import React, { useReducer } from 'react';

// reducer 함수
function reducer(state, action) {
  switch (action.type) {
    case 'setName':
      return { ...state, name: action.payload };
    case 'setAge':
      return { ...state, age: action.payload };
    case 'toggleIsStudent':
      return { ...state, isStudent: !state.isStudent };
    default:
      throw new Error('Unknown action');
  }
}

function UserProfile() {
  const initialState = { name: '', age: 0, isStudent: false };
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <p>Name: {state.name}</p>
      <p>Age: {state.age}</p>
      <p>Is Student: {state.isStudent ? 'Yes' : 'No'}</p>

      <input
        type="text"
        placeholder="Set Name"
        onChange={(e) => dispatch({ type: 'setName', payload: e.target.value })}
      />
      <input
        type="number"
        placeholder="Set Age"
        onChange={(e) => dispatch({ type: 'setAge', payload: parseInt(e.target.value) })}
      />
      <button onClick={() => dispatch({ type: 'toggleIsStudent' })}>
        Toggle Is Student
      </button>
    </div>
  );
}

export default UserProfile;
  • reducer를 통해 객체 안의 요소를 상태 관리할 수 있다.

결론

  • useReducer은 useState보다 더 복잡한 상태 관리가 필요할 때 유용한 도구이다.
  • 상태 업데이트 로직이 복잡해지거나 다양한 경우를 처리해야 한다면, useReducer로 전환하여 관리할 수 있다.

리액트의 상태 관리

  • 애플리케이션이 커짐에 따라 상태가 어떻게 구성되고 구성 요소 간에 데이터가 어떻게 흐르는 지에 대해 더 의도적으로 생각하는 것은 도움이 된다.
  • 중복되거나 중복된 상태는 버그의 일반적인 원인이다.

Redux

  • 리덕스는 자바스크립트 애플리케이션에서 상태 관리를 간편하게 하기 위해 사용하는 대표적인 라이브러리 중 하나이다. 특히, 리액트와 함께 자주 사용되지만 리덕스는 리액트에 한정되지 않고 모든 자바스크립트 애플리케이션에서 사용할 수 있다.

리덕스의 핵심 개념

  • 스토어
    • 애플리케이션의 상태를 담고 있는 객체이다. 스토어는 단 하나만 존재하며, 이 곳에 애플리케이션의 모든 상태가 저장된다.
  • 액션
    • 상태를 변화시키기 위한 의도를 나타내는 객체이다. 액션은 ‘type’ 이라는 속성을 필수적으로 가져야 하며, 이 외에도 상태 변화를 위한 추가 데이터를 포함할 수 있다.
  • 리듀서
    • 액션을 받아 상태를 업데이트하는 함수이다. 이전 상태와 액션을 인자로 받아 새로운 상태를 반환하는 순수 함수이다.
  • 디스패치
    • 액션을 스토어에 전달하는 함수이다. 디스패치된 액션은 리듀서를 통해 상태를 업데이트되게 한다.
  • 구독
    • 상태가 변경될 때마다 특정 동작을 실행하도록하는 함수이다. 리덕스는 상태가 변경되면 자동으로 리액트 컴포넌트를 다시 렌더링하게 할 수 있다.

리덕스의 작동 원리

graph TD;
    A[사용자의 이벤트 발생] --> B[특정 액션 생성];
    B --> C[디스패치];
    C --> D[리듀서로 전달];
    D --> E[전달받은 액션 + 현재 상태];
    E --> F[새로운 상태가 스토어에 저장];
    F --> G[스토어 상태 업데이트, 해당 상태 구독 시 컴포넌트 재렌더링]

리덕스의 장점

  • 예측 가능하다
    • 상태 변화가 예측 가능하게 이루어지므로 애플리케이션의 동작을 쉽게 파악할 수 있다.
  • 중앙 집중식 관리
    • 모든 상태가 하나의 스토어에서 관리되기 때문에 상태를 추적하기 쉽다.
  • 디버깅 용이성
    • 리덕스 개발 도구를 통해 상태 변화 과정을 시각적으로 확인할 수 있어 디버깅이 용이하다.
  • 유연성
    • 미들웨어를 활용해 비동기 작업이나 로직, 에러 처리 등을 쉽게 추가할 수 있다.

Redux-saga

  • 리덕스 애플리케이션에서 비동기 작업을 관리하기 위한 미들웨어
  • 리덕스 자체는 동기적인 상태 관리에 초점을 맞추기 때문에 비동기 로직을 처리하는 데 추가적인 도구가 필요하기 때문에 만들어졌다.

리덕스 사가의 작동 원리

graph TD;
    A[특정 액션 디스패치] --> B[Watcher Saga가 감지];
    B --> C[액션에 따른 Worker Saga 실행];
    C --> D[비동기 작업 수행];
    D --> E[수행 결과에 따라 액션 재디스패치];
    E --> F[리듀서의 상태 업데이트];
   

Redux-saga를 사용하는 이유

  • 리덕스 애플리케이션에서 비동기 작업이 복잡해질수록 단순한 상태관리에는 한계가 있다.
  • 복잡한 API 호출 순서, 타이밍, 에러 처리 등의 작업이 필요한 경우 매우 유용하다.

옵저버블

  • 옵저버블은 비동기 데이터 스트림을 관리하는 개념이다.
  • 주로 리액티브 익스텐션 라이브러리를 통해 사용된다.
  • 함수형 프로그래밍과 반응형 프로그래밍의 개념을 결합하여 데이터 흐름을 효율적으로 처리하는 도구이다.

옵저버블의 장점

  • 조합성
    • 여러 데이터 스트림을 조합하거나 변환할 수 있어 복잡한 비동기 로직을 간결하게 표현할 수 있다.
  • 유연성
    • 옵저버블은 데이터를 처리하는 시점을 제어할 수 있어 실시간 데이터 흐름을 다루는 데 유리하다.
  • 함수형 프로그래밍
    • 순수 함수와 연산자를 사용하여 데이터를 처리하는 함수형 프로그래밍 스타일 지원한다.

옵저버블과 프로미스 비교

  • 프로미스
    • 단일 비동기 작업의 결과를 나타낸다. 작업이 완료되면 하나의 값을 반환한다.
  • 옵저버블
    • 여러 비동기 이벤트의 스트림을 나타낸다. 여러 개의 값을 발행할 수 있으며, 데이터 흐름을 제어할 수 있다.

Context-API

  • 전역적으로 데이터를 관리하고 여러 컴포넌트 간에 쉽게 데이터를 공유할 수 있도록 도와주는 기능
  • 일반적으로 리액트에서는 데이터가 부모에서 자식 컴포넌트로 props를 통해 전달하지만 깊게 중첩된 컴포넌트 구조에서는 이 방식이 비효율적이기 때문에 사용한다.

Context API의 주요 개념

  • Context
    • 전역적으로 관리하고 있는 데이터를 저장하는 컨테이너 역할.
    • React.createContext() 함수를 사용하여 생성된다.
    • 생성된 Context는 Provider과 Consumer을 통해 데이터를 주고 받습니다.
  • Provider
    • Context에서 제공하는 컴포넌트로 데이터를 전달하는 역할을 한다. Provider은 컴포넌트 트리에서 Context를 구독하는 하위 컴포넌트에게 값을 전달한다. 이 때 Value 속성을 통해 전달할 데이터를 정의한다.
  • Consumer
    • Context에서 제공하는 또 다른 컴포넌트로 데이터를 소비하는 역할을 한다. Consumer은 Context로부터 전달된 데이터를 받아 사용하는 컴포넌트이다. 함수형 컴포넌트는 useContext 훅을 사용하여 더 간단하게 Context 데이터를 소비할 수 있다.
  • useContext
    • 함수형 컴포넌트에서 Context를 쉽게 사용할 수 있게 도와주는 리액트 훅이다. Consumer를 사용하는 것보다 코드가 간결해지고 직관적으로 Context에 접근할 수 있다.

Context API의 장점

  • Prop Drilling 방지
    • 부모에서 자식으로 일일이 ‘Props’를 전달 해야하는 번거로움을 해결할 수 있다. 이를 통해 코드가 더 간결하고 관리하기 쉬워진다.
  • 전역 상태 관리
    • Context API를 사용하면 애플리케이션 전역에서 상태를 관리하고 필요한 곳에서 쉽게 접근할 수 있다. 간단한 상태 관리에서 리덕스 같은 라이브러리 없이도 Context API만으로 충분히 해결할 수 있다.
  • 재사용성
    • Context를 통해 전역적으로 데이터를 관리하면 특정 상태나 설정을 여러 컴포넌트에서 재사용할 수 있다

클래스형과 함수형의 차이

클래스형 컴포넌트

  • ES6을 사용하여 정의된다.
  • 상태와 라이프사이클 메서드를 사용하여 컴포넌트의 동작을 제어한다.
  • this 키워드를 사용해 상태 및 메서드에 접근한다.
  • 장점
    • 상태와 라이프사이클 관리
      • 클래스형 컴포넌트는 상태를 관리하고, 라이프사이클 메서드를 사용하여 컴포넌트의 생명주기를 제어할 수 있다.
    • 오래된 프로젝트에서 많이 사용
      • 리액트 초기에는 상태 관리와 라이프사이클 메서드를 사용해야 할 때 클래스형 컴포넌트가 필수적이다.

함수형 컴포넌트

  • 단순한 자바스크립트 함수로 정의
  • React Hooks를 사용하여 상태 및 라이프사이클을 관리할 수 있다.
  • useState, useEffect 등 다양한 훅을 사용하여 클래스형 컴포넌트에서 사용했던 기능들을 함수형 컴포넌트에서 쉽게 구현할 수 있다.
  • 장점
    • 간결한 코드
      • this 피워드가 필요하지 않아 코드 작성이 더 쉽다.
    • React Hooks의 도입
      • 훅을 사용해 상태 관리, 사이드 이펙트 처리 등을 할 수 있으며, 함수형 컴포넌트에서도 클래스형 컴포넌트의 기능을 모두 사용할 수 있다.
    • 재사용성 및 테스트 용이성
      • 함수형 컴포넌트는 일반 함수처럼 동작하므로, 재사용성과 테스트가 더 용이하다.

라이프사이클 메소드

라이프사이클의 단계

  1. 마운팅
    • 컴포넌트가 DOM에 처음 추가될 때 호출되는 메소드
  2. 업데이트
    • 컴포넌트가 업데이트될 때 호출되는 메소드
  3. 언마운팅
    • 컴포넌트가 DOM에서 제거될 때 호출되는 메소드

출처

https://react.dev/learn/managing-state

'TIL' 카테고리의 다른 글

WAS ; Web Application Server  (0) 2024.11.13
AMP (Accelerated Mobile PAges)  (0) 2024.08.26
자바스크립트의 ES6  (0) 2024.08.21
자바스크립트의 this  (0) 2024.08.20
자바스크립트의 타입  (0) 2024.08.19

Babel 이란?

  • 바벨은 자바스크립트의 트랜스파일러이다.
    • 컴파일 : 한 언어로 작성된 소스 코드를 다른 언어로 변환
    • 트랜스파일: 한 언어로 작성된 소스 코드를 비슷한 수준의 추상화를 가진 다른 언어로 변환
      • 공식 페이지에서는 컴파일러라고 명시하는데 의미상은 트랜스파일러라고 하는 곳도 많아 혼용하는 곳이 많은 것 같다.
  • 주로 ES6 버전을 오래된 브라우저나 환경에 맞추기 위해 이전 버전의 자바스크립트로 변환하는데 사용된다.
  • 바벨이 해줄 수 있는 일은
    • 문법 변환
    • 구형 브라우저에서도 최신 브라우저에서 제공하는 기능을 사용할 수 있도록 해준다.
    • 소스 코드 변환

ES6에서 추가된 스펙

var, let, const

  • var (ES6 이전에도 있던 기존 변수 정의 방식)
    • var로 선언된 변수는 함수 스코프를 가진다.
    • 변수가 선언된 함수 내에서만 유효하고, 블록 내에서 선언해도 해당 블록 외부에서 접근할 수 있다.
    • 호이스팅 문제 : var로 선언된 변수는 선언이 끌어올려지지만 선언과 동시에 할당된 값을 끌어올려지지 않는다.
    • console.log(a); // undefined var a = 10; console.log(a); // 10
  • let
    • let은 블록 스코프 안에서 변수를 정의할 수 있게 해준다.
    • let도 호이스팅이 발생하지만 var과 달리 초기화되지 않은 상태에서 접근하려고 하면 ReferenceError가 발생한다.
    • console.log(b); // ReferenceError let b = 20; console.log(b); // 20
  • const
    • const는 변하지 않는 변수를 지정할 때 사용한다.
    • const 역시 블록 스코프를 가진다.
    • const c = 30; c = 40; // TypeError: Assignment to constant variable. const obj = { name: "Alice" }; obj.name = "Bob"; // 객체의 프로퍼티는 변경 가능 console.log(obj.name); // "Bob"
  • var과의 차이점
    • let은 재선언될 수 없지만, 업데이트 될 수 있다.
    • var 변수는 재선언되고, 업데이트 될 수 있다.
    • const는 재선언도, 업데이트도 불가능하다.

화살표 함수

  • 기존의 함수 표현식에 비해 문법이 짧고 특히 this 키워드의 바인딩 방식에서 차이가 크다.
// 기존 함수 표현식
const add = function(a, b) {
    return a + b;
};

// 화살표 함수 표현식
const add = (a, b) => a + b;
  • function 키워드를 생략하고, 파라미터 목록 뒤에 ⇒ 기호를 사용한다.
  • 암시적 반환
    • 함수 본문이 하나의 표현식인 경우 중괄호를 생략할 수 있으며 해당 표현식의 결과가 자동으로 반환된다.
  • 화살표 함수는 기존 함수 표현식과 달리 this 키워드의 바인딩이 고정된다.
  • 화살표 함수는 자신이 정의된 위치에서 this를 상속받고 this가 호출시점에 변경되지 않는다.

객체 구조분해 할당

  • 객체의 속성을 쉽게 추출하여 변수에 할당하는 기능
  • const person = { name: "Alice", age: 25, job: "Engineer" }; // 객체 구조분해 할당 const { name, age, job } = person; console.log(name); // "Alice" console.log(age); // 25 console.log(job); // "Engineer"

배열 구조분해 할당

  • 배열의 요소들을 쉽게 변수에 할당한다.
  • 객체 구조분해 할당과 비슷하지만 배열의 인덱스를 기준으로 요소를 할당하는 점에서 차이가 있다.
  • const numbers = [1, 2, 3]; // 배열 구조분해 할당 const [a, b, c] = numbers; console.log(a); // 1 console.log(b); // 2 console.log(c); // 3

스프레드 연산자

  • 배열이나 객체의 요소를 개별 요소로 분해하거나 이를 다른 배열 또는 객체에 간단하게 복사하고 병합할 수 있는 방법을 제공한다.
    • 배열 복사
    • 배열 병합
    • 객체 복사
    • 객체 병합
    • 합수 인자 분해

for of 루프

  • 이터러블 객체의 요소들을 반복할 때 사용된다.
    • 이터러블 객체는 배열, 문자열, Map, Set 등과 같이 반복 가능한 값을 가진 객체를 말한다.
    const array = [1, 2, 3, 4];
    
    for (const element of array) {
        console.log(element);
    }
    // 출력: 1, 2, 3, 4
    
    • 배열과 다르게 객체에는 사용할 수 없다.
  • for…of 와 for…in의 차이점
    • of 는 배열, 문자열 등의 값을 반복한다.
    • in은 객체의 키를 반복한다.

템플릿 리터럴

  • 백틱을 사용하여 문자열을 정의하는 법
  • 문자열 내에서 변수를 삽입하고 여러 줄의 문자열도 처리할 수 있다.
  • const name = "Alice"; const greeting = `Hello, ${name}!`; console.log(greeting); // "Hello, Alice!"

클래스

  • 객체 지향 프로그래밍을 지원하는 클래스 문법을 도입
    • 프로토타입 기반 상속을 좀 더 직관적이고 깔끔하게 사용할 수 있도록 한다.
    • 생성자, 메서드, 상속 등을 지원
    class Person {
        // 생성자 메서드
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
    
        // 클래스 메서드
        greet() {
            console.log(`Hello, my name is ${this.name}`);
        }
    }
    
    // 클래스 인스턴스 생성
    const person1 = new Person('Alice', 25);
    person1.greet(); // "Hello, my name is Alice"
    

디폴트 매개변수

  • 함수의 매개변수에 기본값을 설정할 수 있다.
    • 함수 호출 시 인자 제공을 안 하면 기본값을 사용
    function greet(name = 'Guest') {
        console.log(`Hello, ${name}`);
    }
    greet(); // "Hello, Guest"
    

Promise

  • 비동기 작업의 결과를 처리하기 위한 객체
  • 비동기 작업의 성공과 실패를 처리할 수 있다.
    • 정리한 적 있다!
    https://asmallroom.tistory.com/14

출처

https://babeljs.io/docs/

https://www.w3schools.com/js/js_es6.asp

This

  • 대부분의 경우 this의 값은 함수를 호출한 방법에 의해 결정된다.
  • 실행 중에는 할당으로 설정할 수 없고 함수를 호풀할 때마다 다를 수 있다.

ES5의 경우

  • 함수를 어떻게 호출했는 지 상관하지 않고 this 값을 설정할 수 있는 bind 메서드를 도입

ES6의 경우

  • 스스로의 this 바인딩을 제공하지 않는 화살표 함수를 추가

단독으로 쓴 this

  • 냅다 this를 호출하는 경우에 this는 global object를 가리킨다.
    • [object Window]
    'use strict';
     
    var x = this;
    console.log(x); //Window
    
    

함수 안에서 쓴 this

  • 함수 안에서 this는 함수의 주인에게 바인딩된다.
    • 함수의 주인 ⇒ window 객체
    function f1() {
      return this;
    }
    
    // 브라우저
    f1() === window; // true
    
    // Node.js
    f1() === global; // true
    
  • 엄격 모드에서 this 값은 실행 문맥에 진입하며 설정되는 값을 유지한다.
  • function f2() { "use strict"; // 엄격 모드 참고 return this; } f2() === undefined; // true

메서드 안에서 쓴 this

  • 일반 함수가 아니고 메서드인 경우
  • 메서드 호출 시 내부 코드에서 사용된 this는 해당 메서드를 호출한 객체로 바인딩된다.
  • var num = 0; function showNum() { console.log(this.num); } showNum(); //0 var obj = { num: 200, func: showNum, }; obj.func(); //200

이벤트 핸들러 안에서 쓴 this

  • 이벤트 핸들러에서 this는 이벤트를 받는 HTML 요소를 말한다.
  • var btn = document.querySelector('#btn') btn.addEventListener('click', function () { console.log(this); //#btn });

생성자 안에서 쓴 this

  • 생성자 함수가 생성하는 객체로 this가 바인딩 된다.
  • function Person(name) { this.name = name; } var kim = new Person('kim'); var lee = new Person('lee'); console.log(kim.name); //kim console.log(lee.name); //lee // new 키워드를 빼면 일반 함수 호출과 같아져서 window가 바인딩된다.

명시적 바인딩을 한 this

  • 명시적 바인딩은 짝을 짓는다.
    • apply()
      • apply()에서 매개변수로 받은 첫번째 값은 함수 내부에서 사용되는 this에 바인딩된다.
      • 두번째 값인 배열은 자신을 호출한 함수의 인자로 사용한다.
      func.apply(thisArg, [argsArray]);
      
      //인수들의 단일 배열을 받는다.
      
      function Character(name, level) {
        this.name = name;
        this.level = level;
      }
       
      function Player(name, level, job) {
        Character.apply(this, [name, level]);
        this.job = job;
      }
       
      var me = new Player('Nana', 10, 'Magician');
      
    • call()
      function whoisThis() {
        console.log(this);
      }
       
      whoisThis(); //window
       
      var obj = {
        x: 123,
      };
       
      whoisThis.call(obj); //{x:123}
      
    • ⇒ 인자를 this로 만드는 기능
  • 두 메서드 모두 보통 유사배열 객체에게 배열 메서드를 쓰고자 할 때 사용한다.

화살표 함수로 쓴 this

  • 화살표 함수에서 this는 자신을 감싼 정적 범위이다.
  • 전역 코드에서는 전역 객체를 가리킨다.
  • 화살표 함수는 전역 컨텍스트에서 실행되더라도 this를 새로 정의하지 않고, 바로 바깥 함수나 클래스의 this를 쓴다.
  • var Person = function (name, age) { this.name = name; this.age = age; this.say = function () { console.log(this); // Person {name: "Nana", age: 28} setTimeout(() => { console.log(this); // Person {name: "Nana", age: 28} console.log(this.name + ' is ' + this.age + ' years old'); }, 100); }; }; var me = new Person('Nana', 28); //Nana is 28 years old

Call / Apply / Bind

  • Call
    • this를 바인딩하면서 함수를 호출하는 것
    • 두번째 인자를 apply와 다르게 하나씩 넘기는 것
  • Apply
    • this를 바인딩하면서 함수를 호추라는 것
    • 두번째 인자가 배열
  • Bind
    • 함수를 호출하는 것이 아닌 this가 바인딩 된 새로운 함수를 리턴

출처

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/this

https://nykim.work/71

'TIL' 카테고리의 다른 글

React (Redux , Context API, 클래스형과 함수형)  (0) 2024.08.22
자바스크립트의 ES6  (0) 2024.08.21
자바스크립트의 타입  (0) 2024.08.19
비동기 프로그래밍 (Callback, Promise, Async/Await)  (0) 2024.08.16
웹 프로토콜  (0) 2024.08.15

+ Recent posts