리액트의 binding에 대해 알아보기 전에 bind의 개념에 대해 먼저 알아보자.
this.x = 9;
var module = {
x: 81,
getX: function() { return this. x; }
};
module.getX(); // 81
var retrieveX = module.getX;
// 여기서 var 변수는 전역에 선언됨
// module.getX의 내용은 function() { return this. x; }과 동일
// 즉, var retrieveX = function() { return this. x; }가 되어 this가 전역을 가리킨다.
console.log(retrieveX()); // 9
var boundGetX = retrieveX.bind(module);
// retrieveX, 즉 function() { return this. x; }의 this를 module 객체에 명시적으로 묶어준다.
console.log(boundGetX()); // 81
bind 메서드를 호출하면 새로운 함수를 리턴하게 된다.
bind 함수의 첫 인자로는 참조할 this 객체를 설정하고 추가적인 파라미터는 바인드된 함수에 파라미터가 있을 경우 해당 함수의 파라미터로 들어가게 된다.
import React from 'react';
class Baemin extends Component {
constructor(props) {
super(props);
this.state = {
isChecked: false,
};
}
handleClick(e) {
this.setState({ isChecked: true }); // Error !! this = undefined
}
render() {
return <button onClick={this.handleClick}>우와</button>;
}
}
해당 코드는 handleClick 부분에서 에러를 발생시킨다. javascript 클래스 내부에서 함수에 대한 binding 처리가 되어 있지 않아서 handleClick 함수 내부에서 this를 가져올려 할때 에러가 난다.
이러한 문제를 해결하기 위해 다양한 방법으로 binding 처리가 가능하다.
constructor(props) {
super(props);
this.state = {
isChecked: false,
};
**this.handleClick = this.handleClick.bind(this);**
}
장점은 제일 기본적인 방법이며, 앞으로 나올 방식에 비해 성능이슈를 야기하지 않는다는 것이다.
그러나 단점은 바인딩이 필요한 함수를 유지보수할 때(함수를 정의, 수정, 삭제)할 때 생성자 함수에도 매번 추가해줘야 한다.
render() {
return <button onClick={this.handleClick.bind(this)}>우와</button>;
}
장점은 방법 1보다 유지 보수성이 높으면서(함수 호출 부분을 작성할 때 .bind(this)만 추가해주면 된다) 방법 1과 마찬가지로 성능 이슈를 야기하지 않는다.
단점은 .bind(this)를 추가하는 부분에서 가독성이 좀 떨어진다.
render() {
return <button onClick={(e) => this.handleClick(e)}>우와</button>;
}
장점은 방법 2와 동일하다. (유지 보수성, 성능 이슈 x)
단점은 콜백으로 선언했기 때문에 컴포넌트가 새로 렌더링될 때마다 콜백 객체가 새로 생성된다. 만약 하위 컴포넌트로 전달될 경우 매번 새로운 객체가 생성되어 불필요한 리렌더링이 수행될 수도 있다.
handleClick = (e) => {
this.setState({ isChecked: true });
}
장점
특히 가독성 측면에서 화살표 함수는 이벤트 핸들러를 처리할 때 이벤트 객체 외에 추가적인 변수를 받을 경우 엄청난 가독성을 자랑한다.
handleClick = index => e => {
this.setState({ isChecked: true });
}
아래 커링 방식에 비해, 인수를 연속으로 받아도 간편하게 처리할 수 있다.
handleClick() {
return function (e) {
this.setState({ isChecked: true });
};
}
결론부터 얘기하면 '아니다'. 아래는 바벨 트랜스컴파일 결과물이다.
화살표 함수로 작성된 함수와 일반 함수는 트랜스파일 결과물이 다르다.
클래스에서 new Class() 같이 새로운 객체를 생성하고 그 안에서 method를 호출했을 때 prototype 안에 있는 method일 경우 그 method를 찾는 검색 시간이 짧아진다. 또한 각 객체에서 새 method를 생성하는 게 아니고 prototype에 있는 method를 공유한다.
즉 prototype에 있는 함수에 접근하는 경우 엔진 단에서 성능 최적화를 해주지만, 화살표 함수로 클래스 내에서 선언되는 경우 prototype에 포함되지 않아 성능이 좀 더 떨어진다. (Arrow Functions in Class Properties Might Not Be As Great As We Think)
성능과 유지보수성 두 마리의 토끼를 한번에 잡을 수 없을까?
화살표 함수는 사용하기 편하고 가독성이 좋지만 성능 문제가 있다. 일반 bind 방식은 성능은 좀 더 좋지만 가독성이 나쁘고 유지보수 측면에서 사용하기 불편하다.
두 마리 토끼를 그나마 잡을 수 있는 방법은 autobind-decorator 라는 라이브러리를 사용하는 것이다. 이 라이브러리를 사용하면 함수를 선언할 때 데코레이터만으로 자동으로 bind를 해 준다.
import {boundMethod} from 'autobind-decorator'
class Component {
constructor(value) {
this.value = value
}
@boundMethod
method() {
return this.value
}
}
그러나 추가 라이브러리가 필요한 데다가 아직 데코레이터 문법은 javascript 내에 표준이 아니어서 팀 내 새로온 개발자들이 봤을 때 익숙치 않을 수 있다.
성능을 좀 더 중요시 하는 팀, 여러 프로젝트에서 사용되는 디자인 시스템을 만드는 팀에서는 화살표 함수로 작성하는 binding을 피해야 될 것이다.
그와 반대로 표준이 아닌 게 꺼려지는 팀, 코드 가독성이 중요한 팀, 하나의 컴포넌트가 다른 곳에서 사용되는 경우가 적은 팀이라 성능 이슈가 거의 일어나지 않을 팀은 화살표 함수로 간단히 binding을 처리해도 될 것이다.
아직은 이게 정답이거나 최선이라는 방법은 없으니 팀이나 개인의 상황에 맞춰서 잘 선택하면 될 듯하다.
추가로 binding은 반드시 필요한 곳에만 사용하자. binding 함수는 일반 함수보다 성능적으로 느려서 binding이 필요없는 곳까지 전부 다 사용하면 성능 이슈를 야기할수 있다.
잘봤습니다 :)