본문 바로가기

학습노트/React

[React] (10) Create 기능 구현 (CRUD) - shouldComponentUpdate 설명

 

 

1. 누르면 mode가 변하는 작업

  미리 진행되어 있는 작업에 components를 추가하려고 한다. components 파일의 하부에 Control.js를 만들어서 아래 코드를 삽입한다.

import React, { Component } from 'react';

class Control extends Component {
  render() {
    console.log('Control 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;

 

 

그리고 App.js에 아래처럼 Control을 넣어준다.

<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({ 
            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>
      <Content title={_title} desc={_desc}></Content>
    </div>

 

 

코드를 적용하게 되면 아래 그림처럼 create와 update 그리고 delete가 생길 것이다. 각 버튼을 클릭하게 되면 mode가 바뀌는 것을 알 수 있다.

 

 

 


2. mode가 변하고 해당하는 state 가져오기

우선 CreateContent.js라는 파일을 components에 만든다. 그리고 원래 Contents.js는 ReadContent.js로 변경한다.

import React, { Component } from 'react';

class CreateContent extends Component {
  render() {
    console.log('contents render')
    return (
      <article>
        <h2>Create</h2>
        <form action="/create_process" method="post"
        onSubmit={function(e) {
          e.preventDefault();
          alert("create!!")
        }.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;

 

 

App.js에서 아래 코드처럼 if문에 따라 mode에 따라서 변하는 값을 수정해 주면된다.

render() {
    console.log('App render')
    let _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") {
      for(let i = 0; this.state.contents.length; i++) {
        let data = this.state.contents[i];
        if(data.id === this.state.selected_content_id) {
          _title = data.title;
          _desc = data.desc;
          break;
        }
      }
      _article = <ReadContent title={_title} desc={_desc}></ReadContent>
    } else if(this.state.mode === "create") {
      _article = <CreateContent></CreateContent>
    }

 

이렇게 되면 create를 누르고 submit 버튼을 눌렀을 때 alert 창이 뜰 것이다.

 


 

3. form태그에 입력한 값들을 state.contents 배열에 추가하기

state.contents에 배열을 추가하려면 App.js에서 onSubmit이라는 함수를 만들어줘서 사용하면 된다.

App.js

import React, { Component } from 'react';
import TOC from "./components/TOC"
import ReadContent from "./components/ReadContent"
import CreateContent from './components/CreateContent';
import Subject from "./components/Subject"
import Control from "./components/Control"
import './App.css';

class App extends Component {
// render 함수보다 먼저 실행이되면서 초기화시켜주고 싶은 것은 constructor안에다가 짜라
// 부모
  constructor(props) {
    super(props);
    this.max_content_id = 3;
    // 초기화 끝내고 state를 초기화 시킨다.
    this.state = {
      mode: "create",
      selected_content_id: 1,
      subject: {title: "Web", sub: "World Wide Web!"},
      welcome: {title:"Welcome", decs:"Hello, React!!!"},
      contents:[
        {id: 1, title: 'HTML', desc: 'HTML is for information'},
        {id: 2, title: 'CSS', desc: 'CSS is for design'},
        {id: 3, title: 'JAVASCRIPT', desc: 'JAVASCRIPT is for interactive'}
      ]
    }
  }

  render() {
    console.log('App render')
    let _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") {
      for(let i = 0; this.state.contents.length; i++) {
        let data = this.state.contents[i];
        if(data.id === this.state.selected_content_id) {
          _title = data.title;
          _desc = data.desc;
         break;
        }
      }
      _article = <ReadContent title={_title} desc={_desc}></ReadContent>
    } else if(this.state.mode === "create") {
      _article = <CreateContent onSubmit={function(_title, _desc) {
        this.max_content_id = this.max_content_id + 1;
        // push는 원본데이터를 변경함.
        // this.state.contents.push({ id: this.max_content_id, title: _title ,desc: _desc});
        // this.setState({
        //   contents: this.state.contents
        // })

        // concat으로 해야 원본 데이터를 바꾸지 않는다.
        let _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>
    }

    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({ 
            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>
    );
  }
}

export default App;

  위 코드에서 else if(this.state.mode === "create") 이 부분에 <CreateContent onSubmit ~~ 이 부분에 정의되어있다. 그리고 this.max_content_id는 사용자에게 보여주는 것이 아니라 내부에서만 참조하기 위해 만든 변수라서 state밖에 선언해주어도 된다.

 

  이제 만들어진 onSubmit함수를 CreateContent.js에서 this.props.onSubmit을 활용하여 가져오면 된다. 아래에 설명은 주석을 달아두었다.

import React, { Component } from 'react';

class CreateContent extends Component {
  render() {
    console.log('contents render')
    return (
      <article>
        <h2>Create</h2>
        <form action="/create_process" method="post"
        onSubmit={function(e) {
          e.preventDefault();
          // e의 속성 값을 이용해서 e.target.id값 으로 받아온다.
          this.props.onSubmit(e.target.title.value, e.target.desc.value);
          alert("create!!")
        }.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;

 

 


shouldComponentUpdate()란? (push(원본 변형) concat(원본 유지)의 차이 설명)

  간단한 예를 들기 위해 TOC.js에서 render() 함수 위에 shouldComponenteUpate(newProps, newState)를 사용하였다.

import React, { Component } from 'react';

class TOC extends Component {
  shouldComponentUpdate(newProps, newState) {
    console.log(newProps.data, this.props.data)
    return true;
  }
  render() {
    let lists = [];
    let data = this.props.data;
    for(let i = 0; i < data.length; i++) {
      lists.push(
      <li key = {data[i].id}>
        <a 
          href={"/content/" + data[i].id}
          //data-id = {data[i].id}
          onClick = {function(id, e) {
            e.preventDefault();
            this.props.onChangePage(id);
          }.bind(this, data[i].id)}
        >{data[i].title}</a>
      </li>);
    }
    console.log('TOC render')
    return (
      <nav>
        <ul>
          {lists}
        </ul>
      </nav>
    ); 
  }
}

export default TOC;

 

  위에서 보이는 것 같이 console.log()를 찍었을 때 shouldComponentUpdate는 newProps와 newState를 받아 올 수 있다. 그리고 또 하나의 특징은 return 값이 기본적으로 true이고 true이면 render 함수가 호출되고 false를 사용하면 아래 render 함수는 나타나지 않는다. 즉, render 함수보다 shouldComponentUpdate 함수가 먼저 실행된다는 것을 알 수 있다.

 

  여기서! 위에 내용을 넣을 때 push를 하는 것과 concat을 쓰는 차이가 나온다. 만약 push를 쓰면 this.props.data와 newProps.data가 같아서 shouldComponentUpdate를 이용하지 못할 수도 있다. 아래 코드처럼 쓰고 싶을 때 못할 수도 있다는 말이다.

class TOC extends Component {
  shouldComponentUpdate(newProps, newState) {
    console.log(newProps.data, this.props.data)
    if(this.props.data === newProps.data) {
      return false;
    } else {
      return true;
    }
  }

 

 

이후 진행하는 프로젝트에서 저는 concat을 활용할 것입니다.


* shouldComponentUpdate를 쓰는 사람을 위한 code tips

배열에서 복사 array.from(배열)

let a = [2, 3];
let b = Array.from(a);

console.log(b); // [2, 3]
console.log(a === b); // false

 

 

오브젝트에서 복사 Object.assign(오브젝트)

let a = {2, 3};
let b = Object.assign(a);

console.log(b); // {2, 3}
console.log(a === b); // false

 

 

두 개 모두 완벽히 같은 것은 아니고 내용만 같은 것이라서 등호를 해보면 false가 나온다.