📚 클로저란 무엇인가요?

클로저란, ECMAScript에선 공식 사양으로 등장하진 않지만, 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다.

❓ 클로저는 왜 사용 하나요?

클로저는 함수형 프로그래밍 방식에서 정보 은닉과 캡슐화를 통해 정보를 보다 안전하게 관리하기 위해 사용한다.

C++과 같은 객체지향 프로그래밍 언어와 달리 자바스크립트는 private 같은 접근 제한자를 제공하지 않는다.

때문에 자바스크립트에서 객체 프로퍼티를 안전하게 참조 및 변경하기 위해 클로저를 사용한다.

❓ 클로저는 언제, 어떻게 사용하나요?

클로저를 구현하는 방법은 아래 두가지 조건을 만족시켜 구현할 수 있다.

  1. 함수 안에 내부함수를 정의하고 내부함수는 외부함수의 식별자를 참조한다.
  2. 외부 함수의 반환값으로 내부함수를 반환하여 변수에 할당한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const counter = (function () {
// 카운트 상태 변수
let num = 0;

// 클로저인 메서드를 갖는 객체를 반환한다.
return {
increase() {
return ++num;
},
decrease() {
return num > 0 ? --num : 0;
},
};
})();

console.log(counter.increase()); // 1
console.log(counter.increase()); // 2

console.log(counter.decrease()); // 1
console.log(counter.decrease()); // 0

❗️ 주의점

클로저는 외부함수의 실행 컨텍스트가 제거되어도 내부함수가 외부함수의 변수를 기억하고 있는데 이는 추후에 메모리 누수가 발생할 수 있다.

예를 들어 리액트에서 useEffect 함수를 사용하여 렌더링 이후에 이벤트를 등록하였을 때, 이를 다시 렌더링이 되었을 때, 기존 이벤트를 클린업 해주지 않는 다면 같은 역할을 하는 이벤트가 중복등록이 되고 이는 성능상 문제를 일으킬 수 있으므로 이러한 점을 주의해야한다.

📌 정리

클로저는 보다 안전하게 객체를 다루기 위해 사용한다.

하지만 ES6에서 클래스가 도입되어 다른 객체지향 프로그래밍 언어처럼 private 접근 제한자가 생겨나 객체를 안전하게 관리할 수 있게 되었다.

예전에 페어프로그래밍을 하면서 DOM 요소를 참조하고 변경하는 클로저함수만 외부로 빼내어 해당 함수로만 DOM 요소를 조작할 수 있도록 코드를 구현해본 경험이 있습니다.

댓글 공유

프로토타입이란?

자바스크립트는 프로토타입을 기반으로 상속을 구현하는 언어이다. 프로토타입은 엄밀히 말하면 프로토타입 객체이다.

프로토타입 객체는 어떤 객체의 상위(부모) 객체의 역할을 한다.

프로토타입을 왜 사용하나요?

객체 간 상속을 구현하기 위해 사용한다. 즉, 프로토타입을 상속 받은 하위(자식) 객체는 상위(부모) 객체의 프로퍼티를 자신의 프로퍼티처럼 자유롭게 사용할 수 있다.

프로토타입이 어떻게 동작하나요?

프로토타입은 객체 생성 방식에 따라 객체가 생성될 때 결정되고 해당 객체의 [[Prototype]] 내부 슬롯에 저장된다.

__proto__접근자 프로퍼티를 통해서 프로토타입에 간접적으로 접근이 가능하다.

prototype

  • 생성자 함수가 생성한 객체 : __proto__접근자 프로퍼티로 자신의 프로토타입에 간접적 접근 가능
  • 프로토타입 : 자신의 constructor 프로퍼티로 생성자 함수에 접근 가능
  • 생성자 함수 : 자신의 prototype 프로퍼티로 프로토타입에 접근 가능

사실 모든 객체는 __proto__접근자 프로퍼티를 가지고 있다. 그러므로 모든 객체는 __proto__접근자 프로퍼티로 자신의 프로토타입 객체에 간접적으로 접근할 수 있다.

