클래스

쏘뽀끼·2025년 1월 8일
0

react

목록 보기
24/25

16.8버전이 나오기 전까지 리액트에서는 모든 컴포넌트가 클래스로 작성돼 있었다.
즉 함수를 컴포넌트로 작성하기 시작한 건 리액트 역사로 보았을 때 비교적 최근의 일이다.

과거에 작성된 리액트 코드를 읽기 위해서, 또 그러한 코드를 함수 컴포넌트로 개선하기 위해서 자바스크립트의 클래스가 어떤 식으로 작동하는지 이해해야 한다.
클래스를 이해하게 된다면, 리액트가 왜 패러다임을 바꾼지도 알 수 있고, 오래된 리액트 코드를 리팩터링하는 데도 도움이 될 것이다.

또한 자바스크립틔의 프로토타입 기반으로 작동하는 클래스의 원리를 이해하면 프로토타입에 대해서도 알 수 있다.



클래스란 무엇인가

클래스란

특정한 객체를 만들기 위한 일종의 템플릿 같은 개념
= 특정한 형태의 객체를 반복적으로 만들귀 위해 사용되는 것

클래스를 활용 => 객체를 만드는데 필요한 데이터, 이를 조작하는 코드를 추상화 가능
==> 객체 생성을 편리하게 할 수 있음

//Car 클래스 선언 
class Car {
//constructor는 생성자다. 최초에 생성할 때 어떤 인수로 받을지 결정할 수 있으며, 
//객체를 초기화하는 용도로도 사용된다. 
constructor(name){
 this.name = name
 }
 
 
 //메서드
 honk(){
  console.log(`${this.name}이 경적을 울립니다!`)
 }
 
 //정적 메서드
 static.hello(){
  console.log('저는 자동차입니다')
 }
 
 //setter
 set age(value){
  this.carAge = value
 }
 
 //getter
 get age(){
  return this.carAge
 }
 
}

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

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

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

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

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

constructor

constructor는 생성자로, 객체를 생성하는 데 사용하는 특수한 메서드
단 하나만 존재할 수 있다.
여러 개를 사용할 시 에러가 발생한다.
그러나 생성자에서 별 다르게 수행할 작업이 없다면 생략 가능하다.

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

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



프로퍼티

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

class Car{
 constructor(name){
  //값을 받으면 내부에 프로퍼티로 할당된다. 
  this.name = name
  }
 }
 
 const myCar = new Car('자동차') //프로퍼티 값을 넘겨주었다.

기본적으로 인스턴스 생성 시 constructor 내부에는 빈 객체가 할당돼 있는데 바로 이 객체에 프로퍼티의 키와 값을 넣어서 활용할 수 있게 도와준다.




getter와 setter

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

class Car{
 constructor(name){
  this.name = name
 }
 
 get firstCharactoer(){
  return this.name[0]
  }
 }
const myCar = new Car('자동차')

myCar. firstCharacter //자

반대로 setter란 클래스 필드에 값을 할당할 때 사용한다.
마찬가지로 set이라는 키워드를 먼저 선언하고, 그 뒤를 이어서 이름을 붙이면 된다.

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

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

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

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

myCar.firstCharacter = '자'

//차를 할당한다
myCar.firstCharacter = '차'

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



인스턴스 메서드

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

class Car{
 constructor(name){
  this.name = name
  }
 
 //인스턴스 메서드 정의 
hello(){
 console.lg(`안녕하세요, ${this.name}입니다.`)
 }
}

위 예제에서 Car이라는 클래스를 선언하고, 그 내부에 hello라고 하는 인스턴스 메서드를 정의했다.
이 인스턴스 메서드는 다음과 같이 선언할 수 있다.

const myCar = new Car('자동차')
myCar.hello()//안녕하세요, 자동차입니다.

위와 같이 새롭게 생성한 객체에서 클래스가 선언한 hello인스턴스 메서드에 접근할 수 있는 것을 확인할 수 있다.
이렇게 접근이 가능한 이유는 앞서 프로토타입 메서드라고 불리는 이유, 즉 메서드가 prototype에 선언됐기 때문이다.

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

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

