Let's Write_ 프론트엔드

순열 재귀 DFS + 백트래킹

카테고리 없음

https://leetcode.com/problems/permutations/

46. Permutations

 

## answer1  Memory: 64.3mb

/**
* @param {number[]} nums
* @return {number[][]}
*/

var permute = function (nums) {
let result = [];

function createElement(currentArray, left) {
if (left.length === 0) return result.push(currentArray);
left.forEach((n, i) =>
createElement(
[...currentArray, n],
[...left.slice(0, i), ...left.slice(i + 1)]
)
);
forEach 내부 동작 (재귀)
/*([], [1,2,3,4])
//n = 1, i=0
  createElement([1], [2,3,4])
//n'=2, i'=0
createElement([1,2],[3,4])
//n''=3, i''=0
createElement([1,2,3], [4])
//n'''=4, i''' = 0
createElement([1,2,3,4], [])
left === 0 , return;
//n''=4, i''=1
createElement([1,2,4], [3])
//n'''=3, i''' = 0
createElement([1,2,4,3], [])
left === 0 , return;
//n'=3, i'=1
createElement([1,3],[2,4])
//n''=2, i''=0
createElement([1,3,2],[4])
//n'''=4, i''' = 0
createElement([1,3,2,4], [])
left === 0 , return;
....
*/
}

createElement([], nums);

return result;
};

 

 

시간 복잡도 O(N∗N!)

n!: 가능한 순열의 개수

n: 하나의 순열이 n개의 숫자를 포함하므로 배열 복사 시 O(n) 추가됨

공간 복잡도 O(N∗N!)

 

 

| 방법                              | 시간 복잡도    | 공간 복잡도                    | 특징                             |

| ------------------------------- | --------- | ------------------------- | ------------------------------ |

| 📌 DFS + 백트래킹 (`dfs([], nums)`) | O(n × n!) | O(n) 스택 + O(n × n!) 결과 저장 | 간결, 직관적, 가장 널리 쓰임              |

| 📌 DFS + `used[]` 배열 (방문 여부 체크) | O(n × n!) | O(n) + O(n × n!)          | 배열 복사를 줄일 수 있음                 |

| 📌 Heap's Algorithm             | O(n × n!) | O(n × n!)                 | 재귀 없이 구현 가능. 배열 내부 스왑으로 메모리 절약 |

| 📌 BFS / Queue 기반               | O(n × n!) | O(n × n!)                 | 큐 사용. 구조는 복잡하지만 반복문 기반         |

 

 

✅ 실전에서 가장 좋은 함수: DFS + 백트래킹 (slice로 분리)

👍 장점

코드가 짧고, 읽기 쉽고, 직관적.

 

재귀 스택 외엔 특별한 메모리를 쓰지 않음.

 

디버깅 및 로직 이해가 가장 쉬움.

 

👎 단점

slice()와 spread (...) 연산으로 인해 메모리 복사 비용 있음.

 

구조 분석

스택 기반 구조가 적절히 활용됨

재귀는 내부적으로 콜스택(stack) 을 활용함.

 

순열 생성은 깊이 우선 탐색(DFS)이기 때문에 스택과 궁합이 좋음.

 

명시적으로 스택을 구현하지 않아도 콜스택이 자동으로 관리해줌.

🔍 재귀의 단점은 없을까?

단점  대응 방법

콜스택 오버플로우 위험 (깊은 재귀)  순열은 n!이 커서 실제 문제에서는 n ≤ 10 정도로 제한됨 → 실질적 문제 없음

성능 최적화가 어렵다 Heap's Algorithm처럼 반복문 기반도 있지만, 일반적 상황에서는 필요 없음

✅ 결론

순열 문제에서는 재귀가 거의 항상 최적 선택입니다.

코드가 짧고 명확하며,

문제의 재귀적 성질과 구조적으로 딱 맞아떨어지고,

유지보수와 확장성에도 유리합니다.

*/

 

📌 스왑(Swap)은?

배열의 두 요소 값을 맞바꾸는 것

스왑은 메모리 복사 없이 배열을 바꾸는 가장 효율적인 방법이에요.

 

arr[0] <-> arr[2] 스왑

[arr[0], arr[2]] = [arr[2], arr[0]];

 

/** 동시할당

 *  배열 형태로 동시에 할당하는 이유

값이 덮어쓰기 되기 전에, 미리 복사해두고 바꿔야 하기 때문입니다.

덮어쓰기 문제를 회피하기 위한 안전한 스왑 방식입니다.

 

Destructuring Assignment는 내부적으로 오른쪽 값을 먼저 평가 → 임시 버퍼에 저장 → 왼쪽 변수에 순차적으로 할당합니다.

그래서 원래 값이 덮여도 영향받지 않음.

 * */

 

/* 📌 DFS (Depth-First Search, 깊이 우선 탐색)

트리나 그래프에서 루트(root)부터 한 갈래로 끝까지 내려가면서 탐색하는 방식.

한 가지 선택지를 끝까지 가보고,

더 이상 갈 곳이 없으면 되돌아와서(backtrack) 다른 선택지를 탐색하는 구조예요.

*/

 

/* 📌 백트래킹 (Backtracking)

탐색 도중 "이 선택은 정답이 될 수 없어"라고 판단되면, 직전 단계로 돌아가서 다른 선택지를 탐색하는 기법이에요.

DFS를 할 때, 잘못된 경로는 빨리 포기하고 돌아가는 것.

그래서 DFS + 백트래킹은 모든 경우를 다 보는 게 아니라, 일부 경우는 가지치기해서 속도를 높이기도 해요.

*/

 

/* 함수 slice, splice 차이

| 구분    | `slice(start, end)`     | `splice(start, deleteCount)` |

| ----- | ----------------------- | ---------------------------- |

| 원본 변경 | ❌ **변경 없음 (immutable)** | ✅ **원본 배열 변경 (mutable)**     |

| 반환값   | 잘라낸 **새 배열**            | 삭제된 요소로 구성된 **배열**           |

| 용도    | 복사해서 새로운 배열을 만들 때       | 배열의 일부를 제거/삽입할 때             |

| 성능    | 비교적 빠름 (메모리 복사)         | 느릴 수 있음 (원본 변경, 구조 순서 재조정)      |

 

 

✅ 순열 문제에서 splice() 사용의 문제점

만약 splice()를 원본 배열에 직접 쓰면, 다른 재귀 호출에서도 동일 배열이 영향을 받음.

이건 재귀 백트래킹에서는 치명적입니다.

 

 

*/

// ex

function dfs(picked, unpicked) {

  // ❌ splice는 원본 unpicked를 바꿈 → 다른 브랜치에서 오류 발생 가능

  const num = unpicked.splice(i, 1);

  dfs([...picked, num], unpicked);

}

//🎯 정리:

// 재귀 함수 내에서 배열 상태를 안전하게 유지하려면 immutable한 slice() 또는 spread 연산이 필수입니다.

 