프로토타입 체인이란?

자바스크립트의 모든 객체는 프로토타입 기반 상속을 받고 있다. 자바스크립트는 객체의 프로퍼티에 접근할 때 해당 객체에 접근하려는 프로퍼티가 없다면, [[Prototype]] 내부슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색한다.

이를 프로토타입 체인이라고 한다.

프로토타입 체인

댓글 공유

this는 무엇인가?

this는 자바스크립트 언어에서 매우 추상적인 부분 중에 하나이다. 그래도 한마디로 단정짓는다면, this는 자기 자신을 나타내기 위한 키워드이다.

좀 더 자세히 말하면 자신이 속한 객체나 자신이 생성할 객체를 가리키는 자기 참조 변수이다.

  • this는 자바스크립트 엔진에 의해 암묵적으로 생성된다.

함수를 호출하면 arguments 객체와 this가 암묵적으로 함수 내부에 전달된다. arguments 객체를 함수 내부에서 지역변수처럼 사용할 수 있듯이 this도 마찬가지이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// this로 자신이 속한 객체 참조
const test = {
value: 10,
getValue() {
return this.value;
},
};

console.log(test.getValue()); // 10

// this로 자신이 생성할 객체 참조
function Circle(radius) {
this.radius = radius;
}

Circle.prototype.getDiameter = function () {
return 2 * this.radius;
};

const circle = new Circle(5);
console.log(circle.getDiameter()); // 10

this에 바인딩될 값은 함수 호출 방식에 의해 동적으로 결정되는 특징이 있다.

그러므로 this를 어떻게 호출하느냐에 따라 this 값이 달라지게 된다.

this의 작동원리 in JavaScript

1. 일반 함수로 호출

일반 함수로 호출할 경우 해당 함수 내부의 this는 전역 객체에 바인딩된다.

이 때, 중첩함수, 콜백함수가 일반함수로 호출되는 경우에도 마찬가지이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const obj = {
value: 100,
foo() {
console.log("foo's this: ", this); // {value: 100, foo: ƒ}
console.log("foo's this.value: ", this.value); // 100

// 메서드 내에서 정의한 중첩 함수
function bar() {
console.log("bar's this: ", this); // window
console.log("bar's this.value: ", this.value); // 1
}

// 메서드 내에서 정의한 중첩 함수도 일반 함수로 호출되면 중첩 함수 내부의 this에는 전역 객체가 바인딩된다.
bar();
},
// 콜백함수가 일반함수로 호출되는 경우
boo() {
console.log("foo's this: ", this); // {value: 100, foo: ƒ}
// 콜백 함수 내부의 this에는 전역 객체가 바인딩된다.
setTimeout(function () {
console.log("callback's this: ", this); // window
console.log("callback's this.value: ", this.value); // 1
}, 100);
},
};

2. new 키워드 사용하여 생성자 함수로 호출

new 키워드로 함수를 호출할 경우 자바스크립트는 해당 함수를 생성자 함수로 판단하고 암시적으로 다음 행동을 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Circle(radius) {
// 1. 암묵적으로 인스턴스가 생성되고 this에 바인딩된다.

// 2. this에 바인딩되어 있는 인스턴스를 초기화
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};

// 3. 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다.
}

// 인스턴스 생성. Circle 생성자 함수는 암묵적으로 this를 반환한다.
const circle = new Circle(1);
  1. 새로운 객체를 생성하고 this를 바인딩한다.
  2. this에 바인딩 되어 있는 객체를 초기화한다.
  3. 해당 함수는 return 하는 값이 없다면 this가 암묵적으로 반환된다.

생성자 함수는 this가 바인딩된 객체를 암묵적으로 반환하기 때문에, 생성자 함수로 호출 시 생성자 함수 내부의 this는 생성자 함수가 미래에 생성할 객체에 바인딩된다.

