이러한 변화 덕에 JS가 적극적으로 DOM 조작, Ajax를 이용한 클라이언트와 서버의 통신을 하기 시작했다.
> "\=\=", "\=\=\=" 와 Object.is의 차이점
> - "\=\="는 비교 전 양쪽이 같은 타입이 아니라면 비교할 수 있도록 강제 형변환을 한 후 비교한다.
> - "\=\=\="과도 차이가 있는데, Object.is가 개발작가 기대하는 방식으로 비교한다.
-0 === +0 // true
Object.is(-0, +0) // false
Number.NaN === NaN // false
Object.is(Number.NaN, NaN) // true
NaN === 0 / 0 // false
Object.is(NaN, 0 / 0) // true
하지만, 객체간의 비교의 경우 "\=\=", "\=\=\="와 같이 동작한다.
Object.is({}, {}) // false
const a = {
hello = 'hi',
}
const b = a;
Object.is(a, b) // true
a === b // true
Object.is는 ES6에서 제공하는 기능이기 때문에 리액트에서는 이를 구현한 폴리필을 함께 사용한다.
import is from "./objectIs";
// 다음 코드는 Object.prototype.hasOwnProperty다.
// 객체에 특정 프로퍼티가 있는지 확인하는 메서드다.
import hasOwnProperty from "./hasOwnProperty";
/*
주어진 객체의 키를 순회하면서 두 값이 엄격한 동등성을 가지는지 보고 다른 값이 있다면 false 반환.
두 객체 간의 모든 키와 값이 동일하면 true 반환.
*/
// 단순히 Object.is를 수행하는 것 뿐만 아니라 객체간의 비교도 추가.
// 원래는 flow언어로 작성되어있어 mixed라는 자료형을 가지지만, ts로 작성하기 위한 pollyfill이다.
// mixed는 모든 타입들을 포함하는 타입이나, 몇 연산자의 사용이 타입 체킹 없이는 제한된다.
type mixed = any;
function shallowEqual(objA: mixed, objB: mixed): boolean {
if (is(objA, objB)) {
// 같은 참조 주소를 가지는 객체의 경우 true 반환
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);
// 배열의 길이가 다르다면 false
if (keysA.length !== keysB.length) {
return false;
}
// A를기준으로, B에 같은 키가 있는지, 그리고 그 값이 확인한다.
for (let i = 0; i < keysA.length; i++) {
const currentKey = keysA[i];
if (
!hasOwnProperty.call(objB, currentKey) ||
!is(objA[currentKey], objB[currentKey])
) {
return false;
}
}
return true;
}
리액트에서는 이 동등비교를 이용해 shallowEqual이라는 함수를 만들어 사용한다. 이 함수를 이용해 의존성 비교 등 리액트의 동등비교가 필요한 다양한 곳에서 사용된다.
// Object.is는 참조가 다른 객체에 대해 비교가 불가하다.
Object.is({a: 1}, {a: 1}) // false
// 리액트 팀에서 구현한 shallowEqual은 객체의 1depth까지 비교 가능하다.
shallowEqual({a: 1}, {a: 1}) // true
// 2 depth는 비교하지 못한다.
shallowEqual({b: {a: 1}}, {b: {a: 1}}) // false
리액트에서 사용하는 JSX props는 객체이고, 일차적으로만 비교하면 되기 때문이다. 이러한 특성 때문에 props로 또다른 객체를 넘겨준다면 리액트 렌더링이 예상치 못하게 작동한다.
type DeeperProps = {
counter: {
counter: number;
};
};
const DeeperComponent = memo((props: DeeperProps) => {
useEffect(() =>
console.log("Deeper Component has been rendered!");
});
return <h1>{props.counter.counter}</h1>;
});
위와 같이 props가 깊어지는 경우, React.memo는 컴포넌트에 실제로 변경된 값이 없음에도 불구하고 메모이제이션된 컴포넌트를 반환하지 못한다. 상위 컴포넌트에서 강제로 렌더링을 일으킬 경우, shallowEqual을 이용하는 Component 함수는 위 로직에 따라 객체간 비교를 수행해 렌더링을 방지해주지만 DeeperComponent 함수는 제대로 비교하지 못해 memo가 작동하지 않는다.
function add(a, b){
return a + b;
}
const sum = function(a, b){
return a + b;
}
sum(10, 24) // 34
함수 표현식과 선언 식의 차이
const add = new Function('a', 'b', 'return a + b');
add(10, 20) // 20
→ 함수의 몸통을 모두 문자열로 작성, 클로저가 생성되지 않는다. eval 만큼이나 실제로 사용이 되지 않는 방법.
const add = (a, b) => {
return a + b;
}
add(10, 20) // 20
const Car = (name) => {
this.name = name;
}
const myCar = new Car('하이') // TypeError: Car is not a constuctor
const hi = () => {
console.log(arguments)
}
hi(1, 2, 3) // ReferenceError: arguments is not defined
(function(a, b){
return a + b;
})(10, 24); //34
((a, b) => {
return a + b;
})(10, 24); //34
const add = function(a){
return function(b){
return a + b;
}
}
add(1)(2);
함수를 작게 만들어라.
누구나 이해 가능한 이름을 붙여라
```js
useEffect(function apiRequest(){
// do something
}, [])
## 1.3 클래스
- 리액트 16.8 버전이 나오기 전까지 모든 컴포넌트가 클래스로 작성되어있었다.
- 클래스형 컴포넌트에 대한 이해는 클래스, 프로토타입, this에 달려있다.
- 바벨로 ES6 → ES5 문법으로 변환
```js
"use strict";
// 클래스가 함수처럼 호출되는 것을 방지
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
// 프로퍼티를 할당하는 코드
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
// 프로토타입 메서드와 정적 메서드를 선언하는 코드
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
var Car = /*#__PURE__*/ (function () {
function Car(name) {
_classCallCheck(this, Car);
this.name = name;
}
_createClass(
Car,
[
{
key: "honk",
value: function honk() {
console.log(
"".concat(
this.name,
"\uC774 \uACBD\uC801\uC744 \uC6B8\uB9BD\uB2C8\uB2E4!"
)
);
},
},
{
key: "age",
set: function set(value) {
this.carAge = value;
},
get: function get() {
return this.carAge;
},
},
],
[
{
key: "hello",
value: function hello() {
console.log("저는 자동차입니다.");
},
},
]
);
return Car;
})();
{
var a = 100;
let b = 200;
const c = 300;
}
console.log(a); // 100
console.log(b); // b is not defined
console.log(c); // c is not defined
function Component() {
const [state, seState] = useState();
function handleClick() {
// useState 호출은 위에서 끝났지만,
// setState는 계속 내부의 최신값(prev)를 알고있다.
// 이는 클로저를 활용했기 때문에 가능하다.
setState((prev) => prev + 1);
}
}
외부 함수(useState)가 반환한 내부함수(setState)는 외부함수의 호출이 끝났음에도 자신이 선언된 외부함수가 선언된 환경을 기억해 계속해 state를 사용할 수 있다. → 클로저 발생
V8, Spider monkey와 같은 자바스크립트 런타임 엔진에서 비동기 실행을 위한 장치를 가지고 있다.
호출 스택은 자바스크립트에서 수행해야할 코드나 함수를 순차적으로 담아두는 스택이다.
function bar() {
console.log('bar');
}
function baz() {
console.log('baz');
}
function foo() {
console.log('foo');// (2) 콜스택: c.l() - foo
// (3) c.l() 함수 실행 끝, 콜스택: foo
// c.l() 함수는 이후에 생략
bar(); // (4) 콜스택: bar - foo
// (5) bar 함수 실행 끝, 콜스택: foo
baz(); // (6) 콜스택: baz - foo
// (7) baz 함수 실행 끝, 콜스택: foo
}
foo() // (1) 콜스택: foo
// (8) foo 함수 실행 끝, 콜스택: (empty)
function bar() {
console.log('bar');
}
function baz() {
console.log('baz');
}
function foo() {
console.log('foo'); // (2) 콜스택: c.l() - foo
// (3) c.l() 함수 실행 끝, 콜스택: foo
// c.l() 함수는 이후에 생략
setTimeout(bar, 0); // (4) 콜스택: setTimeout - foo
// (5) setTimeout 함수 실행 끝, 콜스택: foo
// (6) 이벤트 타이머가 실행되며 bar가 태스크 큐로 들어간다. 태스크 큐: bar
baz(); // (7) 콜스택: baz - foo / 태스크 큐: bar
// (8) baz 함수 실행 끝, 콜스택: foo / 태스크 큐: bar
}
foo(); // (1) 콜스택: foo
// (9) foo 함수 실행 끝, 콜스택: (empty) / 태스크 큐: bar
// 이벤트 루프가 콜스택이 빈 것을 확인해 태스크 큐의 요소 중 실행 가능한 가장 오래된 태스크를 가져온다.
태스크 큐란?
- 태스크 큐는 실행해야할 태스크(비동기 함수의 콜백함수, 이벤트 핸들러 등...)의 집합을 의미한다.
- 이벤트 루프는 태스크 큐를 한 개 이상 가지고 있다.
- 이름과는 다르게 Queue가 아닌 Set 형태를 지니고 있다. 선택된 큐 중에서 실행 가능한 가장 오래된 태스크를 가져와야하기 때문이다.
- 이벤트 루프는 호출 스택이 비었는가 여부 체크, 태스크 큐에 대기 중인 함수가 있는지 여부 체크, 호출 스택이 비었다면 태스크큐가 빌때까지 실행 가능한 오래된 작업부터 순차적으로 꺼내와 실행한다.
function foo() {
console.log("foo");
}
function bar() {
console.log("bar");
}
function baz() {
console.log("baz");
}
setTimeout(foo, 0);
Promise.resolve().then(bar).then(baz);
// bar
// baz
// foo
console.log("a");
setTimeout(() => {
console.log("b");
}, 0);
Promise.resolve().then(() => {
console.log("c");
});
window.requestAnimationFrame(() => {
// requestAnimationFrame 메서드는 브라우저 리페인트 전에 실행된다.
console.log("리페인팅 전입니다");
});
// a → c → 리페인팅 전입니다 → b