본문 바로가기

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

[2023 신입부원 심화 스터디] 신진욱 #마지막주 React.js 섹션 3 - 6 #귀칼보단 리액트지 ㅋ

반응형

섹션 3 - State 소개

1. state 소개

- state는 props의 값에 따라서 내부에 구현에 필요한 데이터들 

 

2. state의 사용

class App extends Component {
  constructor(props) {
    super(props);
    this.state = { // state에서 subject를 초기화
      Subject: { title: "WEB", sub: "World wid Web!" } // 하나의 값을 다룰 때!
    }; 
  }
  render() {
    return (
      <div ClassName="App">
        <Subject>
          title={this.state.Subject.title}
          sub={this.state.Subject.sub}
        </Subject>
        <TOC></TOC>
        <Content></Content>
      </div>
    );
  }
}
export default App;

- App.js가 내부적으로 사용된다면 state를 통해 사용한다.

- Component를 실행할 때 constructor 함수는 가장 먼저 실행되며 초기화를 담당한다.

- state 값을 subject 컴포넌트에 props 값으로 전달 -> 상위 컴포넌트(App)에서 하위 컴포넌트(Subject)로 값 전달

 

3. key

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      Subject: { title: "WEB", sub: "World wid 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></Content>
      </div>
    );
  }
}
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={"/contenst/" + data[i].id}>{data[i].title}</a></li>);
      // 여러 개의 목록을 자동으로 생성할 때는 key를 지정해줘야 한다.
      i = i + 1;
    }
    return (
      <nav>
        <ul>
       	  {lists}
        </ul>
      </nav>
    );
  }
}

export default TOC;

- TOC.js를 통하지 않고 내부 데이터의 목록을 바꾸기 가능

- lists.push(<li><a href={"/contenst/" + data[i].id}>{data[i].title}</a></li>); 처럼 여러개의 element를 자동으로 생성하는 경우에는 key라고 하는 props를 가져야 한다고 에러 발생

- App 컴포넌트의 내부 state(contents)를 TOC에 주입하는 것(data)을 통하여 TOC의 내부 데이터를 바뀌게 할 것

 

 

섹션 4 - 이벤트 

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

- props, state, event가 서로 상호작용하면서 역동성을 부여한다.

- react에서는 props나 state의 값이 바뀌면 해당되는 컴포넌트의 render()함수가 호출되도록 약속되어있다. 

- render 함수는 어떤 html을 표시할 것인가를 결정한다.

- state의 값이 바뀌면 해당 state 값을 가지고 있는 컴포넌트의 render 함수 재호출 → render 함수에 있는 컴포넌트들 재호출 ⇒ 결론적으로 html이 다시 그려짐

import React, { 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 wid Web!" },
      welcome: { title: "Welcom", 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;

- mode 값에 따라서 다른 render()함수가 호출되게 하도록! (아직 직접 mode를 바꿔줘야함.)

 

2. 이벤트 설치 

- Subject 컴포넌트 내용을 직접 가지고 와서 App에 도입 후 이벤트를 구현

import React, { 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 wid Web!" },
      welcome: { title: "Welcom", 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> */}
        <header>
          <h1>
            <a
              href="/"
              onClick={function (e) {
                e.preventDefault(); // 이벤트의 기본 동작을 못하게 함 (여기서는 초기화 방지)
              }}>{this.state.Subject.title}</a></h1>
          	  {this.state.Subject.sub}
        </header>
        <TOC data={this.state.contents}></TOC>
        <Content title={_title} desc={_desc}></Content>
      </div>
    );
  }
}

export default App;

- 아직 Web을 클릭 했을 때 mode가 welcome으로 바뀌지 않음

 

3. 이벤트에서 state 변경하기 

<header>
	<h1><a href="/" onClick={fuction(e) {
	  e.preventDefault(); // 이벤트의 기본 동작을 못하게 함 (여기서는 초기화 방지)
	  // this.state.mode = 'welcome'; 
	  // -> 1. bind 함수가 없으면 this가 어떤 컴포넌트인지 모른다. 2. bind 함수 사용해도 state 변경 값 전달 불가
	  this.setState({
		mode:'welcome'
	  })
	}.bind(this)}>{this.state.subject.title}</a></h1>
    {this.state.subject.sub}
</header>

- 이벤트 함수.bind(this) → 이벤트 함수 내의 this 컴포넌트 지정 (여기선 App 컴포넌트!)

- this.setState({변경값}) → 이벤트 함수 내에서 state 값 변경 전달

- Web 클릭하면 mode 값이 welcome으로 바뀜

 

 

4. 이벤트 bind함수 이해하기

- render 함수 내에서 this는 render 함수가 속한 컴포넌트를 가리키는데 함수에서는 this가 아무값도 없다.

- ex) 실습 과정에 있는 header 태그 안 이벤트 함수 속 this는 undefined이다.

- this값을 확실히 하 위해서 bind함수를 사용하는 것

- bind() → 기존 함수를 복제하여 this 값을 지정 후 새로 만든다.

https://devmoony.tistory.com/71

 

