React Basic - (7) 데이터 필터링
2020. 3.21
React Basic - (7) 데이터 필터링
데이터 필터링 구현하기
- App.js에 input을 할당하고, input값에 따라 데이터를 필터링하는 예제
App.js
import React, {Component} from 'react';
import PhoneForm from './PhoneForm';
import PhoneInfoList from './PhoneInfoList';
class App extends Component {
id = 2
state = {
// state에는 렌더링되는 값만 넣어줌
information : [
{
id : 0,
name : 'paige',
phone : '010-0000-0000'
},
{
id : 1,
name : 'tiger',
phone : '010-1111-1111'
}
],
keyword : ''
}
handleCreate = (data) => {
const {information} = this.state;
this.setState({
information : information.concat({
id:this.id++, ...data
})
})
console.log(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
)
})
}
// !! 이 부분이 추가됨
handleChange = (e) => {
this.setState({
keyword: e.target.value,
});
}
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 배열을 할당함
data={information}
onRemove={this.handleRemove}
onUpdate={this.handleUpdate}/>
</div>
);
}
}
export default App;onChange 이벤트로 인한 자식 컴포넌트의 불필요한 rendering 이슈
- App 컴포넌트에서 input의 onChange 파라미터에 handleChange(e) 함수를 할당하였다.
- handleChange() 함수는 이벤트가 변경될 때마다 호출되는 함수이기 때문에, input 이벤트가 변경되어 App 컴포넌트의 상태가 업데이트 되면, 컴포넌트의 리렌더링이 발생하게 되고 자식 컴포넌트들도 리렌더링 된다.
- 하지만 필터링될 data가 변경되지 않는다면 자식 컴포넌트들이 리렌더링될 필요가 없기 때문에, 일반적인 방법으로 구현했을 때 렌더링 횟수가 늘어나므로 자원을 아껴야 할 필요가 있다.
-
따라서 LifeCycle API 중, shouldComponentUpdate() 함수를 선언하여 현재 props.data와 nextProps.data가 다를 때만 true로 설정하면, 변화가 필요하지 않을 때에는 render()함수가 호출되지 않는다.
⇒ shouldComponentUpdate()를 이용하면, 변화가 필요하지 않을 때는 render 함수가 호출되지 않는다.
데이터의 불변성을 유지해야 하는 이유?
const array = [1,2,3,4];
const sameArray = array;
sameArray.push(5);
console.log(array !== sameArray); // false- sameArray = array를 한 경우, 레퍼런스가 복사되기 때문에 새로운 배열의 값을 변경하면 원래 배열값도 변경된다. ⇒ 결국 동일한 배열이므로 비교할 수 없음
const array = [1,2,3,4];
const differentArray = [...array, 5];
// 혹은 = array.concat(5)
console.log(array !== differentArray); // true- 원래 배열의 불변성을 유지한 채 배열을 복사하면, 새로운 배열을 변경했을 때 원래 배열과 비교가 가능하다.
새 데이터를 등록할 때, PhoneInfo 컴포넌트의 모든 info의 불필요한 리렌더링 이슈
- App.js의 PhoneForm 컴포넌트에서 새로운 데이터가 추가되면, App 컴포넌트와 자식 컴포넌트들도 리렌더링된다.
- 하지만 PhoneInfo 컴포넌트가 리렌더링되면, 포함된 모든 info 데이터가 리렌더링되므로 자원의 낭비가 발생한다.
- 새로 추가된 info 데이터만 리렌더링되면 되므로, PhoneInfo에서 shouldComponentUpdate()를 작성하여 최적화한다.
전체 Code
App.js
import React, {Component} from 'react';
import PhoneForm from './PhoneForm';
import PhoneInfoList from './PhoneInfoList';
class App extends Component {
id = 2
state = {
// state에는 렌더링되는 값만 넣어줌
information : [
{
id : 0,
name : 'paige',
phone : '010-0000-0000'
},
{
id : 1,
name : 'tiger',
phone : '010-1111-1111'
}
],
keyword : ''
}
handleCreate = (data) => {
const {information} = this.state;
this.setState({
information : information.concat({
id:this.id++, ...data
})
})
console.log(data);
}
handleRemove = (id) => {
const {information} = this.state;
this.setState({
// information 배열에서 제거할 id와 같은 id만 제거하고 필터링
information : information.filter(
info=> info.id !== id)
})
}
handleUpdate = (id, data) => {
// id : 변경하고자 하는 data의 id
// data : 변경하고자 하는 새로운 값
const {information} = this.state;
this.setState({
// information 배열에서 수정할 id와 같은 id인 경우, 새로운 info 객체를 만들어서 기존의 값과 새로운 data 값을 할당,
// 다른 경우 기존 값을 그대로 유지
information : information.map(info =>
id === info.id?
{...info, ...data}
: info
)
})
}
handleChange = (e) => {
this.setState({
keyword: e.target.value,
});
}
render() {
const {information, keyword} = this.state;
// information 배열의 특정 info에서, keyword 문자열이 포함된 index가 있을 경우 남기고 없을 경우는 모두 제외한 새로운 filteredList 배열 생성
const filteredList = information.filter(
info => info.name.indexOf(keyword) !== -1
);
return (
<div>
{/* PhoneForm : input 관리 */}
<PhoneForm
onCreate={this.handleCreate}/>
<p>
<input
placeholder="검색할 이름 입력"
onChange={this.handleChange}
value={keyword}
/>
</p>
<hr />
<PhoneInfoList
// data에 filteredList 배열을 할당함
data={filteredList}
onRemove={this.handleRemove}
onUpdate={this.handleUpdate}/>
</div>
);
}
}
export default App;PhoneForm.js
import React, {Component} from 'react';
class PhoneForm extends Component {
state = {
name : '',
phone : ''
}
handleChange = (e) => {
this.setState({
[e.target.name] : e.target.value
// e.target.value : 이벤트 객체에 담겨있는 현재 text 값.
});
}
handleSubmit = (e) => {
e.preventDefault();
// 원래 이벤트가 해야 하는 작업을 방지시킴
this.props.onCreate(this.state);
this.setState({
name:'',
phone:''
})
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
placeholder="name"
value={this.state.name}
// onChange : input의 text값이 바뀔 때마다 발생하는 이벤트
onChange={this.handleChange}
name ="name"
/>
<input
placeholder="phone"
value={this.state.phone}
onChange={this.handleChange}
name="phone"
/>
<button type="submit">submit</button>
{/* <div>{this.state.name} {this.state.phone}</div> */}
</form>
);
}
}
export default PhoneForm;PhoneInfoList.js
import React, {Component} from 'react';
import PhoneInfo from './PhoneInfo';
class PhoneInfoList extends Component {
static defaultProps = {
list : [],
// onRemove 함수에 아무것도 할당되지 않았으면 경고 문구 출력
onRemove : () => console.warn('onRemove not defined'),
onUpdate : () => console.warn('onUpdate not defined'),
}
// App 컴포넌트에서 input의 onChange 파라미터에 handleChange(e) 함수를 할당하였다.
// handleChange() 함수는 이벤트가 변경될 때마다 호출되는 함수이기 때문에, input 이벤트가 변경되어 App 컴포넌트의 상태가 업데이트 되면, 컴포넌트의 리렌더링이 발생하게 되고 자식 컴포넌트들도 리렌더링 된다.
// 하지만 필터링될 data가 변경되지 않는다면 자식 컴포넌트들이 리렌더링될 필요가 없기 때문에, 일반적인 방법으로 구현했을 때 렌더링 횟수가 늘어나므로 자원을 아껴야 할 필요가 있다.
// 따라서 LifeCycle API 중, shouldComponentUpdate() 함수를 선언하여 현재 props.data와 nextProps.data가 다를 때만 true로 설정하면, 변화가 필요하지 않을 때에는 render()함수가 호출되지 않는다.
shouldComponentUpdate(nextProps, nextState) {
return nextProps.data !== this.props.data;
}
render() {
console.log('render PhoneInfoList');
// 부모 컴포넌트 (PhoneInfo)에서 data를 받아옴
const {data, onRemove, onUpdate} = this.props;
// 받아온 data를 map을 통하여 컴포넌트로 반환해 list에 저장
const list = data.map(
info => (
<PhoneInfo
key={info.id}
info={info}
onRemove={onRemove}
onUpdate={onUpdate}
/>)
);
return (
<div>
{list}
</div>
);
}
}
export default PhoneInfoList;PhoneInfo.js
import React, {Component} from 'react';
class PhoneInfo extends Component {
// defaultProps을 통하여 info의 기본값 설정 => 컴포넌트가 undefined 되는 것 방지
static defaultProps = {
info: {
name: '이름',
phone: '010-0000-0000',
id: 0
}
}
state = {
editing: false,
name:'',
phone:'',
}
handleRemove = () => {
const {info, onRemove} = this.props;
onRemove(info.id);
}
handleToggleEdit = () => {
const {editing} = this.state;
// editing 값을 반전시킴
this.setState({editing:!editing});
}
handleChange = (e) => {
// input에서 onChange 이벤트가 발생될 때 호출됨
const {name, value} = e.target;
this.setState({
[name] : value
});
}
// 업데이트 과정 : 각 data에 수정 버튼을 할당하여, edit 모드가 아닌 경우 클릭 시 state의 editing을 true로
// 변경 handleToggleEdit() : editing 값을 반전시키는 함수 handleChange() : edit
// 모드에서 input의 onChange 이벤트가 발생될 때 호출되는 함수
componentDidUpdate(prevProps, prevState) {
const {info, onUpdate} = this.props;
if(!prevState.editing && this.state.editing) {
// 일반 모드 -> 수정 모드로 변환될 때
// 선택한 data의 info를 현재 state로 설정해야 함
this.setState({
name : info.name,
phone: info.phone
});
}
if(prevState.editing && !this.state.editing) {
// 수정 모드 -> 일반 모드로 변환될 때
// 현재 state를 info에 update 해야 함
onUpdate(info.id, {
name : this.state.name,
phone : this.state.phone
});
// onUpdate() 함수를 호출하여 선택된 id의 현재 상태를 update
}
}
shouldComponentUpdate(nextProps, nextState) {
if(!this.state.editing && !nextState.editing && nextProps.info === this.props.info) {
// 현재 수정 상태가 아니고, 다음 상태도 수정 상태가 아니며, 현재 info와 다음 info값이 같다면 리렌더링 안함
return false;
}
return true;
}
render() {
console.log('render PhoneInfo'+ this.props.info.id);
const style = {
border: '1px solid black',
padding: '8px',
margin: '8px'
};
const {editing} = this.state;
if (editing) {
// 수정 모드
return (
<div style={style}>
<div>
<input placeholder="이름"
value={this.state.name}
onChange={this.handleChange}
name="name"/>
</div>
<div>
<input
placeholder="전화번호"
value={this.state.phone}
onChange={this.handleChange}
name="phone"/>
</div>
<button onClick={this.handleToggleEdit}>적용</button>
<button onClick={this.handleRemove}>삭제</button>
</div>
);
}
// 일반 모드
else {
const {name, phone} = this.props.info;
return (
<div style={style}>
<div>
<div>
<b>{name}</b>
</div>
<div>{phone}</div>
<button onClick={this.handleToggleEdit}>수정</button>
<button onClick={this.handleRemove}>삭제</button>
</div>
</div>
);
}
}
}
export default PhoneInfo;실행 결과

React 개발의 목표 및 정리
- 재사용 가능한 컴포넌트를 만든다.
- props 는 부모에게서 전달받는 값이다.
- state 는 자기 자신이 지니고 있는 데이터이다.
- props 나 state 가 바뀌면 컴포넌트는 리렌더링 한다.
- LifeCycle API 를 통해서 컴포넌트 마운트, 업데이트, 언마운트 전후로 처리 할 로직을 설정하거나 리렌더링을 막아줄 수 있다.
