React로 CRUD 구현(생활코딩)

김지원·2020년 9월 14일
0

React

목록 보기
4/31

npm: node.js 계의 앱 스토어

npm install -g creat-react-app
권한이 없다고 뜬다면
sudo npm install create-react-app

npm vs npx

npm: 프로그램을 설치하는 프로그램
npx: create-react-app이라는 것을 딱 한번만 실행시키고 지우는 애

개발환경 구축

디렉토리 만들기
$create-react-app .

npm run start로 실행 가능

npm run build로 빌드 가능 (실 서버 환경)

npx serve -s build

컴포넌트의 이름들로 정리하므로써 복잡도를 낮췄다.

State vs Props

부모인 app의 입장에서는 state 라는 내부 정보를 사용했고,
자식한테 전달할때는 props라는 것을 사용해서 전달한다.

//Appjs
import React, { Component } from 'react';
import './App.css';
import TOC from "./components/toc";
import Content from "./components/content";
import Subject from "./components/subject";

class App extends Component{
  constructor(props) {
    super(props);
    /*state 초기화 컴포넌트가 실행될때 constructor가 젤 먼저 실행*/
  this.state={
    subject:{title:"WEB", sub:"World Wide Web"},
    contents:[
      {id:1, title:'HTML1', desc:'HTML1 is for information'},
      {id:2, title:'HTML2', desc:'HTML2 is for information'},
      {id:3, title:'HTML3', desc:'HTML3 is for information'}
    ]
  }
  }

  render() {
    return(
      <div className="App">
        <Subject 
        title={this.state.subject.title} 
        sub={this.state.subject.sub}>
        </Subject>
        <Subject title="React" sub="reactreactreact"></Subject>
        <TOC data={this.state.contents}></TOC>
        <Content title="HTML" des></Content>
      </div>
    );
  }
}
export default App;
//toc.js
import React, { Component } from 'react';

class TOC extends Component{
    render() {
        var lists = [];
        var data = this.props.data;
        var i = 0;
        while(i <data.length){
            lists.push(<li key={data[i].id}><a href={"/content/"+data[i].id}> {data[i].title} </a></li>);
            i=i+1;
        }
      return (
        <nav>
      <ul>
         {lists}
      </ul>
      </nav>
      );
    }
  }

  export default TOC;

React에서는 props나 state 값이 바뀌면 해당하는 컴포넌트의 render 함수가 호출되도록 약속되어있다.
=> props나 state 값이 바뀌면 화면이 다시 그려진다.

이벤트 처리

하려고 하는 일: WEB을 클릭하면 mode가 read에서 welcome으로 바뀌게

<header>
        <h1><a href="/" onClick={function(e){
          console.log(e);
          e.preventDefault();//reload 안되게 해줌
          //this.state.mode='welcome';
<문제점>
1) 이벤트가 호출됐을 때 실행되는 함수 안에서는 this의 값이 컴포넌트 
자기 자신을 가리키지 않고 아무 값도 세팅되어있지 않다.
=> .bind(this) 추가해주기
2)this를 읽어와도 mode가 바뀌지 않음
=>this.setState({ mode: 'welcome' });
        }}>{this.state.subject.title}</a></h1>
        {this.state.subject.sub}
</header>

적용 후

<header>
       <h1><a href="/" onClick={function(e){
         console.log(e);
         e.preventDefault();
         //this.state.mode='welcome';
         this.setState({
           mode:'welcome'
         });
       }.bind(this)}>{this.state.subject.title}</a></h1>
       {this.state.subject.sub}
</header>

bind 함수

>var obj = { name: 'egoing'};
>function vindTest(){
  console.log(this.name);
  }
  
>bindTest();
<-undefined

>var bindTest2=bindTest.bind(obj);

>bindTest2();
<-egoing

setState

동적으로 state를 바꾸고 싶을때 setState를 사용해야한다.
this.state.mode='welcome' 이런 식으로 하면 render가 호출 되지 않음
constructor에서는 위와 같이 바꿔도 됨.

컴포넌트 이벤트 만들기

//App.js
<Subject 
        title={this.state.subject.title} 
        sub={this.state.subject.sub}
        onChangePage={function(){
          alert('hihi');
        }.bind(this)}>
        </Subject>