new 키워드를 사용한 함수 내부에서 return을 명시적으로 해준다는 것은 this를 암묵적으로 반환하는 생성자 함수 행동을 훼손하므로 이는 안티 패턴이다.

3. 메서드로 호출

메서드란, 객체의 프로퍼티 값이 함수인 경우를 해당 함수를 메서드라고 부른다.

메서드로 호출될 경우 메서드 내부의 this는 해당 메서드가 속한 객체가 아닌 메서드를 호출한 객체에 바인딩된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const person = {
name: "Lee",
getName() {
// 메서드 내부의 this는 메서드를 호출한 객체에 바인딩된다.
return this.name;
},
};
const people = {
name: "Kim",
getName() {
return this.name;
},
};

console.log(person.getName()); // Lee
console.log(people.getName()); // Kim

getName()이라는 메서드가 person 객체 내부에 있으니 메서드로 호출 시 this 바인딩이 메서드를 포함하는 객체인 person에 될 것이라 생각하는 것은 잘못된 생각이다.

1
2
3
4
5
6
7
8
const anotherPerson = {
name: "Kim",
};
// getName 메서드를 anotherPerson 객체의 메서드로 할당
anotherPerson.getName = person.getName;

// getName 메서드를 호출한 객체는 anotherPerson이다.
console.log(anotherPerson.getName()); // Kim
  • 위와 같이 getName 메서드를 anotherPerson 객체의 메서드로 할당한 경우, getName 메서드 내부의 this는 자신을 호출한 객체에 바인딩 되기 때문에 anotherPerson.name을 참조하게 된다.

4. apply,call,bind 함수에 의한 간접 호출

이 경우 함수 내부의 this는 인수로 전달된 객체에 바인딩된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function getThisBinding() {
return this;
}

// this로 사용할 객체
const thisArg = { a: 1 };

console.log(getThisBinding()); // window

// getThisBinding 함수를 호출하면서 인수로 전달한 객체를 getThisBinding 함수의 this에 바인딩한다.
console.log(getThisBinding.apply(thisArg)); // {a: 1}
console.log(getThisBinding.call(thisArg)); // {a: 1}

// bind 메서드는 첫 번째 인수로 전달한 thisArg로 this 바인딩이 교체된
// getThisBinding 함수를 새롭게 생성해 반환한다.
console.log(getThisBinding.bind(thisArg)); // getThisBinding
// bind 메서드는 함수를 호출하지는 않으므로 명시적으로 호출해야 한다.
console.log(getThisBinding.bind(thisArg)()); // {a: 1}
  • call, apply의 반환값은 호출한 함수의 반환값이다.
  • bind는 함수를 호출하지 않고 인수로 전달받은 객체로 this 바인딩이 교체된 함수를 새롭게 생성하여 반환한다.

과거에는 this를 불일치 문제를 bind 함수를 사용하여 해결하였다.

1
2
3
4
5
6
7
8
9
10
const person = {
name: "Lee",
foo(callback) {
setTimeout(callback, 100);
},
};

person.foo(function () {
console.log(`Hi! my name is ${this.name}.`); // ② Hi! my name is .
});
  • 일반 함수로 호출된 콜백 함수 내부의 this.name은 브라우저 환경에서 window.name과 같다.
  • 브라우저 환경에서 window.name은 브라우저 창의 이름을 나타내는 빌트인 프로퍼티이며 기본값은 ‘’이다.
1
2
3
4
5
6
7
8
9
10
const person = {
name: "Lee",
foo(callback) {
setTimeout(callback.bind(this), 100);
},
};

person.foo(function () {
console.log(`Hi! my name is ${this.name}.`); // ② Hi! my name is Lee.
});

5. 화살표 함수로 호출

화살표 함수로 호출할 경우 위의 규칙을 모두 무시하고 화살표 함수가 생성된 시점에서 상위 스코프의 this에 바인딩된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const arrow = () => {
return this; // Window
};

