React-기초

Daniel-Lim·2022년 1월 13일
0

React

목록 보기
8/8
post-thumbnail

React

React 개발환경 설정하기

node.js 설치

홈페이지에서 node.js 설치 후에 설치 되었는지 확인하려면(윈도우 기준)

  1. 시작+R -> cmd 실행
  2. npm -v 실행 후 버전 숫자가 나오면 정상 설치 되었다는걸 확인

React 설치

  1. 전역에 설치
npm install -g create-react-app # 설치

create-react-app -V # 정상 설치되어있는지 버전확인
  1. 공식문서 npx 이용 설치
npx create-react-app my-app

npm과 npx의 차이

npm은 프로그램을 설치하는 역할

npx는 create-react-app 이라는 프로그램을 임시로 설치해서 딱 한번만 실행시키고 지우는 역할(컴퓨터 공간을 낭비하지 않는다. 항상 최신상태로 설치한다.)

  1. CDN을 이용해 사용중인 HTML파일에 필요할때마다 부분적으로 React 기능을 추가
    • 초보자에겐 권장하지 않는다.

실행하기

npm start

종료하기

터미널에서 컨트롤+C

첫 설치 시 코드 살펴보기

처음 실행 시 위와 같은 화면이 출력된다.

위의 화면을 출력하는건 index.html 이고 조작 하는건 index.js 이다.

위의 디렉토리 목록처럼 public에서는 화면에 보여지는것이 담길 것이고 src 에선 조작,컴포넌트를 담당하게 될것이다.

public은 create-react-app 에서 npm start를 했을때 파일을 찾는 document root 이다.

우리가 살펴봐야 할것은 index.html의 root id를 가진 div이다.

저 id로 react 코드를 연결시켜 줄 것이다.

root id를 연결시켜주는건 index.js의 ReactDom 안에 있다.

안에 App은 컴포넌트를 의미하며 import 하여 사용하고있다.

경로 끝에 ./App은 .js가 생략되어있다.

App 컴포넌트는 App.js에 있다.

첫 실행시 함수형 컴포넌트로 생성되지만 학습을 위해 클래스형 컴포넌트로 변형하였다.

컴포넌트의 내용에는 반드시 최상단이 하나의 태그(div 등)로 묶여있어야 한다. 없애보면 에러가 뜨는걸 확인할수 있다.

App.js 안에 App.css가 import되어있다.

  • 이것은 App.js 안에 들어있는 리액트의 컴포넌트가 로드됐을때 App.css도 같이 로드가 된다. 그러면서 디자인도 같이 할수 있게된다.
  • App 이라고 하는 컴포넌트의 디자인을 그 App 안에 넣는다 라고 이해

배포하기

npm run build

build 디렉토리와 파일들 생성하는데 배포할때 사용

npx serve -s build

컴포넌트 만드는법

각 클래스는 하나의 컴포넌트이며 render는 함수이다.

우리가 알고 있는 일반적인 함수는 function이 붙어야 하는데 자바스크립트의 최신버전은 클래스 안의 함수는 function을 생략한다.

리액트 컴포넌트를 만들때는 위의 Subject2 양식을 지켜줘야 한다.

App 컴포넌트 안에 Subject2를 호출하고 있는 코드이다.

props

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

위의 방식으로 this.props. 으로 컴포넌트의 속성을 표현할수 있다.

// import React,{Component} from 'react';
import React from 'react';
import './App.css';

class Subject extends React.Component {
  render() {
    return (
      <header>
        <h1>{this.props.title}</h1>
        {this.props.sub}
      </header>
    );
  }
}

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

class App extends React.Component {
  render() {
      return (
        <div className="App">
          <Subject title="WEB" sub="world wide web!" />
          <Subject title="React" sub="For UI" />
          <Content title="HTML" desc="HTML is HyperText Markup Language."></Content>
        </div>
      );
  }
}

export default App;

props를 사용해본 코드이다.

props를 사용함으로써 항상 정적인 값을 반환하는 컴포넌트들을 동적으로 사용할수 있게 된다.

이다.

컴포넌트 안에서 자기로 전달된 props를 변경하는것은 금지!! 되어있다.

하지만 컴포넌트 밖에서 props를 바꾸는건 허용이된다.(전달할때)

props are read-only

tool

크롬 확장프로그램으로 React Developer Tools를 설치하면 리액트 툴을 사용할수 있다.

