모던 자바스크립트 Deep Dive를 성경처럼 여기던 나에게 모던 리액트 Deep Dive가 출간되었다는 소식은 구약다음 신약이 나왔다는 것과 다름없었고 정신을 차려보니 이미 내 통장에선 48000원이 인출되어 있었다.
거금을 들여 구매한 책이니만큼 후회없이 내것으로 만들리라. 그리고 이 후 포스팅들은 그 과정들이다.
리액트는 얼마전 전자정부 표준 프레임워크의 프런트엔드 개발 도구로 채택되었다.
정부에서 담당하는 프로젝트에 리액트가 채택되었다는 것은 그만큼 리액트의 안정성이 확보되었으며, 많은 개발자들 사이에서 널리 쓰이고 있다는 것을 의미한다.
그렇다면 왜 리액트는 지난 몇년간 많은 개발자들이 선호하는 라이브러리가 되었을까?
다양한 이유들이 있지만 이 책에서 소개하는 몇 가지 이유는 다음과 같다.
명시적인 상태 변경
리액트는 단방향 바인딩만 지원한다. 즉, 데이터의 흐름이 한쪽으로만 간다. Angular에서 사용하는 양방향 바인딩은 뷰의 변화가 컴포넌트에 영향을 미칠 수도, 반대로 컴포넌트의 상태가 변경되면 뷰의 상태가 변경될 수 있다. 양방향 바인딩 또한 편리함을 제공하지만 규모가 커질수록 상태변화의 원인을 파악하기 어려워진다.
리액트의 상태 변화는 단방향으로, 그리고 명시적으로 이뤄진다. 상태가 변화했다면 그 상태 변화를 명시적으로 일으키는 함수만 찾으면 된다. 즉, 다른 뷰에서 상태를 변화시켰는지 찾아볼 필요가 없다.
이러한 리액트의 명시적인 상태 업데이트는 개발자들에게 간단함과 유연함을 제공하며 코드를 읽기가 쉽고 버그를 야기할 가능성을 줄여준다.
JSX
Angular는 뷰를 표헌하기 위해 문자열 템플릿을 사용한다. 또한 Angular 디렉티브라고 하여 ngif처럼 Angular에서만 사용되는 전용 문법을 익여혀야 한다. 하지만 리액트는 HTML에 자바 스크립트 문법을 더한 JSX를 사용하는데, 이는 자바스크립트 문법에 HTML을 가미한 수준이어 쉽게 JSX 코드를 구현할 수 있다.
비교적 배우기 쉽고 간결함
강력한 커뮤니티, 그리고 메타
그렇다면 리액트는 어떤 과정을 통해 프런트엔드의 주류로 자리잡을 수 있었을까?
2000년대의 웹 생태계는 LAMP 스택(Linux, Apache, MySQL, PHP)을 이용한 웹 개발이 주를 이루는 시기였다고 한다.
이러한 웹 개발 패턴은 대부분 DB에서 데이터를 불러온 다음, 웹 서버에서 HTML 페이지를 만들어서 클라이언트에 제공하는 방식으로 작동한다.
콘텐츠는 사용자나 기타 다른 환경에 따라 서버에서 동적으로 생성하고, 웹 브라우저는 이를 단순히 다운로드 받아 렌더링하며, 자바스크립트는 폼 처리와 같은 부수적인 역할만 하는 방식 이었다.
2010년대에 들어서면서 제이쿼리, 로컬 스토리지, 웹소켓, SVG, 지오로케이션이 지원되기 시작하였다.
이러한 브라우저 생태계 변화에 맞춰 자바스크립트는 간단한 폼 처리만이 아닌 DOM을 직접적으로 수정하며 사용자에게 다양한 인터랙션을 보여주었고, AJAX(Asynchronous Javascript and XML)를 활용 해 서버뿐 아니라 클라이언트에서도 서버와 통신해서 데이터를 불러오기 시작했다.
이 때 페이스북은 전세계 7억 명 정도의 사용자가 이용하는 SNS로 자리 잡았다.
페이스북은 성능을 위해 최대한 서버에서 렌더링하는 기술을 사용했고, JS 번들의 크기를 줄이는데 심혈을 기울였다.
허나 이는 한계가 있을 수밖에 없었따.
페북의 타임라인에는 자신의 상태를 업로드할 수 있고, 여기에 다른 사람들의 댓글이 추가되는 것을 실시간으로 확인할 수 있다.
그런데 이러한 작업이 모두 서버렌더링으로 작동한다면 사용자가 글 하나 올릴 때 마다 매번 새로운 페이지를 보여주기 위해 서버에서 렌더링하면서 화면이 깜빡거리거나 느리게 작동하는것처럼 보일 것이다.
페이스북팀은 당시 존재하던 다양한 옵션(제이쿼리, Angular 등..)은 사용자에게 만족스러운 경험을 주기 어려울것이라 판단했고 BoltJS라는 새로운 프레임 워크를 만들어 페이스북 그 자체를 BoltJS로 다시 만들기 시작하였으나 실패 하였다.
BoltJS를 개선할 필요성을 느낀 개발자들이 내놓은 아이디어 중 하나가 Functional Bolt 였다.
이것은 훗날 리액트의 시초가 된다.
어플리케이션에서 API의 변화에 따라 뭔가 변경되면 단순히 UI를 초기화하고 새로 렌더링 하자는 것이었는데
이 당시 대부분의 프레임워크는 양방향 바인딩 구조를 채택해 모델과 뷰가 밀접한 관계를 맺고 서로가 서로를 변경할 수 있는 구조였다.
이러한 방식은 코드를 작성하는것은 간단하지만 변경된 DOM을 추적하는 것이 어렵고, 왜 이렇게 변경됐는지 추적하는것도 어려웠고 이는 많은 버그를 발생시키는 원인이 되었다.
그런데 이러한 방식 대신, 모델이 뷰를 변경하는 단방향 방식으로, 모델의 데이터가 변경되어 뷰가 변경되어야 하면 이전의 DOM을 버리고 새로 렌더링하는 방식을 제안되었다.
그러나 DOM의 변경을 최소한으로 하는 것이 성능을 위한 최선의 방법으로 여겨졌던 시기라 이러한 방식이 성능을 보장할 수 있을지 의구심을 품는 개발자들이 많았다.
그럼에도 한편으로는 유용한 방식이었다. 페북 프론트 개발자들이 느끼는 가장 큰 어려움은 DOM을 업데이트 하는 것이었다. 좋아요 버튼의 클릭 이벤트 리스너를 등록하고, 이를 제거하고 다시 찾고, 다시 또 속성을 변경하는 작업은 매우 복잡했고 버그의 주요 원인이었으며 때로는 개발자 스스로도 그 흐름을 제대로 파악하기 어려운 이이었기 때문이다.
성능은 둘째치고 그냥 새롭게 다시 렌더링 해보리는 것이 어찌보면 좋은 생각이 될 수도 있었다.
이러한 생각을 기반으로 데모로 구현된 리액트는 많은 사람들에게 영감을 주었고, 성능 또한 썩 나쁘지 않다는 것을 확인 받을 수 있었다.
그렇게 리액트 프로젝트가 시작 되었다.
허나 JSConf US 2013에서 공개된 리액트에 대한 반응은 그다지 좋지 못했다.
아니 사람들은 엄청 싫어했다고 한다.
대다수의 개발자들은 JSX 구문의 특징, 자바스크립트 코드 내에 HTML을 추가한다는 것에 그다지 호의적이지 않았다.
당시에는 컴퓨터 공학에서 말하는 관심사 분리의 원칙을 지키기위한 가장 기초적인 사실이 HTML, JS, CSS 를 다른 파일로 분리하는 것이었기 때문이다.
허나 리액트의 구조도 관심사 분리의 원칙을 따른다고 볼 수 있다.
당시의 HTML, JS, CSS를 각기 다른 폴더와 파일로 분리한것이 파일의 역할별로 관심사가 분리되는 것에 초점이 맞춰줘 있었다면 리액트의 관심사 분리는 컴포넌트 기반으로, 컴포넌트의 역할에 따라 관심사가 분리 되어있기 때문이다.
즉, 관심사 분리의 원칙이라는 개념하에 리액트와 기존 프론트엔드 프로젝트가 서로 다른 방식을 채택한 차이만 있을 뿐이었다.
다만 당시 페이스북팀은 점점 복잡해져가는 웹 어플리케이션을 좀 더 효율적으로 관리하기 위한 새로운 프레임워크에 초점을 맞추었지만 이부분이 2013 JSConf를 통해 잘 전달되지 않은것으로 보인다.
허나 이후 커뮤니티의 지지를 받으며 이제는 가장 인기 있는 프레임워크로 자리 잡았다.
과거 리액트는 클라이언트에 초점을 맞추고 있었고, 앞으로도 브라우저와 클라이언트에서의 작동을 개선할 예정라 밝혔다. 그러나 리액트팀은 클라이언트에서는 할 수 없는 서버에서의 작업, 그리고 서버 환경이 가지고 있는 가능성에 무게를 두고 앞으로도 서버에서 작동할 수 있는 다양한 기능이나 유스케이스를 추가할것으로 보인다.
따라서 앞으로도 리액트를 계속해서 공부할 것이라면 프런트엔드 개발자들도 Node.js 같은 서버 환경을 공부하는것이 기본 소양으로 자리 잡게 될 것이다.
일반 함수의 this는 동적으로 결정된다.
즉, 호출시점에 this가 정해진다는 의미이다.
그러나 화살표 함수는 정적으로 this를 바인딩할 객체를 정한다.
즉, 함수를 선언할 때 this에 바인딩할 객체가 정해진다.
이를 Lexical this라고 한다.
인스턴스 메서드는 prototype에 선언된 프로토타입 메소드로 불린다.
그러나 정적 메서드는 클래스의 인스턴스가 아닌 이름으로 호출할 수 있는 메서드다.
class Car {
static hello() {
console.log('안녕 하셔요')
}
}
const myCar = new Car()
myCar.hello() // TypeError : myCar.hello is not a function
Car.hello() // 안녕 하셔요
정적 메서드 내부의 this는 클래스로 생성된 인스턴스가 아닌, 클래스 자신을 가리키기 때문에 다른 메서드에서 일반적으로 사용하는 this를 사용할 수 없다.
정적 메서드는 비록 this에 접근할 수 없지만 인스턴스를 생성하지 않아도 사용할 수 있다는 점, 객체를 생성하지 않아도 여러곳에서 재사용이 가능하다는 장점이 있다.
이때문에 어플리케이션 전역에서 사용하는 유틸 함수를 정적 메서드로 많이 활용하는 편이다.
클래스는 ES6에서 나온 개념으로 클래스의 코드를 함수형으로 바꾸면 다음과 같이 바뀐다.
class Car {
constructor(name) {
this.name:name
}
honk() {
console.log(`${this.name}이 경적을 울립니다!`)
}
static hello() {
console.log('저는 자동차입니다')
}
set age(value){
this.carAge = value
}
get age() {
return this.age
}
}
var Car = (function() {
function Car(name){
this.name = name
}
// 프로토타입 메서드. 실제로 프로토타입에 할당해야 프로토타입 메서드로 작동한다.
Car.prototype.honk = function(){
console.log(`${this.name}이 경적을 울립니다!`)
}
// 정적 메서드. 인스턴스 생성 없이 바로 호출 가능하므로 직접 할당했다.
Car.hello = function(){
console.log('저는 자동차입니다')
}
// Car 객체에 속성을 직접 정의했다.
Object.defineProperty(Car, 'age', {
// get, set은 각각 접근자, 설정자로 사용할 수 있는 예약어이다. Object.get은 이미 내장되어있는 메소드이며 get 속성에 접근하면 바인딩된 함수가 자동으로 호출된다. set도 마찬가지로 set속성에 접근하면 바인딩된 함수가 자동으로 호출된다.
get: function() {
return this.carAge
},
set: function() {
this.carAge = value
},
})
return Car
})()
Javascript 엔진은 Object의 prototype을 생성할 때 마다 해당 property의 상태를 나타내는 property attribut를 default 값으로 자동 정의한다.
[Property Attribute]
property attribute는 내부 슬롯이며 이 값들에는 직접 접근할 수 없다.
내부 슬록/메서드는 javascript 엔진에선 실제로 동작하지만 개발자가 실제로 접근할 순 없다.
허나 Object.getOwnPropertyDescripters 같은 메서드를 통해 간접적으로 접근할 수 있다.
그리고 Object.defineProperty 메서드를 사용하면 객체의 property를 정의/수정할 수 있다.
정의/수정은 직접접근은 아니다.(setter같은 느낌이라고 생각하면 될듯)
Object.defineProperty 를 사용하여 객체의 property를 정의/수정할 때 configurable 값을 정의해주지 않으면 프로퍼티를 수정할 수 없다.
const person = {};
Object.defineProperty(person, 'firstName', {
value: 'Ungmo',
enumerable: true,
writable: true,
configurable: true
});
Object.defineProperty(person, 'lastName', {
value: 'Lee'
});
person.firstName = 'Boyeon';
// TypeError: Cannot redefine property : lastName
person.lastName = 'Jin';