본문 바로가기

[자기계발]/유튜브로 코딩배우기

[생활코딩]React 강의-4 18강~19강 create

반응형

18강 베이스 캠프

APP 이 상위 컴퍼넌트 , TOC,  CONTENT, SUBJECT가 하위 컴퍼넌트

 

상위 컴퍼넌트가 하위 컴퍼넌트로 값을 전달할 때는

하위 컴퍼넌트의 조작 장치인 PROPS를 통해서 전달한다.

하위컴퍼넌트가 상위컴퍼넌트를 바꾸고싶을 때는 이벤트를 통해서 한다.

        <TOC
        onChangePage={function(id){
        this.setState({mode:'read',
        selected_content_id:Number(id)
        });
        }.bind(this)}
        data={this.state.contents}>
        </TOC>

상위 컴퍼넌트인 APP이 하위 컴퍼넌트인 내부적으로 사용하는 TOC에 값을 전달할 때는

data={this.state.contents}> 라는 props에 값을 전달한다.

TOC를 클릭했을 때, APP(상위 컴퍼넌트)에 state와 seleceted_content_id를 값을

변경하려고 하려면 

        onChangePage={function(id){

        this.setState({mode:'read',

        selected_content_id:Number(id)

        });

        }.bind(this)}

라는 이벤트를 구현하는 것을 통해서 그 이벤트가 실행 됐을 때 상위 컴퍼넌트의

state 값을 호출하는 것을 통해 상위 컴퍼넌트의 state값을 바꿀 수 있다.

 

#리덕스는 각각의 컴퍼넌트들을 막 분산해서 보관하는 게 아나라 하나의 데이터 저장소가 있어서

여기 있는 데이터 저장소에 값이 바뀌면 애랑 관련된 모든 컴퍼넌트가 알아서 바뀌게 하는 기술

 

배운 것 설명하기 

1. props와 state의 차이점 설명해보라

 

2. 상위 컴퍼넌트가 하위 컴퍼넌트에게 어떤 데이터를 전달할 때는 props를 쓴다.

하위 컴퍼넌트가 상위 컴퍼넌트로 데이터를 전달할 때는 props값을 바꿀 수 없기

때문에 이벤트를 쓴다. 이게 무슨 의미인지 설명해보라

 

#

  1. props는 스마트폰의 볼륨버튼이라면 사용자가 볼륨버튼을 누르면 state는 스마트폰안에서 스스로의 상태인 볼륨이 바뀌게 해놓은 모든 조치(회로,프로그래밍 등등)라고 할 수 있습니다.
  2. 상위 컴포넌트는 하위 컴포넌트에게 props를 통해 값을 전달해 내부의 state를 바꾸기 때문에 컴포넌트 스스로 외부에서 전달되는 props를 변경하는 것은 금지되어 있습니다. 또한 하위 컴포넌트가 상위 컴포넌트를 동작시키려면 props를 전달하는 것이 아니라 상위 컴포넌트 안에 이벤트를 심고 그 안에 setState로 값을 바꿔야 합니다.

#

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

 

#

사장과 직원개념으로 비유하면 사장=state, 직원=props로 사장이 일 목록을 만들면 직원은 목록에 있는 것을 골라서 일을 처리할 수 있지만, 직원이 새로운 일을 하려면 기획안(이벤트)을 만들어 사장에게 허락을 구하는 것으로 비유

 

#

  1. props : 고정값(기본값으로 사용),상위 값
  2. state : 가변값,하의값,사용자가 이벤트 요청시 변경

#

먼저 다음과 같은 두 가지 전제를 깔고 시작합니다. 아래의 두 조건이 틀리면 제가 이해한 방식도 틀린 설명이 되겠네요. 1. 상위 컴포넌트에서 하위 컴포넌트로 전달된 데이터는 하위 컴포넌트에서 자체적으로 수정할 수 없다.

2. props는 상위 컴포넌트 -> 하위 컴포넌트로 데이터를 전달하기 위해 탄생했다.

위의 전제에 비추어 props와 state를 설명하겠습니다. props는 태생이 하위 컴포넌트에 데이터를 전달하기 위해 탄생했습니다. 다시 말해 props를 넘겨주는 부모 컴포넌트에서는 props를 사용할 일이 없고, 오직 자식 컴포넌트에서 props를 사용하기 때문에 1번 전제에 따라 read-only로 고정이 되어있는거죠. 어차피 수정하면 안되니까요.