위의 그림처럼 사용할수 있고 컴포넌트별로 어떻게 작동하는지 확인도 가능하다.

저기서 title을 수정해서 바뀐값을 확인할수도 있다.

컴포넌트 파일로 분리하기

src 안에 컴포넌트라는 디렉토리를 만들고 TOC 라는 클래스 컴포넌트를 분리해보기 위해 TOC.js 파일을 만들어보자

import React from "react";

class TOC extends React.Component {
  render () {
    return (
    <nav>
      <ul>
        <li><a href="1.html">HTML</a></li>
        <li><a href="2.html">CSS</a></li>
        <li><a href="3.html">JavaScript</a></li>
      </ul>
    </nav>
    );
  }
}

export default TOC;

TOC.js 파일 안에는 기존에 작성했던 코드를 잘라 붙여오고 react를 사용하기위해 import 해준다.

그리고 외부에서 다른파일에 있는 컴포넌트를 사용하기 위해선

나는 클래스 컴포넌트를 외부에서 사용할수 있게 허용할것인가를 코드로 작성한다.(마지막줄 export)

import TOC from './components/TOC';

그리고 사용할 곳에서 TOC를 import 해주고 사용하면 된다.

state

states는 컴포넌트의 내부정보

class App extends React.Component {
  constructor (props) {
    super (props);
    this.state = {
      subject: {
        titie:"WEB",
        sub:"World Wid Web!"
      }
    }
  }

  render() {
      return (
        <div className="App">
          <Subject title={this.state.subject.titie} sub={this.state.subject.sub} />
          <Subject title="React" sub="For UI" />
          <TOC />
          <Content title="HTML" desc="HTML is HyperText Markup Language."></Content>
        </div>
      );
  }
}

상위 컴포넌트인 App의 state 값을 하위 컴포넌트인 Subject에 props의 값으로 전달하는 코드

컴포넌트를 실행할때 constructor 라는 함수가 있다면 constructor 가 제일 먼저 실행되어서 초기화를 담당한다.

state를 사용할때 constructor 함수와 super 함수는 필수로 작성해야한다.

key

