React

<컴포넌트 제대로 만들기> 읽기

State를 분리하고 컴포넌트를 추상화하기
React.memo & PureComponent
Controlled / Uncontrolled 컴포넌트
Functional Component with Hooks
글로벌 컴포넌트 (Portal)
컴포넌트 결합 패턴 (Component Composition)
class JoinForm extends React.Component { email = '' password = '' render() { console.log('render') return ( <form onSubmit={this.handleSubmit}> <input type="email" placeholder="이메일"
JavaScript
복사
state가 없다면 render는 실행되지 않는다.
state가 없다면 DOM의 상태는 접근할 수 없으니 제어할 수 없다.
따라서 아래와 같이 state를 가지면
class JoinForm extends React.Component { state = { email: '', password: '', } render() { console.log('render') const { email, password } = this.state
JavaScript
복사
전자는 Uncontrolled component고 후자는 Controlled Component가 된다.
더 정확하게는 다른 차이도 존재한다. 알아본다.

Input 컴포넌트 만들기

class Input extends React.Component { renderCount = 0; render() { const { type, placeholder, value, onChange } = this.props console.log(placeholder, 'Rendered', ++this.renderCount) return <input type={type} placeholder={placeholder} value={value} onChange={onChange} /> } }
JavaScript
복사
이메일 입력시에 render 호출이 2번 발생하면서 이메일 Rendered 22, 비밀번호 Rendered 22가 출력됐다.
해결방법은 2가지가 있다.

Uncontrolled Component

Input 컴포넌트의 내부의 상태로 만들면 Input은 Uncontrolled라고 할 수 있다.
class Input extends React.Component { renderCount = 0; state = { value: '' } render() { const { type, placeholder } = this.props const { value } = this.state console.log(placeholder, 'Rendered', ++this.renderCount) return <input type={type} placeholder={placeholder} value={value} onChange={this.handleChange} /> }
JavaScript
복사
하지만 초기화 버튼이 동작하지 않게 된다.
class JoinForm extends React.Component { email = '' password = '' state = { reset: 0 } render() { const { reset } = this.state return ( <form onSubmit={this.handleSubmit}> <Input key={reset + 'email'} type="email" placeholder="이메일" onChange={this.handleChangeEmail} /> <Input key={reset + 'password'} type="password" placeholder="비밀번호" onChange={this.handleChangePassword} /> <button type="submit">가입하기</button> <button type="button" onClick={this.handleReset}>초기화</button> </form> ); } handleChangeEmail = (value) => { this.email = value } handleChangePassword = (value) => { this.password = value } handleSubmit = () => { console.log(this.email, this.password) } handleReset = () => { this.email = '' this.password = '' this.setState({ reset: this.state.reset + 1 }) } }
JavaScript
복사
key는 배열내에서 특정 아이템을 “identify”하기 위한, 특정짓기 위한 도구에요. 따라서 React의 관점에서 key가 다른 노드는 그냥 다른 노드로 분류되는 것이죠. 예를 들어 여기서 key만 바꿔준 email-1과 email-2 는 우리 입장에서 봤을 때는 같은 것 같지만, React의 관점에서는 아예 다른 노드인겁니다. 이렇게 key를 바꾸게 되면 단순히 상태가 초기화 되는 것이 아니라 컴포넌트 인스턴스 자체가 사라진 후, 다시 생깁니다.
새로 만드는 작업은 당연하게도 State를 단순히 초기화하는 것보다 성능이 나쁠 거라고 예상하게 되지만, 실제로는 성능차이가 별로 중요하지 않은 수준이고 특정 상황에서는 State를 초기화는 것보다도 빠를 수 있다고 해요. 저는 사실 Uncontrolled Component를 선호하는 편입니다. State가 외부에 공개되지 않아서(캡슐화) 가지는 장점이 크다고 생각해요.
컴포넌트의 상탯값을 부모 컴포넌트에서 사용할 일이 대부분이다보니 해당 state를 만들어서 자식 컴포넌트로 전달해주고 받아와서 사용하곤 하는데 이 때 render가 얼마나 자주 호출되는지 불필요한 호출인지 확인해서 위와 같이 내부의 상태로 만들어주고 Uncontrolled Component로 만든 다음 리렌더링 횟수를 줄여주는 것도 현명한 방법이라고 생각했다.
하지만 초기화 상탯값 하나가 추가된 것과 자식 컴포넌트의 값을 원하는 값으로 변경시켜줄 수 없다는 점이 단점이다.

PureComponent

Component는 항상 render를 다시 실행하지만 PureComponent는 Props나 State를 얕은 비교해서 변경점이 없으면 render를 다시 실행하지 않아요.
클래스형 컴포넌트를 사용하지 않다보니 React.PureComponent를 언제 사용하는지 모르고 있었는데 위와 같은 개념이라는 것을 알게 되었다.
class JoinForm extends React.Component { state = { email: '', password: '', } render() { const { email, password } = this.state return ( <form onSubmit={this.handleSubmit}> <Input type="email" placeholder="이메일" value={email} onChange={this.handleChangeEmail} /> // Input class Input extends React.PureComponent { renderCount = 0; render() { const { type, placeholder, value, onChange } = this.props console.log(placeholder, 'Rendered', ++this.renderCount) return <input type={type} placeholder={placeholder} value={value} onChange={onChange} /> } }
JavaScript
복사
PureComponent는 리렌더할 때 비교 로직이 들어가므로 항상 써야하는 것은 아니다.
Think about it. If component’s props are shallowly unequal more often than not, it re-renders anyway, but it also had to run the checks. — Dan Abramov (@dan_abramov) January 15, 2017

PureComponent가 불리한 상황

class JoinForm extends React.Component { state = { email: '', password: '', } render() { const { email, password } = this.state return ( <form onSubmit={this.handleSubmit}> <Input type="email" placeholder="이메일" value={email} onChange={({ target: { value } }) => this.setState({ email: value })} />
JavaScript
복사
render 메서드 내에서 인라인 함수를 사용하고 있다. 매 render 실행시마다 함수 인스턴스가 새로 생성되므로 아래와 같이 비교된다.
(() => null) === (() => null); // false
JavaScript
복사
매 render 실행마다 Input에 Props로 내려오는 함수가 모두 다르다는 것이고, 함수의 실행과는 관계 없이 Shallow compare로 함수 자체가 다른지를 비교하기 때문에 항상 다르다는 결과를 반환하겠죠. 결론적으로 항상 re-render 되는 것입니다.
render 메서드 내에서 객체를 새로 만드는 경우도 마찬가지의 문제가 생긴다.
또 한 가지 정말 쉽게 실수할 수 있는 케이스는 ReactNode를 넘길 때, children등을 사용할 때

Portal(Global) Component

App 렌더링 >> Parent 렌더링 >>...