/*📊 시간 복잡도

함수  시간 복잡도  설명

slice() O(k)  k: slice된 부분의 길이. 새 배열 생성

splice()  O(n)  삭제 후 배열 뒤 요소들 재배치 발생

 

slice()는 단순 복사 → 상대적으로 빠름.

splice()는 배열 내용이 바뀌면서 shift 발생 → 느림.

 

특히 splice(i, 1)은 i 이후 요소를 모두 한 칸 앞으로 당김 → O(n)

*/

 

/*✅ 실무적 권장사항

상황  추천 방법

안전하게 배열 일부 제거 : slice(0, i).concat(slice(i+1)) 또는 [...slice(0, i), ...slice(i+1)]

배열 변형을 직접 해야 할 때  : splice() (단, 반드시 복사본에만 적용!)

성능이 매우 중요한 경우 : in-place swap + DFS (예: Heap’s Algorithm)

*/

 

/*

🔚 결론

재귀적 순열 알고리즘에서는 slice()가 권장됩니다.

 

splice()는 원본을 파괴하기 때문에, 복사 없이 쓰면 큰 버그가 날 수 있어요.

 

성능도 slice()가 더 빠르고 안정적입니다.

 

다만 splice()는 "복사본에 대해 안전하게 쓸 때"만 쓰세요.

*/

 

# 공간복잡도

// ##answer2 Memory: 56mb

/**
* @param {number[]} nums
* @return {number[][]}
*/
var permute = function (nums) {
let res = [];
function gen(comb, nums) {
if (!nums.length) res.push(comb);
for (let i = 0; i < nums.length; ++i) {
gen(
[...comb, nums[i]],
nums.filter((_, ii) => ii != i)
);
}
}
gen([], nums);
return res;
};
 

/* answer1 vs answer2 메모리

## answer1

[…currentArray, n]는 새 배열 1개,

left.slice(0, i)가 새 배열 1개,

left.slice(i + 1)가 새 배열 1개,

이 두 개를 [ ... , ... ]로 다시 합쳐 또 하나의 새 배열이 생깁니다.

총 4개의 새 배열 인스턴스 생성.

 

slice + spread 방식이 filter 방식보다 훨씬 더 많은 배열을 찍어내기 때문에, 호출 횟수가 쌓일수록 메모리 사용량이 크게 늘어납니다.

 

## answer2

메모리 사용량이 낮은 answer2 구현은

[…comb, nums[i]]는 새로운 배열 1개,

nums.filter(...)도 새로운 배열 1개를 만듭니다.

총 2개의 새 배열 인스턴스 생성.

 

전역 결과 배열 하나만 사용

filter 한 번으로 배열을 복제

반환 값이 콜 스택에 남지 않음

 

 

## 반복문 vs. forEach

 

for (let i=0; …)

단순 인덱스 기반 루프 구조로, 콜백 오버헤드가 없습니다.

 

left.forEach((n,i) => …)

클로저 형태의 콜백 함수를 호출하므로, 내부적으로 더 많은 함수 객체가 생성·수명 관리됩니다.

 

함수 한 번 호출당 콜백 생성 비용이 추가되고, 무수히 많은 재귀 호출마다 이 콜백들이 살아 있어야 하니 메모리 사용이 더 높아집니다.

그러나

forEach와 전통적인 for 문 간의 메모리 사용량 차이는 배열 복제 방식에 비하면 거의 무시할 만한 수준입니다. forEach는 내부적으로 콜백 함수를 호출하면서 스택 프레임을 생성하지만, 이 오버헤드는 수천~수만 번의 배열 클론(예: slice + spread)보다 훨씬 작습니다. 실제 V8 엔진에서도 forEach와 for 루프 간에 메모리 사용량 차이는 거의 없다고 보고되고 있습니다

 

벤치마크를 보면, forEach는 약간의 함수 호출 오버헤드로 인해 성능이 미세하게 떨어질 수 있지만, 메모리 사용 면에서는 for 루프와 거의 동일한 수준을 유지합니다. 즉, 56 MB vs. 61 MB의 차이는 forEach vs. for 자체보다는, slice×2 + spread로 생성된 임시 배열 수가 주된 원인입니다

 

*/

* 메모리 피크(peak)란?*

메모리 피크(peak)란 프로그램이 실행되는 동안 한 시점에 사용된 메모리의 최고치를 가리키며, 흔히 ‘최대 메모리 사용량’이라고도 합니다.

# 정의 및 중요성

정의: 프로그램 시작부터 종료 시점까지 순간순간 사용된 힙(heap), 스택(stack), 그리고 런타임이 관리하는 기타 메모리 영역의 합 중 가장 높은 값을 의미합니다.

중요성: 메모리 피크를 알아야 OOM(Out-Of-Memory) 오류를 방지하고, 배포환경(컨테이너, 서버리스)의 메모리 한계를 적절히 설정할 수 있습니다.

# 메모리 피크 발생 지점

데이터 구조 생성: 대용량 배열, 객체, 버퍼를 한꺼번에 만들 때

재귀 호출: 깊은 재귀가 쌓여 스택 프레임과 임시 자료구조가 동시에 메모리에 존재할 때

GC 대기: 가비지 컬렉션 이전까지 해제되지 않은 객체들이 많은 경우

# 측정 방법

언어별 도구:

JavaScript (Node.js): process.memoryUsage().heapUsed

Python: tracemalloc 모듈

Java: -Xmx 설정과 jstat

외부 프로파일러: Valgrind Massif, VisualVM, Chrome DevTools 등으로 힙 스냅샷(snapshot) 분석

# 관리 전략

메모리 절약 기법:

인플레이스(in-place) 연산, 스트리밍(streaming) 처리

청크(chunk) 단위로 나눠 작업

GC 튜닝: 가비지 컬렉터 주기와 동작 방식을 조정

 

리소스 한계 설정: 컨테이너 메모리 리밋, 서버리스 타임아웃 환경 제약을 미리 계산하여 설정

코드리뷰 작성 가이드

TIL

## **우리는 왜 코드리뷰를 해야하나요?**

:fire: “소프트웨어를 유지보수하는 조직에서 코드 한 줄을 변경한다고 했을 때, 코드리뷰가 도입되기 전에는 그러한 변경의 55% 정도가 문제를 일으켰다.

그러나 리뷰 과정이 도입된 이후에는 그러한 변경의 2% 정도에서만 문제가 발생했다.”

- **버그의 조기 발견**(불필요한 시간과 비용을 절감)
- **개발 컨벤션 준수**(가독성과 유지보수 편의성 극대화)
- **중복 코드 방지 및 모듈의 재사용성 증대**
- **배움의 기회**(‘아, 이렇게 쉬운 방법이 있었구나?’)

## **우리의 코드 리뷰 규칙**