const obj = {
far() {
return arrow();
},
};

const anotherObj = {
baz() {
return (() => this)();
},
};

obj.far(); // Window
anotherObj.baz(); // anotherObj

this 화살표함수 예시

  • arrow 함수는 생성된 시점에서 상위 스코프의 this는 전역객체(Window)이다.
  • anotherObj 객체의 baz 메서드 내부의 this는 자신을 호출한 객체에 바인딩된다. 그러므로 화살표 함수 내부의 this는 자신이 생성된 시점에서 상위 스코프인 baz의 this와 일치한다.

댓글 공유

Emotion Styled Component Cascading 문제

Emotion에서 Styled Component 를 사용하면 해당 컴포넌트의 클래스 이름이 재정의된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const StyledTabPanel = styled.div<TabPanelProps<"div">>`
padding: 5px;
border: 2px solid hsl(219deg 1% 72%);
border-radius: 0 5px 5px;
background: hsl(220deg 43% 99%);
min-height: 10em;
min-width: 550px;
overflow: auto;
display: ${(props) =>
props.dataIndex === +(props.id?.slice(-1) as unknown as number)
? "block"
: "none"};
`;

function TabPanel({ children, className, ...restProps }: TabPanelProps<"div">) {
const { selected, setSelected } = useTabsContext();

return (
<StyledTabPanel
role="tabpanel"
tabIndex={0}
dataIndex={selected}
className={className}
{...restProps}
>
{children}
</StyledTabPanel>
);
}

styled component를 사용하여 TabPanel 컴포넌트를 생성하였다. 브라우저에서 해당 요소를 확인해보면 다음과 같이 알 수 없는 클래스로 나오는 것을 알 수 있다.

emotion styled component

TypeScript 오류 - 사용량 덮어씜

ts덮어씜

props를 생성하고 해당 props를 통해 styled Component에서 조건부 스타일을 주려고 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
type Component<T extends React.ElementType> = {
className?: string;
children?: React.ReactNode;
} & React.ComponentPropsWithRef<T>;

type TabPanelProps<T extends React.ElementType> = {
dataIndex?: TabsContextValue["selected"];
} & Component<T>;

const StyledTabPanel = styled.div<TabPanelProps<"div">>`
padding: 5px;
border: 2px solid hsl(219deg 1% 72%);
border-radius: 0 5px 5px;
background: hsl(220deg 43% 99%);
min-height: 10em;
min-width: 550px;
overflow: auto;
display: ${(props) =>
props.dataIndex === +(props.id?.slice(-1) as unknown as number)
? "block"
: "none"};
`;

function TabPanel({ children, className, ...restProps }: TabPanelProps<"div">) {
const { selected, setSelected } = useTabsContext();

return (
<StyledTabPanel
role="tabpanel"
tabIndex={0}
dataIndex={selected}
className={className}
{...restProps}
>
{children}
</StyledTabPanel>
);
}
  • 해당 타입을 정의할 때, 필수가 아닌 선택 사항으로 정의해주어서 해결하였다.
  • styled component 타입 지정 시 해당 컴포넌트의 props라고 지정을 해줘야지만 props에서 찾을 수 있다.

댓글 공유

이벤트 위임에 대해 알기 전 우선 이벤트와 이벤트 핸들러에 대해 알고 가자.

이벤트와 이벤트 핸들러

브라우저는 사용자가 어떤 행동을 하였을 때, 이를 감지하여 이벤트를 발생시킨다.

ex) 참된 개발자: 브라우저야 사용자가 제출하기 버튼을 클릭 했을 때, 정보를 제출하는 함수를 호출(실행)해줘~

이 때 사용자가 언제 행동을 할지 모르기 때문에 개발자는 브라우저에게 대신 함수를 호출해달라고 한다.