//subject
class Subject extends Component{
    render() {
      return (
        <header>
        <h1><a href="/" onClick={function(e){
          e.preventDefault();//link를 클릭했을 때 페이지가 바뀌는 것을 방지
          this.props.onChangePage();
        }.bind(this)}>{this.props.title}</a></h1>
        {this.props.sub}
        </header>
      );
    }
  }

subject라는 컴포넌트에 onChangePage 이벤트를 만들었다.
그리고 이벤트에다가 함수를 설치해주면
그 이벤트가 발생되었을 때 (링크를 클릭했을 때) props로 전달된 onChangePage라는 함수를 호출하면 된다.

하고싶은 것: toc의 목록의 링크를 눌렀을 때 해당하는 id값의 내용을 보여주기

1)속성을 이용해서 하기

//toc.js
class TOC extends Component{
    render() {
        var lists = [];
        var data = this.props.data;
        var i = 0;
        while(i <data.length){
            lists.push(<li key={data[i].id}>
              <a 
              href={"/content/"+data[i].id}
              data-id = {data[i].id}
              onClick={function(e){
 //onClick이라고 하는 속성을 이용하여 이벤트를 실행시킬 때 e.target.dataset.id을 통해서 값을 추출했는데 data-id가 data-abcd이면 e.target.dataset.abcd로 바뀌면 된다.
                e.preventDefault();
                this.props.onChangePage(e.target.dataset.id);
              }.bind(this)}
              > {data[i].title} </a></li>);
            i=i+1;
        }
      return (
        <nav>
      <ul>
         {lists}
      </ul>
      </nav>
      );
    }
  }

2)속성을 이용하지 않고 하기

//toc.js
class TOC extends Component{
    render() {
        var lists = [];
        var data = this.props.data;
        var i = 0;
        while(i <data.length){
            lists.push(<li key={data[i].id}>
              <a 
              href={"/content/"+data[i].id}
             
              onClick={function(id, e){
                e.preventDefault();
                this.props.onChangePage(id);
              }.bind(this, data[i].id)}
 //bind의 두번째 인자로 data[i].id 넘겨주기
 //bind는 두번째 인자로 들어온 값을 함수의 첫번째 매개변수의 값으로 넣어준다.
              > {data[i].title} </a></li>);
            i=i+1;
        }
      return (
        <nav>
      <ul>
         {lists}
      </ul>
      </nav>
      );
    }
  }
  
//App.js
return(
    <div className="App">
      <Subject 
      title={this.state.subject.title} 
      sub={this.state.subject.sub}
      onChangePage={function(){
        this.setState({mode:'welcome'});
      }.bind(this)}>
      </Subject>
      <Subject title="React" sub="reactreactreact"></Subject>
      <TOC
       onChangePage={function(id){
       this.setState({
         mode:'read',
         selected_content_id: Number(id)
      });
      }.bind(this)}
        data={this.state.contents}
        ></TOC>
      <Content title={_title} desc={_desc}></Content>
    </div>
  );
  
propsstate
props are read onlystate changes can be asynchronous
props can not be modifiedstate can be modified using this.setState

컴포넌트 안에서 전달된 props를 바꾸면 에러가 남
컴포넌트 밖에서 바꿀 수 있음.

상위 컴포넌트 -> 하위 컴포넌트 값 전달: props 이용
하위 컴포넌트 -> 상위 컴포넌트 값 바꾸려고 하면: 이벤트 사용

상위 컴포넌트(app.js)가 하위 컴포넌트(subject,content,toc.js)로 값을 전달할 때 props를 통해 전달
하위 컴포넌트가 상위 컴포넌트의 값을 바꾸고 싶을 때 이벤트를 통해서 함

상위 컴포넌트 > props > 하위 컴포넌트
하위 컴포넌트 > 이벤트 실행 > 상위 컴포넌트의 state 호출 > state 값 수정

create 구현

pushconcat
원본을 변경원본 변경하지 않음
>var arr=[1,2];
>arr.push(3);
>arr
<-[1,2,3]

>var arr2=[1,2];
>var result = arr2.concat(3);
>result
<-[1,2,3]
>arr2
<-[1,2]