- 2일 이내에 리뷰를 완료하여 리뷰의 병목을 해소합니다. (피쳐 마감 일자에 따라 우선순위를 결정합니다.)
- 이슈가 있다면 페어프로그밍으로 같이 해결합니다.
- 각자의 개발스타일은 다릅니다. 기존에 자신의 스타일이 정답이라는 생각을 버리고, “나의 의견은 이러한데 당신은 어떻게 생각하시나요?”와 같이 의견을 묻는 것이 좋습니다.
- 리뷰어가 진행해주는 QA 중 이슈가 발생된다면 리뷰이는 코드 변경사항과 개발된 피쳐와 관련된 모든 사항에 대해 전체 테스트 및 확인을 다시 진행합니다. 놓친 부분이 한 군데 발생된다면 다른 곳도 꼼꼼하게 다시 한번 점검하는 것이 좋습니다.
- 공통 컴포넌트를 수정한다면 사용하고 있는 모든 부분을 체크하여 side effect 여부를 확인합니다. 
- 앞으로도 함께 만들어가는 문화이니 어떻게 진행할지 같이 고민해봐요 :slight_smile: 언제든 의견 주세요<span dir="">\~</span>

## 리뷰 체크리스트

:grinning: MR올리기 전 아래 내용을 확인해주세요.

* [ ] **merge할 브랜치의 위치를 확인**하셨나요?
* [ ] **코드의 맥락(CONTEXT)을 이해할 수 있는 설명을 추가하셨습니까?** (ex.로그인 시 발생하는 이메일 인증 오류에 대한 버그 수정입니다)
* [ ] **기능이 정상적으로 동작**하나요?
* [ ] 변수명 등을 **통일성 있게, 축약 또는 생략하지 않고** 작성했습니까?
* [ ] **개발 컨벤션에 준수**하여 작성했습니까?

:grin: MR 올린후

* [ ] **기존 코드와 충돌이 발생**하면 머지 버튼이 비활성화 되기도 합니다! 그 부분까지 체크하여 충돌을 해결해야 리뷰를 진행할 수 있습니다.

:robot: **프로젝트를 진행하면서 체크리스트는 더효율적인 코드 리뷰를 위해 수정될 수 있습니다.**

## 리뷰어 체크리스트

:laughing: 팀원들은 체크리스트를 보며 코드리뷰를 진행해주세요.

* [ ] **왜 개선이 필요한지 이유를 충분한 설명**해주셨습니까?
* [ ] 작은 커밋 단위만을 보지 말고, **전체 코드의 맥락을 살피면서 리뷰**를 해주세요.
* [ ] **이해가 안가는 부분이 있다면 질문**해주세요.
* [ ] 리뷰를 위한 리뷰를 하지 마세요. **피드백 할 게 없으면 칭찬**해 주세요.:heartbeat:

:raising_hand_tone1:**리뷰를 어떻게 해야할지 모르겠다면 이 글을 한번 봐주세요**

https://tech.kakao.com/2022/03/17/2022-newkrew-onboarding-codereview/

https://blog.logi-spot.com/코드리뷰의-진짜-목적은-따로있다/

https://xo.dev/github-collaboration-guide/ 깃헙에 코드리뷰 요청 보내는법

https://www.youtube.com/watch?v=9FZaYz0s8s4 라매개발자 깃헙 코드리뷰 협업

cf. 체크사항

> ### **배울만한 점은 없는지**
>
> 코드리뷰에 많은 사람이 오해하는 부분 중 하나는, 경력이 많거나 실력이 뛰어난 개발자가 후배 개발자의 코드를 검사한다고 생각하는 것입니다. 코드리뷰에서 코드의 작성자와 리뷰어는 누가 더 경력이 높거나 낮을 필요가 없습니다. 또한, 코드리뷰의 목적은 코드 작성자에 게 피드백을 주는 것도 있지만, 해당 코드를 보면서 ‘아, 이런 식으로 코드를 작성하는 것도 가능하구나?’, ‘아, 이렇게 쉬운 방법이 있었구나?’와 같은 학습효과도 함께 가지고 있습니다. 그렇기 때문에 코드를 리뷰할 때는 피드백을 주기 위한 시각과 좋은 점을 배우려는 시각, 이 두 가지 시각의 균형을 맞추며 진행하는 것이 좋습니다.

우리 프론트엔드팀이 함께 논의하여 정한 팀내 네이밍 규칙

카테고리 없음

읽기 좋은 코드의 작성의 중요성을 알고 코드의 통일감을 위해 팀내에서 정했었던 규칙이다. 참고하셔도 좋을듯 하여 여기 적어봅니다. 

 

네이밍 만으로도 바로 어떤용도의 함수인지 알기 쉽기 위해서 React-Query, api service 호출 hook, interface 등의 네이밍 규칙을 정하였다. 

 

누가 작성하더라도 규칙을 따른다면 누구든 파악하기 쉽고 새로운 멤버 또한 파악하기 쉬워질 것이다. 

따라서 사용하는 단어도 통일하기로 했다. 

 

내부적으로 정한 것이므로 참고하는 용도로 이런식으로도 정하는구나 하고 보면 될것 같다. 

 

---

react-query 작성 규칙

  • query key 는 해당 파일 상단에 작성한다.
  • interface 는 query key 정의해둔 하단부터 시작해 작성한다.
    • RequestDto 먼저 작성하고 ResponseDto를 작성한다. (request를 먼저 보내고 response를 받기 때문에 읽기에 더욱 편함)
    • ex) CharacterRequestDto (Request)
    • ex) CharacterResponseDto (Response)
  • 작성 전 스튜디오 <-> 어드민 간 확인하여 같은 api에 대해서는 일치하도록 작성한다.(스튜디오와 어드민에서 사용하는 api 가 다르므로 파일이 완전히 일치하지는 않음.)
  • 작성 순서 요약
    • query key
    • interface (type 정의)
    • react query hook

Naming 규칙

interface 네이밍 규칙

common.d.ts

  • 공통으로 쓰이는 공통틀은 끝에 Type 으로 네이밍. ex.GetListType
  • 서버에서 결과로 받은 값은 끝에 Dto 를 붙여 네이밍
  • 여러 곳에서 쓰이는 Dto(해당 api 뿐만이 아닌 여러곳에서 사용되는 값) ex. FileImageType 는 /image 외에도 /titles, /chapters 에서도 사용된다

query hooks 네이밍 규칙

네이밍규칙 1: method 관련하여 이렇게 시작

  • get : useGet
  • post: useCreate
  • put: useUpdate
    • create, update 두 가지 역할 할 때에는 ‘usePut’으로 작성
  • delete: useDelete

네이밍규칙 2: id 를 param으로 받아서 한개에 대한 정보를 찾을 경우 네이밍 “useGetOneTitleCategory”

redux 내부는 context API 로 돌아간다고 하던데 그 작동원리를 알려줄 수 있어?

TIL