5. 이벤트 setState함수 이해하기 

- 컴포넌트 생성할 때(constructor) state 값은 직접 변경 가능

- 컴포넌트의 생성이 끝난 후에 동적으로 state의 값을 바꿀 때에는 this.state.mode = ‘welcome’;처럼 변경 불가.

→ setState()함수를 통해 변경하고 싶은 객체를 전달하는 방식으로 변경해야함.

 

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

import React, {Component} from 'React';
import TOC from "./components/TOC";
import Subject from "./components/Subject";
import Content from "./components/Content";
import './App.css';

class App extends Component {
	constructor(props) {
	  super(props);
	  this.state = {
		mode:'welcome',
		selected_contented_id:2,
		subject:{title:'WEB', sub:'World Wid Web!'},
		welcome:{title:'Welcome', desc:'Hi, 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') {
			var i = 0;
			while(i < this.state.contents.length){
			  var data = this.state.contents[i];
			  if(data.id === this.state.selected_content_id {
				_title = this.state.contents[i].title;
				_desc = this.state.contents[i].desc;
				break;
			  }
			  i = i + 1;
			}
		}
	return (
		<div className="App">
			<Subject 
				title={this.state.subject.title}
				sub={this.state.subject.sub}>
				onChangePage={fuction(){ // 이벤트 실행 했을때 사용할 함수 지정
					this.setState({mode:'welcome'});
				}.bind(this)}
			</Subject>
			<TOC 
				onChangePage={function(id){
				this.setState({
					mode:'read',
					selected_content_id:Number(id) // id가 문자열이기 때문에 숫자로 강제변환
				  });
				}.bind(this)}
				data={this.state.contents}
			></TOC>
				<Content title={_title} desc={_desc}></Content>
			</div>
        ); 
    }
}

export default App;
import React, {Component} from 'React';

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

export default Subject;
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-id={data[i].id} // data-""
					onClick={function(e){ // e는 a태그를 가리킨다.
						e.preventDefault();
						this.props.onChangePage(e.target.dataset.id); // dataset.""
					}.bind(this)}
				>{data[i].title}</a>
			</li>);
		i = i + 1;
	}
        return ( 
		  <nav>
			<ul>
			  {lists}
			</ul>
		  </nav>
        );
    }
}

export default TOC;

- Subject, TOC 컴포넌트에 onChangePage라는 이벤트를 만든다.

- 해당 이벤트에 함수를 설치해주면 이벤트가 발생되었을 때,

- props로 전달된 onChagePage라는 함수를 호출

- e.target.dataset.id 를 통해 값을 꺼낸다.

 

섹션 5 - Create 구현

1. 베이스캠프

- redux : 하나의 데이터 저장소가 있어서, 그와 관련된 모든 컴포넌트들이 알아서 바뀌게 한다.

 

2. Create 구현 : mode 변경 기능

- Control.js 추가 구현하기

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

 

3. create 구현 : mode 전환 기능 

- App컴포넌트의 mode가 create가 될때, ReadContent가 CreateContent로 바꾸기

// App.js
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 = this.state.contents[i].title;
			_desc = this.state.contents[i].desc;
			_article = <ReadContent title={_title} desc={_desc}></ReadContent>;
				break;
			}
			i = i + 1;
		}
	} else if (this.state.mode === 'create') {
		_article = <CreateContent></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>
		<Content title={_title} desc={_desc}></Content>
	</div>
);
// 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;
//CreateContent.js
import React, { Component } from "react";

class CreateContent extends Component {
  render() {
    return (
      <article>
        <h2>Create</h2>
        <form>
        </form>
      </article>
    );
  }
}

export default CreateContent;

 

4. create 구현 : form

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는 form 자체를 가리키고 있음
			     e.preventDefault();
			   }.bind(this)} // html fomr 태그가 가지고 있는 고유 기능
			 >
			   <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;

 

5. create 구현 : onSubmit 이벤트 

// App.js
else if (this.state.mode === 'create') {
	_article = <CreateContent onSubmit={function(_title, _desc){
	}.bind(this)}></CreateContent>
}
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는 form 자체를 가리키고 있음
			     e.preventDefault();
				 this.props.onSubmit(
				   e.target.title.value,
				   e.target.desc.value
				 );
			   }.bind(this)} // html fomr 태그가 가지고 있는 고유 기능
			 >
			   <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;

 

6. create 구현 : contents변경

  • 배열에 데이터를 추가하는 방법으로는 2가지가 있다.
    • push → 원래 배열이 바뀐다.
    • concat → 원래 배열이 바뀌지 않는다. (새로운 배열 리턴)