//CreateContent.js

import React, { 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,
  //submit 버튼 눌렀을 때 입력되어있던 title 값
            e.target.desc.value
 //submit 버튼 눌렀을 때 입력되어있던 description 값
            );
           
       }.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;

//control.js

import React, { Component } from 'react';

class Control extends Component{
    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;


//ReadContent.js

import React, { Component } from 'react';

class ReadContent extends Component{
  render() {
    return (
      <article>
        <h2>{this.props.title}</h2>
       {this.props.desc}
    </article>
    );
  }
}

export default ReadContent;

//App.js

import React, { Component } from 'react';
import './App.css';
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";

class App extends Component{
constructor(props) {
super(props);
/state 초기화 컴포넌트가 실행될때 constructor가 젤 먼저 실행/
this.max_content_id =3;
//contents에 id를 추가하기 위해 변수 설정
this.state={
mode:'read',
selected_content_id:2,
subject:{title:"WEB", sub:"World Wide Web"},
welcome:{title:"welcome", desc:"hello, React!!"},
contents:[
{id:1, title:'HTML1', desc:'HTML1 is for information'},
{id:2, title:'HTML2', desc:'HTML2 is for information'},
{id:3, title:'HTML3', desc:'HTML3 is for information'}
]
}
}

  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>
//바뀔수 있는 _aritcle을 모드에 따라 컴포넌트 설정
//<ReadContent>, <CreateContent> 등...

    }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;
          break;
        }
        i=i+1;
      }
      _article=<ReadContent title={_title} desc={_desc}></ReadContent>
    }
    
//create 모드 일때
    else if(this.state.mode === 'create'){
      _article=<CreateContent onSubmit={function(_title, _desc){
//create 누른다음 입력한 제목과 내용 값 

        //add content to this.state.contents
        
        this.max_content_id+=1;
//max_content_id의 다음 값
        
/*push 보단 concat 사용하기*/
        //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}
         
        )
        
//state 변경하려면 setState 사용해야함
        this.setState({
          contents:_contents
        })
      }.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>
        <Subject title="React" sub="reactreactreact"></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;

shouldComponentUpdate()

상황: toc가 불필요하게 render 되고 있다.

1)render 이전에 shouldComponentUpdate가 실행된다.
2)shouldComponentUpdate return 값이 true면 render가 호출이 되고,return 값이 false면 render가 호출되지 않도록 약속되어 있다.
3)새롭게 바뀐 값과 이전 값에 접근 할 수 있다.
(concat 대신 push를 사용하면 원본과 같아져서 바뀐 값과 이전 값을 비교 할 수 없음)

toc에 있는 목록 클릭하면 render함수는 호출되지 않음
create해서 새로운거 추가하면 render함수 호출된다

class TOC extends Component{
  shouldComponentUpdate(newProps, newState){
    console.log('=======>TOC render shouldComponentUpdate'
    ,newProps.data
    ,this.props.data);
    //바뀐 값과 원래의 값을 알 수 있음

    if(this.props.data === newProps.data){
      return false;
    }
    return true;
  }
    render() { ...

원본을 바꾸지 않는다. 불변성 immutable

배열의 경우

>var a = [1,2];
>var b = Array.from(a);

>console.log(a,b,a===b);
<-[1,2] [1,2] false

>b.push(3);
복제한 것에 그냥 push를 해도 됨

객체의 경우

>var a = {name: 'egoing'};
>var b = Object.assign({}, a);

>console.log(a,b,a===b);
<-{name:"egoing"} {name:"egoing"} false

cf) immutable.js 찾아보기

update 구현

//UpdateContent.js

이렇게 하면 props는 readonly이므로 바뀌지 않음

<input 
   type="text" 
   name="title" 
   placeholder="title"
   value={this.props.data.title}
//props는 READONLY이므로 바꿀 수 없다. 
></input></p>

getReadContent와 getContent 함수로 코드를 정리해줌
App.js

getReadContent(){
  var i =0;
      while(i<this.state.contents.length){
        var data = this.state.contents[i];
        if(data.id === this.state.selected_content_id){
         return data;
        
        }
        i=i+1;
      }
}
getContent(){
  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  _content = this.getReadContent();
      _article=<ReadContent title={_content.title} desc={_content.desc}></ReadContent>;
  }
  
