//src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './containers/App';
ReactDOM.render(<App />, document.getElementById('root'));
3. Counter 컴포넌트 생성
//src/components/Counter.js
//presentational component
// view를 담당, 리덕스 스토어에 직접 접근할 권한은 없으며 오직 props로만 데이터를 가져올 수 있음, 주로 함수형 컴포넌트로 작성
import React from 'react';
import PropTypes from 'prop-types';
import './Counter.css';
const Counter = ({ number, color, onIncrement, onDecrement, onSetColor }) => {
return (
<div
className="counter"
onClick={onIncrement}
onContextMenu={(e) => { //마우스 오른쪽 버튼을 눌렀을 때 메뉴가 열리는 이벤트
e.preventDefault(); //메뉴가 열리는 것을 방지
onDecrement(); // => 마우스 오른쪽 클릭 : 감소
}}
onDoubleClick={onSetColor}
style={{ backgroundColor: color }}
>
{number}
</div>
);
};
//{ number, color, onIncrement, onDecrement, onSetColor
Counter.propTypes = {
number: PropTypes.number,
color: PropTypes.string,
onIncrement: PropTypes.func,
onDecrement: PropTypes.func,
onSetColor: PropTypes.func
};
Counter.defaultProps = {
number: 0,
color: 'black',
onIncrement: () => console.warn('onIncrement function is not defined'),
onDecrement: () => console.warn('onDecrement function is not defined'),
onSetColor: () => console.warn('onSetColor function is not defined')
}
export default Counter;
개발 서버를 실행하고 크롬 개발자 도구Performance탭에서 동그란 record 버튼을 누르고
새로고침을 한뒤stop을해보자 Timmings 부분을 확인하면 아래처럼 처리한 작업들의 기록이 시각화되어 한눈에 확인 할 수 있다.
리액트 개발자 도구 Highligh Updates
크롬 react 개발자 도구 확장 프로그램에서Highlight Updates를 on 시키면 리랜더링이 될때마다 시각적으로 확인할 수 있다.
문제점 찾기
인풋에 타이핑을 해보자 input만 변경되는데 모든 컴포넌트들이 리랜더링 되고 있다. 여기서 리랜더링은 실제 브라우져의 DOM의 랜더링이 아니라 Virtual DOM의 리랜더링을 의미한다. 이러한 Virtual DOM의 불필요한 리랜더링을shouldComponentUpdate로 방지하여야 한다.
최적화 하기
shouldComponentUpdate: false가 return 되면 랜더링 하지 않는다. 이름을 잘보자! should component update?
TodoList 컴포넌트 최적화
문제점 찾기에서 input폼의 값을 변경했는데 불필요하게 TodoList가 리랜더링 되었다.
TodoList 컴포넌트가 뭔지 잘 생각해보고 언제 TodoList가 다시랜더링 되어야 할까?
TodoList는 todos가 변경 되었을때 업데이트 되어야한다.
아래와 같이shouldComponentUpdate메소드에서 todos를 비교하여 다를경우만True를 리턴하여 리랜더링 될수 있게 코드를 추가한다.
더미를 5000개 두고 테스트했을때shouldComponentUpdate로 최적화를 시키지 않았을때는 인풋에 값을 변경하면 리랜더링 하는데 시간이 오래걸려 연속적인 타이핑이 힘들고 버벅거렸는데 최적화 코드를 적용한 후에는 다음과같이 inputTodo 컴포넌트만 0.17ms만에 업데이트 된것을 확인할 수 있다.
TodoItem 컴포넌트 최적화
이번에는 TodoItem Toggle 쪽을 살펴보자 토글(done)값이 바뀌기 때문에 TodoList가 리랜더링 되는것은 맞지만 하나의 아이템만 Toggle했는데 모든 TodoItem들이 리랜더링 되고 있다.
아래와 같이 done의 값이 다른 아이템만 true값을 반환하여 리랜더링 되도록 설정한다.
//src/components/TodoItem/TodoItem.js
import React, {Component} from 'react';
import styles from './TodoItem.scss';
import classNames from 'classnames/bind';
const cx = classNames.bind(styles);
class TodoItem extends Component {
shouldComponentUpdate(nextProps, nextState, nextContext) {
return this.props.done !== nextProps.done;
}
render() {...}
}
export default TodoItem;
토글을 하여 정상적으로 하나의 아이템만 리랜더링 되는지 확인해본다.
정리
리액트는 부모 컴포넌트가 리렌더링 되면 자식 컴포넌트도 리랜더링 된다.
shouldComponentUpdate를 구현해야되는 경우
리스트로 컴포넌트 배열이 랜더링 되는 경우
아이템 컴포넌트가 리스트 컴포넌트 내부에 있는 경우
자식 컴포넌트가 많고, 리랜더링이 불필요한 상황에서 리랜더링이 될때
리스트를 랜더링 할때는 항상shouldComponentUpdate를 구현하고 나머지의 경우 성능 최적화가 필요하다고 판단될 경우 상황에 따라 구현한다.
데이터 수정과 마찬가지로 id로 해당 인덱스를 찾고 해당 인덱스의 객체를 제거한 새로운 객체배열을 생성하여 state값을 업데이트한다.
개발 서버를 실행하여[지우기]버튼을 눌르면 예상한것처럼 데이터가 삭제되는것이 아니라 토글이 작동하게 된다.
이와같이 부모요소와 자식요소에 onClick 이벤트가 함께 설정되어 있으면 자식 → 부모 순으로 메서드를 실행한다. 즉, 토글의 onClick 이벤트와 [지우기]의 onClick 이벤트가 겹쳐서 우리가 예상한 동작을 하지 않는것이다. 이것을propagation이라고 한다. 이를 방지하려면 자식 요소의 onClick 처리 함수 내부에서 e.stopPropagation 함수를 호출해주어야 한다.
컴포넌트의 관리를 위해 /src 아래에 /src/components 디렉토리를 생성후 App.js 파일을 생성
//src/components/App.js
import React, { Component } from 'react';
class App extends Component {
render() {
return (
<div> To-do list </div>
);
}
}
export default App;
5.index.js 설정.
//src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './styles/main.scss';
import App from './components/App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root')); serviceWorker.unregister();