본문 바로가기

WINK-(Web & App)/HTML & CSS & JS 스터디

[2023 신입부원 심화 스터디] 이정욱 #마지막주차 React.js 섹션3 ~섹션7

반응형

State

state와 props

  1. props
  • props는 컴포넌트 외부에서 전달되는 읽기 전용 데이터이다.
  • Props는 함수 매개변수처럼 동작하며, 컴포넌트가 렌더링될 때마다 값이 전달됩니다.
  1. state
  • state는 컴포넌트의 내부 상태를 나타내는 데이터입니다.
  • 컴포넌트 내에서 선언하고 초기화되며, 컴포넌트의 생명주기 동안 변경될 수 있습니다.
  • 컴포넌트 내에서 **this.state**를 통해 접근하고, **this.setState()**를 사용하여 업데이트합니다.

App.js

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      subject: { title: "WEB", sub: "World Wide Web!!!" },
      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() {
    return (
      <div className="App">
        <Subject
          title={this.state.subject.title}
          sub={this.state.subject.sub}
        ></Subject>
        <TOC data={this.state.contents}></TOC>
        <Content
          title="HTML"
          desc="HTML is Hyper Text Markup Language"
        ></Content>
      </div>
    );
  }
}

export default App;

component가 실행될 때 constructor라는 함수가 있다면 가장 먼저 실행돼서 초기화를 해준다.

constructor에서는 App이라는 Component는 자바스크립트의 Component를 상속받았으므로 super(props);를 통해 Componenet의 생성자를 호출하여 초기화를 해준다. 그 뒤에 App의 초기화를 진행해준다.

위에서는 subject와 contents라는 state들을 초기화 해주었다.

TOC.js

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

TOC.js도 App.jsp에서 data라는 props를 통해 this.state.contents의 내용을 받아왔다. 해당 내용을 통해 받아온 내용을 토대로 TOC컴포넌트를 구성해준다.

4.이벤트

16.1. 이벤트 state props 그리고 render 함수

react에서 props나 state의 값이 바뀌면 해당되는 컴포넌트의 render() 함수가 다시 호출되어 화면을 재구성한다.

// import logo from './logo.svg';
import { Component } from "react";
import TOC from "./components/TOC";
import Content from "./components/Content";
import Subject from "./components/Subject";
import "./App.css";

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      mode: "welcome",
      subject: { title: "WEB", sub: "World Wide Web!!!" },
      welcome: { title: "welcome", desc: "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() {
    var _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.contents[0].title;
      _desc = this.state.contents[0].desc;
    }
    return (
      <div className="App">
        <Subject
          title={this.state.subject.title}
          sub={this.state.subject.sub}
        ></Subject>
        <TOC data={this.state.contents}></TOC>
        <Content title={_title} desc={_desc}></Content>
      </div>
    );
  }
}

export default App;

render안의 if문을 통해 this.state.mode 의 값에 따라 state의 welcome 값을 변경해주었다.

16.2. 이벤트설치

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

App.js 에서 기존에 Subject Component를 불러오던 부분에 Subject.js의 내용을 대신 넣어주었다.

그리고 onClick이 발생하면 e.preventDefault();함수를 통해 이벤트가 발생히면 해당 태그에서 기본적으로 발생하는 기능을 하지 않도록 설정하였다.

16.3. 이벤트에서 state변경하기 ~ 16.5. 이벤트 setState 함수 이해하기

onClick={function (e) {
        console.log(e);
        e.preventDefault();
        // this.state.mode = "welcome";
        this.setState({
          mode: "welcome",
        });
      }.bind(this)}

onClick 내용을 수정하여 이벤트가 발생하면 mode를 ‘welcome’으로 변경해주었다.

state를 변경해주는데 그냥 초기화할때와 같이 this.state.mode = "welcome"; 이런식으로 해서는 안되고this.setState() 를 이용해야 한다.

그 이유는 이미 컴포넌트가 생성된 이후에 동적으로 변경을 해줄 때는 setState()를 해주어야 한다. 그 이유는 react가 state의 값이 바뀌었다는 사실을 해당 함수를 통해 인지시켜주기 위해서이다.

여기서 함수 맨 뒤에 붙어있는 bind() 함수는 해당 함수에서 this의 값을 고정시켜주는 함수이다.

17. 컴포넌트 이벤트 만들기

App.js

// import logo from './logo.svg';
import { Component } from "react";
import TOC from "./components/TOC";
import Content from "./components/Content";
import Subject from "./components/Subject";
import "./App.css";

class App extends Component {
  constructor(props) {
    super(props);
    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: "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");
    var _title,
      _desc = null;
    if (this.state.mode === "welcome") {
      _title = this.state.welcome.title;
      _desc = this.state.welcome.desc;
    } 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;
      }
    }
    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>
        <Content title={_title} desc={_desc}></Content>
      </div>
    );
  }
}

export default App;

TOC.js

