Q. 리액트를 배우는데, 왜 자바스크립트의 동등 비교를 알아야할까?
A. 리액트에서 훅을 사용하다보면, 의존성 배열을 넣어주는 일이 있다. 이 의존성 배열의 동작 원리도 JS의 동등 비교가 포함된다.
또한, 리액트의 리랜더링 되는 조건 중 하나인 props의 변경을 감지 하는 원리 또한 동등 비교 로직이 포함되어 있다.
원시 타입 (7가지)
boolean
null -> typeof null === 'object' // true 바보 같은 JS의 실수
undefined
number
string
symbol
bigint
객체 타입
object (배열, 함수, 정규식, 클래스 등)
개체 타입은 다른 말로 "참조 타입이라고도 한다" 그렇게 말하는 이유에 대해서 알아보자
[원시타입 예시]
let num1 = 42;
let num2 = num1; // num2에 num1의 값(42)를 넣어줍니다.
num1 = 100; // 그 후 처음 num1 변경
console.log(num1); // Output: 100
console.log(num2); // Output: 42 -> num2에는 불변의 값 자체를 저장했기에 영향이 없습니다.
[객체타입 예시]
let obj1 = { name: "John" }; // ex 101 주소에 name은 'John' 이다
let obj2 = obj1; // 복사 => obj2는 obj1의 주소값을 저장합니다. (101 저장)
obj1.name = "Jane"; // 주소값 안에 있는 값을 변경합니다. -> 101 주소에 name은 'Jane'
console.log(obj1.name); // Output: "Jane" -> 101주소는 Jane
console.log(obj2.name); // Output: "Jane" -> 101주소는 Jane
-0 === +0 // true
Object.is(-0,+0) // false
그치만 객체비교에서는 별차이가 없습니다.
리액트에서 동등비교를 하는 로직은 위에서 설명한 Object.is를 사용합니다. (with polyfill)
https://github.com/facebook/react/blob/main/packages/shared/objectIs.js
function is(x: any, y: any) {
return (
(x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare
);
}
const objectIs: (x: any, y: any) => boolean =
// $FlowFixMe[method-unbinding]
typeof Object.is === 'function' ? Object.is : is;
export default objectIs;
import is from './objectIs';
import hasOwnProperty from './hasOwnProperty';
function shallowEqual(objA: mixed, objB: mixed): boolean {
if (is(objA, objB)) {
return true;
}
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
// Test for A's keys different from B.
for (let i = 0; i < keysA.length; i++) {
const currentKey = keysA[i];
if (
!hasOwnProperty.call(objB, currentKey) ||
// $FlowFixMe[incompatible-use] lost refinement of `objB`
!is(objA[currentKey], objB[currentKey])
) {
return false;
}
}
return true;
}
export default shallowEqual;
함수는 코드를 재사용할 수 있게 해주며, 코드의 구조를 분명하게 하여 가독성과 유지보수성을 향상시킵니다.
기본적으로 매개변수를 받아서, 특정 값을 return 해주는 방식입니다.
function greet(name) {
return `Hello, ${name}!`;
}
const greet = function(name) {
return `Hello, ${name}!`;
};
const greet = (name) => `Hello, ${name}!`;
(function() {
console.log('This runs right away');
})();
function createPerson(name) {
return {
name: name,
greet: function() {
return `Hello, ${this.name}!`;
}
};
}
const person1 = createPerson('Alice');
일반 함수에서 this의 값은 함수가 호출되는 방식에 의해 결정됩니다. 즉, 실행 컨텍스트(execution context)에 따라 this가 달라집니다.
function regularFunction() {
console.log(this);
}
const myObj = {
method: function() {
console.log(this);
}
};
regularFunction(); // 전역 객체 또는 엄격 모드에서는 undefined
myObj.method(); // myObj를 가리킴
화살표 함수에서는 this가 다르게 작동합니다. 화살표 함수는 자신이 선언된 시점의 실행 컨텍스트의 this를 "캡처"합니다. 이는 화살표 함수가 this의 값을 자신이 생성될 때의 렉시컬 컨텍스트(lexical context)에서 가져오는 것을 의미합니다
const arrowFunction = () => {
console.log(this);
};
const myObj2 = {
method: arrowFunction
};
arrowFunction(); // 전역 컨텍스트의 this를 가리킴 (브라우저에서는 window 객체)
myObj2.method(); // 여전히 전역 컨텍스트의 this를 가리킴, 화살표 함수는 자신이 속한 객체의 this를 바인딩하지 않음
Q 비교 코드 문제
const myObject = {
value: 'My Object',
regularMethod: function() {
console.log(this.value); // 'My Object' - 일반 함수는 호출하는 객체의 컨텍스트를 `this`로 가짐
setTimeout(function() {
console.log(this.value); // undefined - 일반 함수 내의 `this`는 전역 객체를 가리킴
}, 1000);
},
arrowMethod: function() {
setTimeout(() => {
console.log(this.value); // 'My Object' - 화살표 함수는 자신을 둘러싼 렉시컬 컨텍스트의 `this`를 가짐
}, 1000);
}
};
myObject.regularMethod();
myObject.arrowMethod();
함수 선언문의 호이스팅:
함수 선언문은 전체 함수가 호이스팅되므로, 함수 선언문으로 정의된 함수는 실제 코드상 위치에 관계없이 코드의 어느 곳에서든 호출할 수 있습니다.
console.log(greet('Alice')); // "Hello, Alice!"
function greet(name) {
return `Hello, ${name}!`;
}
함수 표현식의 호이스팅:
함수 표현식(변수에 할당된 함수)은 변수 호이스팅의 규칙을 따릅니다. 변수 선언은 호이스팅되지만, 할당은 호이스팅되지 않습니다. 따라서 함수 표현식으로 정의된 함수는 해당 함수가 할당되기 전에는 호출할 수 없습니다.
console.log(greet('Alice')); // TypeError: greet is not a function
var greet = function(name) {
return `Hello, ${name}!`;
};
특정 객체를 반복해서 만들기 위한 기본 템플릿, 붕어빵(객체)를 만들기 위한 붕어빵 틀(클래스)라고 비유 하기도 한다.
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}
// 인스턴스 생성
const person1 = new Person('Alice', 30);
person1.greet();
// 출력: Hello, my name is Alice and I am 30 years old.
class Employee extends Person {
constructor(name, age, position) {
super(name, age); // 부모 클래스의 constructor 호출
this.position = position;
}
introduce() {
console.log(`Hello, my name is ${this.name}, I am ${this.age} years old and I work as a ${this.position}.`);
}
}
// Employee 인스턴스 생성
const employee1 = new Employee('Bob', 25, 'Software Engineer');
employee1.introduce(); // 출력: Hello, my name is Bob, I am 25 years old and I work as a Software Engineer.
employee1.greet(); // 부모 클래스의 메서드 호출 가능
getter와 setter
class Person {
constructor(name, age) {
this._name = name;
// _name은 내부 프로퍼티로, 직접적인 접근을 권장하지 않음
this._age = age;
}
get name() {
return this._name;
}
set name(newName) {
this._name = newName;
}
// age에 대한 getter와 setter도 유사하게 구현할 수 있음
}
자바스크립트에서는 원래 프로토타입이라는 개념이 있고, 클래스 또한 이 개념을 바탕으로 작동합니다. 그리고 클래스로 구성된 코드는 같은 동작을 하는 함수로도 변경할 수 있으니 어느 하나가 맞다가 아닌 필요에 따라 사용할 것