  else if(this.state.mode === 'create'){
      _article=<CreateContent onSubmit={function(_title, _desc){
        //add content to this.state.contents
        this.max_content_id+=1;
        //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
        })
      }.bind(this)}></CreateContent>
    }
    
    else if(this.state.mode === 'update'){
      _content = this.getReadContent();
      _article=<UpdateContent data={_content} onSubmit={function(_title, _desc){
        //add content to this.state.contents
        this.max_content_id+=1;
        //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
        })
      }.bind(this)}></UpdateContent>
    }
    return _article;
}

  render() {
    return(
      <div className="App">
        <Subject 
        title={this.state.subject.title} 
        sub={this.state.subject.sub}
        onChangePage={function(){
          this.setState({mode:'welcome'});
        }.bind(this)}>
        </Subject>
        <Subject title="React" sub="reactreactreact"></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>
       {this.getContent()}
      </div>
    );
  }
}
export default App;

UpdateContent.js (변경 가능하게 됨)
constructor로 state
onChange 함수 사용

import React, { Component } from 'react';

class UpdateContent extends Component{
    constructor(props){
        super(props);
        this.state = {
            title:this.props.data.title,
            desc:this.props.data.desc
        }
    }
  render() {
    return (
      <article>
        <h2>update</h2>
       <form action="/create_process" method="post"
       onSubmit={function(e){
           e.preventDefault();
           this.props.onSubmit(
            e.target.title.value,
            e.target.desc.value
            );
           
       }.bind(this)}>
           <p>
               <input 
                 type="text" 
                 name="title" 
                 placeholder="title"
    //this.props.data.title에서 this.state.title로 변경
                 value={this.state.title}
   //onChange를 사용해야만 글자를 수정할 수 있음!!
                 onChange={function(e){
                    this.setState({title:e.target.value});
                 }.bind(this)}
                 ></input></p>
           <p>
               <textarea name="desc" placeholder="description" value={this.state.desc} 
                onChange={function(e){
                    this.setState({desc:e.target.value});
                 }.bind(this)}></textarea>
           </p>
           <p>
               <input type="submit"></input>
           </p>
       </form>
    </article>
    );
  }
}

export default UpdateContent;

중복을 없애기 위해 handler 함수로 묶음

import React, { Component } from 'react';

class UpdateContent extends Component{
    constructor(props){
        super(props);
        this.state = {
            title:this.props.data.title,
            desc:this.props.data.desc
        }
  
  //inputFormHnadler 뒤에 bind(this) 안붙여도되게함
  this.inputFormHandler=this.inputFormHandler.bind(this);
    }

    inputFormHandler(e){
//이벤트가 발생하고 있는 태그의 이름이 뭔지 확인하는 e.target.name
        this.setState({[e.target.name]:e.target.value});
    }
  render() {
    return (
      <article>
        <h2>update</h2>
       <form action="/create_process" method="post"
       onSubmit={function(e){
           e.preventDefault();
           this.props.onSubmit(
            e.target.title.value,
            e.target.desc.value
            );
           
       }.bind(this)}>
           <p>
               <input 
                 type="text" 
                 name="title" 
                 placeholder="title"
                 value={this.state.title}
                 //onChange를 사용해야만 글자를 수정할 수 있음!!
                 onChange={this.inputFormHandler}
                 ></input></p>
           <p>
               <textarea name="desc" placeholder="description" value={this.state.desc} 
                onChange={this.inputFormHandler}></textarea>
           </p>
           <p>
               <input type="submit"></input>
           </p>
       </form>
    </article>
    );
  }
}

export default UpdateContent;

어디를 업데이트 할 것인지 식별자로 id값 줌
Update.js

import React, { Component } from 'react';

class UpdateContent extends Component{
    constructor(props){
        super(props);
        this.state = {
            id:this.props.data.id,
            title:this.props.data.title,
            desc:this.props.data.desc
        }
        this.inputFormHandler=this.inputFormHandler.bind(this);
    }

