React (4) Create

2020-06-25

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 안에다가 적어줄 경우, 오히려 불필요한 렌더링이 발생하여
성능 저하를 유발할 가능성이 생긴다.