[Day 42] React Advance(do not Controlled Component, Performance Optimization, Immutability, Reconciliation)

React Advance

제어되지 않는 컴포넌트

  • 제어되는 컴포넌트 –> 폼 데이터가 React에 의해 제어된다.

  • 제어되지 않는 컴포넌트 –> 폼 데이터가 DOM의 자체 기능에 의해 제어된다.

제어되지 않는 컴포넌트를 만들 때는 ref를 사용해서 폼 데이터를 DOM으로부터 가져와서 사용한다.

(장점!) 진리의 원천을 DOM에 두기 때문에 코드의 양이 상대적으로 적다.

폼 요소의 기본값 지정하기

폼 요소에 value attribute 를 쓰면 제어되는 컴포넌트가 되기 때문에, value 대신 defaultValue attribute를 사용해서 DOM의 상태변화는 제어되지 않는 상태로 두면서 초기값을 지정할 수 있다.

  • <input type="checkbox"><input type="radio"> 엘리먼트는 defaultChecked={true} 로 설정한다.

  • <select><textarea>defaultValue={anything} 로 설정한다.

The File Input Tag

리액트에서 <input type="file" />언제나 제어되지 않는 컴포넌트다.

File Input 요소의 값은 오로지 사용자에 의해서만 지정될 수 있기 때문에 프로그래밍을 통해 값을 미리 지정하는 등의 제어가 불가능하다.

성능 최적화 (Performance Optimization)

리액트는 내부적으로 UI 업데이트를 위해 필요한 비용이 많이 드는 DOM 연산의 개수를 최소화하기 위한 기술을 사용한다.

리액트는 setState() 메소드를 통해 상태를 변화시킬 때마다 해당 컴포넌트의 모든 하위 컴포넌트의 render() 메소드를 호출해서 화면을 갱신한다.

(중요!) 만약 props와 state의 속성이 바뀌지 않았다면 화면을 다시 그릴 필요가 없다. 즉, render() 메소드를 호출할 필요가 없다. –> 성능 최적화의 원리

React를 import 하면서 Component 대신 PureComponent를 import 하면 최적화가 자동으로 되는 Component를 상속받는다.

비교조정 (Reconciliation) 피하기

PureComponent를 사용하면 state나 props가 바뀌지 않았다면 render() 메소드를 다시 호출하지 않는다.

—> PureComponent 내부에 존재하는 라이프사이클 함수 shouldComponentUpdate()에서 false를 반환하는 경우 컴포넌트를 업데이트할 필요가 없는 것으로 간주해서 이 컴포넌트 및 하위에서 호출하는 render()를 포함한 전체 렌더링 프로세스를 스킵할 수 있다.

PureComponent를 적용하면, state와 props의 속성이 변경되었을 때만 render() 메소드를 호출해서 화면을 새로 그린다.

(중요!) PureComponent 에서 state와 props의 속성이 바뀐 것을 인식하려면 참조가 바뀌어야 한다. 만약 참조가 안바뀌고 값만 바뀐다면 속성이 바뀐 것을 인식하지 못하고 render() 메소드를 호출하지도 않는다.

즉, 새로운 참조를 만들어서(복사를 해서) 그것으로 상태를 업데이트 하면 PureComponent가 속성이 바뀐 것을 인식한다. (ex. slice() 메소드로 배열을 복사해서 사용한다.)

(중요!) 리액트가 값을 비교하는 (깊은 비교)를 하지 않고, 참조를 비교하는 (얕은 비교)를 하는 이유는 깊은 비교의 연산의 비용이 굉장히 많이 들기 때문이다. PureComponent는 연산을 빨리 해서 빨리 그리기 위한 목적이기 때문에 얕은 비교를 하는 것이다.

(매우 중요!!) 불변성 (Immutability)

내용이 변경되었을 때, 참조 역시 변경되는 성질을 말한다.

(장점!) 내용이 바뀌었는지를 알기 위해 깊은 비교를 할 필요 없이 참조만 비교하면 된다.

(단점..) 매번 새로운 배열 or 객체를 만들어야 하기 때문에 메모리를 많이 차지하고 속도가 느려질 수 있다. 아주 빠른 속도로 값이 변경되는 경우에는 매번 새로운 배열이나 객체를 만든다면 성능이 느려지게 된다.

불변이 아닌 값(가변인 값)이 변경되었는지를 확인하는 작업은 깊은 비교를 해야 하기 때문에 연산 비용이 많이 든다.

리액트에서 PureComponent를 사용하기 위해서는 연산을 빨리 하기 위해 가변인 값도 불변인 값처럼 코딩을 해서 참조를 바꿔준다.

Immutable.js —> 가변성을 갖는 배열이나 객체를 불변성을 갖도록 바꿔주는 라이브러리

Immer.js —> 내장 배열이나 내장 객체를 이용하면서도 불변성을 갖도록 바꿔주는 라이브러리

비교조정 (Reconciliation)

원본과 변경본을 비교해서 필요한 부분만 갱신하는 작업.

리액트의 비교조정 알고리즘

  1. 다른 타입을 가진 두 엘리먼트는 다른 트리를 만들어 낼 것이다.
  2. 개발자가 제공한 key prop을 이용해 여러번의 렌더링 속에서도 변경되지 말아야 할 자식 엘리먼트가 무엇인지를 알아낼 수 있을 것이다.
  • 엘리먼트 타입 또는 key가 바뀌면 더 이상 비교하지 않고 새로 그린다.

다른 타입의 엘리먼트인 경우

루트 엘리먼트의 타입이 다르면 이전 트리를 버리고 트리를 새로 구축한다.

트리를 버릴 때, 이전 DOM 노드들은 모두 파괴된다.

또한 컴포넌트 인스턴스의 componentWillMount() 훅이 실행되고, 그 다음 componentDidMount() 훅이 실행된다.

—> 이전 트리에 연결되어 있던 모든 state가 파괴된다.

리액트의 상태(state)는 화면에 그려질 때만 존재할 수 있다.

같은 타입의 DOM 엘리먼트인 경우

양쪽의 속성을 살펴본 뒤 같은 것들은 유지시키고 변경된 속성만을 갱신한다.

하나의 DOM 노드를 처리하고, 뒤이어 해당 노드의 자식들을 재귀적으로 처리한다.

같은 타입의 컴포넌트 엘리먼트인 경우

새로운 엘리먼트의 내용을 반영하기 위해 컴포넌트 인스턴스의 props를 갱신한다.

또한 그 인스턴스의 componentWillReceiveProps()componentWillUpdate() 훅을 호출한다. (props와 라이프사이클만 변경되고 엘리먼트가 날라가지 않는다.)

그 다음으로 render() 메소드를 호출하고, 이전 트리와 새로운 트리를 재귀적으로 처리한다.

키 (Key)

키를 이용해 원래 트리의 자식과 새 트리의 자식 간이 일치하는지를 결정할 수 있다.

비교 알고리즘을 효율적으로 수행하는 기준이 된다.

  • 같은 자료 내에서 키는 계속 같은 것을 사용해야 한다. (다른 키는 다른 자료로 인식한다.)

  • 키는 형제 레벨에서는 ‘유일’ 해야 한다. (다른 스코프에서는 같아도 된다.)

(중요!) 키가 바뀌면 해당 컴포넌트를 언마운트 시켰다가 다시 마운트 시킨다. (상태 초기화)

태그: ,

업데이트:

댓글남기기