npm: node.js 계의 앱 스토어
npm install -g creat-react-app
권한이 없다고 뜬다면
sudo npm install create-react-app
npm: 프로그램을 설치하는 프로그램
npx: create-react-app이라는 것을 딱 한번만 실행시키고 지우는 애
디렉토리 만들기
$create-react-app .
npm run start로 실행 가능
npm run build로 빌드 가능 (실 서버 환경)
npx serve -s build
컴포넌트의 이름들로 정리하므로써 복잡도를 낮췄다.
부모인 app의 입장에서는 state 라는 내부 정보를 사용했고,
자식한테 전달할때는 props라는 것을 사용해서 전달한다.
//Appjs
import React, { Component } from 'react';
import './App.css';
import TOC from "./components/toc";
import Content from "./components/content";
import Subject from "./components/subject";
class App extends Component{
constructor(props) {
super(props);
/*state 초기화 컴포넌트가 실행될때 constructor가 젤 먼저 실행*/
this.state={
subject:{title:"WEB", sub:"World Wide Web"},
contents:[
{id:1, title:'HTML1', desc:'HTML1 is for information'},
{id:2, title:'HTML2', desc:'HTML2 is for information'},
{id:3, title:'HTML3', desc:'HTML3 is for information'}
]
}
}
render() {
return(
<div className="App">
<Subject
title={this.state.subject.title}
sub={this.state.subject.sub}>
</Subject>
<Subject title="React" sub="reactreactreact"></Subject>
<TOC data={this.state.contents}></TOC>
<Content title="HTML" des></Content>
</div>
);
}
}
export default App;
//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;
React에서는 props나 state 값이 바뀌면 해당하는 컴포넌트의 render 함수가 호출되도록 약속되어있다.
=> props나 state 값이 바뀌면 화면이 다시 그려진다.
하려고 하는 일: WEB을 클릭하면 mode가 read에서 welcome으로 바뀌게
<header>
<h1><a href="/" onClick={function(e){
console.log(e);
e.preventDefault();//reload 안되게 해줌
//this.state.mode='welcome';
<문제점>
1) 이벤트가 호출됐을 때 실행되는 함수 안에서는 this의 값이 컴포넌트
자기 자신을 가리키지 않고 아무 값도 세팅되어있지 않다.
=> .bind(this) 추가해주기
2)this를 읽어와도 mode가 바뀌지 않음
=>this.setState({ mode: 'welcome' });
}}>{this.state.subject.title}</a></h1>
{this.state.subject.sub}
</header>
적용 후
<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>
>var obj = { name: 'egoing'};
>function vindTest(){
console.log(this.name);
}
>bindTest();
<-undefined
>var bindTest2=bindTest.bind(obj);
>bindTest2();
<-egoing
동적으로 state를 바꾸고 싶을때 setState를 사용해야한다.
this.state.mode='welcome' 이런 식으로 하면 render가 호출 되지 않음
constructor에서는 위와 같이 바꿔도 됨.
//App.js
<Subject
title={this.state.subject.title}
sub={this.state.subject.sub}
onChangePage={function(){
alert('hihi');
}.bind(this)}>
</Subject>
//subject
class Subject extends Component{
render() {
return (
<header>
<h1><a href="/" onClick={function(e){
e.preventDefault();//link를 클릭했을 때 페이지가 바뀌는 것을 방지
this.props.onChangePage();
}.bind(this)}>{this.props.title}</a></h1>
{this.props.sub}
</header>
);
}
}
subject라는 컴포넌트에 onChangePage 이벤트를 만들었다.
그리고 이벤트에다가 함수를 설치해주면
그 이벤트가 발생되었을 때 (링크를 클릭했을 때) props로 전달된 onChangePage라는 함수를 호출하면 된다.
하고싶은 것: toc의 목록의 링크를 눌렀을 때 해당하는 id값의 내용을 보여주기
1)속성을 이용해서 하기
//toc.js
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}
onClick={function(e){
//onClick이라고 하는 속성을 이용하여 이벤트를 실행시킬 때 e.target.dataset.id을 통해서 값을 추출했는데 data-id가 data-abcd이면 e.target.dataset.abcd로 바뀌면 된다.
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>
);
}
}
2)속성을 이용하지 않고 하기
//toc.js
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}
onClick={function(id, e){
e.preventDefault();
this.props.onChangePage(id);
}.bind(this, data[i].id)}
//bind의 두번째 인자로 data[i].id 넘겨주기
//bind는 두번째 인자로 들어온 값을 함수의 첫번째 매개변수의 값으로 넣어준다.
> {data[i].title} </a></li>);
i=i+1;
}
return (
<nav>
<ul>
{lists}
</ul>
</nav>
);
}
}
//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>
<Subject title="React" sub="reactreactreact"></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>
);
props | state |
---|---|
props are read only | state changes can be asynchronous |
props can not be modified | state can be modified using this.setState |
컴포넌트 안에서 전달된 props를 바꾸면 에러가 남
컴포넌트 밖에서 바꿀 수 있음.
상위 컴포넌트 -> 하위 컴포넌트 값 전달: props 이용
하위 컴포넌트 -> 상위 컴포넌트 값 바꾸려고 하면: 이벤트 사용
상위 컴포넌트(app.js)가 하위 컴포넌트(subject,content,toc.js)로 값을 전달할 때 props를 통해 전달
하위 컴포넌트가 상위 컴포넌트의 값을 바꾸고 싶을 때 이벤트를 통해서 함
상위 컴포넌트 > props > 하위 컴포넌트
하위 컴포넌트 > 이벤트 실행 > 상위 컴포넌트의 state 호출 > state 값 수정
push | concat |
---|---|
원본을 변경 | 원본 변경하지 않음 |
>var arr=[1,2];
>arr.push(3);
>arr
<-[1,2,3]
>var arr2=[1,2];
>var result = arr2.concat(3);
>result
<-[1,2,3]
>arr2
<-[1,2]
//CreateContent.js
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.preventDefault();
this.props.onSubmit(
e.target.title.value,
//submit 버튼 눌렀을 때 입력되어있던 title 값
e.target.desc.value
//submit 버튼 눌렀을 때 입력되어있던 description 값
);
}.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 CreateContent;
//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>
);
}
}
export default Control;
//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;
//App.js
import React, { Component } from 'react';
import './App.css';
import TOC from "./components/toc";
import ReadContent from "./components/ReadContent";
import CreateContent from "./components/CreateContent";
import Subject from "./components/subject";
import Control from "./components/control";
class App extends Component{
constructor(props) {
super(props);
/state 초기화 컴포넌트가 실행될때 constructor가 젤 먼저 실행/
this.max_content_id =3;
//contents에 id를 추가하기 위해 변수 설정
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:'HTML1', desc:'HTML1 is for information'},
{id:2, title:'HTML2', desc:'HTML2 is for information'},
{id:3, title:'HTML3', desc:'HTML3 is for information'}
]
}
}
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>
//바뀔수 있는 _aritcle을 모드에 따라 컴포넌트 설정
//<ReadContent>, <CreateContent> 등...
}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>
}
//create 모드 일때
else if(this.state.mode === 'create'){
_article=<CreateContent onSubmit={function(_title, _desc){
//create 누른다음 입력한 제목과 내용 값
//add content to this.state.contents
this.max_content_id+=1;
//max_content_id의 다음 값
/*push 보단 concat 사용하기*/
//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}
)
//state 변경하려면 setState 사용해야함
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>
<Subject title="React" sub="reactreactreact"></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;
상황: toc가 불필요하게 render 되고 있다.
1)render 이전에 shouldComponentUpdate가 실행된다.
2)shouldComponentUpdate return 값이 true면 render가 호출이 되고,return 값이 false면 render가 호출되지 않도록 약속되어 있다.
3)새롭게 바뀐 값과 이전 값에 접근 할 수 있다.
(concat 대신 push를 사용하면 원본과 같아져서 바뀐 값과 이전 값을 비교 할 수 없음)
toc에 있는 목록 클릭하면 render함수는 호출되지 않음
create해서 새로운거 추가하면 render함수 호출된다
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() { ...
배열의 경우
>var a = [1,2];
>var b = Array.from(a);
>console.log(a,b,a===b);
<-[1,2] [1,2] false
>b.push(3);
복제한 것에 그냥 push를 해도 됨
객체의 경우
>var a = {name: 'egoing'};
>var b = Object.assign({}, a);
>console.log(a,b,a===b);
<-{name:"egoing"} {name:"egoing"} false
cf) immutable.js 찾아보기
//UpdateContent.js
이렇게 하면 props는 readonly이므로 바뀌지 않음
<input
type="text"
name="title"
placeholder="title"
value={this.props.data.title}
//props는 READONLY이므로 바꿀 수 없다.
></input></p>
getReadContent와 getContent 함수로 코드를 정리해줌
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;
}
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+=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
})
}.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+=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
})
}.bind(this)}></UpdateContent>
}
return _article;
}
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>
<Subject title="React" sub="reactreactreact"></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 (변경 가능하게 됨)
constructor로 state
onChange 함수 사용
import React, { Component } from 'react';
class UpdateContent extends Component{
constructor(props){
super(props);
this.state = {
title:this.props.data.title,
desc:this.props.data.desc
}
}
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"
//this.props.data.title에서 this.state.title로 변경
value={this.state.title}
//onChange를 사용해야만 글자를 수정할 수 있음!!
onChange={function(e){
this.setState({title:e.target.value});
}.bind(this)}
></input></p>
<p>
<textarea name="desc" placeholder="description" value={this.state.desc}
onChange={function(e){
this.setState({desc:e.target.value});
}.bind(this)}></textarea>
</p>
<p>
<input type="submit"></input>
</p>
</form>
</article>
);
}
}
export default UpdateContent;
중복을 없애기 위해 handler 함수로 묶음
import React, { Component } from 'react';
class UpdateContent extends Component{
constructor(props){
super(props);
this.state = {
title:this.props.data.title,
desc:this.props.data.desc
}
//inputFormHnadler 뒤에 bind(this) 안붙여도되게함
this.inputFormHandler=this.inputFormHandler.bind(this);
}
inputFormHandler(e){
//이벤트가 발생하고 있는 태그의 이름이 뭔지 확인하는 e.target.name
this.setState({[e.target.name]:e.target.value});
}
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"
value={this.state.title}
//onChange를 사용해야만 글자를 수정할 수 있음!!
onChange={this.inputFormHandler}
></input></p>
<p>
<textarea name="desc" placeholder="description" value={this.state.desc}
onChange={this.inputFormHandler}></textarea>
</p>
<p>
<input type="submit"></input>
</p>
</form>
</article>
);
}
}
export default UpdateContent;
어디를 업데이트 할 것인지 식별자로 id값 줌
Update.js
import React, { 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){
//이벤트가 발생하고 있는 태그의 이름이 뭔지 확인하는 e.target.name
this.setState({[e.target.name]:e.target.value});
}
render() {
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
);
}.bind(this)}>
{/*어디를 업데이트 할 것인지 식별자*/}
<input type="hidden" name="id" value={this.state.id}></input>
<p>
<input
type="text"
name="title"
placeholder="title"
value={this.state.title}
//onChange를 사용해야만 글자를 수정할 수 있음!!
onChange={this.inputFormHandler}
></input></p>
<p>
<textarea name="desc" placeholder="description" value={this.state.desc}
onChange={this.inputFormHandler}></textarea>
</p>
<p>
<input type="submit"></input>
</p>
</form>
</article>
);
}
}
export default UpdateContent;
App.js
수정하기 위해 원본 복사하고
contents 안에 있는 id 중 수정하고자 하는 id 찾기
else if(this.state.mode === 'update'){
_content = this.getReadContent();
_article=<UpdateContent data={_content} onSubmit={
function(_id, _title, _desc){
//수정하기 위해 원본 복사
var _contents = Array.from(this.state.contents);
//contents 안에있는 id중 수정하고자 하는 id 찾기
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
})
}.bind(this)}></UpdateContent>
}
create와 update 후 mode를 read로 변경하기
App.js
else if(this.state.mode === 'create'){
_article=<CreateContent onSubmit={function(_title, _desc){
//add content to this.state.contents
this.max_content_id+=1;
//push 사용하기
//this.state.contents.push(
// {id:this.max_content_id, title:_title, desc:_desc}
// );
//concat 사용하기
// var _contents = this.state.contents.concat(
// {id:this.max_content_id, title:_title, desc:_desc}
// )
//Array.from 사용하기
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'){
_content = this.getReadContent();
_article=<UpdateContent data={_content} onSubmit={
function(_id, _title, _desc){
//수정하기 위해 원본 복사
var _contents = Array.from(this.state.contents);
//contents 안에있는 id중 수정하고자 하는 id 찾기
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,
//수정하고 나서 제출을 눌렀을 때 read모드로 변경!!!!
mode:'read'
});
}.bind(this)}></UpdateContent>
}
<Control onChangeMode={function(_mode){
if(_mode === 'delete'){
if(window.confirm("really?")){
var _contents = Array.from(this.state.contents);
var i =0;
while(i< _contents.length){
if(_contents[i].id === this.state.selected_content_id){
_contents.splice(i,1);
break;
}
i=i+1;
//getReadContent 이용해서 해본거
//var _content = this.getReadContent();
//var index = _content.id-1;
//_contents.splice(index,1);
}
this.setState({
mode:'welcome',
contents:_contents
});
alert("delete!")
}
}else{
this.setState({
mode:_mode
})
}
}.bind(this)}></Control>