state는 렌더링 정보를 동적으로 바꿔나가기 위해 해당 컴포넌트에서 선언합니다. props의 형태로 state를 하위 컴포넌트에 넘겨주기도 하는데, 이 때에도 1번 전제에 따라 state를 하위 컴포넌트에서 자체적으로 수정할 수는 없고, 상위 컴포넌트에서 state를 수정하도록 선언된 함수(ex.onChangePage)를 하위 컴포넌트에 props의 형태로 전달하여 하위 컴포넌트의 이벤트와 바인딩시킴으로써 이벤트가 발생할 시에 상위 컴포넌트 함수가 호출되고, 다시 상위 컴포넌트의 함수로 돌아와 해당 컴포넌트에서 state를 수정합니다. 이러한 방식으로 props와 state간 상호 작용을 통해, 다시말해 하위 컴포넌트와 상위 컴포넌트간 상호 작용을 통해 동적으로 렌더링되는 single page application을 만들 수 있게 됩니다.

 

#

상위 = 부모 하위 = 자식 이라고 예시를 들겠습니다.

부모 컴포넌트가 자식컴포넌트에게 주는 고유한 값을 props라고 생각합니다. 부모 컴포넌트는 자식컴포넌트에게 주는 고유한 값을 바꿀 수 있지만 자식컴포넌트에서는 고유한 값을 바꿀 수 없기 때문입니다.

state는 state를 선언한 컴포넌트에서만 바꿀 수 있는 state라고 생각합니다. 부모 컴포넌트가 자식 컴포넌트나 부모의 부모 컴포넌트를 바꾸려면 부모의 부모 컴포넌트에게는 props로 보내는 이벤트로 바꾸지만 state를 바꾸는 명령을 한것은 부모 컴포넌트이지만 부모의 부모의 state를 바꾸는 setState는 부모의 부모 컴포넌트에 있기 때문입니다.

부모 컴포넌트가 어떠한 이벤트(함수)를 설정하면 자식 컴포넌트는 어떠한 이벤트를 props로 받고 실행해서 state를 바꿀 수 있습니다. state를 바꿀 수 있는 범위는 state가 선언된 함수 혹은 클래스 안에서 setState로 바꿀 수 있는데 그 setState를 사용하는 함수를 어떠한 이벤트가 발생됬을때 실행시킬 수 있는 props로 자식컴포넌트에게 넘겨준다고 생각합니다. 여기서 이벤트란 onClick onChange등 무엇이 바뀌었거나 클릭되었을때 할 할 행동을 이벤트라고 생각합니다.

19.1강 create 구현 : 소개

모든 정보기술은 CRUD 안에 갇혀있다. Create와 Read는 핵심적인 요소다. 

리엑트를 이용해서 read하는 것을 보았고, 이제는 어떻게 react를 통해서 create 할 것인가를 알아보자.

Create 

Read 

Update

Delete

19.2강 create 구현 : mode 변경 가능

<TOC></TOC> 사이에다가 create, update, delete 라는 3가지 mode로 진입하는 버튼을 만들자

import React, {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;

create, read 와 update는 어떤 특정한 페이지로 가서 거기서 operation이 실행된다.

하지만 delete는 그 버튼을 클릭할 때, 실제로 삭제가 일어나기 전에 할 것인데 

그런 경우 create 와 read와 같이 링크를 쓰게 되면 큰 문제가 발생할 수 있다,

E.G.)사용자들이 어떤 페이지 방문을 빨리 할 수 있게 미리 방문을 해두는 소프트웨어가 깔려있다.

그럼 저 delete 로 되어있는데도 미리 방문을 해서 삭제를 해둘 수 가 있기에, 링크 같은 페이지 개념이 아니라 이런 버튼과 같은 operation 개념의 기능을 쓰는 것이 맞다. 그냥 버튼을 쓴다고 생각하라

 

3개중에 하나를 클릭했을 때 어떤일이 일어나게 해야한다 

=> onChangeMode라는 이벤트 컨트롤 컴퍼넌트를 정의 해야한다 = 이벤트 핸들러 설치

(이벤트가 실행 됐을 때 실행되어야 하는 함수를 핸들러라고 부른다)

 

create, update, delete를 선택했을 때 onChangeMode라는 핸들러가 실행되게 할 것.

