
리액트 컴포넌트의 렌더링이 일어나는 이유 중 하나이다.
동등 비교는 객체의 얕은 비교를 기반으로 이루어지는데, 이를 제대로 이해하지 못하면 렌더링 최적화에 어려움을 겪을 가능성이 크다.
원시 타입 : 객체가 아닌 모든 타입. 메서드를 갖지 않는다.
객체 타입 : 배열, 함수, 정규식, 클래스 등
원시 타입과 객체 타입의 가장 큰 차이점이다. 원시타입은 불변 형태의 값으로 저장된다.
이 값은 변수 할당 시점에 메모리 영역을 차지하고 저장된다.
let hello = 'hello';
let hi = hello;
console.log(hello===hi); // true
let hello = 'hello';
let hi = 'hello';
console.log(hello===hi); // true
반면 객체는 프로퍼티를 삭제, 추가, 수정할 수 있으므로 원시 값과 다르게 변경 가능한 형태로 저장되며 값을 복사할 때도 값이 아닌 참조를 전달하게 된다.
let hello = {
greet: "hello",
}
let hi = = {
greet: "hello",
}
console.log(hello === hi) // false
console.log(hello.greet === hi.greet) // true 원시 값인 내부 속성값을 비교하면 동일하다.
객체는 값을 저장하는 게 아니라 참조를 저장하기 때문에 false을 반환한다.
== vs Object.is : ==는 강제 형변환 후 값을 비교하지만, Object.is는 형변환 작업을 거치지 않는다. 객체 비교에는 별 차이가 없다.
Object.is로 먼저 비교 수행 → 객체 간 얕은 비교 한 번 더 수행
이유 ? 리액트에서 사용하는 JSX props는 객체이고, props만 일차적으로 비교하면 되기 때문이다.
작업을 수행하거나 값을 계산하는 등의 과정을 표현하고, 이를 하나의 블록으로 감싸서 실행 단위로 만들어 놓은 것이다.
함수 선언문
function add(a, b) {
return a + b
}
함수 표현식
const sum = function add(a, b) {
return a + b
}
sum(10,24)
add(10,24) // add is not defined
add는 실제 함수 내부에서만 유효한 식별자일 뿐, 함수를 외부에서 호출하는 데에는 사용할 수 없는 식별자이다.
함수 표현식과 선언 식의 차이
선언 식은 호이스팅이 되지만, 표현식은 호이스팅이 되지 않아 런타임 이전에 undefined로 초기화된다.
Function 생성자
const add = new Function("a", "b", "return a + b")
이는 매개변수, 함수의 몸통 등을 모두 문자열로 작성해야 하기 때문에 좋은 방법이 아니다.
화살표 함수
const add = (a,b) => {
return a+b
}
단점
1. constructor를 사용할 수 없다.
2. arguments가 존재하지 않는다.
3. 함수 자체의 this 바인딩을 갖지 않는다. 따라서 상위 스코프의 this를 그대로 따르게 된다.
즉시 실행 함수
(function add(a, b) {
return a + b
})(10,24)
단 한 번만 호출되고, 다시금 호출할 수 없는 함수다. 그래서 일반적으로 함수에 이름을 붙이지 않는다.
고차함수
const doubleArray = [1,2,3].map((item) => item * 2 )
일급 객체라는 특징을 활용하여 함수를 인수로 받거나 결과로 새로운 함수를 반환시킬 수 있다.
과거에 작성된 리액트 코드를 읽기 위해서, 이 코드를 함수 컴포넌트로 개선하기 위해서는 자바스크립트의 클래스가 어떤 식으로 작동하는지 이해해야 한다.
특정한 객체를 만들기 위한 일종의 템플릿과 같다.
constructor : 생성자
프로퍼티 : 클래스로 인스턴스를 생성할 때 내부에 정의할 수 있는 속성 값
getter와 setter : 값을 가져오고, 값을 할당할 때 사용
인스턴스 메서드 : 클래스 내부에서 선언한 메서드
정적 메서드 : 클래스의 인스턴스가 아닌 이름으로 호출할 수 있는 메서드
상속 : extends로 기존 클래스를 상속받아서 자식 클래스에서 상속받은 클래스를 기반으로 확장하는 개념
클래스가 작동하는 방식은 자바스크립트의 프로토타입을 활용하는 것이라고 볼 수 있다.
직접 객체에서 선언하지 않았음에도 프로토타입에 있는 메서드를 찾아서 실행을 도와주는 것
함수 컴포넌트의 구조와 작동 방식, hook의 원리, 의존성 배열 등 함수 컴포넌트의 대부분의 기술이 모두 클로저에 의존하고 있기 때문에 클로저에 대해 이해하는 것이 필수이다.
“함수와 함수가 선언된 어휘적 환경의 조합”
전역 레벨에 선언하는 것 보다는 클로저 내부에서만 접근 가능하게 코드를 짜야 한다.
클로저의 원리를 사용하고 있는 대표적인 것 중 하나가 바로 useState이다.
function Componenet(){
const [state,setState] = useState();
function handleClick(){
setState((prev) => prev + 1)
}
}
❗️setState가 useState 내부의 최신 값을 어떻게 계속 확인할 수 있을까?
클로저가 useState내부에서 활용되기 때문이다. 외부함수(useState)가 반환한 내부 함수(setState)는 외부함수의 호출이 끝났음에도 자신이 선언된 외부함수가 선언된 환경을 기억하기 때문에 계속해서 state값을 사용할 수 있는 것이다.
for(var i = 0; i< 5; i++) {
setTimeout(function() {
console.log(i)
}, i * 1000)
}
0,1,2,3,4를 출력할 것 같지만 모두 5가 출력된다.
var로 선언된 i는 함수레벨 스코프를 갖기 때문에 for문의 존재와 상관없이 해당 구문이 선언된 함수 레벨 스코프를 바라보고 있기 때문이다.
for문을 다 순회한 후 태스크 큐에 있는 setTimeout을 실행하려고 하면 이미 전역 레벨에 있는 i는 5로 업데이트가 완료돼 있다.
해결법
따라서, 클로저에 필요한 작업만 남겨두지 않는다면 메모리를 불필요하게 잡아먹는 결과를 야기할 수 있고, 클로저 사용을 적절한 스코프로 가둬두지 않는다면 성능에 악영향을 미친다. 사용할 때 주의가 필요하다.
깔끔한 정리가 보기 좋네요! 잘 읽고갑니다