create
, update
, delete
라는 이름의 리스트를 만들어서 클릭하면 App.js
의 state
의 mode
값을 변경하여 각 기능을 실행할 수 있도록 구현한다. 컴포넌트 하나를 새로 생성하여 이벤트 동작을 통해 mode
를 변경할 수 있도록 한다.
import { Component } from 'react';
class Control extends Component {
render() {
console.log('Subject render')
return (
<ul>
<li><a href="/create" onClick={function(e) {
e.preventDefault();
this.props.onChangeMode('create');
}.bind(this)}>create</a></li>
<li><a href="/update" onClick={function(e) {
e.preventDefault();
this.props.onChangeMode('update');
}.bind(this)}>update</a></li>
<li><input onClick={function(e) {
e.preventDefault();
this.props.onChangeMode('delete');
}.bind(this)} type="button" value="delete"></input></li>
</ul>
)
}
}
export default Control;
<Control onChangeMode={function(_mode) {
this.setState({
mode: _mode
})
}.bind(this)}>
</Control>
리스트를 클릭할 때 이벤트 동작으로 바뀐 mode
마다 다른 컨텐츠 내용이 표시될 수 있도록 한다.
render() {
console.log('App render');
// 컨텐츠 영역에 표시할 _article도 정의해준다.
var _title, _desc, _article = null;
if(this.state.mode === 'welcome') {
_title = this.state.welcome.title;
_desc = this.state.welcome.desc;
_article = <ReadContent title={_title} desc={_desc}></ReadContent>
} else if(this.state.mode === 'read') {
// while을 이용한 id값 통제
var i = 0;
while(i < this.state.contents.length) {
var data = this.state.contents[i]
if(data.id === this.state.selected_content_id) {
_title = data.title;
_desc = data.desc;
break;
}
i = i + 1;
}
_article = <ReadContent title={_title} desc={_desc}></ReadContent>
} else if(this.state.mode === 'create') {
_article = <CreateContent></CreateContent>
}
return (
<div className="App">
<Subject
title={this.state.subject.title}
sub={this.state.subject.sub}
onChangePage={function() {
this.setState({
mode: 'welcome'
})
}.bind(this)}>
</Subject>
<TOC
onChangePage={function(id) {
this.setState({
// 기본모드는 read로 해두고, string형식인 id값을 숫자형식으로 변경해준다.
mode: 'read',
selected_content_id: Number(id)
})
}.bind(this)} data={this.state.contents}></TOC>
<Control onChangeMode={function(_mode) {
this.setState({
mode: _mode
})
}.bind(this)}></Control>
{/* 컨텐츠 영역에 표시할 내용을 변수로 지정 */}
{_article}
</div>
);
}
CreateContent
컴포넌트를 생성해서 제목과 내용을 입력할 수 있는 Form을 만든다.
Form
은 기본적인 생성방식과 똑같이 생성onSubmit
이벤트를 사용해서 양식을 제출했을 때 페이지가 새로고침되지 않도록 preventDefault()
함수를 적용하고 props
값으로 제목과 내용을 전달할 수 있도록 한다.import { Component } from 'react';
class CreateContent extends Component {
render() {
return (
<article>
<h2>Create</h2>
<form action="/create_process" method="post"
onSubmit={function(e) {
e.preventDefault();
this.props.onSubmit(
e.target.title.value,
e.target.desc.value
);
alert('Submit!')
}.bind(this)}
>
<p>
<input type="text" name="title" placeholder="title"></input></p>
<p>
<textarea name="desc" placeholder="description"></textarea>
</p>
<p>
<input type="submit"></input>
</p>
</form>
</article>
);
}
}
export default CreateContent;
배열을 이용하기 위한 준비: contents
배열의 마지막 id
값을 변수로 지정한다.
constructor(props) {
super(props);
// contents의 마지막 id값을 변수로 지정
this.max_content_id = 3;
this.state = {
mode: 'create',
// 기본으로 지정하는 id값
selected_content_id: 1,
welcome: {title:'Welcome', desc:'Hello, React!'},
subject:{title:'WEB', sub:'World Wide Web'},
contents:[
{id:1, title:'HTML', desc:'HTML is HyperText Markup Language'},
{id:2, title:'CSS', desc:'CSS is for design'},
{id:3, title:'JavaScript', desc:'JavaScript is for interactive'}
]
}
}
원본 배열을 직접 변경하는 경우
❗
push
를 이용해서 원본 배열을 직접 변경하는 경우에는 react의 성능 개선(유지보수)에 좋지 않다.
else if(this.state.mode === 'create') {
_article = <CreateContent onSubmit={function(_title, _desc) {
// add content to this.state.contents
this.max_content_id = this.max_content_id + 1;;
this.state.contents.push(
{ id: this.max_content_id, title: _title, desc: _desc });
this.setState({
contents: this.state.contentss
})
console.log(_title, _desc);
}.bind(this)}></CreateContent>
}
concat()
을 이용하는 경우
원본 배열을 변경하지 않으므로 권장하는 방법
else if(this.state.mode === 'create') {
_article = <CreateContent onSubmit={function(_title, _desc) {
// add content to this.state.contents
this.max_content_id = this.max_content_id + 1;;
// push()를 사용한 원본 배열 변경은 권장하지 않음.
// this.state.contents.push(
// { id: this.max_content_id, title: _title, desc: _desc });
var _contents = this.state.contents.concat(
{ id: this.max_content_id, title: _title, desc: _desc }
)
this.setState({
contents: _contents
})
console.log(_title, _desc);
}.bind(this)}></CreateContent>
}
push()
: 원본 배열에 데이터값을 추가
var arr = [1,2]
arr.push(3)
arr
> (3) [1, 2, 3]
concat()
: 원본 배열에 데이터값 추가하지 않고, 변수를 사용하여 새로운 변수에 변경된 데이터값을 저장할 수 있음
var arr2 = [1,2]
arr2.concat(3)
arr2
> (2) [1, 2]
var result = arr2.concat(3)
result
> (3) [1, 2, 3]
불필요하게 화면이 렌더링되어 성능 저하를 일으키지 않게 하기 위해 사용하는 함수
리액트에서는 부모 컴포넌트가 렌더링될 때마다 자식 컴포넌트도 렌더링된다.
예) 각 리스트를 클릭할 때마다 TOC
컴포넌트가 불필요하게 렌더링되는 경우
특징
newProps
와 newState
라는 2개의 매개변수를 가진다.shouldComponentUpdate(newProps, newState) {
console.log('=====>TOC render shouldComponentUpdate'
,newProps.data
,this.props.data);
return false;
}
=====>TOC render shouldComponentUpdate
// 새로운 props값 출력
(4) [{…}, {…}, {…}, {…}]
0: {id: 1, title: "HTML", desc: "HTML is HyperText Markup Language"}
1: {id: 2, title: "CSS", desc: "CSS is for design"}
2: {id: 3, title: "JavaScript", desc: "JavaScript is for interactive"}
3: {id: 4, title: "React", desc: "React is for UI"}
length: 4
__proto__: Array(0)
// 기존의 props값 출력
(3) [{…}, {…}, {…}]
0: {id: 1, title: "HTML", desc: "HTML is HyperText Markup Language"}
1: {id: 2, title: "CSS", desc: "CSS is for design"}
2: {id: 3, title: "JavaScript", desc: "JavaScript is for interactive"}
length: 3
__proto__: Array(0)
return
값이 true
일 때 render()
함수를 호출하고, false
일 때는 render()
함수를 호출하지 않는다.
shouldComponentUpdate(newProps, newState) {
console.log('=====>TOC render shouldComponentUpdate'
,newProps.data
,this.props.data
);
if(this.props.data === newProps.data) {
return false;
}
return true;
}
// props값이 바뀌지 않았을 때
App render
TOC.js:5 =====>TOC render shouldComponentUpdate (3) [{…}, {…}, {…}] undefined
Control.js:5 Subject render
App.js:29 App render
TOC.js:5 =====>TOC render shouldComponentUpdate (3) [{…}, {…}, {…}] undefined
Control.js:5 Subject render
App.js:29 App render
TOC.js:5 =====>TOC render shouldComponentUpdate (3) [{…}, {…}, {…}] undefined
Control.js:5 Subject render
App.js:29 App render
TOC.js:5 =====>TOC render shouldComponentUpdate (3) [{…}, {…}, {…}] undefined
Control.js:5 Subject render
// props값이 바뀌었을 때
App.js:62 React React is for UI
App.js:29 App render
// render()함수 호출
=====>TOC render shouldComponentUpdate (4) [{…}, {…}, {…}, {…}] (3) [{…}, {…}, {…}]
TOC.js:15 =====>TOC render
Control.js:5 Subject render
📌 이때 push()를 사용하면 원본인
this.props.data
가 바뀌기 때문에shouldComponentUpdate()
함수의 if문이 작동해서 데이터가 바뀌었음에도TOC
컴포넌트는 새로 렌더링이 되지 않는 문제가 발생한다. 따라서 원본 데이터를 바꾸지 않는concat()
함수를 사용하는 것을 권장한다.
리액트의 컴포넌트는 불변성을 가져야 한다.
📌 리액트의 불변성을 위한 자바스크립트 라이브러리인 immutable.js 사용에 대해 알아보기