onChangeMode가 호출될 때 첫 번째 인자를 받을 수 있어야 한다. 첫 번쨰 인자를 _mode라는 값으로 받자.

그러면 저 함수 안에서는 mode의 값이 _mode라는 변수로 현재 상태가 전달이 될 것이다. 

그럼 우리는 여기있는 이 함수가 호출될 때마다 APP 컴퍼넌트에 mode state값을 바꾸면 된다.

       </TOC>
        <Control onChangeMode={function(_mode){
          this.setState({
          mode:_mode
         });
        }.bind(this)}></Control>
        <Content title={_title} desc={_desc}></Content>
      </div>

이것을 통해서 우리가 한것은 현재의 상태에 따라서 다른 mode가 mode라는 state값을 바뀌게 했다.

이제는 create, update, delete 의 mode에 따라서 Content가 표시되는 영역의 컴포넌트가 적당한

것으로 바뀌게 하는 작업을 할 것이다.

19.3강 create 구현 : mode 전환 기능

create를 클릭했을 때, 읽기에서 사용되는 컴퍼넌트를 create에서 사용된 컴퍼너트로 교체시킬 것

1. Content.js -> ReadContent.js 바꾸기

import React, {Component} from 'react';

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


  export default ReadContent;

2. CreateContent.js 만들기

import React, {Component} from 'react';

class CreateContent extends Component {
    render() {
      console.log('Content render');
      return (
        <article>
          <h2>Create</h2>
          <form>
            
          </form>
          </article>
      )
    }
  }


  export default CreateContent;

mode의 값이 뭐냐에 따라서 여기 있는 article 영역이 교체되는 코드는

  render() {
    console.log('App 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;
        break;
        }
        i= i + 1;
      }

이것인데 ReadContent 영역이 가변적으로 바뀔 수 있도록 하기 위해서 {_article}이라는 변수로 처리한다.

  render() {
    console.log('App 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;
        break;
        }
        i= i + 1;
      }
      _article = <ReadContent title={_title} desc={_desc}></ReadContent>
    } else if(this.state.mode === 'create'){
      _article = <CreateContent></CreateContent>
    }

mode가 welcome이거나 read 인 경우에는 article 값이 ReadContent가 된다.

조금 코딩하고 확인을 반복해야지 문제가 복수 개가 되는 것을 막을 수 있다.

else if를 추가함으로서 mode가 create 일 때 CreateContent가 화면에 출력되도록 할 수 있다.

19.4강 create 구현 : form

placeholder: 값이 아무것도 입력이 안 됐을 때, 박스안에 투명한 글씨로 "xx"가 출력되게 하는 것

textarea: 우리가 입력하는 텍스트가 여러줄일 때 쓰는 것

type="submit": 전송 버튼 구현

app.js 에서 mode:'read' -> 'create' 로 바꾼다. 그러면 기본 화면이 create로 고정되어서 코딩하기 편할 것.

        <article>
          <h2>Create</h2>
          <form action="/create_process" method="post"
            onSubmit={function(e){
              e.preventDefault()
              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>

 

form 태그 자체를 정해야한다. React 안 쓸 때, 이 데이터를 어디에다가 전송할 거냐라는 action 값을 준다.

<form action="/create_process"> => 'create_process라는 페이지로 사용자가 입력한 정보를 전송할 것이다' 라는 뜻

 

method: 사용자가 데이터를 추가하거나 뭐 수정하거나 이런걸 할 때는 삭제 포함해서 method가 post 방식으로

가야한다. 그래야 URL에 노출이 안된다.

 

onSubmit이라는 이벤트 노출. submit 버튼을 클릭했을 때, 이 버튼을 포함하고 있는 form 태그의

onSubmit이라는 이벤트를 설치해 놓으면, 그 이벤트가 실행되도록 약속되어 있다. 

이것은 html의 form 기능이 고유하게 가지고 있는 기능이다. react 기능이 아니다.

 

e.preventDefault 라고 하게 되면 원래 form 태그에서는 submit 버튼을 클릭했을 때, 이 액션이 가리키고 있는 페이지로 화면이 이동한다. 근데 우리는 react를 통해서 페이지 전환이 없는 app을 만들고 싶은 상태이기 때문에 페이지가 바뀌면 안된다. 그래서 onSubmit 이라는 이벤트가 일어났을 때, 기본적인 동작인 페이지가 바뀐다 라는 것을 못하게 한 것이다.

19.5강 create 구현 : onSubmit 이벤트

APP 컴퍼넌트의 contents 라는 데이터의 끝에다가 데이터를 추가하는 방법을 살펴보자 

 

submit 버튼을 클릭했을 떄, CreateContent 의 이벤트로 설치된 함수를 실행시켜야 한다.

      _article = <ReadContent title={_title} desc={_desc}></ReadContent>
    } else if(this.state.mode === 'create'){
      _article = <CreateContent onSubmit={function(_title, _desc){
        // add content to this.state.contents
        console.log(_title, _desc)
      }.bind(this)}></CreateContent>
    }

 CreateContent의 onSubmit이라 하는 props를 호출 할 것이다.

        <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)}
          >