class App extends Component {
		constructor(props) {
			super(props);
			this.max_content_id = 3; // ui에 영향을 줄 값이 아니기에 state 값으로 하지 않는다.
			...
    render() {
			...
				} else if (this.state.mode === 'create') {
					_article = <CreateContent onSubmit={function(_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({
						content:_contents
					});
					}.bind(this)}></CreateContent>
				}
        return (
            <div className="App">
                <Subject 
				  title={this.state.subject.title}
				  sub={this.state.subject.sub}>
				  onChangePage={fuction(){ // 이벤트 실행 했을때 사용할 함수 지정
				  	this.setState({mode:'welcome'});
				  }.bind(this)}
				</Subject>
				<TOC 
				  onChangePage={function(id){
					this.setState({
					  mode:'read',
					  selected_content_id:Number(id) // id가 문자열이기 때문에 숫자로 강제변환
					});
				  }.bind(this)}
				  data={this.state.contents}
				></TOC>
				<Control onChangeMode={function(_mode){
				  this.setState({
					mode:_mode;
				  });
				  }.bind(this)}></Control>
				  {_article}
			</div>
        ); 
    }
}

export default App;

 

섹션 6 - Update와 Delete 구현

1. update 구현

//UpdateContent.js
import React, { Component } from "react";

class UpdateContent extends Component {
  render() {
    return (
      <article>
        <h2>Update</h2>
        <form
          action="/create_process" method="post" onSubmit={function (e) { // e는 form 태그 자체를 가리키고 있음
            e.preventDefault();
          }.bind(this)} // html form 태그가 가지고 있는 고유 기능
        >
          <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 UpdateContent;

2. update 구현 : form

- inputFormHandler 함수를 만들어서 코드를 간결하게 표현

//UpdateContent.js
import React, { Component } from "react";

class UpdateContent extends Component {
  constructor(props){
  	super(props);
    this.state = {
      title:this.props.data.title,
      desc:this.props.date.desc
    }
    this.inputFormHandler = this.inputFormHandler.bind(this); // 이렇게 하면 이 함수를 쓸 때 bind 함수를 안적어도 된다.
   }
   
   inputFormHandler(e){
     this.setState({[e.target.name]:e.target.value});
   }
   
  render() {
    return (
      <article>
        <h2>Update</h2>
        <form
          action="/create_process" method="post" onSubmit={function (e) { // e는 form 태그 자체를 가리키고 있음
            e.preventDefault();
          }.bind(this)} // html form 태그가 가지고 있는 고유 기능
        >
          <p>
            <input 
            type="text" 
            name="title" 
            placeholder="title"
            value={this.state.title}
            // this.props.date.title로 하면은 리액트가 개입해서 readonly로 바뀌어 수정 불가
            // but 아직 수정 불가 -> onChange를 써줘야한다! 
            ></input>
          </p>
          <p>
            <textarea onChange={this.inputFormHandler}
            name="desc" 
            placeholder="description"></textarea>
          </p>
          <p>
            <input type="submit"></input>
          </p>
        </form>
      </article>
    );
  }
}

export default UpdateContent;

 

3. update 구현 : state변경

import React, {Component} from 'React';

class UpdateContent extends Component {
		constructor(props){
			super(props);
			this.state = {
				title:this.props.data.title,
				desc:this.props.date.desc
			}
			this.inputFormHandler = this.inputFormHandler.bind(this);
		}
		
		inputFormHandler(e){
			this.setState({[e.target.name]:e.target.value});
		}

    render() {
        return ( 
			<article>
				<h2>Update</h2>
				<form action="/create_process" method="post"
					onSubmit={function(e){ // e는 form 자체를 가리키고 있음
						e.preventDefault();
						this.props.onSubmit(
							this.state.id,
							this.state.title,
							this.state.desc
						);
					}.bind(this)} // html form 태그가 가지고 있는 고유 기능
				>
				<input type="hidden" name="id" value={this.state.id}></input> 
				// id가 사용자에게 보이지 않게 하기 위해 hidden 폼 사용
				// change 일어날 일 x, 눈에 보이지 않기 때문에 onChange 필요 x
				<p>
					<input 
						type="text" 
						name="title"
						placeholder="title"
						value={this.state.title} 
						onChange={this.inputFormHandler}
					></input>
				</p>
				<p>
					<textarea onChange={this.inputFormHandler}
						name="desc" 
						placeholder="description" 
						value={this.state.desc}></textarea>
				</p>
				<p>
					<input type="submit"></input>
				</p>
				</form>
			</article>
		);
    }
}

export default UpdateContent;

4. delete구현

- window.confirm → 사용자에게 진짜 삭제할 건지 확인

// App.js
class App extends Component { 
			...
			<Control onChangeMode={function(_mode){
				if(_mode === 'delete'){
					if(window.comfirm('really?')){ // 정말 삭제할건지 확인 받기
						var _contents = Array.from(this.state.contents);
						var i = 0;
						while(i < this.state.contents.length){
							if(_contents[i].id === this.state.selected_content_id){
								_contents.splice(i,1) // 어디부터 어디까지를 지울 것인가? (원본을 바꿈)
								break;
							}
							i = i + 1;
						}
						this.setState({
							mode:'welcome',
							contents:_contents
						});
						alert('deleted!');
						}
					} else {
						this.setState({
							mode:_mode;
							});
						}
					}.bind(this)}></Control>
					{this.getContent()}
        ); 
    }
}

export default App;
// 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>
			);
		}
반응형