React (4) Create
props는 read-only이다.
props를 전달받은 하위 component 내부에서
전달받은 props의 값을 바꿀 수는 없다. 에러가 발생한다.
props를 전달하는 상위 component에서
전달하는 props의 값을 바꾸는 거는 당연히 문제 없음.
props & state
props와 state 모두 render 함수 호출을 유발하기 때문에
이 값들을 수정해주는 것을 통해 UI를 변경할 수 있다.
props & event
상위 component가 하위 component로 값을 전달할 때는 props를 통해 전달
하위 component가 상위 component의 값을 바꾸고 싶을 때는 event를 통해 변경
(React에서 event는 ‘상위 component의 state를 변경하는 방법’이라고 생각하는 게 편함)
CRUD
CRUD의 구현
지금부터 CRUD를 구현할 예정.
이를 위해서 Control이라는 component를 만들어준다.
class Control extends Component {
render() {
return(
<ul>
<li><a href="/create">create</a></li>
<li><a href="/update">update</a></li>
<li><input type="button" value="delete"></input></li>
</ul>
);
}
}
Delete 구현 시 주의점
create나 update는 링크를 걸어놓고, 그 링크를 클릭하면
create나 update 페이지로 이동하게 해서 구현하는 것이 가능.
하지만 delete의 경우 링크로 구현하면
해당 링크에 의도치 않은 접속으로 인해 의도치 않은 삭제가 일어날 수 있다.
그래서 delete는 주로 링크 말고 버튼으로 구현하는 편.
Create 구현
mode 변경
링크 혹은 버튼을 클릭하면 state의 mode가
create, update, delete 셋 중 하나의 값을 가지도록 할 것.
그러면 Control component에 있는 세 개의 링크(버튼) 중 하나를 클릭했을 때
App component의 state에 변화가 일어나야 한다.
→ 상위 component로 값을 전달해야 한다
→ event를 이용한다
→ Control component에 onChangeMode라는 이벤트를 정의해주자.
(이벤트 핸들러를 설치한다고도 한다.
이벤트 핸들러는 이벤트가 발생할 때 실행되는 함수를 의미한다.)
링크(버튼)을 클릭했을 때 저 onChangeMode라는 핸들러가 실행되어야 함.
<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 type="button" value="delete" onClick={function(e) {
e.preventDefault();
this.props.onChangeMode('delete');
}.bind(this)}></input></li>
</ul>
호출부를 통해 구상을 했으니, 이제 onChangeMode를 정의해줄 차례.
create, update 등의 인자를 전달했으므로, 그 인자들을 받을 수 있게 구현해주면 된다.
<Control onChangeMode={function(_mode) {
this.setState({ mode: _mode });
}.bind(this)}></Control>
state.mode 값을 하위 component에서 날라온 _mode로 바꿔줌.
mode에 따라 Content 바꾸기
바뀐 mode에 따라 Content가 바뀌도록 해보자.
기존의 Content component의 이름을 ReadContent로 바꾸고
mode가 바뀔 때마다 CreateContent, UpdateContent 등으로 바뀌도록 하자.
이를 위해, 기존에 <ReadContent></ReadContent>
가 있던 부분을
변수로 처리하고(여기선 _article), return문 바깥에서 if문으로
그 변수에 원하는 component를 집어 넣어주면 된다.
mode가 welcome이나 read이면 _article이 ReadContent이고 mode가 create인 경우에는 _article이 CreateContent가 되도록.
class App extends Component {
constructor(props) {
// 대충 constructor 내용
}
render() {
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") {
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;
}
i = i + 1;
}
_article = <ReadContent title={_title} desc={_desc}></ReadContent>;
} else if (this.state.mode === "create") {
_article = <CreateContent></CreateContent>;
}
return (
<div>
// 대충 DOM
{_article}
</div>
);
}
}
Create form 코드
Create는 form태그로 구현한다.
class CreateContent extends Component {
render() {
return(
<article>
<h2>Create</h2>
<form action="/create_process" method="post" onSubmit={function(e) {
e.preventDefault();
}.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>
);
}
}
Create 구현
form에서 onSubmit 이벤트가 일어났을 때 App component의 state.contents에
form에 입력했던 데이터가 추가되도록 만들어보자.
이 역시 상위 component의 state를 변경하는 일이므로
CreateContent에 이벤트 핸들러를 설치하고, submit이 일어났을 때 실행시켜주면 된다.
form의 value들을 가져오는 방법
form에서 onSubmit 이벤트가 발생했을 때, 그 form에 입력된 value 값들을 가져오려면?
submit 이벤트 객체의 target은 해당 form 자체를 가리킨다.
그 form이 가진 속성들 중에서 input의 name을 이름으로 가지는 속성이 있음.
걔의 속성을 펼쳐서 또 찾아보면 value라는 속성이 나옴.
여기서는, 첫 번째 input의 name이 title이므로
e.target.title.value
라는 코드를 통해 해당 input의 value에 접근 가능.
textarea의 경우 name이 desc이므로 동일한 방법으로
e.target.desc.value
로 해당 value에 접근할 수 있다.
이 값들을 인자로 전달해서 App의 state.contents 마지막에다 추가하면 됨.
{/* App component의 render 내부 */}
else if (this.state.mode === "create") {
_article = <CreateContent onSubmitting={function(_title, _desc) {
this.max_content_id += 1;
var _contents = this.state.contents.concat({id:this.state.selected_content_id, title: _title, desc: _desc});
this.setState({contents : _contents});
}.bind(this)}></CreateContent>;
}
{/* CreateContent 내부의 form */}
<form action="/create_process" method="post" onSubmit={function(e) {
e.preventDefault();
this.props.onSubmitting(e.target.title.value, e.target.desc.value);
}.bind(this)}>
array.concat
array에다가 값을 추가하는 방법은 두 가지가 있다.
하나는 push, 하나는 concat.
push는 원본 배열을 변경하는 작업임.
arr = [1, 2];
arr.push(3);
console.log(arr); // [1, 2, 3]
이렇게, 원본 배열인 arr 자체가 변하는 연산임.
반면, concat은 원본 배열에다가 원하는 값을 추가한 새로운 배열을 만드는 연산.
arr = [1, 2];
arr2 = arr.concat(3);
console.log(arr, arr2); // [1, 2] [1, 2, 3]
state 안에 배열이 있거나 할 경우
거기에다가 값을 추가하는 가장 좋은 방법은 concat임.
push는 원본의 값을 변경하기 때문.
state 원본의 값을 변경하면 안되는 이유는 다음 포스팅에서.
state 바깥에 변수 선언
위에 나오는 this.max_content_id의 경우 component 내부에서 사용하는 정보이지만
UI와의 관련은 1도 없는 데이터이다.
이런 애들은 굳이 state에다가 넣어줄 필요가 없음.
constructor(props) {
super(props) {
this.max_content_id = 3;
this.state = {
...
}
}
}
만약 얘를 state 안에다가 적어줄 경우, 오히려 불필요한 렌더링이 발생하여
성능 저하를 유발할 가능성이 생긴다.