일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 막탄
- 사기
- 마사지
- 맛사지
- 중고나라
- js
- REACT
- Hooks
- plugin
- 스노쿨링
- 특수문자
- 정직하게사세요
- 세부
- Webpack
- webpack.config.js
- 스쿠버다이빙
- autocomplate
- 중고나라사기
- 유효성검사
- 자동완성
- 구분
- 정규식
- 여행
- 삼성무선청소기제트
- 중고거래사기
- ES6
- JavaScript
- 해외여행
- 네이버페이사기
- Today
- Total
Ryu.log
[ React-Tutorial-09 ] 불변성을 지키는 이유와 업데이트 최적화 본문
지난 섹션에서 배열을 어떻게 다뤄야 하는지에 대해서 알아보았다. 데이터를 업데이트하는 과정에서 불변성을 지켜야한다는 것을 강조 하였는데,
왜 그래야 하는지 알아보자.
01. 데이터 필터링 구현하기
// file: src/App.js import React, { Component } from 'react'; import PhoneForm from './components/PhoneForm'; import PhoneInfoList from './components/PhoneInfoList'; class App extends Component { id = 2 state = { information: [ { id: 0, name: '유준호', phone: '010-0000-0000' }, { id: 1, name: '한승훈', phone: '010-0000-0001' } ], keyword: '' } handleChange = (e) => { this.setState({ keyword: e.target.value, }); } handleCreate = (data) => { const { information } = this.state; this.setState({ information: information.concat({ id: this.id++, ...data }) }) } handleRemove = (id) => { const { information } = this.state; this.setState({ information: information.filter(info => info.id !== id) }) } handleUpdate = (id, data) => { const { information } = this.state; this.setState({ information: information.map( info => id === info.id ? {...info, ...data} : info ) }) } render() { const { information, keyword } = this.state; return ( <div> <PhoneForm onCreate={this.handleCreate} /> <p> <input placeholder="검색 할 이름을 입력하세요.." onChange={this.handleChange} value={keyword} /> </p> <hr /> <PhoneInfoList data={information} onRemove={this.handleRemove} onUpdate={this.handleUpdate} /> </div> ); } } export default App;
검색어를 입력했을 때 필터링을 하는 것은 나중에 구현하도록 하자. 지금의 상황에선, input에 입력을 했을 때 업데이트가 필요한것은 오직 input 뿐이다.
하지만 App 컴포넌트의 상태가 업데이트 되면, 컴포넌트의 리렌더링이 발생하게 되고, 컴포넌트가 리렌더링이 되면 그 컴포넌트의 자식 컴포넌트도 리렌더링이 된다.
확인을 한번해보자 PhoneInfoList 컴포넌트에서 render 함수의 상단에 아래코드를 넣어보자.
// src/components/PhoneInfoList.js ... render() { console.log('render PhoneInfoList'); const { data, onRemove, onUpdate } = this.props; const list = data.map( info => ( <PhoneInfo key={info.id} info={info} onRemove={onRemove} onUpdate={onUpdate} /> ) ); return ( <div> {list} </div> ); } ...
이렇게 하고 검색어 input을 수정한 다음에 콘솔을 확인해보자.
App이 리렌더링됨에 따라 PhoneInfoList도 리렌더링이 되고 있다. 물론, 실제로 변화가 일어나진 않으니 지금은 Virtual DOM 에만 리렌더링한다.
지금의 상황에는 별로 큰 문제가 되지 않는데, 리스트 내부의 아이템이 몇백개, 몇천개가 된다면 이렇게 Virtual DOM에 렌더링 하는 자원은 아낄 수 있다면 아끼는게 좋다.
이러한 낭비되는 자원을 아끼기위해선 우리가 이전에 배웠던 shouldComponentUpdate LifeCycle API를 사용하면 된다.
자, PhoneInfoList 에서 shouldComponentUpdate를 구현해보자
그냥 단순히 다음 받아올 data가 현재 data랑 다른 배열일 때 true로 설정하게 하면 된다.
// src/components/PhoneInfoList.js import React, { Component } from 'react'; import PhoneInfo from './PhoneInfo'; class PhoneInfoList extends Component { static defaultProps = { data: [], onRemove: () => console.warn('onRemove not defined'), onUpdate: () => console.warn('onUpdate not defined') } shouldComponentUpdate(nextProps, nextState) { return nextProps.data !== this.props.data; } render() { console.log('render PhoneInfoList'); const { data, onRemove, onUpdate } = this.props; const list = data.map( info => ( <PhoneInfo key={info.id} info={info} onRemove={onRemove} onUpdate={onUpdate} /> ) ); return ( <div> {list} </div> ); } } export default PhoneInfoList;
이제 변화가 필요하지 않을 때는 render 함수가 호출되지 않게 된다.
우리는 shouldComponentUpdate 로직을 굉장히 간단하게 작성해주었는데 어떻게 이런게 가능한 것일까?
02. 불변성에 대해 알아보자
const array = [1,2,3,4]; const sameArray = array; sameArray.push(5); console.log(array !== sameArray); // false
우리가 sameArray = array를 했다고 해서 기존에 있던 배열이 복사되는 것이 아니라 똑같은 배열을 가르키고 있는 레퍼런스 하나가 만들어진 것이기 때문에,
우리가 sameArray 에 push를 하게 된다고 해서 array와 sameArray가 달라지지 않는다.
하지만, 우리가 불변성을 유지하면
const array = [1,2,3,4]; const differentArray = [...array, 5]; // 혹은 = array.concat(5) console.log(array === differentArray); // true
위 코드와 같이 바로바로 비교가 가능하다는 것이다.
이는 객체를 다룰때도 마찬가지이다.
// NO const object = { foo: 'hello', bar: 'world' }; const sameObject = object; sameObject.baz = 'bye'; console.log(sameObject !== object); // false
// YES const object = { foo: 'hello', bar: 'world' }; const differentObject = { ...object, baz: 'bye' }; console.log(differentObject !== object); // true
03. 기능 마저 구현하기
구현하던 기능을 마저 끝내보겠다.
App컴포넌트에서 keyword 값에 따라서 information 배열을 필터링 해주는 로직을 작성하고, 필터링된 결과를 PhoneInfoList에 전달해 줘보자.
// file: src/App.js import React, { Component } from 'react'; import PhoneForm from './components/PhoneForm'; import PhoneInfoList from './components/PhoneInfoList'; class App extends Component { id = 2 state = { information: [ { id: 0, name: '유준호', phone: '010-0000-0000' }, { id: 1, name: '한승훈', phone: '010-0000-0001' } ], keyword: '' } handleChange = (e) => { this.setState({ keyword: e.target.value, }); } handleCreate = (data) => { const { information } = this.state; this.setState({ information: information.concat({ id: this.id++, ...data }) }) } handleRemove = (id) =>> { const { information } = this.state; this.setState({ information: information.filter(info => info.id !== id) }) } handleUpdate = (id, data) => { const { information } = this.state; this.setState({ information: information.map( info => id === info.id ? {...info, ...data} : info ) }) } render() { const { information, keyword } = this.state; const filteredList = information.filter( info => info.name.indexOf(keyword) !== -1 ); return ( <div> <PhoneForm onCreate={this.handleCreate} /> <p> <input placeholder="검색 할 이름을 입력하세요.." onChange={this.handleChange} value={keyword} /> </p> <hr />> <PhoneInfoList data={filteredList} onRemove={this.handleRemove} onUpdate={this.handleUpdate} /> </div> ); } } export default App;
필터링이 잘되었는지 확인해보자!, 참고로 지금 상황에서는 키워드 값에 따라 PhoneInfoList가 전달받는 data가 다르므로, 키워드 값이 바뀌면
shouldComponentUpdate 도 true를 반환하게 된다.
04. 계속해서 최적화
render() { console.log('render PhoneInfo ' + this.props.info.id);
그 다음에, 새 데이터를 등록하고나서 개발자 콘솔을 확인하면
콘솔을 보면 처음 렌더링이 되었을 때 0과 1이 렌더링이 되었다. 그 다음에, 새 ㅔ이터가 나타났을 때 사실상 맨마지막 데이터만 새로 렌더링해주면 되는데,
그 위에있는 컴포넌트도 렌더링 되엇다. 이것도 아까 다뤘던것과 마찬가지로 실제로 바뀌지 않는 컴포넌트들은 DOM 변화가 일어나지는 않겠지만,
Virtual DOM에 그리는 자원도 아껴주기 위해서 우리는 shouldComponentUpdate를 통하여 최적화 해줄 수 있다.
// file: src/components/PhoneInfo.js shouldComponentUpdate(nextProps, nextState) { // 수정 상태가 아니고, info 값이 같다면 리렌더링 안함 if (!this.state.editing && !nextState.editing && nextProps.info === this.props.info) { return false; } // 나머지 경우엔 리렌더링함 return true; } ...
낭비 렌더링이 사라졌다!
이 글은 Velopert님의 블로그에서 React 포스팅을보며 실습하며 공부한 자료입니다.
'Prev-content' 카테고리의 다른 글
[ Riot Games API 활용 전적 검색 App 02] - Riot Games API Key 신청 (5) | 2018.07.05 |
---|---|
[ Riot Games API 활용 전적 검색 App 01] - Project Info (0) | 2018.07.05 |
[ React-Tutorial-08 ] 배열다루기2 제거와 수정 (0) | 2018.06.05 |
[ React-Tutorial-07 ] 배열다루기1 생성과 랜더링 (0) | 2018.05.30 |
[ React-Tutorial-06 ] input 상태관리 (0) | 2018.05.30 |