Redux가 Context API를 내부적으로 사용한다는 것은 정확한 설명입니다. Redux가 최신 버전(주로 React Redux v7 이상)에서 React의 Context API를 활용하여 전역 상태를 제공하는 방식이기 때문입니다. 이를 이해하려면 Redux와 React Redux의 작동 원리를 간단히 살펴보겠습니다.

1. Redux의 기본 구조

Redux는 세 가지 핵심 개념으로 구성됩니다:

  • 스토어(Store): 애플리케이션의 전역 상태를 보유하는 객체입니다. 이 객체는 createStore()를 통해 생성됩니다.
  • 액션(Action): 상태에 대한 변경을 요청하는 객체입니다. 액션은 반드시 type 속성을 가져야 하며, 추가적으로 데이터를 담을 수 있습니다.
  • 리듀서(Reducer): 상태 변경 로직을 정의하는 순수 함수입니다. 액션을 받아 현재 상태를 기반으로 새 상태를 반환합니다.

2. React Redux의 역할

React Redux는 Redux를 React 애플리케이션에 통합하기 위한 공식 바인딩 라이브러리입니다. React 컴포넌트가 Redux의 스토어에 접근할 수 있게 하고, 상태 변경이 발생하면 자동으로 UI를 업데이트합니다.

3. Context API의 활용

React Redux v7부터는 React의 Context API를 활용하여, 전역 상태를 자식 컴포넌트들에게 전달합니다. 이 과정을 단계별로 살펴보면 다음과 같습니다:

a. 스토어를 Context로 전달

React Redux의 <Provider> 컴포넌트는 Redux 스토어를 React의 Context로 전달합니다. Provider는 다음과 같이 동작합니다:

```
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import rootReducer from './reducers';

const store = createStore(rootReducer);

<Provider store={store}>
  <App />
</Provider>
```
 

여기서 Provider 컴포넌트는 React.createContext()를 사용하여 Context를 생성하고, 그 Context를 통해 store 객체를 하위 컴포넌트들에게 전달합니다.

b. useSelector와 useDispatch 훅의 동작

React Redux에서 useSelector와 useDispatch 훅을 사용하여 스토어의 상태를 조회하거나 액션을 디스패치할 수 있습니다.

  • useSelector: 이 훅은 Context에서 스토어를 가져와, 컴포넌트가 스토어의 특정 상태를 구독(subscribe)하게 만듭니다. 상태가 변경되면 해당 컴포넌트는 자동으로 리렌더링됩니다.
  • useDispatch: 이 훅은 Context를 통해 스토어의 dispatch 메서드를 가져옵니다. 이를 통해 액션을 디스패치할 수 있습니다.

c. Context API로 상태 변화 반영

React Redux의 Provider는 Context를 사용하여 하위 컴포넌트들이 상태 변화를 감지할 수 있게 합니다. 예를 들어, useSelector 훅을 사용하는 컴포넌트는 Context에서 현재 스토어 상태를 가져오고, 이 상태가 변경될 때마다 해당 컴포넌트가 자동으로 리렌더링됩니다.

4. 작동 원리 요약

  • Context 생성: Provider 컴포넌트가 Context를 생성하고, 그 안에 Redux 스토어를 담아 하위 컴포넌트들에게 전달합니다.
  • Context 활용: useSelector, useDispatch 훅이 이 Context에 접근하여 상태를 구독하거나 액션을 디스패치합니다.
  • 상태 변경과 리렌더링: Context를 통해 전달된 스토어의 상태가 변경되면, 이 상태를 구독하는 컴포넌트들이 리렌더링됩니다.

이러한 방식으로 React Redux는 Context API를 활용하여 React 컴포넌트들이 전역 상태를 쉽게 사용할 수 있게 하고, 상태 변화에 따라 UI가 자동으로 업데이트되도록 합니다.

 

# Context API를 직접 사용하는 것과 Redux를 사용하는 것의 장단점은 무엇일까요?

장점

  1. 간단함:
    • Context API는 React에 내장된 기능으로, 추가적인 라이브러리 설치가 필요 없습니다. 기본적인 전역 상태 관리에는 충분히 사용할 수 있습니다.
    • 설정과 사용법이 비교적 단순하며, 학습 곡선이 낮습니다.
  2. 작은 프로젝트에 적합:
    • 작은 규모의 애플리케이션이나 상태가 복잡하지 않은 경우 Context API를 사용하여 쉽게 상태를 공유할 수 있습니다.
  3. 유연성:
    • 특정 컴포넌트 계층구조 내에서 전역 상태를 관리하고 싶을 때 적합합니다. Context는 필요한 부분에서만 사용하도록 범위를 제한할 수 있습니다.

단점

  1. 성능 이슈:
    • Context API를 사용할 때, 상태가 변경되면 해당 Context를 구독하는 모든 컴포넌트가 리렌더링됩니다. 상태의 변화가 빈번하거나 구독자가 많은 경우 성능 저하가 발생할 수 있습니다.
    • 특히, 상태가 특정 컴포넌트에만 관련 있는 경우에도, 전체 Context가 재생성되어 불필요한 리렌더링이 발생할 수 있습니다.
  2. 복잡한 상태 관리에 부적합:
    • 복잡한 전역 상태나 많은 상태를 관리해야 하는 경우, Context API는 적합하지 않을 수 있습니다. 상태의 변화 추적이나 비동기 작업 관리 등에서 한계가 있습니다.
    • 미들웨어를 통해 비동기 작업을 효율적으로 처리하기 어려운 경우가 많습니다.
  3. 디버깅 도구 부족:
    • Redux와 같은 강력한 디버깅 도구가 부족하여, 상태 변화 추적이 어려울 수 있습니다.

Redux의 장단점

장점

  1. 예측 가능한 상태 관리:
    • Redux는 상태의 흐름을 명확하게 규정하고, 상태 변화가 언제 어디서 발생하는지 예측할 수 있게 합니다. 이는 특히 대규모 애플리케이션에서 중요한 장점입니다.
  2. 미들웨어 지원:
    • Redux는 redux-thunk, redux-saga와 같은 미들웨어를 사용하여 비동기 작업을 쉽게 처리할 수 있습니다. 이를 통해 복잡한 비동기 로직도 깔끔하게 관리할 수 있습니다.
  3. 강력한 디버깅 도구:
    • Redux DevTools를 사용하면 상태의 변화를 시간순으로 추적하고, 특정 시점으로 되돌리거나 재생(replay)할 수 있습니다. 이는 디버깅과 개발 과정에서 매우 유용합니다.
  4. 모듈화와 확장성:
    • Redux는 상태와 로직을 리듀서로 모듈화하여, 애플리케이션의 규모가 커져도 코드가 체계적으로 관리될 수 있습니다. 다양한 패턴과 구조를 지원해, 대규모 애플리케이션에서도 확장 가능성을 제공합니다.