Createc

e.target.title.value,

e.target.desc.value

를 넣는다.

 

onSubmit 이라는 이벤트가 발생했을 때 CreateContent Component의 props.onSubmit을 

실행하도록 했다 = 밑의 코드가 호출될 것이다.

      _article = <CreateContent onSubmit={function(_title, _desc){

        // add content to this.state.contents

      }.bind(this)}></CreateContent>

console.log 를 추가하면 console기능을 켰을 떄 확인이 가능하다.

title과 desc 값이 잘 전달 될 것이다.

19.6강 create 구현 : contents 변경

이제 우리가 얻은 title과 desc 값을 state에 content 끝에 추가해주는 작업을 하면 된다.

기존에 추가되었던 id값을 쭉 읽어서 1 더 큰 id값을 만들어야 한다.

 

this.max_content_id = 3;

위의 3은 마지막 컨텐트의 id와 같아야한다.

 

this.state가 아니라 객체의 값으로 한 이유는 max_content_id 는 우리가 어떤 데이터를 추가할 때

push할 때 id값을 뭐를 할 것인가 라고 할 떄 사용하는 정보일 뿐

ui에 영향을 줄 이유가 없는 친구이기 때문에 이런 애들은 우리가 state값으로 안해도 된다.

할 시에 불필요한 랜더링이 발생할 것이다.

 

    } 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}
        );
        console.log(_title, _desc)
      }.bind(this)}></CreateContent>
    }

이렇게 push를 이용해서 추가하면 될까 안될까?

안된다. 이렇게 state 값을 직접 수정하면 React가 변경 됐다는 것을 모른다. 고로 setState를 써야한다.

        this.max_content_id = this.max_content_id+1;
        this.state.contents.push(
        {id:this.max_content_id, title:_title, desc:_desc}
        );
        this.setState({
          content:this.state.contents
        });

이렇게 하고 실행하면 끝에 하나가 추가 된다. 다만, 이렇게 하면 안되는 것은 아닌데

나중에 React의 어떤 성능을 개선하려고 할 때 굉장히 까다롭거나 불가능한 상황이 생길 수가 있다.

그래서 이 방법은 좋은방법이 아니다. 고로, concat(concatenate-결합하다)를 사용해야한다.

 

console에서

var arr = [1,2];
arr.push(3);;
=>3
arr
(3) [1,2,3]
push는 원본을 바꾼다

var arr =[1,2];
var result = arr2.concat(3);
result
(3) [1,2,3]
concat은 원본을 바꾸지 않는다. 원본은 그대로 인데 원본을 변경한 새로운 배열이 리턴된다.

arr2
(2) [1,2]

이렇듯 state에다가 값을 추가할 때는 'push'와 같이 오리지널 데이터를 변경하는 것을 쓰지 말고

'concat'처럼 오리지널 데이터를 변경하지 않고 새로운 데이터를 추가하는 것을 써야한다.

 

this.state.contents.push는 오리지널 데이터 state 컨텐츠를 바꾼다. 

        var _contents = this.state.contents.concat(

          {id:this.max_content_id, title:_title, desc:_desc}

        )

이렇게 나온 리턴값을 _contents 라는 변수에 담자. 이 _contents를 setState 값으로 주자.

그러면 기존의 setState에서 가지고 있었던 값이 새롭게 만들어진 데이터로 교체돼 버린다.

      _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}
        // );
        var _contents = this.state.contents.concat(
          {id:this.max_content_id, title:_title, desc:_desc}
        )
        this.setState({
          contents:_contents
        });

