8편: 배열 다루기 (2) 제거와 수정
우리는 지난 섹션에서 배열에 데이터를 추가하는 방법과 배열 내부의 내용들을 화면에 보여주는 기능을 구현해보았습니다. 이번에는, 배열 내부의 데이터를 제거하는 방법과 수정을 하는 방법을 알아보겠습니다.
일단 제거부터 시작해볼까요?
데이터 제거
기존의 배열 데이터를 건들이지 않으면서 데이터를 제거하기 위해선, 여러가지 방법이 있을 수 있습니다.
컴포넌트에서 직접 구현을 하기 전에, 먼저 예제삼아 일반 자바스크립트 배열을 가지고 연습을 해보겠습니다.
const arr = [1, 2, 3, 4, 5];
배열에서 3을 제거 할건데요, 기존의 배열은 그대로 유지하고 새 배열을 만들어서 3을 제외시키겠습니다.
첫번째 방법은 slice 와 concat 을 이용하는겁니다. 3 왼쪽의 배열과 그 우측의 배열을 서로 합쳐주는 것이죠.
array.slice(0,2).concat(array.slice(3,5)) // [1, 2, 4, 5]
배열 전개 연산자를 사용하면 다음과 같이 구현을 할 수도 있습니다.
[ ...array.slice(0,2), ...array.slice(3,5) ];
하지만 이것보다 훨씬 간단한 방법으로도 구현 할 수 있습니다. 우리는 단순히 값이 3인걸 없애는 것이죠?
배열에는 filter 라는 내장함수가 있는데, 이 함수는 특정 조건에 부합되는 원소들만 뽑아내서 새 배열을 만들어줍니다.
따라서, 3이 제외된 배열을 만들기 위해서 이러한 코드를 작성 할 수도 있지요.
array.filter(num => num !== 3); // [1, 2, 4, 5]
이렇게 하면, 3이 아닌 것들만 필터링을 해서 새 배열을 보여주겠죠?
그럼, 동일한 방식으로, 전화번호정보를 데이터에서 제외시키는 기능을 구현해보겠습니다.
id 를 파라미터로 받아오는 handleRemove 라는 함수를 만드시고, 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'
}
]
}
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)
})
}
render() {
const { information } = this.state;
return (
<div>
<PhoneForm
onCreate={this.handleCreate}
/>
<PhoneInfoList
data={information}
onRemove={this.handleRemove}
/>
</div>
);
}
}
export default App;
PhoneInfoList 에서는 props 로 전달받은 onRemove 를 그대로 전달해주겠습니다. 이 함수가 전달되지 않았을 경우를 대비하여 해당 props 를 위한 defaultProps 도 설정하세요.
// file: src/components/PhoneInfoList.js
import React, { Component } from 'react';
import PhoneInfo from './PhoneInfo';
class PhoneInfoList extends Component {
static defaultProps = {
list: [],
onRemove: () => console.warn('onRemove not defined'),
}
render() {
const { data, onRemove } = this.props;
const list = data.map(
info => (
<PhoneInfo
key={info.id}
info={info}
onRemove={onRemove}
/>)
);
return (
<div>
{list}
</div>
);
}
}
export default PhoneInfoList;
그 다음에는, PhoneInfo 쪽에서 삭제 기능을 구현해주겠습니다. 우리는 삭제 버튼을 만들어서 해당 버튼에 이벤트를 설정하겠습니다.
import React, { Component } from 'react';
class PhoneInfo extends Component {
static defaultProps = {
info: {
name: '이름',
phone: '010-0000-0000',
id: 0
},
}
handleRemove = () => {
// 삭제 버튼이 클릭되면 onRemove 에 id 넣어서 호출
const { info, onRemove } = this.props;
onRemove(info.id);
}
render() {
const style = {
border: '1px solid black',
padding: '8px',
margin: '8px'
};
const {
name, phone
} = this.props.info;
return (
<div style={style}>
<div><b>{name}</b></div>
<div>{phone}</div>
<button onClick={this.handleRemove}>삭제</button>
</div>
);
}
}
export default PhoneInfo;
삭제 버튼을 눌러보세요. 데이터가 제대로 제거 되나요?
데이터 수정
이번엔 데이터 수정을 해보겠습니다. 수정할때도 마찬가지로 불변성을 지켜줘야합니다. 기존의 배열과, 그리고 그 내부에있는 객체를 절대로 직접적으로 수정하시면 안됩니다.
예를 들어서 다음과 같은 객체로 이뤄진 배열이 있다고 가정해봅시다.
const array = [
{ id: 0, text: 'hello', tag: 'a' },
{ id: 1, text: 'world' , tag: 'b' },
{ id: 2, text: 'bye', tag: 'c' }
];
여기서 기존의 값을 건들이지 않고 id 가 1인 객체의 text 값을 'Korea' 라는 값으로 바꾼 새로운 배열을 만들어보겠습니다.
const modifiedArray = array.map(item => item.id === 1
? ({ ...item,. text: 'Korea' }) // id 가 일치하면 새 객체를 만들고, 기존의 내용을 집어넣고 원하는 값 덮어쓰기
: item // 바꿀 필요 없는것들은 그냥 기존 값 사용
그러면 한번, 같은 원리를 사용하여 우리의 전화번호 정보를 수정해보겠습니다.
우리는 handleUpdate 라는 함수를 만들건데요, 이 함수는 id 와 data 라는 파라미터를 받아와서 필요한 정보를 업데이트 해줍니다. 이 handleUpdate 는 PhoneInfoList 의 onUpdate 로 전달해주세요.
// 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'
}
]
}
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 } // 새 객체를 만들어서 기존의 값과 전달받은 data 을 덮어씀
: info // 기존의 값을 그대로 유지
)
})
}
render() {
const { information } = this.state;
return (
<div>
<PhoneForm
onCreate={this.handleCreate}
/>
<PhoneInfoList
data={information}
onRemove={this.handleRemove}
onUpdate={this.handleUpdate}
/>
</div>
);
}
}
export default App;
그럼 이제 PhoneInfoList 컴포넌트를 업데이트 해줘야 하겠죠?
// file: 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'),
}
render() {
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;
그리고, 데이터를 컴포넌트로 렌더링하는 과정에서 PhoneInfo 에 onUpdate 를 그대로 전달해주었습니다.
그럼 이젠 PhoneInfo 컴포넌트를 업데이트 해줄 차례입니다. 이번엔 수정할 코드는 꽤 많은데요, 주석을 읽어가면서 코드를 작성해주세요.
import React, { Component } from 'react';
class PhoneInfo extends Component {
static defaultProps = {
info: {
name: '이름',
phone: '010-0000-0000',
id: 0
},
}
state = {
// 우리는 수정 버튼을 눌렀을 떄 editing 값을 true 로 설정해줄것입니다.
// 이 값이 true 일 때에는, 기존에 텍스트 형태로 보여주던 값들을
// input 형태로 보여주게 됩니다.
editing: false,
// input 의 값은 유동적이겠지요? input 값을 담기 위해서 각 필드를 위한 값도
// 설정합니다
name: '',
phone: '',
}
handleRemove = () => {
// 삭제 버튼이 클릭되면 onRemove 에 id 넣어서 호출
const { info, onRemove } = this.props;
onRemove(info.id);
}
// editing 값을 반전시키는 함수입니다
// true -> false, false -> true
handleToggleEdit = () => {
const { editing } = this.state;
this.setState({ editing: !editing });
}
// input 에서 onChange 이벤트가 발생 될 때
// 호출되는 함수입니다
handleChange = (e) => {
const { name, value } = e.target;
this.setState({
[name]: value
});
}
componentDidUpdate(prevProps, prevState) {
// 여기서는, editing 값이 바뀔 때 처리 할 로직이 적혀있습니다.
// 수정을 눌렀을땐, 기존의 값이 input에 나타나고,
// 수정을 적용할땐, input 의 값들을 부모한테 전달해줍니다.
const { info, onUpdate } = this.props;
if(!prevState.editing && this.state.editing) {
// editing 값이 false -> true 로 전환 될 때
// info 의 값을 state 에 넣어준다
this.setState({
name: info.name,
phone: info.phone
})
}
if (prevState.editing && !this.state.editing) {
// editing 값이 true -> false 로 전환 될 때
onUpdate(info.id, {
name: this.state.name,
phone: this.state.phone
});
}
}
render() {
const style = {
border: '1px solid black',
padding: '8px',
margin: '8px'
};
const { editing } = this.state;
if (editing) { // 수정모드
return (
<div style={style}>
<div>
<input
value={this.state.name}
name="name"
placeholder="이름"
onChange={this.handleChange}
/>
</div>
<div>
<input
value={this.state.phone}
name="phone"
placeholder="전화번호"
onChange={this.handleChange}
/>
</div>
<button onClick={this.handleToggleEdit}>적용</button>
<button onClick={this.handleRemove}>삭제</button>
</div>
);
}
// 일반모드
const {
name, phone
} = this.props.info;
return (
<div style={style}>
<div><b>{name}</b></div>
<div>{phone}</div>
<button onClick={this.handleToggleEdit}>수정</button>
<button onClick={this.handleRemove}>삭제</button>
</div>
);
}
}
export default PhoneInfo;
코드를 다 작성하셨다면, 결과물이 다음과 같이 나타나는지 확인해보세요.
수정이 잘 되나요?
정리
이제 리액트 state 에 있는 배열 안의 데이터를 삭제하는 방법과, 수정하는 방법도 배워보았습니다. 작업을 하시면서, 왜 배열과 객체 값을 직접 수정하면 안되고 불변성을 유지하면서 데이터를 새로 생성하는 방식으로 교체해줘야 하는지에 궁금하실 수도 있습니다.
그것에 대해선, 다음 섹션에서 이름으로 전화번호를 찾는 기능을 구현하면서, 불변성을 왜 유지해야 하는지 알아보겠습니다.