리액트 개발을 위해 꼭 알아야 할 자바스크립트 (2)

리액트 개발을 위해 꼭 알아야 할 자바스크립트 (2)

모던 리액트 Deep Dive: 리액트의 핵심 개념과 동작 원리부터 Next.js까지, 리액트의 모든 것 - 01장 리액트 개발을 위해 꼭 알아야 할 자바스크립트

3. 클래스

🛠️ 참고

클래스란 무엇인가?

특정한 형태의 객체를 반복적으로 만들기 위해 사용되는 것이 바로 클래스다.

// Car
class Car {
  // constructor는 생성자다. 최초에 생성할 때 어떤 인술르 받을지 결정할 수 있으며,
  // 객체를 초기화하는 용도로도 사용한다.
  constructor(name) {
    this.name = name
  }

  // 메서드
  hook() {
    console.log(`${this.name}이 경적을 울립니다.`)
  }

  // 정적 메서드
  static hello() {
    console.log('저는 자동차입니다')
  }

  // setter 
  set age(value) {
    this.carAge = value
  }

  // getter 
  get age(value) {
    return this.carAge
  }
}

// Car 클래스를 활용해 car 객체를 만들었다.
const myCar = new Car('자동차')

// 메서드 호출
myCar.hook()

// 정적 메서드는 클래스에서 직접 호출한다.
Car.hello()

// 정적 메서드는 클래스로 만든 객체에서는 호출할 수 없다.
// Uncaught TypeError: myCar.hello is not a function
myCar.hello()

// setter를 만들면 값을 할당할 수 있다.
myCar.age = 32

// getter로 값을 가져올 수 있다.
console.log(myCar.age, myCar.name) // 32 자동차

constructor

constructor는 생성자로, 이름에서 알 수 있는 것처럼 객체를 생성하는 데 사용하는 특수한 메서드다.

  • 단 하나만 존재할 수 있으며, 여러 개를 사용한다면 에러가 발생한다.

  • 그러나 생성자에서 별다르게 수행할 작업이 없다면 생략하는 것도 가능하다.

// ❌
class Car {
  constructor(name) {
    this.name = name
  }

  // SyntaxError: A class may only have one constructor
  constructor(name) {
    this.name = name
  }
}

// ⭕
class Car {
  // constructor는 없어도 가능하다.
}

프로퍼티

프로퍼티란 클래스로 인스턴를 생성할 때 내부에 정의할 수 있는 속성값을 의미한다.

class Car {
  constructor(name) {
    // 값을 받으면 내부에 프로퍼티로 할당된다.
    this.name = name
  }
}

const myCar = new Car('자동차') // 프로퍼티 값을 넘겨주었다.

getter와 setter

getter란 클래스에서 무언가 값을 가져올 때 사용된다. getter를 사용하기 위해서는 get 키워드를 앞에 붙여야 하고, 뒤이어서 getter의 이름을 선언해야 한다.

class Car {
  constructor(name) {
    this.name = name
  }

  get firstCharacter() {
    return this.name[0]
  }
}

const myCar = new Car('자동차')

myCar.firstCharacter // 자

setter란 클래스 필드에 값을 할당할 때 사용한다. setter를 사용하기 위해서는 set 키워드를 앞에 붙여야 하고, 뒤이어서 setter의 이름을 선언해야 한다.

class Car {
  constructor(name) {
    this.name = name
  }
  get firstCharacter() {
    return this.name[0]
  }

  set firstCharcter(char) {
    this.name = [char, ...this.name.slice(1)].join('')
  }
}

  const myCar = new Car('자동차')

  myCar.firstCharacter // 자

  myCar.firstCharcter = ''

  console.log(myCar.firstCharacter, myCar.name) // 차, 차동차

인스턴스 메서드

클래스 내부에서 선언한 메서드를 인스턴스 메서드라고 한다. 이 인스턴스 메서드는 실제로 자바스크립트의 prototype에 선언되므로 프로토타입 메서드로 불리기도 한다.

class Car {
  constructor(name) {
    this.name = name
  }

  // 인스턴스 메서드 정의
  hello() {
    console.log(`안녕하세요. ${this.name}입니다.`)
  }
}
const myCar = new Car('자동차')
myCar.hello() // 안녕하세요. 자동차입니다.

이렇게 접근할 수 있는 이유는 앞서 프로토타입 메서드라고도 불리는 이유, 즉 메서드가 prototype에 선언됐기 때문이다.

const myCar = new Car('자동차')

Object.getPrototypeOf(myCar) // {constructor: f, hello: f}

Object.getPrototypeOf를 사용하면, 인수로 넘겨준 변수의 prototype를 확인할 수 있다.

Object.getPrototypeOf(myCar) === Car.prototype // true
myCar.__proto__ === Car.prototype // true

__proto__는 가급적 사용해서는 안 되는 코드다. 그 이유는 호환성을 지키기 위해서만 존재하는 기능이기 때문이다.