    inputFormHandler(e){
        //이벤트가 발생하고 있는 태그의 이름이 뭔지 확인하는 e.target.name
        this.setState({[e.target.name]:e.target.value});
    }
  render() {
    return (
      <article>
        <h2>update</h2>
       <form action="/create_process" method="post"
       onSubmit={function(e){
           e.preventDefault();
           this.props.onSubmit(
            this.state.id,
            this.state.title,
            this.state.desc
            );
           
       }.bind(this)}>
         {/*어디를 업데이트 할 것인지 식별자*/}
           <input type="hidden" name="id" value={this.state.id}></input>
           <p>
               <input 
                 type="text" 
                 name="title" 
                 placeholder="title"
                 value={this.state.title}
                 //onChange를 사용해야만 글자를 수정할 수 있음!!
                 onChange={this.inputFormHandler}
                 ></input></p>
           <p>
               <textarea name="desc" placeholder="description" value={this.state.desc} 
                onChange={this.inputFormHandler}></textarea>
           </p>
           <p>
               <input type="submit"></input>
           </p>
       </form>
    </article>
    );
  }
}

export default UpdateContent;

App.js
수정하기 위해 원본 복사하고
contents 안에 있는 id 중 수정하고자 하는 id 찾기

 else if(this.state.mode === 'update'){
      _content = this.getReadContent();
      _article=<UpdateContent data={_content} onSubmit={
        function(_id, _title, _desc){
          //수정하기 위해 원본 복사
          var _contents = Array.from(this.state.contents);
          //contents 안에있는 id중 수정하고자 하는 id 찾기
          var i =0;
          while(i< _contents.length){
            if(_contents[i].id === _id){
              _contents[i] = {id:_id, title:_title, desc:_desc}
              break;
            }
            i=i+1;
          }
          this.setState({
            contents:_contents
          })
      }.bind(this)}></UpdateContent>
    }

create와 update 후 mode를 read로 변경하기
App.js

else if(this.state.mode === 'create'){
      _article=<CreateContent onSubmit={function(_title, _desc){
        //add content to this.state.contents
        this.max_content_id+=1;
        //push 사용하기
        //this.state.contents.push(
        //  {id:this.max_content_id, title:_title, desc:_desc}
        //  );

        //concat 사용하기
       // var _contents = this.state.contents.concat(
        //  {id:this.max_content_id, title:_title, desc:_desc}
       // )

       //Array.from 사용하기
       var _contents = Array.from(this.state.contents);
       _contents.push({id:this.max_content_id, title:_title, desc:_desc});
       
       
//모드 변경!!!!!!
        this.setState({
          contents:_contents,
          mode:'read',
          selected_content_id:this.max_content_id
        });
        
        
      }.bind(this)}></CreateContent>
    }
    
    else if(this.state.mode === 'update'){
      _content = this.getReadContent();
      _article=<UpdateContent data={_content} onSubmit={
        function(_id, _title, _desc){
          //수정하기 위해 원본 복사
          var _contents = Array.from(this.state.contents);
          //contents 안에있는 id중 수정하고자 하는 id 찾기
          var i =0;
          while(i< _contents.length){
            if(_contents[i].id === _id){
              _contents[i] = {id:_id, title:_title, desc:_desc}
              break;
            }
            i=i+1;
          }
          this.setState({
            contents:_contents,
            
//수정하고 나서 제출을 눌렀을 때 read모드로 변경!!!!
            mode:'read'
          });
          
          
      }.bind(this)}></UpdateContent>
    }

delete 구현

<Control onChangeMode={function(_mode){
          if(_mode === 'delete'){
            if(window.confirm("really?")){
              var _contents = Array.from(this.state.contents);
              var i =0;
              while(i< _contents.length){
                if(_contents[i].id === this.state.selected_content_id){
                  _contents.splice(i,1);
                  break;
                }
                i=i+1;
              
              //getReadContent 이용해서 해본거
               //var _content = this.getReadContent();
               //var index = _content.id-1;
               //_contents.splice(index,1);
              }
              this.setState({
                mode:'welcome',
                contents:_contents
              });
              alert("delete!")
            }
          }else{
            this.setState({
              mode:_mode
            })
          }
        }.bind(this)}></Control>

0개의 댓글

관련 채용 정보