Object.getPrototypeOf를 사용하면, 인수로 넘겨준 변수의 prototype을 확인할 수 있다.
{constructor:f, hello:f}를 반환받아 Carprototype을 받은 것으로 짐작할 수 있다.

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

Object.getPrototypeOf(myCar)Car.prototype과 비교한 결과 true가 반환되는 것을 볼 수 있다.

myCar.__proto__ === Car.prototype //true

__proto__또한 Object.getPrototypeOf와 동일하게 작동하는 것을 알 수 있다.

__proto__는 가급적 사용해서는 안되는 코드다.
typeof null === 'object'와 유사하게 원래 의도한 표준은 아니지만 과거 브라우저가 이를 사용했기 때문에 유지되는, 호환성을 지키기 위해서만 존재하는 기능이기 때문이다.


직접 객체에 선언하지 않았음에도 프로토타입에 있는 메서드를 찾아서 실행을 도와주는 것을 바로 프로토 타입 체이닝이라고 한다. 모든 객체는 프로토타입을 가지고 있는데, 특정 속성을 찾을 때 자기 자신부터 시작해서 이 프로토타입을 타고 최상위 객체인 `Object`까지 훑는다. 이 경우 `myCar`에서 시작해서 부모인 `Car`에서 `hello`를 찾는 프로토타입 체이닝을 거쳐서 비로소 `hello`를 호출할 수 있게 됐다. 결론적으로 프로토타입과 프로토타입 체이닝 특성 때문에 생성한 객체에서도 직접 선언하지 않은, 클래스에 선언한 `hello()`메서드를 호출할 수 있고, 이 메서드 내부에서 `this`도 접근해 사용할 수 있게 된다.



정적 메서드

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

class Car{
 static hello(){
  console.log('안녕하세요!')
  }
 }
 
 const myCar = new Car()
 
 myCar.hello()//Uncaught TypeError: myCar.hello is not a function 
 Car.hello() // 안녕하세요! 

정적 메서드 내부의 this는 클래스로 생성된 인스턴스가 아닌, 클래스 자신을 가리키기 때문에 다른 메서드에서 일반적으로 사용하는 this를 사용할 수 없다.
이러한 이유로 리액트 클래스 컴포넌트 생명주기 메서드인 static getDerivedStateFromProps(props, state)에서는 this.state에 접근할 수 없다.

정적메서드는 비록 this에 접근할 수 없지만 인스턴스를 생성하지 않아도 사용할 수 있다.
생성하지 않아도 접근이 가능하기에 객체를 생성하지 않더라도 여러 곳에서 재사용이 가능하다.
이 때문에 애플리케이션에서 전역에서 사용하는 유틸 함수를 정적 메서드로 많이 활용하는 편이다.




상속

리액트에서 클래스 컴포넌트를 만들기 위해서 extends React.Component 또는 extends React.Pure Component를 선언한 걸 본 적이 있을 것이다.
extends는 기존 클래스를 상속받아서 자식 클래스에서 이 상속받는 클래스를 기반으로 확장하는 개념이라 볼 수 있다.

class Car{
 constructor(name){
  this.name = name
 }
 
 honk(){
  console.log(`${this.name} 경적을 울립니다!`)
  }
 }
 
class Truck extends Car {
 constructor(name){
  //부모 클래스의 constructor, 즉 Car의 constructor를 호출한다. 
  super(name)
  }
  
  load(){
   console.log('짐을 싣습니다.')
   }
  }
  
  const myCar = new Car('자동차')
  myCar.honk()//자동차 경적을 울립니다! 
  
  const truck = new Truck('트럭')
  
  truck.honk()// 트럭 경적을 울립니다!
  truck.load(0//짐을 싣습니다!

CarextendsTruck이 생성한 객체에서도, Truck이 따로 정의하지 않은 honk메서드를 사용할 수 있는 것을 볼 수 있다.
extends를 활용하면 기본 클래스를 기반으로 다양하게 파생된 클래스를 만들 수 있다.




클래스와 함수의 관계

클래스는 ES6에서 나온 개념으로, ES6 이전에는 프로토타입을 활용해 클래스의 작동 방식을 동일하게 구현할 수 있었다.
반대로 말하면, 클래스가 작동하는 방식은 자바스크립트의 프로토타입을 활용하는 것이라고 볼 수 있다.

0개의 댓글