직접 객체에서 선언하지 않았음에도 프로토타입에 있는 메서드를 찾아서 실행을 도와주눈 것을 바로 포로토타입 체이닝이라고 한다.

모든 객체는 프로토타입을 가지고 있는데, 특정 속성을 찾을 때 자기 자신부터 시작해서 이 프로토타입을 타고 최상위 객체인 Object까지 홅는다.

정적 메서드

정적 메서드는 특이하게 클래스의 인스턴스가 아닌 이름으로 호출할 수 있는 메서드다.

class Car {
  static hello() {
    console.log('안녕하세요.')
  }
}

const myCar = new Car()

myCar.hello() // TypeError: myCar.hello is not a function
Car.hello() // 안녕하세요.

정적 메서드 내부의 this는 클래스로 생성된 인스턴스가 아닌, 클래스 자신을 가리키기 때문에 다른 메서드에서 일반적으로 사용하는 this를 사용할 수 없다.

상속

extends는 기존 클래스를 상속받아서 자식 클래스에서 이 상속받은 클래스를 기반으로 확장하는 개념이라 볼 수 있다.

class Car {
  constructor(name) {
    this.name = name
  }

  hook() {
    console.log(`${this.name} 경적을 울립니다.`)
  }
}

class Truck extends Car {
  constructor(name){
    // 부모 클래스의 constructor, 즉 Car의 constructor을 호출한다.
    super(name)
  }

  load() {
    console.log('짐을 싣습니다')
  }
}

const myCar = new Car('자동차')
myCar.hook() // 경적을 울립니다.

const truck = new Truck('트럭')

truck.hook() // 경적을 울립니다.
truck.load() // 짐을 싣습니다.

클래스와 함수의 관계

클래스가 작동하는 방식은 자바스크립트의 프로토타입을 활용하는 것이라고 볼 수 있다.


4. 클로저

함수형 컴포넌트의 구조와 작동 방시그 훅의 원리, 의존성 배열 등 함수형 컴포넌트의 대부분의 기술이 모두 클로저에 의해 의존하고 있다.

함수형 컴포넌트 작성을 위해서는 클로저에 대한 이해가 필요하다.

클로저의 정의

MDN에서 찾아보면 클로저는 함수와 함수가 선언된 어휘적 환경(Lexical Scope)의 조합이라고 돼 있다.

function add() {
  const a = 10
  function innerAdd() {
    const b = 20
    console.log(a + b)
  }
  innerAdd() // 30
}

add()

“선언된 어휘적 환경”이라는 것은, 변수가 코드 내부에서 어디서 선언됐는지를 말하는 것이다.

코드가 작성되는 순간에 정적으로 결정되며 클로저는 이러한 어휘적 환경을 조합해 코딩하는 기법이다.

변수의 유효 범위, 스코프

변수의 유효 범위에 따라서 어휘적 환경이 결정된다. 이러한 변수의 유효 범위를 스코프(scope)라고 한다.

전역 스코프

전역 레벨에 선언하는 것을 전역 스코프(global scope)라고 한다.

var global = 'global scope'

function hello() {
  console.log(global)
}

console.log(global) // global scope
hello() // global scope
console.log(global === window.global) // true

함수 스코프

자바스크립트는 기본적으로 함수 레벨 스코프를 따른다. 즉, {} 블록이 스코프 범위를 결정하지 않는다.

if(true){
  var global = 'global scope'
}

console.log(global) // 'global scope'
console.log(global === window.global) // true
function hello() {
  var local = 'local variable'
  console.log(local) // local variable
}

hello() 
console.log(local) // Uncaught ReferenceError: local is not defined

if 블록과는 다르게 함수 블록 내부에서는 스코프가 결정된다.

클로저의 활용

리액트가 관리하는 내부 상태 값은 리액트의 별도로 관리하는 클로저 내부에서만 접근할 수 있다.

function Counter() {
  var counter = 0

  return {
    increase: function() {
      return ++counter
    },
    decrease: function() {
      return --counter
    },
    counter: function() {
      console.log('counter에 접근!')
      return counter
    }
  }
}

var c = Counter()

console.log(c.increase()) // 1
console.log(c.increase()) // 2
console.log(c.increase()) // 3
console.log(c.decrease()) // 2
console.log(c.counter()) // 2

리액트에서는 useState의 변수를 저장해두고, useState의 변수 접근 및 수정 또한 클로저 내부에서 확인이 가능해 값이 변하면 렌더링 함수를 호출하는 등의 작업이 이루어질 것이다.

리액트에서의 클로저

클로저의 원리를 사용하고 있는 것은 useState다.

function Components() {
  const [state, setState] = useState()

  function handleClick() {
    // useState 호출은 위에서 끝났지만, 
    // setState는 계속 내부의 최산값(prev)을 알고 있다.
    // 이는 클로저를 활용했기 때문에 가능하다.
    setState((prev) => prev + 1)
  }
}

© 2021. All rights reserved.

Powered by Hydejack v9.1.6