이벤트 발생 시 호출할 함수를 이벤트 핸들러라고 한다.

  • 이벤트 발생 시 호출될 함수: 이벤트 핸들러
  • 이벤트 발생 시 브라우저에게 이벤트 핸들러 호출을 위임: 이벤트 핸들러 등록
  • 이벤트 핸들러를 등록하는 방법은 여러가지가 있는데 설명을 위해 이번 장에서는 이벤트 리스너 방식을 사용하겠습니다.

이벤트 위임

이벤트 위임은 이벤트 리스너를 하위 요소에 추가하는 대신 상위 요소에 추가하여 이벤트를 위임하는 것을 말한다.

이벤트 리스너는 이벤트 버블링(Event Bubbling)으로 인해 하위 요소에서 이벤트가 발생할 때 마다 이벤트 리스너가 실행된다.

아래 그림은 이벤트 전파가 일어나는 흐름을 설명한 그림이다. 이벤트 전파에 대한 설명은 그림으로 대체한다.

이벤트 Flow

이벤트 위임을 사용하는 이유

이벤트 위임을 사용하면 하위 요소에 같은 동작을 하는 이벤트를 중복해서 등록해주지 않고 상위 요소에 이벤트 위임을 통해 등록하여 중복을 피하고 불필요한 메모리 낭비를 줄일 수 있다.

또한, 제거된 요소에 이벤트를 해제하고 새 요소에 이벤트를 다시 등록하는 번거로운 작업을 할 필요가 없어지므로 적절한 상황에 맞게 유용하게 사용할 수 있다.

댓글 공유

모의 면접 회고

카테고리 CS

📌 모의 면접 회고

HTML 수업 때 데레사 강사님께 기술면접을 1:1로 본 이후로 오래간만에 모의 면접을 보게되었다.

박신영 강사님께서 인성면접을, 다른 수강생은 기술면접을 면접관 입장이 되어 나에게 질문해주었다.

1. 😃 인성 면접

어떤 개발자가 되길 원하는가?, 프로젝트 및 협업 과정에서 어떤 갈등을 겪었고 어떻게 해결하였는지 등 개발자로서 어떤 마인드를 가지고 있고 어떤 사람인지 판단할 수 있을 만한 질문들이었다.

모든 질문에 어떻게 답변했는지는 기억나지 않지만 피드백을 받은 것을 이야기해보자면,

👍 좋았던 점

  1. 시작할 때 웃는 모습이 좋은 사람이라는 인상을 주고 마음을 편안하게 해주어 좋았다.
  2. 커뮤니케이션과 협업을 일관되게 강조하고 중요시하고 있다는 점이 좋았다.
  3. 말의 끝맺음을 제대로 하여 듣는 사람 입장에서 좋았다.
    • 추가로 대화에서, 개발에서 정리를 잘하는 사람인데 이 부분으로 어필할 수 있으면 더욱 좋을듯!
  4. Why? 에 대해 생각하는 사람으로 느껴져서 좋았다.
  5. 약점에 대해서 스스로 대안이 있어서 인상적이었다.

💦 아쉬운 점

  1. 의견, 주장이 강해보여 의견 충돌 시 강한 타입처럼 느껴질 수 있을 것 같아보인다.
  2. 어떤 개발자가 되고 싶냐는 질문에 좀 더 직무, 역량 관점에서 답변할 수 있으면 좋을 것 같다.
  3. 면접볼 때는 흔들의자에 앉지 말고 고정된 의자에 앉도록 하자.
  4. 개발자로서 역량 설명할 때 커뮤니케이션 부분에서 누구와 협업을 하는지에 대해서도 간략하게 짚고 넘어갔으면 좋았을 것 같다.
  5. 협업 경험에 대해 설명할 때, 부정적인 얘기를 했던게 기억에 남을 것 같다. 이 부분을 나의 약점을 잘 살려냈던 것처럼 풀어나가보면 좋을 것 같다.
  6. 신입,주니어 라는 용어 사용 금지

2. 🛠 기술 면접