단점

  1. 복잡성:
    • Redux의 설정은 복잡할 수 있으며, 특히 작은 프로젝트에서는 오히려 오버헤드가 될 수 있습니다. 초기 설정과 보일러플레이트 코드가 많아 간단한 작업에도 불필요한 복잡성이 추가됩니다.
  2. 추가적인 학습 필요:
    • Redux의 개념(액션, 리듀서, 스토어, 미들웨어 등)을 이해하고 활용하려면 추가적인 학습이 필요합니다. 특히 팀원 모두가 이 개념들을 잘 이해해야 원활한 협업이 가능합니다.
  3. 가독성 저하 가능성:
    • 비록 Redux가 코드 구조를 체계적으로 관리할 수 있게 해주지만, 잘못 설계된 Redux 코드베이스는 오히려 코드의 가독성을 저하시킬 수 있습니다. 모든 상태 변화가 중앙 집중식으로 처리되므로, 코드가 길어지고 복잡해질 수 있습니다.

결론

  • Context API는 간단한 전역 상태 관리가 필요한 소규모 프로젝트에서 적합하며, 별도의 라이브러리 설치 없이 React의 내장 기능을 활용할 수 있습니다. 그러나 상태가 복잡해지거나 많은 컴포넌트가 상태에 의존할 경우 성능 이슈가 발생할 수 있습니다.
  • Redux는 대규모 애플리케이션이나 복잡한 상태 관리가 필요한 경우에 적합합니다. 예측 가능하고 체계적인 상태 관리를 제공하며, 미들웨어와 디버깅 도구가 풍부합니다. 하지만 설정과 사용법이 복잡하여 작은 프로젝트에서는 오버헤드가 될 수 있습니다.

 


# Redux 도 내부적으로는 context api 를 사용하고 있는데 redux도 그럼 모든 컴포넌트가 리렌더링이 일어나나?

Redux는 내부적으로 Context API를 사용하고 있지만, 모든 컴포넌트가 리렌더링되지 않도록 최적화되어 있습니다. 이를 이해하기 위해서는 React Redux가 어떻게 작동하는지 살펴볼 필요가 있습니다.

1. React Redux의 최적화 메커니즘

React Redux는 useSelector 훅과 connect 함수로 컴포넌트들이 스토어의 상태를 구독할 수 있게 합니다. 이때 Redux는 상태 변경에 따른 불필요한 리렌더링을 방지하기 위해 몇 가지 중요한 최적화를 적용합니다:

a. useSelector와 리렌더링 최적화

  • useSelector 훅은 상태의 특정 부분만 구독하도록 설계되어 있습니다. 이 훅은 컴포넌트가 관심 있는 상태만 선택해 구독하며, 상태의 해당 부분이 변경될 때만 컴포넌트를 리렌더링합니다.
  • 내부적으로 useSelector 훅은 === 연산자를 사용해 이전 상태와 새 상태를 비교합니다. 만약 상태의 특정 부분이 변경되지 않았다면, 리렌더링을 트리거하지 않습니다.

b. connect 함수와 shouldComponentUpdate

  • connect 함수는 클래스형 컴포넌트에서도 사용되며, 기본적으로 shouldComponentUpdate 메서드를 통해 상태 변화가 해당 컴포넌트에 영향을 미치는지를 판단합니다.
  • connect는 상태의 특정 부분을 mapStateToProps로 매핑하고, 이 부분에 변화가 있는 경우에만 컴포넌트를 리렌더링합니다.

2. Context API와 Redux의 차이

Context API 자체는 구독자가 상태를 구독할 때, 상태가 변경되면 해당 Context를 사용하는 모든 컴포넌트를 리렌더링하게 합니다. 그러나 React Redux는 이러한 문제를 해결하기 위해 훨씬 세밀한 최적화를 적용합니다.

  • 구독 단위: Redux는 Context를 통해 스토어를 전달하지만, 각 컴포넌트는 useSelector나 connect를 통해 특정 상태만 구독합니다. 이로 인해 전체 컴포넌트 트리가 아닌, 관심 있는 컴포넌트만 리렌더링됩니다.
  • 구독 최적화: Redux는 상태의 특정 부분만 구독하고, 이 부분에 변화가 있는지 여부를 효율적으로 비교하여 불필요한 리렌더링을 방지합니다.

3. Redux가 리렌더링을 최소화하는 방법

  • 메모이제이션: useSelector와 같은 훅은 상태 비교를 통해 메모이제이션을 사용합니다. 이전과 동일한 상태라면 컴포넌트는 리렌더링되지 않습니다.
  • 선택적 구독: 각 컴포넌트가 전체 상태가 아닌, 필요한 부분만 선택적으로 구독함으로써 불필요한 리렌더링을 피합니다.
  • 의도적 리렌더링 제어: mapStateToProps와 같은 함수는 개발자가 어떤 상태 변화가 컴포넌트에 영향을 미치는지를 명확하게 정의할 수 있게 해줍니다.

결론

Redux는 내부적으로 Context API를 사용하지만, 모든 컴포넌트가 리렌더링되지 않도록 철저히 최적화되어 있습니다. 컴포넌트는 Redux 스토어의 특정 부분만 구독하며, 해당 상태에 변화가 있을 때만 리렌더링됩니다. 이러한 최적화 덕분에 Redux는 대규모 애플리케이션에서도 효율적인 상태 관리를 제공할 수 있습니다.

 

 

# zustand는 작동원리에 context API 가 들어가? 안들어간다면 zustnad의 작동원리를 알려줘

Zustand는 React 애플리케이션에서 전역 상태를 관리하기 위한 상태 관리 라이브러리입니다. 다른 상태 관리 라이브러리와는 달리, Zustand는 Context API를 사용하지 않습니다. 대신, 더 간단하고 효율적인 상태 관리 접근 방식을 제공합니다. 이제 Zustand의 작동 원리를 살펴보겠습니다.

1. Zustand의 핵심 개념

Zustand는 상태 관리를 위해 몇 가지 주요 개념을 사용합니다:

  • Store: Zustand의 스토어는 전역 상태를 보유하는 객체입니다. 이 스토어는 create 함수를 통해 생성됩니다.
  • Selectors: 상태의 특정 부분을 선택하고 구독할 수 있게 하는 함수입니다.
  • Actions: 상태를 변경하는 함수를 정의하는 곳입니다.

2. Zustand의 작동 원리

Zustand는 React의 훅(Hooks) 시스템을 기반으로 상태를 관리하며, 주요 작동 원리는 다음과 같습니다:

a. 스토어 생성

Zustand에서 상태를 관리하려면 먼저 스토어를 생성해야 합니다. 이는 create 함수를 사용하여 이루어집니다.

이 코드에서 useStore는 React 컴포넌트에서 사용할 수 있는 커스텀 훅입니다. set 함수는 상태를 변경하는 데 사용됩니다.

b. 상태 구독

Zustand에서는 상태를 구독(subscribe)하기 위해 간단히 useStore 훅을 사용합니다

 