class App extends React.Component {
  constructor (props) {
    super (props);
    this.state = {
      subject: {
        titie:"WEB",
        sub:"World Wid Web!"
      },
      content:[
        {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() {
      return (
        <div className="App">
          <Subject title={this.state.subject.titie} sub={this.state.subject.sub} />
          <Subject title="React" sub="For UI" />
          <TOC data={this.state.content} />
          <Content title="HTML" desc="HTML is HyperText Markup Language."></Content>
        </div>
      );
  }
}
class TOC extends React.Component {
  render () {
    let data = this.props.data;
    let i = 0;
    let list = [];
    while(i<data.length){
      list.push(<li><a href={"/content"+data[i].id}>{data[i].title}</a></li>)
      i++
    };
    return (
    <nav>
      <ul>
        {list}
      </ul>
    </nav>
    );
  }
}

여러개의 엘리먼트를 자동으로 생성할때는 key가 필요하다.

예시로 App컴포넌트에 여러개의 객체를 가진 배열 state를 props 해주고

TOC컴포넌트에서 여러개의 li 태그를 자동으로 생성하는 코드를 작성해보았다.

결과물은 출력 되지만 콘솔에서 에러가 뜬다.

react가 내부적으로 필요해서 요청하는 에러이기 때문에 그러려니 하고 만들어 넣어주면 된다.

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

각각의 li 목록을 다른것들과 구분할수 있는 식별자를 넣어줘야하는데 id값을 정해놨으니 key 속성에 각 id값을 넣어주면 된다.

이벤트 state props 그리고 render함수

class App extends React.Component {
  constructor (props) {
    super (props);
    this.state = {
      mode:"welcome",
      welcome:{title:"Welcome", desc:"Hello, React!"},
      subject: {
        titie:"WEB",
        sub:"World Wid Web!"
      },
      content:[
        {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() {
    let _title, _desc = null;
    if(this.state.mode === "welcome"){
      _title = this.state.welcome.title
      _desc = this.state.welcome.desc
    } else if(this.state.mode === "read"){
      _title = this.state.content[0].title
      _desc = this.state.content[0].desc
    }
      return (
        <div className="App">
          <Subject title={this.state.subject.titie} sub={this.state.subject.sub} />
          <Subject title="React" sub="For UI" />
          <TOC data={this.state.content} />
          <Content title={_title} desc={_desc}></Content>
        </div>
      );
  }
}

mode state를 설정했고 mode에 따라서 값이 달라지는 코드를 작성해볼것이고 일부만 작성했다.

mode가 변경될때마다 Content 컴포넌트의 title과 desc가 변경될 것을 예상해보았다.

state, props 값이 변경되면 해당되는 모든 컴포넌트의 render 함수가 다시 호출된다. 즉, 관련된 모든 화면이 다시 그려진다.

render 함수는 어떤 html을 그릴것인가를 결정하는 함수이다.

이를 확인해 보기 위해 각 컴포넌트에 console.log를 찍고 크롬 확장프로그램인 리액트 툴로 모드를 read와 welcome을 번갈아 가면서 바꿔보면 콘솔에 모든 컴포넌트의 console.log가 작성되는걸 확인할 수 있다.

위의 코드에선 콘솔에 App, Subject, TOC, Content 순서대로 출력이 된다.

props, state !

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

하위컴포넌트가 상위컴포넌트에게 데이터를 전달할때는 props 의 값을 바꿀수 없기 때문에 이벤트를 쓴다.

이벤트 설치, 이벤트에서 state 변경하기

class App extends React.Component {
  constructor (props) {
    super (props);
    this.state = {
      mode:"welcome",
      welcome:{title:"Welcome", desc:"Hello, React!"},
      subject: {
        title:"WEB",
        sub:"World Wid Web!"
      },
      content:[
        {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() {
    let _title, _desc = null;
    if(this.state.mode === "welcome"){
      _title = this.state.welcome.title
      _desc = this.state.welcome.desc
    } else if(this.state.mode === "read"){
      _title = this.state.content[0].title
      _desc = this.state.content[0].desc
    }
      return (
        <div className="App">
          {/* <Subject title={this.state.subject.title} sub={this.state.subject.sub} /> */}

          <header>
              <h1><a href="/" onClick={function(e){
                e.preventDefault();
                this.setState({
                  mode:'welcome'
                })
              }.bind(this)}>{this.state.subject.title}</a></h1>
              {this.state.subject.sub}
          </header>
          <Subject title="React" sub="For UI" />
          <TOC data={this.state.content} />
          <Content title={_title} desc={_desc}></Content>
        </div>
      );
  }
}

onClick={} react에서는 대문자C , 중괄호를 사용한다.(카멜케이스, JSX 문법)

리액트 이벤트 함수의 첫번째 파라미터 매개변수의 값으로 이벤트라고 하는 객체를 주입해주기로 약속 되어있다.

preventDefault 대신 debugger; 를 작성하면 크롬 개발자도구에서 debugger라고 되어있는부분에서 실행을 멈춤

e.preventDefault() ; a태그의 기본적인 동작방법을 금지시키는 메서드

이벤트에서 state를 변경할때는 2가지를 조심해야한다. setState와 this

이벤트 함수안에서 호출되는 this는 컴포넌트 자기 자신을 가르키지 않는다.

리액트에서 state를 변경할때는setState를 써야한다.

setState에 변경하고싶은 값을 객체의 형태로 넣어준다.

그럴때는 이벤트함수.bind(this) 를 작성(bind 함수)하면 되고 이 함수에서 this는 해당 컴포넌트를 가르키게 된다.

  • 화살표 함수를 사용하면 bind 하지 않아도 된다.

이벤트 bind 함수의 이해

render 안에서 this는 컴포넌트 자신을 가르킨다.

bind함수는 첫번째 인자로 넣을 this는 App 컴포넌트를 가르킨다.

이벤트 함수에 bind함수의 인자로 App 컴포넌트를 가르키는 this를 넘겨주고

이벤트 함수안의 this는 넘겨받은 App 컴포넌트를 의미한다.

그렇게 동작하는 새로운 함수가 복제가 되어 만들어진다.

(1) 공식문서의 예제

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // 콜백에서 `this`가 작동하려면 아래와 같이 바인딩 해주어야 합니다.
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

(2)공식문서의 예제

class LoggingButton extends React.Component {
  // 이 문법은 `this`가 handleClick 내에서 바인딩되도록 합니다.
  // 주의: 이 문법은 *실험적인* 문법입니다.
  handleClick = () => {
    console.log('this is:', this);
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}

(3) 공식문서의 예제

class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    // 이 문법은 `this`가 handleClick 내에서 바인딩되도록 합니다.
    return (
      <button onClick={() => this.handleClick()}>
        Click me
      </button>
    );
  }
}

매번 bind 함수를 호출하는건 번거로운 일이니 화살표 함수를 사용하자.

class App extends React.Component {
  constructor (props) {
    super (props);
    this.state = {
      mode:"read",
      welcome:{title:"Welcome", desc:"Hello, React!"},
      subject: {
        title:"WEB",
        sub:"World Wid Web!"
      },
      content:[
        {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() {
    let _title, _desc = null;
    if(this.state.mode === "welcome"){
      _title = this.state.welcome.title
      _desc = this.state.welcome.desc
    } else if(this.state.mode === "read"){
      _title = this.state.content[0].title
      _desc = this.state.content[0].desc
    }
      return (
        <div className="App">
          {/* <Subject title={this.state.subject.title} sub={this.state.subject.sub} /> */}

          <header>
              <h1><a href="/" onClick={(e)=>{
                e.preventDefault();
                this.setState({
                  mode:'welcome'
                })
              }}>{this.state.subject.title}</a></h1>
              {this.state.subject.sub}
          </header>
          <Subject title="React" sub="For UI" />
          <TOC data={this.state.content} />
          <Content title={_title} desc={_desc}></Content>
        </div>
      );
  }
}

이벤트 만들기

class App extends React.Component {
  constructor (props) {
    super (props);
    this.state = {
      mode:"read",
      selected_content_id:2,
      welcome:{title:"Welcome", desc:"Hello, React!"},
      subject: {
        title:"WEB",
        sub:"World Wid Web!"
      },
      content:[
        {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() {
    let _title, _desc = null;
    if(this.state.mode === "welcome"){
      _title = this.state.welcome.title
      _desc = this.state.welcome.desc
    } else if(this.state.mode === "read"){
      let i = 0;
      while(i<this.state.content.length){
        let data = this.state.content[i];
        if(data.id === this.state.selected_content_id){
          _title = data.title;
          _desc = data.desc;
          break;
        }
        i++;
      }
    }
      return (
        <div className="App">
          <Subject
            title={this.state.subject.title}
            sub={this.state.subject.sub}
            onChangePage={() => {
              this.setState({
                mode:'welcome'
              });
            }}
            />

          <TOC
            data={this.state.content}
            onChangePage={(id) => {
              this.setState({
                mode:'read',
                selected_content_id:Number(id)

              })

            }}
          />
          <Content title={_title} desc={_desc}></Content>
        </div>
      );
  }
}

selected_content_id 를 state에 생성 해 주고 반복문을 돌며 id값에 따라 해당 타이틀과 내용이 변경되는 이벤트 함수를 만들것이다.

onChangePage라는 이벤트를 우리가 만들어서 제공

class Subject extends React.Component {
  render() {
    return (
      <header>
        <h1><a href="/" onClick={(e) =>{
          e.preventDefault();
          this.props.onChangePage();
        }}>{this.props.title}</a></h1>
        {this.props.sub}
      </header>
    );
  }
}

props 받은 onChangePage 이벤트를 호출하게된다.

class TOC extends React.Component {
  render () {
    let data = this.props.data;
    let i = 0;
    let list = [];
    while(i<data.length){
      list.push(
        <li key={data[i].id}>
          <a
            href={"/content"+data[i].id}
            data-id={data[i].id}
            onClick={(e) => {
              e.preventDefault();
              this.props.onChangePage(e.target.dataset.id);
            }}
          >{data[i].title}</a>
        </li>)
      i++
    };
    return (
    <nav>
      <ul>
        {list}
      </ul>
    </nav>
    );
  }
}

event 객체는 target 이라는 속성이 있고

target은 이벤트가 발생한 태그 a태그를 가리킨다.

event 함수 내에서 e.target은 <a>태그를 가리킨다.

그러하면 <a>태그의 속성에 접근할 수 있다.

data-접두사로 시작되는 속성은 dataset이라는 속성으로 접근할 수 있다.

e.target.dataset.id로 data-id를 찾을 수 있다.

CRUD

Create

create를 누르면 밑에 Create 폼이 생성되게 만들어 줄 것이다.

그리고 제출을 누르면 content 데이터를 하나씩 늘리면서 create 해주는게 목표이다.

App.js

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

class App extends React.Component {
  constructor (props) {
    super (props);
    this.max_content_id = 3;
    this.state = {
      mode:"read",
      selected_content_id:2,
      welcome:{title:"Welcome", desc:"Hello, React!"},
      subject: {
        title:"WEB",
        sub:"World Wid Web!"
      },
      content:[
        {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() {
    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}/>
    } else if(this.state.mode === "read"){
      let i = 0;
      while(i<this.state.content.length){
        let data = this.state.content[i];
        if(data.id === this.state.selected_content_id){
          _title = data.title;
          _desc = data.desc;
          break;
        }
        i++;
      }
      _article = <ReadContent title = {_title} desc={_desc}/>
    } else if(this.state.mode === "create") {
      _article = <CreateContent onSubmit={(_title, _desc) => {
        // add content to this.state.content
        this.max_content_id = this.max_content_id+1;
        // this.state.content.push({id:this.max_content_id,title:_title, desc:_desc})
        let _content = this.state.content.concat({id:this.max_content_id,title:_title, desc:_desc})
        this.setState({content:_content});
        console.log(_title,_desc)
      }} />
    }
      return (
        <div className="App">
          <Subject
            title={this.state.subject.title}
            sub={this.state.subject.sub}
            onChangePage={() => {
              this.setState({
                mode:'welcome'
              });
            }}
            />

          <TOC
            data={this.state.content}
            onChangePage={(id) => {
              this.setState({
                mode:'read',
                selected_content_id:Number(id)
              })
            }}
          />
          <Control
            onChangeMode={(_mode) => {
              this.setState({
                mode:_mode
              });
            }}
          />
          {_article}
        </div>
      );
  }
}

export default App;

ReadContent는 작성된,작성한 title과 desc를 보여줄 컴포넌트이며 _article 변수에 담아서 조건에 따라 출력되게 설정해주었다.

max_content_id 변수를 생성했는데 state안이 아니라 그 위에서 객체로 생성한 이유는 데이터를 추가할때 가장 마지막 id값이 무엇일까를 보기위해서고 UI에 전혀 영향이 가지 않는 변수이기때문에 state에 있을 필요가 없기 때문이다.

Control.js

import React from "react";

class Control extends React.Component {
  render() {
    return (
      <ul>
        <li>
          <a
            href="/create"
            onClick={(e) => {
              e.preventDefault();
              this.props.onChangeMode('create');
            }}
          >create</a>
        </li>
        <li>
          <a
            href="/update"
            onClick={(e) => {
              e.preventDefault();
              this.props.onChangeMode('update');
            }}
          >update</a>
        </li>
        <li>
          <input
            type="button"
            value="delete"
            onClick={(e) => {
              e.preventDefault();
              this.props.onChangeMode('delete');
            }}
        /></li>
      </ul>
    );
  }
}

export default Control;

CRUD a태그들을 누를때마다 해당 mode로 바뀌게 하기 위한 컴포넌트이다.

CreateContent.js

import React from "react";

class CreateContent extends React.Component {
  render () {
    return (
      <article>
      <h2>Create</h2>
      <form action="/create_process" method="post"
        onSubmit={(e) => {
          e.preventDefault();
          alert('sub')
        }}
      >
        <p><input type="text" name="title" placeholder="title" /></p>
        <p><textarea type="text" name="desc" placeholder="description"></textarea></p>
        <p><input type="submit" /></p>
      </form>
    </article>
    );
  }
}

export default CreateContent;

CreateContent는 폼을 만들고 submit하여 content를 추가해주는 역할이다.

shouldComponentUpdate

import React from "react";

class TOC extends React.Component {
  shouldComponentUpdate(newProps,newState){
    if(this.props.data === newProps.data){
      return false;
    }
    return true;
  }
  render () {
    let data = this.props.data;
    let i = 0;
    let list = [];
    while(i<data.length){
      list.push(
        <li key={data[i].id}>
          <a
            href={"/content"+data[i].id}
            data-id={data[i].id}
            onClick={(e) => {
              e.preventDefault();
              this.props.onChangePage(e.target.dataset.id);
            }}
          >{data[i].title}</a>
        </li>)
      i++
    };
    return (
    <nav>
      <ul>
        {list}
      </ul>
    </nav>
    );
  }
}

export default TOC;

성능문제 해결하기위해서 좀더 알아보자

TOC 컴포넌트가 렌더링 될 필요가 없어도 계속해서 같이 렌더링 되는데 큰 프로젝트라면 이런것들이 다 성능저하의 원인이 된다.

그래서 concat 함수를 써서 복제하고 그걸로 state를 변경할때 렌더링될수 있게 하는데 state가 변경될때,props가 변경될때만 렌더링을 할수 있게 조건을 걸수 있는게 shouldComponentUpdate 함수이다.

shouldComponentUpdate 함수는 기본적으로 2개의 매개변수를 인자로 받는데 newProps와 newState이다.

false를 return 하면 render 함수를 실행하지 않고 true를 return하면 render 함수를 실행한다. 위의 코드를 잘 살펴보자

Update

App.js

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

class App extends React.Component {
  constructor (props) {
    super (props);
    this.max_content_id = 3;
    this.state = {
      mode:"welcome",
      selected_content_id:2,
      welcome:{title:"Welcome", desc:"Hello, React!"},
      subject: {
        title:"WEB",
        sub:"World Wid Web!"
      },
      content:[
        {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"}
      ]
    }
  }
  getReadContent() {
    let i = 0;
      while(i<this.state.content.length){
        let data = this.state.content[i];
        if(data.id === this.state.selected_content_id){
          return data;

        }
        i++;
      }
  }
  getContent(){
    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}/>
    } else if(this.state.mode === "read"){
      let _content = this.getReadContent();
      _article = <ReadContent title = {_content.title} desc={_content.desc}/>
    } else if(this.state.mode === "create") {
      _article = <CreateContent onSubmit={(_title, _desc) => {
        // add content to this.state.content
        this.max_content_id = this.max_content_id+1;
        // this.state.content.push({id:this.max_content_id,title:_title, desc:_desc})
        let _content = this.state.content.concat({id:this.max_content_id,title:_title, desc:_desc})
        this.setState({content:_content,mode:'read',selected_content_id:this.max_content_id});
        console.log(_title,_desc)
      }} />
    }else if(this.state.mode === "update") {
      let _content = this.getReadContent();
      _article = <UpdateContent data={_content} onSubmit={(_id,_title, _desc) => {
        let _content = Array.from(this.state.content);
        let i = 0;
        while(i < _content.length) {
          if(_content[i].id === _id) {
            _content[i] = {id:_id, title:_title, desc:_desc};
            break;
          }
          i = i + 1;
        }

        this.setState({content:_content,mode:'read'});
        console.log(_title,_desc)
      }} />
    }
    return _article;
  }

  render() {
    return (
      <div className="App">
        <Subject
          title={this.state.subject.title}
          sub={this.state.subject.sub}
          onChangePage={() => {
            this.setState({
              mode:'welcome'
            });
          }}
          />

        <TOC
          data={this.state.content}
          onChangePage={(id) => {
            this.setState({
              mode:'read',
              selected_content_id:Number(id)
            })
          }}
        />
        <Control
          onChangeMode={(_mode) => {
            if(_mode === 'delete') {
              if(window.confirm('really?')) {
                let i = 0;
                let _content = Array.from(this.state.content);
                while(i < _content.length){
                  if(_content[i].id === this.state.selected_content_id){
                    _content.splice(i,1)
                    break;
                  }
                  i = i + 1;
                }
                this.setState({
                  mode:'welcome',
                  content:_content
                })
              }
            } else {
              this.setState({
                mode:_mode
              });
            }
          }}
        />
        {this.getContent()}
      </div>
    );
  }
}

export default App;

UpdateContent.js

import React from "react";

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

  inputFormHandler(e) {
    this.setState({[e.target.name]:e.target.value});
  }

  render () {
    return (
      <article>
      <h2>Update</h2>
      <form action="/create_process" method="post"
        onSubmit={(e) => {
          e.preventDefault();
          // debugger;
          this.props.onSubmit(
            this.state.id,
            this.state.title,
            this.state.desc,

          );
        }}
      >
        <input type="hidden" name="id" value={this.state.id} />
        <p>
          <input
            type="text"
            name="title"
            placeholder="title"
            value={this.state.title}
            onChange={(e)=>{
              // console.log(e.target.value);
              this.inputFormHandler(e);
            }}
          />
        </p>
        <p>
          <textarea
            type="text"
            name="desc"
            placeholder="description"
            value={this.state.desc}
            onChange={(e)=>{
              this.inputFormHandler(e);
            }}
          ></textarea>
        </p>
        <p><input type="submit" /></p>
      </form>
    </article>
    );
  }
}

export default UpdateContent;

form

바로 props받은 데이터를 넣어버리면 read-only 이기 때문에 값을 수정할수 없음

그래서 state화 시켜준다.

그리고 onChange를 필수로 넣어준다.

[e.target.name]은 target의 name값이 들어 오게 된다.

오리지날 컨텐츠를 직접 수정하지 않고 복제해서 새로운 배열을 만들어내기위해 (불변,나중에 성능 튜닝할때 필요) Array.from 을 사용

Array,from(this.state.contents)을 하면 this.state.contents의 값을 하나 더 복제

수정하면서 저장될때 mode:'read'를 입력하면 수정한것 내용보는곳으로 이동한다.

Delete

App.js

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

class App extends React.Component {
  constructor (props) {
    super (props);
    this.max_content_id = 3;
    this.state = {
      mode:"welcome",
      selected_content_id:2,
      welcome:{title:"Welcome", desc:"Hello, React!"},
      subject: {
        title:"WEB",
        sub:"World Wid Web!"
      },
      content:[
        {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"}
      ]
    }
  }
  getReadContent() {
    let i = 0;
      while(i<this.state.content.length){
        let data = this.state.content[i];
        if(data.id === this.state.selected_content_id){
          return data;

        }
        i++;
      }
  }
  getContent(){
    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}/>
    } else if(this.state.mode === "read"){
      let _content = this.getReadContent();
      _article = <ReadContent title = {_content.title} desc={_content.desc}/>
    } else if(this.state.mode === "create") {
      _article = <CreateContent onSubmit={(_title, _desc) => {
        // add content to this.state.content
        this.max_content_id = this.max_content_id+1;
        // this.state.content.push({id:this.max_content_id,title:_title, desc:_desc})
        let _content = this.state.content.concat({id:this.max_content_id,title:_title, desc:_desc})
        this.setState({content:_content,mode:'read',selected_content_id:this.max_content_id});
        console.log(_title,_desc)
      }} />
    }else if(this.state.mode === "update") {
      let _content = this.getReadContent();
      _article = <UpdateContent data={_content} onSubmit={(_id,_title, _desc) => {
        let _content = Array.from(this.state.content);
        let i = 0;
        while(i < _content.length) {
          if(_content[i].id === _id) {
            _content[i] = {id:_id, title:_title, desc:_desc};
            break;
          }
          i = i + 1;
        }

        this.setState({content:_content,mode:'read'});
        console.log(_title,_desc)
      }} />
    }
    return _article;
  }

  render() {
    return (
      <div className="App">
        <Subject
          title={this.state.subject.title}
          sub={this.state.subject.sub}
          onChangePage={() => {
            this.setState({
              mode:'welcome'
            });
          }}
          />

        <TOC
          data={this.state.content}
          onChangePage={(id) => {
            this.setState({
              mode:'read',
              selected_content_id:Number(id)
            })
          }}
        />
        <Control
          onChangeMode={(_mode) => {
            if(_mode === 'delete') {
              if(window.confirm('really?')) {
                let i = 0;
                let _content = Array.from(this.state.content);
                while(i < _content.length){
                  if(_content[i].id === this.state.selected_content_id){
                    _content.splice(i,1)
                    break;
                  }
                  i = i + 1;
                }
                this.setState({
                  mode:'welcome',
                  content:_content
                })
              }
            } else {
              this.setState({
                mode:_mode
              });
            }
          }}
        />
        {this.getContent()}
      </div>
    );
  }
}

export default App;

한번 다시 확인하는것 confirm 함수

confirm은 무조건 window와 함께 써야한다.

confirm은 확인을 누르면 True가 되고 캔슬을 누르면 false가 된다.

지울땐 splice

어디서부터 몇개를 지울것인가

Control.js

import React from "react";

class Control extends React.Component {
  render() {
    return (
      <ul>
        <li>
          <a
            href="/create"
            onClick={(e) => {
              e.preventDefault();
              this.props.onChangeMode('create');
            }}
          >create</a>
        </li>
        <li>
          <a
            href="/update"
            onClick={(e) => {
              e.preventDefault();
              this.props.onChangeMode('update');
            }}
          >update</a>
        </li>
        <li>
          <input
            type="button"
            value="delete"
            onClick={(e) => {
              e.preventDefault();
              this.props.onChangeMode('delete');
            }}
        /></li>
      </ul>
    );
  }
}

export default Control
profile
Front-End Developer

0개의 댓글