특정 디렉토리를 리액트 앱으로 지정하는 방법
리액트 앱 실행하는 방법
사용자 정의 컴포넌트
App.css는 App.js에 대한 css 파일이다.
index.js에서 사용자 정의 컴포넌트를 사용하여 앱을 구성한다. index.html과 index.css도 마찬가지로 index.js에 대한 파일이다.
리액트 앱 빌드
디렉토리 build 안에 있는 index.html 등의 파일을 보면 기존의 파일에 비해 불필요한 부분이 지워져 있거나 주석 처리되어 있다.
배포했을 때 사용자에게 필요하지 않은 부분은 생략하는 것이다.
※ npm이 아니라 npx
-s build는 build라는 디렉토리에 서버를 설치한다는 의미(?)
서버 설치 후 나오는 두 주소 중 아무거나에 접속하면 된다.
새로고침 했을 때 빌드하지 않은 앱은 1.6MB의 리소스를 갖고 빌드한 앱은 144kB의 리소스를 갖는다.
1MB = 2^10kB = 1024kB
빌드한 앱은 개발자에게만 필요하고 사용자에게는 필요하지 않은 코드를 생략하여 용량을 줄인다.
리액트를 사용하는 이유
리액트 사용 전후 코드 비교
리액트 사용 전
<html>
<body>
<header>
<h1>WEB</h1>
world wide web!
</header>
<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>
<article>
<h2>HTML</h2>
HTML is HyperText Markup Language.
</article>
</body>
</html>
리액트 사용 후
import React, { Component } from 'react';
import './App.css';
class Subject extends Component {
render() {
return (
<header>
<h1>{this.props.title}</h1>
{this.props.sub}
</header>
);
}
}
class TOC extends Component { // Teble of Contents
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>
);
}
}
class Content extends Component {
render() {
return(
<article>
<h2>{this.props.title}</h2>
{this.props.desc}
</article>
);
}
}
class App extends Component {
render() {
return (
<div className="App">
<Subject title="WEB" sub="world wide web!"></Subject>
<Subject title="React" sub="For UI"></Subject>
<TOC></TOC>
<Content title="HTML" desc="HTML is HyperText Markup Language."></Content>
</div>
);
}
}
export default App;
원래 텍스트로 되어 있던 부분을 {this.props.title}처럼 변경하여 함수처럼 사용하는 것을 리팩토링이라고 한다.
컴포넌트를 파일로 분리
import React, { Component } from 'react'; // 리액트로 코딩할 때 필수적인 부분
class TOC extends 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;
App.js에서 import (컴포넌트 이름) from"./components/(컴포넌트 파일)";을 작성하면 컴포넌트를 사용할 수 있다.
여기서 컴포넌트 이름은 컴포넌트 파일에서 export할 때 지정해준 이름이다.
State
App.js의 코드를 constructor를 사용하여 수정했다.
constructor에는 초기값으로 사용할 값을 작성해둔다.
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 = { // 내부적인 부분
subject:{title:'WEB', sub:'World Wide Web!'}
}
}
render() {
return (
<div className="App">
<Subject
title={this.state.subject.title}
sub={this.state.subject.sub}>
</Subject>
<TOC></TOC>
<Content title="HTML" desc="HTML is HyperText Markup Language."></Content>
</div>
);
}
}
export default App;
chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=ko
크롬 확장 프로그램인 React Developer Tools를 다운로드 받으면
이와 같이 컴포넌트에 대한 내부 정보(State)를 볼 수 있다.
Elements 탭에서는 App.js의 코드만 보여주기 때문에 컴포넌트의 이름(<컴포넌트></컴포넌트>)만 볼 수 있다.
State 사용 예시
App.js
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 = { // 내부적인 부분
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 HyperText Markup Language."></Content>
</div>
);
}
}
export default App;
props는 properties의 줄임말이고 constructor에서 subject와 content는 각각 하나의 prop이다.
content의 경우처럼 같은 속성(Attribute)에 대해 여러 가지 값을 가질 때 배열(Array)처럼 작성한다.
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;
리액트에서 <li> 태그에 key 값이 없으면 콘솔에 에러 메시지가 뜬다.
태그의 속성 값이 따옴표 한 쌍으로 묶이지 않는 경우(jsx(js) 문법을 사용하는 경우)에는 중괄호({})로 묶어야 한다.
이벤트
리액트는 state가 바뀔 때마다 render를 다시 호출한다. 이는 각 컴포넌트 코드에 console.log를 사용하여 확인해볼 수 있다.
App.js에서 아래와 같은 부분을
<Subject
title={this.state.subject.title}
sub={this.state.subject.sub}>
</Subject>
<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>
이와 같이(Subject.js의 구조와 같이) 바꿔준다.
e.preventDefault는 현재 태그(<a>)의 역할인 지정된 주소로 이동하는 동작을 막는다.(기본 역할 외에 다른 이벤트를 적용할 때 주로 사용)
this.state.mode = 'welcome'의 경우 this에 아무 값도 세팅되어 있지 않다. 이 상태로 실행하면 this를 찾을 수 없다는 에러가 뜨는데 이는 함수 끝에 .bind(this)를 붙여주면 해결이 된다.
하지만 이렇게 해서는 리액트가 state가 바뀐 걸 인식하지 못한다. 동적으로 작동하게 하려면 this.setState를 사용해야 한다.
※ 참고: debugger; 라는 코드를 넣으면 웹 브라우저에서 개발자 도구를 켰을 때 그 지점에서 실행을 멈춤
※ 참고: bind 함수 이해하기(다시 공부)
컴포넌트 이벤트 만들기
onChangePage라는 이벤트를 만들어 보자.
App.js
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:'read',
subject:{title:'WEB', sub:'World Wide Web!'},
welcome:{title:'Welcome', desc:'Hello, React!!'}, // status가 바뀌면 render 함수가 다시 호출됨
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') {
_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}
onChangePage={function(){
this.setState({mode:'welcome'});
}.bind(this)}>
</Subject>
<TOC data={this.state.contents}></TOC>
<Content title={_title} desc={_desc}></Content>
</div>
);
}
}
export default App;
onChangePage 이벤트를 state를 바꿔주는 함수로 정의했다.
※ 참고: ===
developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Comparison_Operators
Subject.js
import React, { Component } from 'react';
class Subject extends Component {
render() {
console.log('Subject 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;
<a> 태그에 onClick 이벤트를 추가해준다.
우선 <a> 태그의 원래 역할을 막고 onChangePage 이벤트를 호출한다.
함수 맨 끝에 .bind(this) 쓰는 거 잊지 말 것!
App.js에서 TOC의 코드를 변경한다.
<TOC
onChangePage={function(){
alert('hi');
this.setState({mode:'read'});
}.bind(this)}
data={this.state.contents}>
</TOC>
TOC.js
import React, { 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}
onClick={function(e){
e.preventDefault();
this.props.onChangePage();
}.bind(this)}>
{data[i].title}</a>
</li>)
i = i + 1;
}
return (
<nav>
<ul>
{lists}
</ul>
</nav>
);
}
}
export default TOC;
컨텐트 목록 중 하나를 클릭했을 때 hi라고 알림창이 뜨면 정상 작동되는 것이다.
이제 컨텐트를 선택했을 때 각 컨텐트 별로 내용이 바뀌게 해보자.
App.js의 TOC 컴포넌트
<TOC
onChangePage={function(id){
this.setState({
mode:'read',
selected_content_id:Number(id)
});
}.bind(this)}
data={this.state.contents}>
</TOC>
onChangePage 함수의 매개변수로 id를 받는다. 이 id에 따라 selected_content_id의 상태를 바꿔준다.
※ 참고: Number는 js에서 숫자인 문자열을 숫자로 형변환해줌
developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Number
TOC.js
lists.push(
<li key={data[i].id}>
<a
href={"/content/"+data[i].id}
data-id={data[i].id} // dataset
onClick={function(e){
e.preventDefault();
this.props.onChangePage(e.target.dataset.id);
}.bind(this)}>
{data[i].title}</a>
</li>)
접두사로 'data-'가 붙은 변수는 dataset을 의미한다.
e.target을 이용해 e(이벤트)가 소재한 태그를 알 수 있다.
따라서 onChangePage에 매개변수로 넘겨 준 e.target.dataset.id는 a 태그 안의 data-id의 값이다.
(onClick 이벤트가 실행 중일 때 debugger를 이용해 멈추면 콜솔에 e를 입력하고 a->dataset->id를 확인할 수 있다(?).)
※ 참고: 검사 창에서 ESC를 누르면 추가로 Console을 띄울 수 있음
※ 참고: bind 사용법에 대한 추가 내용
베이스 캠프
컴포넌트에서 props를 변경하려고 할 때
import React, { Component } from 'react';
class Content extends Component {
render() {
this.props.title = 'hi'; // 에러
console.log('Content render');
return(
<article>
<h2>{this.props.title}</h2>
{this.props.desc}
</article>
);
}
}
export default Content;
props는 수정이 불가능한 값(read-only)이기 때문에 다섯번째 줄처럼 쓰면 에러가 난다.
상위 컴포넌트에서 하위 컴포넌트의 state를 바꾸려면 props를 사용하고 하위 컴포넌트에서 상위 컴포넌트의 state를 바꾸려면 이벤트를 사용해야 한다.
App.js
<TOC
onChangePage={function(id){ // 하위 컴포넌트에서 상위 컴포넌트의 state 변경
this.setState({
mode:'read',
selected_content_id:Number(id)
});
}.bind(this)}
data={this.state.contents}> // 상위 컴포넌트에서 하위 컴포넌트 state 변경(?)
</TOC>
※ 참고: 베이스 캠프
Create 구현
create, update, delete 기능을 구현해보자.
create를 클릭하면 기존에 각 컨텐트의 설명이 뜨던 부분에 새 컨텐트를 추가할 수 있는 폼이 뜨게 할 것이다.
확실히 구분하기 위해 기존의 Content.js를 ReadContent.js로 변경해주었다.
CreateContent.js
import React, { Component } from 'react';
class CreateContent extends Component {
render() {
console.log('CreateContent render');
return(
<article>
<h2>Create</h2>
<form>
</form>
</article>
);
}
}
export default CreateContent;
App.js
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>
}
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>
);
}
_article이라는 변수를 추가해서 mode의 상태에 따라 _article에 컴포넌트를 바꿔가며 담아 띄운다.
이제 내용을 전달할 수 있는 form 태그를 완성해보자.
CreateContent.js
<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>
form 태그는 원래 html에서 사용되는 태그이고 submit 했을 때 action 값의 주소로 화면을 전환한다.
onSubmit은 submit 버튼을 눌렀을 때 실행되는 함수이다. (함수 뒤에 .bind(this) 잊지 말 것!)
원래 form 태그의 기능은 submit 했을 때 화면이 전환되면서 값을 넘겨주는 것이므로 e.preventDefault()로 막아준다. (화면 전환 없이 동적으로 현재 화면이 변하게 하는 것이 리액트의 특징)
테스트를 위해 App.js의 mode 값을 create로 바꿔준 뒤 실행해본다.
onSubmit을 테스트하기 위해 코드를 추가해준다.
CreateContent.js
onSubmit={function(e){
e.preventDefault();
this.props.onSubmit(
e.target.title.value,
e.target.desc.value
);
alert('Submit!!!!!');
}.bind(this)}
App.js
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>
}
새로 등록된 컨텐츠가 뜨도록 이어서 코드를 추가해보자.
...
constructor(props) {
super(props);
this.max_content_id = 3; // UI에 영향이 없는 값은 state에 쓰지 않는 편이 좋음, 렌더링 오래 걸림
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'}
]
}
}
...
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}
// );
var _contents = this.state.contents.concat(
{id:this.max_content_id, title:_title, desc:_desc}
)
this.setState({
contents:_contents
});
console.log(_title, _desc);
}.bind(this)}></CreateContent>
}
되도록이면 원본을 수정하는 방식으로 코드를 작성하지 않는 것이 좋다.
(테스트용 alert는 지웠다.)
불필요한 렌더링을 줄여보자.
TOC.js
shouldComponentUpdate(newProps, newState){
console.log('===>TOC render shouldCompnentUpdate'
,newProps.data
,this.props.data
);
if(this.props.data === newProps.data){
return false;
}
return true;
}
render 앞에 이 코드를 추가한다.
shouldComponentUpdate는 말 그대로 컴포넌트가 업데이트 되어야하는지 아닌지를 알려주는 역할을 한다.
newProps를 통해서 새로 바뀌어 컴포넌트에 적용되어야할 데이터를 알 수 있다.
여기서 this.props.data는 컴포넌트에 현재 적용되어 있는 데이터이다.
앞서 '원본을 수정하지 않는 방식'으로 코드를 작성했기 때문에 this.props.data와 newProps.data를 비교하는 방법을 사용할 수 있는 것이다. 만약 원본(this.props.data)을 수정했으면 newProps.data와 값이 같아지기 때문에 컴포넌트를 업데이트해야 하는지 아닌지 알기가 어려워진다.
Array.from을 이용해서 코드를 고쳐보자.
App.js
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}
// );
// var _contents = this.state.contents.concat(
// {id:this.max_content_id, title:_title, desc:_desc}
// )
var newContents = Array.from(this.state.contents);
newContents.push({id:this.max_content_id,
title:_title, desc:_desc});
this.setState({
contents:newContents
});
console.log(_title, _desc);
}.bind(this)}></CreateContent>
}
이 방식도 원본을 바꾸지 않는다.
var b = Array.from(a)라고 쓰면 배열 a의 내용이 b에 복사되고 a에는 전혀 영향이 없다.
※ 객체를 바꾸고 싶으면 Object.assign, 배열을 바꾸고 싶으면 Array.from
developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
※ 원본 배열을 수정하는지 안 하는지 헷갈린다면 Immutable 라이브러리를 사용할 수도 있다.(배열과 관련된 모든 함수가 원본을 수정하지 않게 해줌)
immutable-js.github.io/immutable-js/
Update 구현
CreateContent.js를 복제해서 UpdateContent.js 파일을 만든다.
UpdateContent.js
import React, { Component } from 'react';
class UpdateContent extends Component {
render() {
console.log(this.props.data); // 값이 잘 넘어오는지 테스트
console.log('UpdateContent 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"></input></p>
<p>
<textarea name="desc" placeholder="description"></textarea>
</p>
<p>
<input type="submit"></input>
</p>
</form>
</article>
);
}
}
export default UpdateContent;
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;
break;
}
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 = 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}
// )
var newContents = Array.from(this.state.contents);
newContents.push({id:this.max_content_id,
title:_title, desc:_desc});
this.setState({
contents:newContents
});
console.log(_title, _desc);
}.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 = 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}
// )
var newContents = Array.from(this.state.contents);
newContents.push({id:this.max_content_id,
title:_title, desc:_desc});
this.setState({
contents:newContents
});
console.log(_title, _desc);
}.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>
);
}
코드를 보기 좋게 리팩토링 해준다.
기능에 따라 getReadContent()와 getContent() 함수로 분리한다.
ko.reactjs.org/docs/forms.html
UpdateContent.js의 form 부분에 코드를 추가하고 리팩토링 해보자.
...
constructor(props) {
super(props);
this.state = {
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});
}
...
<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>
...
constructor에서 초기 값을 설정해준다.
바뀐 값을 state에 바로바로 적용할 수 있도록 함수 inputFormHandler를 만든다.
여기서 e.target은 이벤트가 발생하는 태그를 알 수 있게 해주는데 대괄호([])를 사용해 이런 식으로 사용할 수도 있다.(대괄호는 리액트의 최신 문법이라고 함)
함수 끝에 붙는 .bind(this)가 중복되므로 constructor를 이용해서 리팩토링 해줄 수 있다.
이제 폼을 submit했을 때 적용된 내용이 보이도록 해보자.
App.js의 getContent()에 코드를 추가해준다.
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){
this.max_content_id = this.max_content_id+1;
var newContents = Array.from(this.state.contents);
newContents.push({id:this.max_content_id, title:_title, desc:_desc});
this.setState({
contents:newContents,
mode:'read',
selected_content_id:this.max_content_id
});
console.log(_title, _desc);
}.bind(this)}></CreateContent>
} else if(this.state.mode === 'update') {
_content = this.getReadContent();
_article = <UpdateContent data={_content} onSubmit={
function(_id, _title, _desc){
this.max_content_id = this.max_content_id+1;
var newContents = Array.from(this.state.contents); // immutable 테크닉
var i = 0;
while(i < newContents.length) {
if(newContents[i].id === _id) {
newContents[i] = {id:_id, title:_title, desc:_desc};
break;
}
i = i + 1;
}
this.setState({
contents:newContents,
mode:'read'
});
console.log(_title, _desc);
}.bind(this)}></UpdateContent>
}
return _article;
}
여기까지 코드를 쓰고 나면 create와 update 기능은 의도한 대로 작동한다.
컨텐츠를 추가하거나 수정할 때 setState로 mode까지 바꿔주는 것을 잊지 말자.
이제 마지막으로 delete 기능을 완성해보자.
App.js
...
<Control onChangeMode={function(_mode){
if(_mode === 'delete') {
if(window.confirm('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>
...
window.confirm은 alert처럼 팝업창으로 뜬다. (이고잉 선생님이 왜 confirm은 alert랑 다르게 앞에 window.를 붙여야 되냐고 궁금해 하셨다. 나도 궁금)
splice(i, 1)은 i번째부터 한 개 제거하는 함수이다. (splice라는 단어의 사전적 의미와 직결되지는 않는듯..?)
setState로 바뀐 컨텐츠를 적용시키고 mode를 welcome으로 바꿔준다.
여기까지 하면 CRUD(create, read, update, delete) 기능이 완성된 것이다!
"과한 공부는 부족한 공부보다 해롭다. 일단 지금 배운 걸 활용해 보자."
원본이 바뀌는 것은 큰 혼란을 초래할 수 있기 때문에 immutable하게 코드를 짜는 것이 좋다. (immutable.js 사용하는 것도 좋음)
React Router: 주소에 따라 특정 ui를 불러올 수 있도록 할 수 있는 플러그인 같은 기능이라고 함.
npm run eject(create-react-app eject): 마음대로 개발 환경 수정 가능, 되돌릴 수 없음
redux: 중앙에 데이터 저장소를 만들고 모든 컴포넌트가 접근 가능
react server side rendering: 서버에서 웹페이지를 완성한 후에 클라이언트에 띄움
react native: 리액트와 같은 방법으로 native앱(안드로이드와 ios 모두에서 구동 가능)을 만들 수 있음
여기까지 24일 걸렸다..!ㅠㅠ 이고잉 선생님 정말 고맙습니다
다들 파이팅!!
'리액트 React' 카테고리의 다른 글
Visual Studio Code 단축키 (0) | 2020.09.15 |
---|---|
리액트 참고 자료 및 사이트 (0) | 2020.09.13 |