c. 리렌더링 최적화

Zustand는 상태의 특정 부분만 구독할 수 있게 해주며, 이로 인해 상태의 일부가 변경될 때만 해당 부분을 구독하는 컴포넌트가 리렌더링됩니다. 이는 useSelector 훅을 사용하는 것과 유사하지만, Context API를 사용하지 않기 때문에 불필요한 리렌더링이 발생하지 않습니다.

d. 미들웨어 및 플러그인

Zustand는 미들웨어를 통해 상태 관리를 확장할 수 있습니다. 예를 들어, 상태를 로컬 스토리지에 저장하거나 Redux DevTools와 통합할 수 있습니다. 이를 통해 복잡한 상태 관리 로직을 구현할 수 있습니다.

 

Zustand 스토어를 로컬 스토리지에 저장하며, Redux DevTools와 통합하여 상태를 디버깅할 수 있습니다.

3. Zustand의 주요 특징

  • 간결함: Zustand는 상태 관리 로직을 간단하게 유지하며, 최소한의 API로 동작합니다.
  • 컨텍스트가 필요 없음: Context API를 사용하지 않으므로 상태를 사용하는 컴포넌트의 리렌더링 성능이 뛰어납니다.
  • 선택적 구독: 상태의 특정 부분만 구독함으로써 불필요한 리렌더링을 방지합니다.
  • 확장성: 미들웨어와 플러그인을 통해 기능을 확장할 수 있습니다.

결론

Zustand는 Context API를 사용하지 않고, React 훅과 간단한 API를 통해 상태를 관리합니다. 이를 통해 효율적인 리렌더링을 제공하며, 상태 관리의 복잡성을 줄입니다. Zustand는 작은 프로젝트부터 중간 규모의 애플리케이션까지 간편하게 사용할 수 있는 상태 관리 도구로, 성능과 간결함을 중시하는 개발자들에게 인기가 많습니다.

 


# Zustand에서 상태 구독 시 성능 최적화를 위해 사용할 수 있는 다른 기법은 무엇이 있을까요?

 
ChatGPT

Zustand에서 상태 구독 시 성능을 최적화하기 위해 사용할 수 있는 몇 가지 추가적인 기법이 있습니다. 이들 기법은 상태 변경이 애플리케이션 성능에 미치는 영향을 최소화하고, 불필요한 리렌더링을 방지하는 데 도움을 줍니다.

1. 상태 슬라이싱 (State Slicing)

Zustand에서 상태를 구독할 때, 상태의 특정 부분만 선택하여 구독하는 것이 좋습니다. 이를 "상태 슬라이싱"이라고 합니다. 예를 들어, 상태가 다음과 같이 정의되어 있다고 가정합니다:

```
const useStore = create((set) => ({
  user: { name: 'Alice', age: 25 },
  posts: [],
  addPost: (post) => set((state) => ({ posts: [...state.posts, post] })),
}));
```
 

user와 posts 상태가 둘 다 존재하지만, 특정 컴포넌트에서 user.name만 필요하다면, 전체 user 객체를 구독하는 대신, 특정 값만 구독하도록 코드를 작성할 수 있습니다:

```

const userName = useStore((state) => state.user.name);

```

이렇게 하면 user 객체의 다른 속성들이 변경되더라도 name을 구독하는 컴포넌트는 리렌더링되지 않습니다.

2. 상태 비교 함수 사용

Zustand의 useStore 훅은 상태의 특정 부분을 구독할 때, 상태가 변경되었는지 확인하는 비교 함수를 사용할 수 있습니다. 이를 통해 보다 정밀하게 리렌더링을 제어할 수 있습니다. 예를 들어:

```

const selectedPosts = useStore(
  (state) => state.posts,
  (oldPosts, newPosts) => oldPosts.length === newPosts.length
);
```

이 비교 함수는 posts 배열의 길이가 동일한지 비교하여, 길이가 변경되지 않으면 리렌더링을 방지합니다.

3. Shallow Equality Comparison

Zustand는 기본적으로 구독한 상태가 변경되었는지 확인하기 위해 얕은 비교(shallow comparison)를 사용합니다. 이를 통해 객체의 속성이 변경되지 않은 경우 리렌더링을 방지할 수 있습니다. 예를 들어:

 
```
const useStore = create((set) => ({
  user: { name: 'Alice', age: 25 },
  updateName: (name) => set((state) => ({ user: { ...state.user, name } })),
}));
```
 

여기서 useStore((state) => state.user)로 전체 user 객체를 구독할 때, 객체 자체가 새로 생성되었지만 속성값이 동일하다면 얕은 비교를 통해 리렌더링을 방지할 수 있습니다. 그러나 상태 슬라이싱을 통해 더 세밀한 구독을 하는 것이 이상적입니다.

4. 배치 업데이트 (Batch Updates)

Zustand는 상태 업데이트가 발생할 때 배치 처리(batch processing)를 지원합니다. 여러 상태 업데이트를 한 번에 처리하여 불필요한 리렌더링을 줄일 수 있습니다. 예를 들어, React의 startTransition 함수를 사용하여 배치 업데이트를 수행할 수 있습니다:j

```
import { startTransition } from 'react';

function handleMultipleUpdates() {
  startTransition(() => {
    useStore.setState({ user: { name: 'Bob' } });
    useStore.setState({ posts: ['Post 1', 'Post 2'] });
  });
}
```

이 방식은 여러 상태 변경을 한 번에 처리하여, 리렌더링 횟수를 최소화합니다.

5. React.memo 사용

상태 구독에 관련된 컴포넌트를 React.memo로 래핑하면, 상태 변경 시 해당 컴포넌트가 불필요하게 리렌더링되지 않도록 할 수 있습니다. React.memo는 props가 변경되지 않은 경우 리렌더링을 방지합니다:

```

const MyComponent = React.memo(() => {
  const userName = useStore((state) => state.user.name);
  return <div>{userName}</div>;
});
```

6. 컴포넌트 트리 분리

상태 구독의 영향을 최소화하기 위해, Zustand에서 상태를 구독하는 컴포넌트와 그렇지 않은 컴포넌트를 트리 구조에서 분리하는 것도 성능 최적화에 도움이 됩니다. 이렇게 하면 상태 변경이 컴포넌트 트리 전체에 불필요한 리렌더링을 유발하지 않게 됩니다.

 

노드 버전 관리자들 중 Volta는 여러 버전의 프로젝트를 유지해야하는 환경에서 유리합니다.

TIL

volta 라는 노드버전관리자가 있다. 

https://velog.io/@sehyunny/5-node-manager-compared?utm_source=substack&utm_medium=email

 

(번역) 5가지 노드 버전 관리자 비교 - 어떤 것이 적합할까요?

이 글에서는 서로 다른 5가지의 Node.js 버전 관리자를 비교해보겠습니다. 이들이 서로 어떻게 다른지 그리고 프로젝트의 상황에 따라 어떤 것을 고려해야하는 지 확인하실 수 있습니다.