기술면접은 프로젝트 경험에 대해 질문해주셨고 기술적으로 깊은 고민을 해본 적이 있는지, 면접자가 기술에 사용 이유와 원리를 알고 사용하는 것인지 확인하는 질문들이었다.

👍 좋았던 점

  1. 페어프로그래밍을 통해 기술적인 단계를 깊이있게 공부하고 블로그에 정리해두었다는 점이 인상깊었다.
  2. 기술 블로그에 대해 피드백을 받고 개선한다는 부분을 차별점으로 어필하는 모습 좋았다.

💦 아쉬운 점

  1. 전반적으로 기술적인 답변이 제대로 나오지 않아 아쉬웠다.
  2. CBD 라이브러리를 만들었다고 할 때 정확히 어떤 부분까지 만들어보았는지 자세한 설명이 부족했다.
  3. 회피와 타협이라는 부정적인 단어 사용이 잦아서 아쉬웠다.
    • 부정적인 단어 대신 “라이브러리를 고치는 것은 비용이 많이 드는 작업이어서 프로젝트 기간에는 해당 부분을 이런 식으로 구현하여 해결하였다”로 사용해보자.
  4. 트러블 슈팅 질문에 대한 답변이 기술적인 이슈 해결이 아닌 다른 답변이 나와서 아쉬웠다.
    • 트러블 슈팅 질문에 대한 소스들을 미리 준비해두면 도움이 될 것 같다.
  5. 리팩터링 질문이 나왔을 때, 너무 협업에만 집중해서 이야기한 것이 아쉽다. 그렇다면 성능 개선, 최적화 등에 대한 고민을 하지 않는 것처럼 보여서 아쉬웠다.

댓글 공유

merge sort

카테고리 Algorithm

merge sort

mergeSort

정렬되지 않는 배열을 각각 하나의 원소만 포함하는 n개의 부분 배열로 분할한다. n개의 부분 배열이 1개가 될 때까지 반복해서 병합할 때 정렬한다. 최종적으로 남은 부분 배열이 정렬된 배열이 된다.

두가지 역할을 하는 함수로 나누어 설명할 수 있다.

  1. mergeSort(arr): 배열을 절반으로 나누는 함수
  2. merge(left, right): 반으로 나뉜 두 배열을 정렬하여 새로운 배열로 병합하는 함수

merge 함수 구현

left[0]과 right[0]를 비교하여 더 작은 수를 새로운 배열에 순서대로 담는다.

만약 left[0]이 right[0]보다 작다면 새로운 배열에 left[0]을 담고 이후에 left[1]과 right[0]을 비교한다.

이렇게 비교하면서 새로운 배열을 생성하기를 left, right 배열의 요소가 하나도 남지 않을 때 까지 반복한다.

이게 가능하기 위해서는 left와 right는 정렬된 상태의 배열이여야만 합니다. left, right를 정렬된 배열로 만들어 주기 위해서 mergeSort 함수를 이용합니다.

mergeSort 함수 구현

mergeSort 함수는 인수로 주어진 배열을 요소가 1개일 때까지 절반으로 나누고 정렬하며 merge 해주는 함수이다.

즉, 요소가 1개인 서로 다른 두 배열을 정렬하면서 하나의 배열로 만들어간다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const arr = [33,2,51,1,10,3];

// 1단계 - 나누기
[33,2,51], [1,10,3]

// 2단계 - 나누기
[33,2],[51],[1,10],[3]

// 3단계 - 나누기
[33],[2],[51],[1],[10],[3]

// 4단계 - 병합
[2,33], [1,51], [3,10]

// 5단계 - 병합
[1,2,33,51], [3, 10]

// 6단계 - 최종 병합
[1,2,3,10,33,51]

코드 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const merge = (left, right) => {
const sorted = [];

// left, right 비교하여 정렬된 배열에 담는다.
while (left.length && right.length) {
if (left[0] <= right[0]) {
sorted.push(left.shift());
} else {
sorted.push(right.shift());
}
}

// left 또는 right 배열 중 하나는 아직 남아있으므로 남은 배열은 정렬되어 있으므로 복사하여 뒤에 붙혀준다.
return [...sorted, ...left, ...right];
}