import { Component } from "react";

class TOC extends Component {
  render() {
    console.log("TOC 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) {
              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>
    );
  }
}

export default TOC;

우리가 원하는 동작은 화면에서 A 태그로 감싸진 WEB, HTML, CSS, JS를 누를 때 마다 그에 맞는 화면을 보여주는 것이다.

이를 위해 우선 TOC 컴포넌트 안에 있는 요소들을 클릭하게 되면 onClick에서 this.props.onChangePage 함수를 통해 클릭된 태그의 id를 넘겨준다. 이를 App.js에서 받아서 selected_content_id: Number(id) 를 통해 해당 변수의 값을 변경해주고 state의 값이 변경되었으니 이를 토대로 다시 렌더링된다.

섹션5. Create기능 구현

  • 앞으로 create, update, delete 기능을 구현하게 될 것임.
  • create를 누르면 title과 desc를 입력하면 해당 내용을 화면에 새로 구성

App.js

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

class App extends Component {
  constructor(props) {
    super(props);
    this.max_content_id = 3;
    this.state = {
      mode: "create",
      selected_content_id: 2,
      subject: { title: "WEB", sub: "World Wide Web!" },
      welcome: { title: "Welcome", desc: "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");
    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
          onSubmit={function (_title, _desc) {
            console.log(_title, _desc);
            this.max_content_id = this.max_content_id + 1;
            var _contents = this.state.contents.concat({
              id: this.max_content_id,
              title: _title,
              desc: _desc,
            });
            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>
        <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;

우선 Control이라는 컴포넌트를 추가해주었다.

Control.js

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

Control이라는 컴포넌트는 create, update, delete 기능들을 담은 버튼(a태그)이 눌릴 때 마다 onChangeMode()함수를 통해 state의 mode값을 변경해준다.

state의 mode값이 변경될 때 다시 랜더링 되는 동안 Content들을 다시 보여주기 위해서 Content 컴포넌트를 아래와 같이 CreateContent와 ReadContent로 분리해주었다.

CreateContent.js

import { 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, e.target.desc.value);
            alert("Sumbit!!!");
          }.bind(this)}
        >
          <p>
            <input type="text" name="title" placeholder="title"></input>
          </p>
          <p>
            <textarea name="desc" placeholder="desc"></textarea>
          </p>
          <p>
            <input type="submit"></input>
          </p>
        </form>
      </article>
    );
  }
}

export default CreateContent;

ReadContent.js

import { Component } from "react";

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

export default ReadContent;

CreateContent에서 form 제출이 되면 title과 desc에 작성된 내용들을 this.props.onSubmit을 통해 넘겨준다.

이렇게 넘겨준 내용을 통해 App.js에서 contents에 추가하여(직접 추가하지 않고 추가된 내용을 새로운 변수에 담는다.) 화면에 보여주게 된다.

섹션6 . Update 기능 구현

App.js

// import logo from './logo.svg';
import { Component } from "react";
import TOC from "./components/TOC";
import ReadContent from "./components/ReadContent";
import Subject from "./components/Subject";
import Control from "./components/Control";
import "./App.css";
import CreateContent from "./components/CreateContent";
import UpdateContent from "./components/UpdateContent";

class App extends Component {
  constructor(props) {
    super(props);
    this.max_content_id = 3;
    this.state = {
      mode: "create",
      selected_content_id: 2,
      subject: { title: "WEB", sub: "World Wide Web!" },
      welcome: { title: "Welcome", desc: "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" },
      ],
    };
  }
  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") {
      console.log("clickedCreate");
      _article = (
        <CreateContent
          onSubmit={function (_title, _desc) {
            console.log(_title, _desc);
            this.max_content_id = this.max_content_id + 1;
            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") {
      console.log("clickedUp");
      _content = this.getReadContent();
      _article = (
        <UpdateContent
          data={_content}
          onSubmit={function (_id, _title, _desc) {
            var _contents = Array.from(this.state.contents);
            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,
              mode: "read",
            });
          }.bind(this)}
        ></UpdateContent>
      );
    }
    return _article;
  }
  render() {
    console.log("App 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>
        <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

import { 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) {
    this.setState({ [e.target.name]: e.target.value });
  }
  render() {
    console.log(this.props.data);
    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
            );
            alert("Sumbit!!!");
          }.bind(this)}
        >
          <input type="hidden" name="id" value={this.state.id}></input>
          <p>
            <input
              type="text"
              name="title"
              placeholder="title"
              value={this.props.data.title}
              onChange={this.inputFormHandler}
            ></input>
          </p>
          <p>
            <textarea
              onChange={this.inputFormHandler}
              name="desc"
              placeholder="desc"
              value={this.state.desc}
            ></textarea>
          </p>
          <p>
            <input type="submit"></input>
          </p>
        </form>
      </article>
    );
  }
}

export default UpdateContent;

왜 title 수정이 안될까요? 열받네

반응형