velog.io

 이 글을 통해 알게되었고

`Volta는 버전 관리자 세계에서 떠오르는 샛별입니다(10k개 GitHub 스타).

Rust로 작성되었으며 크로스 플랫폼을 지원합니다.`

이에 안해볼 수가 없었다. 

 

현재 회사에서 진행하고 있는 프로젝트는 아주 아주 구버전과 최근 구축한 버전이 있다.

그래서 구버전을 유지보수해야할 때에 기존에는 n버전관리를 사용하고 있었기 때문에 할때마다 노드 버전 변경작업과 약간의 변경시간이 필요했는데, 볼타를 사용한다면, package.json 에 이 개발환경을 명시적으로 작성하여 모두에게 공유하여 협업이 용이해질 아니라

자동으로 해당 버전으로 실행시켜준다는 점이 정말 편리하고 마음에 든다. 

 

 업데이트 시키지 못한 나머지 여러버전을 관리해야하는 상황이라면 볼타를 추천한다.

 

1. 일단 설치한다. 

curl https://get.volta.sh | bash

 

2. 볼타를 설치하고 나면 package.json 에 이렇게 작성하기만 하면 된다. 

우리 구 버전은 다음과 같다. 

 

```
  "volta": {
    "node": "14.19.0",
    "yarn": "1.22.0"
  },

````

 

또 다른건 node 버전 20임...^^

 

3. 그리고 실행하면 알아서 찾아서 맞는 환경으로 실행시켜준다. 

React - event handler 이벤트 핸들러의 e 는 어떻게 전달되는 걸까?

TIL

처음 공부를 시작할 때 이해가 잘 안 되는 부분이 있었는데 바로 이벤트 핸들러 함수를 정의할 때의 e 였다.

 

예를 들면 아래에서의 handleSubmit(e) 함수 정의가 있다.

function Form() {
  function handleSubmit(e) {
    e.preventDefault();
    console.log('You clicked submit.');
  }

  return (
    <form onSubmit={handleSubmit}>
      <button type="submit">Submit</button>
    </form>
  );
}

 

onSubmit에서 e는 등장한 적이 없는데 handleSubmit 은 대체 어떻게 e를 받는 거지?

 

이런 의문을 나만 가졌을 리 없다.

 

누군가에게 도움이 되길 바라며 적는 글이다.

 

[TOC]
# 1 . 결론
# 2. 자바스크립트
## 2.1 addEventListener
## 2.2 DOM Spec
## 2.3 listener callback
# 3. React
## 3.1 React listener
## 3.2 Synthetic Event

 

 

결과적으로 기억할 내용은 다음이다.

# 1. 결론

React 에서 JSX 에 이벤트핸들러로 넘기는 이벤트리스너함수(콜백)는 return/render 전에 정의하고, 이벤트에 걸리는 이벤트 리스너는 인자로  해당synthetic event 를 받을 수 있다.

 

이유에 대한 결론은 만들어진 곳에서 넘겨받은 함수는 첫번째 인자로 e(이벤트) 를 받아 호출할 수 있게 정의해 뒀기 때문이다.

호출되는 곳에서는 콜백함수에 인자로 이벤트 객체를 넘기고 넘겨질 콜백함수는 개발자가 정의, 선언하는데
그 때 정의 하는 인자의 네이밍은 e, event,evt 등등 쓰는 사람 마음대로 적혀지곤 한다.
그러나 최고의 네이밍은 누가 봐도, 코딩을 모르는 사람이 봐도 읽을 수는 있는 event 로 적는 것이 가독성과 협업성에 좋으니까 event로 는게 좋다.

 

라고 경험 + 공식문서 내용으로 알 수 있는데 좀 더 파헤쳐 보자.

 

React는  javascript 라이브러리 라는 걸 먼저 기억하자.

 

# 2. 자바스크립트

## 2.1 addEventListener

 

javascript 에서는

EventTarget.addEventListener()

를 이용해서 돔에서 발생하는 이벤트를 잡아 콜백 함수를 설정할 수 있다.

(EventTarget의 addEventListener() 메서드는 지정한 이벤트가 대상에 전달될 때마다 호출할 함수를 설정합니다.)

 

참고로 콜백 함수란, 함수의 인자로 보낼 수 있는 함수로 생각하면 이해하기 쉽다.

 

사용 구문은 다음과 같다.

target.addEventListener(type, listener[, options]);
target.addEventListener(type, listener[, useCapture]);

 

1. 사용할 때 필수적으로 와야 하는 첫 번째 인자 type이란

이벤트의 유형으로 'click', 'submit', 'focus'  등 w3c에서 정의한  이벤트 종류이다.

 

2.  필수적으로 와야 하는 두 번째 인자 listner

앞에서 정의한 이벤트 type의 이벤트가 발생했을 때 실행될 콜백 함수로,

('click' -> 'click' event)를 첫 번째 인자로 받는 함수이다. => 요 리스너가 리액트의 리스너와 같은 역할이다.

 

그리고 관련하여 돔 명세를 봐보자


## 2.2 DOM Spec

obj.addEventListener("load", imgFetched)

function imgFetched(ev) {
  // great success
  …
}
 In the example above ev is the event. ev is passed as an argument to the event listener’s callback (typically a JavaScript Function as shown above). Event listeners key off the event’s type attribute value ("load" in the above example). The event’s target attribute value returns the object to which the event was dispatched (obj above).

 

돔명세에 의하면 이벤트 리스너의 콜백에는 이벤트가 소괄호 안에 argument 로 전달된다.

e든 ev든 evt, event 든 identifier 일 뿐 그 자리에는 이벤트가 들어온다.

 


## 2.3 listener callback

addEventListener 에 오는  콜백함수 형태를 보면

1. function(e) { ...}

2. function(){...}

이런 익명함수들인데,

인자가 없는 건, 함수 내에서 e 를 참조할 필요가 없을 때 굳이 넘겨받지 않아도 되기때문에 인자로도 안받는 것이다.

이 부분이 포인트다.

 

이 부분의 이해를 돕는 예시를 만들어 봤다.

function log(a) {
    console.log(a)
}
log('test') // test
log('test', 2) // test

function alarm(msg){
  alert("!");
}
alarm(msg) // !
alarm() // !

function alarm2(){
  alert("!");
}
function alarm2(123) // !

 

그리고 event.target 은 이벤트가 발생한 객체이다.

 

 이벤트 타겟의 명세는 다음과같다

 

2.7. Interface EventTarget

[Exposed=(Window,Worker,AudioWorklet)]
interface EventTarget {
  constructor();

  undefined addEventListener(DOMString type, EventListener? callback, optional (AddEventListenerOptions or boolean) options = {});
  undefined removeEventListener(DOMString type, EventListener? callback, optional (EventListenerOptions or boolean) options = {});
  boolean dispatchEvent(Event event);
};

callback interface EventListener {
  undefined handleEvent(Event event);
};

dictionary EventListenerOptions {
  boolean capture = false;
};

dictionary AddEventListenerOptions : EventListenerOptions {
  boolean passive = false;
  boolean once = false;
  AbortSignal signal;
};

 

 

# 3. React

자바스크립트에서는 addEventListener를 통해서 이벤트 핸들러를 구현하지만, 리엑트에서는 다르다. 


## 3.1 React listener

When using React, you generally don’t need to call  'addEventListener' to add listeners to a DOM element after it is created. Instead, just provide a listener when the element is initially rendered.

React에서는  DOM element가 생성된 "후"에 리스너를 추가하기 위해 addEventListener를 호출할 필요가 없습니다.  처음 렌더링 될 때 리스너를 제공합니다.

이는 return 위에서 정의한 handleSubmit 함수의 위치를 의미하는 듯하다.

다시 말하자면 리스너는 이벤트가 발생할 때 실행될 콜백 함수이다.

이 콜백 함수를 return 위에서 작성해 주는 것이 react의 방식이다.


## 3.2 Synthetic Event

리액트에서는 합성이벤트 (Synthetic Event) 라는 리액트 이벤트를 사용하는데 이건 브라우저에서 기본적으로 제공하는 

native event 를 참고해서 리액트에서 재가공한 이벤트 객체이다.

이벤트 핸들러는 모든 브라우저에서 이벤트를 동일하게 처리하기 위한 이벤트 래퍼 SyntheticEvent 객체를 전달받습니다. 
Although events are typically dispatched by the user agent as the result of user interaction or the completion of some task, applications can dispatch events themselves by using what are commonly known as synthetic events:
이벤트는 일반적으로 사용자 상호 작용 또는 일부 작업 완료의 결과로 사용자 에이전트에 의해 전달되지만 어플리케이션은 일반적으로 합성 이벤트로 알려진 것을 사용하여 이벤트를 전달할 수 있습니다.

// add an appropriate event listener
obj.addEventListener("cat", function(e) { process(e.detail) })
// create and dispatch the event  [ synthetic event ]  <- 요걸 리액트에서 쓴다
var event = new CustomEvent("cat", {"detail":{"hazcheeseburger":true}}) obj.dispatchEvent(event)

 

따라서 JS DOM 에 정의된 이벤트와는 정확히 일치하지는 않는다. 

 

 

 

 

https://developer.mozilla.org/ko/docs/Web/API/EventTarget/addEventListener

https://developer.mozilla.org/ko/docs/Web/API/EventListener

https://developer.mozilla.org/en-US/docs/Web/API/EventListener/handleEvent

https://ko.reactjs.org/docs/events.html 

 

https://dom.spec.whatwg.org/#introduction-to-dom-events

https://dom.spec.whatwg.org/#interface-eventtarget

 

 

 

시니어 프론트엔드 개발자처럼 크롬 개발자도구 사용하기

TIL

1. Run command
2. [console] 마지막 실행 결과 참조 $_
3. [network] XHR 재요청
4. [network] 화면 로딩상태 추적하기
5. [console] 변수복사 : copy
6. [network] 이미지를 data URI 로 복사하기
7. [console] 테이블로 객체배열 보기
8. [Elements] 드래그앤 드랍
9. [Console] 현재 선택된 요소 참조 : $0
10.[Elements] css 가상 클래스 작동
11. [Elements] 요소 숨기는 단축키 : H/h
12. [Elements → Console] DOM 요소를 글로벌 임시 변수에 저장하기

 

junwoo45.github.io/2020-07-28-chrome_devtools/

 

<번역>시니어 프론트엔드 개발자처럼 크롬 개발자도구 사용하기

시니어 프론트엔드 개발자처럼 크롬 개발자도구 사용하기 이 글은 bitfish의 Use Chrome DevTools Like a Senior Frontend Developer을 번역한 글입니다. 원문은 Use Chrome DevTools Like a Senior Frontend Developer 에서 확인

junwoo45.github.io

 

맥북 좋은점 알려드립니다.

생각일기

왜 개발자들은 맥북을 쓰는가?

 

1. 커맨드를통한 편리한 설치

 

이게 가장 클 것 같네욤.

homebrew 짱짱!

이거 한번 설치해두면 크롬이랑 필요한 많은 것들을 설치 가능!

 

(와우 ,, ,방금 매우 좋은 글 찾았습니다ㅠㅜㅠ 쉘스크립트와 홈브류를 통한 

Homebrew와 Shell Script를 사용하여 맥(Mac) 개발 환경 구축 자동화하기

https://dev-yakuza.github.io/ko/environment/configure-development-environment-on-mac-with-homebrew-and-shell-script/

 

좋은글 작성해 주신 블로그 주인님 감사합니다...

dev-야쿠자 님은 리액트 네이티브 관련해서 책도 쓰신 분이죠 ^^ 우연한 검색으로 또 찾아 들어가게 되네요.

 

2. 직관적인 UI

직전 글에서 쓴 것처럼

토글형 폴더 구조형은 깊은 구조 접근을 편하게 해서 파일관리하기 좋게 합니다.

 

3. 터미널 사용이 편리하다~(사실 윈도우 CMD랑 차이는 잘 모르겠음;;)

 

4. 편리한 트랙패드! 모션을 편리하게 쓸 수 있습니다.

 

5. 아이패드 아이폰이랑 같이 쓰면 좋음

내 아이패드는 구형이라 보조화면으로 쓰는 사이드카 기능은 불가능 하지만, 여튼 요즘 버전들로 사면 가능하구요, 

구형이어도 되는 것은

맥북이랑 아이패드 붙여놓으면 두개 사이에 연동되어서

보던 웹사이트를 공유하거나 복사한 내용 즉 클립보드 연동이 됩니다.

 

아이폰이랑 연동된 경우엔 전화오면 맥북도 울어요...ㅋㅋ

 

...맥북 물먹은거같아서 수리하러 가야하는데,,, ㅠㅠ 슬프다,,

 

맥북 단점

비쌈..

'생각일기' 카테고리의 다른 글

커밍홈~191126 시작하는 글과 오늘일기  (0) 2019.11.26

[1w 4] 프론트엔드 프로젝트가이드

기술서적 - 주4회 10페이지 읽기

~402p

제이쿼리로 구현. 코드를 따라가가며 만들고있는데 구글맵 방식이 바뀌는 등, 작동에 있어 이슈가 있다.
현재 수정중.

작동안되는 부분 수정하고 기능 업그레이드해서 구현해봐야겠다.

[1w 3]http&network

기술서적 - 주4회 10페이지 읽기

p.105-p156

정보들이 나열되어 있어서 비효율적으로 느껴진다.
좀더 효과적으로 공부하기 위해서는 실전형으로 뭔가를 만들어보는 게 더 좋다는 조언을 듣고 이 책은 여기에서 중도 하차