const mergeSort = arr => {
if (arr.length === 1) return arr;
const mid = Math.ceil(arr.length / 2);
const left = arr.slice(0, mid);
const right = arr.slice(mid);

return merge(mergeSort(left), mergeSort(right));
}

const arr = [33,2,51,1,10,3];
const sorted = mergeSort(arr);
console.log(arr); // [33,2,51,1,10,3]
console.log(sorted); // [1,2,3,10,33,51]

정리

  • mergeSort는 O(n log n) 시간 복잡도를 가진다. 왜냐하면 절반씩 나눠서 비교하기 때문이다.
  • 안정 정렬에 속한다.

참고자료

댓글 공유

🏰 useCallback과 useMemo

카테고리 React, Hooks

📌 useCallback과 useMemo 사용 이유

함수 컴포넌트는 렌더링 될 때 마다 몸체가 다시 실행되므로 컨텍스트를 기억하기 위해 Hook을 사용한다. 그리고 상위 컴포넌트는 기억된 상태 또는 업데이트 함수를 하위 컴포넌트에게 전달한다.

이 때 상위 컴포넌트가 다시 실행되면 이벤트 핸들러 함수들도 새로 그려지므로 동일참조를 벗어난다.

🐸 useCallback

useCallback()은 하위 컴포넌트에 전달되는 함수를 기억해두고 이를 이용하여 컴포넌트 업데이트 시 리렌더링이 발생할 때, 기억된 함수를 사용해 불필요한 리렌더링을 줄여 성능을 높이기 위해 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export function Counter({ count: initialCount, step }) {
const [count, setCount] = useState(initialCount);

// 1
const handleIncrement = useCallback(() => {
setCount((count) => count + step);
}, [step]);

// 2
const handleDecrement = useCallback(() => {
setCount(count - step);
}, [count, step]);

return (
<div>
<Counter.Button onClick={handleDecrement}>-</Counter.Button>
<Counter.Display>{count}</Counter.Display>
<Counter.Button onClick={handleIncrement}>+</Counter.Button>
</div>
)
  • 기억해야할 데이터 타입이 함수인 경우 사용한다.
  • useEffect()처럼 종속성 배열을 통해 조건에 따라 기억 여부를 재설정할 수 있다.
  • 1번처럼 콜백함수로 전달해주는 경우는 reduce처럼 함수를 기억하고 있는다. 기억되어있는 정보를 가지고 값을 변경하는 것이다.
  • 2번처럼 값을 전달해주면 newState로 받아들여져서 종속성 배열이 바뀌면 렌더링이 발생하게된다. 기억하지 않고 새로운 값을 전달해주고 그 값으로 렌더링해준다.

🐍 useMemo

1
useCallback(fn, deps) === useMemo(() => fn, deps);

useMemo()는 JavaScript 데이터 타입을 기억해야 할 때 사용합니다. 만약 기억해야할 타입이 함수라면 useCallback을 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
export function Counter({ count: initialCount, step }) {
const [count, setCount] = useState(initialCount);

// useMemo(() => fn, deps)
const handleIncrement = useMemo(
() => () => setCount((prevCount) => prevCount + step),
[step]
);

// useCallback(fn, deps)
const handleDecrement = useCallback(() => {
setCount((prevCount) => prevCount - step);
}, [step]);

// memoized Components
const DecButton = useMemo(
() => <Counter.Button onClick={handleDecrement}>-</Counter.Button>,
[handleDecrement]
);

const IncButton = useMemo(
() => <Counter.Button onClick={handleIncrement}>+</Counter.Button>,
[handleIncrement]
);

return (
<div>
{DecButton}
<Counter.Display>{count}</Counter.Display>
{IncButton}
</div>
);
}
  • useMemo()는 해당 컴포넌트를 기억한다.
  • 종속성 배열 [count, onDecrement, onIncrement, restProps]이 변경되면 useMemo가 반환하는 값을 기억하고 실행한다.
  • setCount()를 기억하고 있어 동일참조를 하므로 count 값을 기억하고 있다.

🏓 소감

값을 기억하는 목적으로 좋지만… 굳이 이것을 사용하려고 복잡하게 또 많은 시간을 할애할 필요가 있을까 ?

댓글 공유

⛱ useRef

카테고리 React, Hooks

🐶 useRef()

리액트에서 ref는 주로 DOM 노드 참조 목적으로 사용된다. 컴포넌트 렌더링에 영향을 주지 않는 값 참조 목적으로 사용된다.

useRef()는 함수 컴포넌트 내부에서 특정 값을 지속적으로 참조할 때 사용한다. useState()와 달리 useRef()현재 값이 변경되어도 컴포넌트가 다시 렌더링되지 않아 애플리케이션 성능을 최적화 할 수 있다.

1
2
3
4
5
// useRef() 훅을 사용해 카운트 참조 생성
const countRef = useRef(0);

// 카운트 참조의 현재 값이 변경되어도 컴포넌트는 다시 렌더링 되지 않음
countRef.current = countRef.current + 1;

클래스는 자신의 인스턴스 멤버를 사용해 렌더링 상관없이 특정 값을 기억할 수 있는데 반해, 함수는 다시 렌더링 되면 매번 함수 몸체가 초기화 되므로 특정 값을 기억할 때 useRef()를 사용하면 유용하다.

🐥 useRef() 사용목적

1. DOM 요소 접근 및 조작

리액트 render() 단계에서는 DOM이 그려지기 전 단계이므로 DOM에 직접 접근할 수 없다.

하지만 useRef()를 사용하여 컴포넌트가 mount된 시점 이후 DOM 요소에 접근하여 조작할 수 있도록 도와준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
  • 아직 DOM이 그려지기 전이라 DOM 요소에 접근하여 focus()를 활성화 시킬 수 없지만, useRef()를 사용하여 클래스 컴포넌트 생명주기를 고려하여 DOM 요소에 접근할 수 있도록 도와준다.

2. 함수 컴포넌트 내부에서 특정 값을 기억

함수 컴포넌트 내부에서 특정 값을 기억하면서 값을 변경해도 컴포넌트 렌더링에 영향을 주지 않아야한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import { useEffect, useRef, useState } from "react";
import randomcolor from "randomcolor";

function DoNotReRender() {
const colorRef = useRef(0);
const [stateValue, setStateValue] = useState(0);
const [stateHex, setStateHex] = useState(randomcolor());
const [refHex, setRefHex] = useState(randomcolor());

useEffect(() => {
setStateHex(randomcolor());
}, [stateValue]);

useEffect(() => {
setRefHex(randomcolor());
}, [colorRef]);

return (
<Grid lang="en">
<GridItem
css={`
background: ${stateHex};
`}
>
<h2>State</h2>
<ChangeColorButton
aria-label="state 컬러 변경"
onClick={() => setStateValue(stateValue + 1)}
/>
<p>컬러 변경 버튼을 누르면 컴포넌트가 다시 렌더링 됩니다.</p>
</GridItem>
<GridItem
css={`
background: ${refHex};
`}
onClick={() => {
colorRef.current += 10;
console.log(`colorRef.current = ${colorRef.current}`);
}}
>
<h2>Ref</h2>
<ChangeColorButton aria-label="ref 컬러 변경" />
<p>컬러 변경 버튼을 누르면 컴포넌트가 다시 렌더링 될까요?</p>
</GridItem>
</Grid>
);
}
  • useState()setStateValue()메서드를 사용하여 값을 변경한 경우 재렌더링이 발생하지만, useRef()의 current 값을 갱신하면 재렌더링이 발생하지 않는다.

댓글 공유

loco9939

author.bio


author.job