이렇게 안하고 기존의 방식을 썼다면

        // this.state.contents.push(
        // {id:this.max_content_id, title:_title, desc:_desc}
        // );

-> 기존에 있었던 컨텐츠의 배열에 값을 하나 추가하는 방식이다.

-> 나중에 퍼포먼스, 성능을 개선할 때 굉장히 까다롭다.

 

CRUD에서 React를 이용해서 create를 했다. 이제 우리는 핵심적인 operation인 create, read을 다 할 줄 아는 사람이 되었다.

19.7강 create 구현 : shouldComponentUpdate

복제본을 setestate 값으로 주어라. 원본을 수정하지 말고 원본의 복제본을 수정하라. shouldComponentUpdate를 안쓴다면 push든 concat 이든 상관이 없을 것. 하지만 나중에 소프트웨어가 커지다보면 언젠가 튜닝이 필요할 수도 있으니 알아둬라.

 

#지금 App.js는 비효율적인 면을 갖고있다. TOC가 redering되기 위해 필요한 데이터는 state의 contents[]이다. 이 내용이 바뀌면 TOC 컴포넌트의 render가 다시 실행 될 것이다. 즉, 만약 contents가 바뀌지 않는다면 TOC의 render()는 호출될 필요가 없다. 그러나 현재 코드에서는 사용자의 모든 act에 TOC의 render()가 실행되고 있다. facebook은 이러한 이슈를 방지하기 위해 shouldComponentUpdate()라는 함수를 만들었다. 이 함수의 return이 false라면 react는 그 밑의 render()함수를 읽지 않는다.

 

또한 shouldComponentUpdate의 매개변수는 newProps, newState로 약속이 되어 있다.

-console.log(newProps, 'A');

-console.log(this.props.data, 'B'); 

B에서는 render()가 호출되지 못하였기 때문에 state.content[] 값을 그대로 갖고온다. 하지만 newProps는 추가된 값까지 가져오는 것을 볼 수 있다. 즉, A는 변경값을 가져오지만 B는 원본값을 갖고온다.

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() {
    console.log('===>TOC render');

만약 쓸데없는 redering을 막기위해 shouldComponentUpdate를 사용했고, 원본값과 변경값을 비교하여 변경값이 있을 때만 TOC가 render된다는 조건을 추가했다고 치자. 이 때, state.contents[]를 push로서 값을 추가했다면 TOC에서 this.props.data를 했을 때 원본 배열에 값을 추가하였기 때문에 shouldComponentUpdate함수 내에 if문으로 조건을 붙일 수 없다. 그러나 concat()을 사용한다면 원본값은 두고 그 원본값을 복제하여 변경값을 추가한다음 render()하기 때문에 원본값과 변경값을 비교할 수 있는 환경을 만들 수 있다.

19.8강 create 구현 : immutable

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

Array from === 배열을 바꾸고 싶다

var a = [1,2];

var b = Array.from(a);

console.log(a, b);

-> (2) [1,2] -> (2) [1,2]



var a = [1,2];

var b = Array.from(a);

console.log(a, b, a===b);

-> (2) [1,2] -> (2) [1,2] false
내용은 같지만 다른 것


var a = [1,2];

var b = Array.from(a);

b.push(3);

console.log(a, b, a===b); 

-> (2) [1,2] -> (3) [1,2,3] false

 

Arrary.assgin  === 객체를 바꾸고 싶다

객체의 내용을 바꾸지 않고 새로운 복제된 객체를 만들고 싶다

var a = {name:'egoing'};

var b = Object.assgin({ },a);

console.log(a, b, a===b);

-> {name: "egoing"} ->{name: "egoing"} false

두개는 내용은 같지만 같은 객체가 아니다.



var a = {name:'egoing'};

var b = Object.assgin({ },a);

b.name = 'leezche' ;

console.log(a, b, a===b);

-> {name: "egoing"} ->{name: "egoing"} false

즉, Object.assgin을 통해서 격체는 복제할 수 있다



var a = {name:'egoing'};

var b = Object.assgin({left:1, right:2}, a);

b.name = 'leezche' ;

console.log(a, b, a===b);

-> {name: "egoing"} ->{left:1, right:2, name: "leezhche"} false

name 추가하는 것.

 

모든 명령어들이 다 불변하기 때문에 일관된 코드를 사용할 수 있고 혼란스러운 여지가 적다.

 

 

반응형