개발을 할 때는 Back, Front를 각각 만들고 각각 테스트를 해서 완성해야 한다.
이후, 둘을 연결하는 작업을 합니다.
한번에 하려고 하면 Back, Front, 둘의 통신 중에서 어디서 문제가 생긴지 모릅니다.
import './App.css'; import React from "react"; import ToDo from "./ToDo"; class App extends React.Component { constructor(props){ super(props); // 여러 개의 객체를 생성해서 state에 items라는 이름으로 저장 // state에 item으로 id, title done을 만든다. this.state={items:[ {id:0, title:"hello React", done:true}, {id:1, title:"Dx Data", done:false} ]}; // 여기를 서버에서 받아오는 데이터로 설정할 것이다. // 더미 데이터로 구조를 만들어 두고, //마지막에 서버에서 데이터를 받아서 완성할 것이다. } // map은 무조건 기억해야 한다. // map : 데이터의 모임을 순회하면서 함수를 적용해 함수의 리턴 값을 가지고 // 데이터의 모임을 만들어주는 함수 // 데이터 변환에 활용, fileter, reduce를 같이 기억해 // python은 list comprehension을 적용할 지 고민하자. // [ for ~] 이게 list comprehension // 기억 안나면 무조건 review하자... python에서 이걸 모르면 멍청이다. // itmes를 순회하면서 item에 넣고 // 함수를 적용해서 변환하여 todoItems 에 넣는다. // react에서는 동일한 모양을 여러개 한다면 //key값을 넣는 것을 강제합니다. render(){ var todoItems=this.state.items.map((item,idx)=>( <ToDo item={item} key={item.id}/> )) return( <div className="App"> {todoItems} </div> ) } } export default App;
- 위에 주석에서 써두었지만, map(python -> list comprehension, lambda)는 죽어도 기억해야 한다.
import { ListItem, ListItemText, InputBase, Checkbox }from "@material-ui/core"; import React from "react"; class ToDo extends React.Component{ constructor(props){ super(props) this.state={item:props.item} } render(){ // this.state 쓰기 싫지? const item=this.state.item; return( <ListItem> <Checkbox checked={item.done}/> <ListItemText> <InputBase inputProps={{"aria-label":"naked"}} type="text" id={item.id} name={item.id} value={item.title} multiline={true} fullWidth={true}/> </ListItemText> </ListItem> ) } } export default ToDo;
조건 && 실행문
&&
는 and이기에 조건이 falsy, truth에 따라 실행문을 수행할 지 선택 가능var todoItems=this.state.items.length>0 && ( <Paper style={{margin:16}}> <List> {this.state.items.map((item,idx)=>( <ToDo item={item}/> ))} </List> </Paper> )
this.state.items.length>0 && (수행문)
에서 앞에 조건이 맞지 않는다면 var todoItems에는 아무것도 들어가지 않을 것이다.
import React from "react"; import{ TextField, Paper, Button, Grid }from "@material-ui/core"; class AddToDo extends React.Component{ constructor(props){ super(props) } render(){ return() } } export default AddToDo;
- material 빼고는 항상 이 구조를 지닌다.
import React from "react"; import{ TextField, Paper, Button, Grid }from "@material-ui/core"; class AddToDo extends React.Component{ constructor(props){ super(props); //입력받은 내용을 저장할 state를 생성 this.state={item:{title:""}} } // size 값은 최대 16으로 본다. render(){ return( <Paper style={{margin:16, padding:16}}> <Grid container> <Grid xs={11} md={11} item style={{padding:16}}> <TextField placeholder="제목을 입력" fullWidth/> </Grid> <Grid xs={1} md={1} item> <Button fullWidth color="secondary" variant="outlined"> + </Button> </Grid> </Grid> </Paper> ) } } export default AddToDo;
import './App.css'; import React from "react"; import ToDo from "./ToDo"; import {Paper, List, Container} from "@material-ui/core" import AddToDo from './AddToDo'; class App extends React.Component { constructor(props){ super(props); this.state={items:[ {id:0, title:"hello React", done:true}, {id:1, title:"Dx Data", done:false} ]}; render(){ var todoItems=this.state.items.length>0 && ( <Paper style={{margin:16}}> <List> {this.state.items.map((item,idx)=>( <ToDo item={item}/> ))} </List> </Paper> ) return( <div className="App"> <Container maxWidth="md"> <AddToDo/> {todoItems} </Container> </div> ) } } export default App;
React나 모바일에서는 좀 다르게 한다.
//데이터 추가를 위한 함수 // Item 1개를 받아서 itmes에 추가하기 add=(item)=>{ // 기존 items를 thisItems에 복제 const thisItems=this.state.items; //추가할 item 생성 item.id="ID-"+thisItems.length; item.done=false; // 복제본에 데이터 추가 thisItems.push(item); // items에 복제본을 추가 // react는 props나 state가 변경되면 자동으로 컴포넌트를 재출력함 this.setState({items:thisItems}) }
React는 변수에 바로 추가하지 못한다.
그러기에 복제를 해서 입력하고 수정해주자
- App.js
return( <div className="App"> <Container maxWidth="md"> <AddToDo add={this.add}/> <AddToDo/> {todoItems} </Container> </div> )
AddToDo.jsx
class AddToDo extends React.Component{ constructor(props){ super(props); //입력받은 내용을 저장할 state를 생성 this.state={item:{title:""}} this.add=props.add; }
- AddToDo 클래스의 메서드에 작성
// 입력 내용이 변경될 때 title 수정하는 메서드 onInputChange=(e)=>{ // item 속성 복제 const thisItem=this.state.item; // 복제된 객체의 title 값을 입력한 내용으로 수정 thisItem.title=e.target.value; // 복제된 객체를 다시 item으로 복사 this.setState({item:thisItem}); } // + 버튼을 눌렀을 때 onButtunClick=(e)=>{ // 데이터 추가 this.add(this.state.item); // title을 clear - 입력 상자도 clear 해아 한다. this.setState({item:{title:""}}) } // Enter를 눌렀을 때, enter 누르면 onButtonClick과 동일하게 작동 enterKeyEnterHandler=(e)=>{ if(e.key==="Enter"){ this.onButtunClick(); } }
- AddToDo class 내부
render(){ return( <Paper style={{margin:16, padding:16}}> <Grid container> <Grid xs={11} md={11} item style={{padding:16}}> <TextField placeholder="제목을 입력" fullWidth value={this.state.item.title} onChange={this.onInputChange} onKeyPress={this.enterKeyEnterHandler} /> </Grid> <Grid xs={1} md={1} item> <Button fullWidth color="secondary" variant="outlined"> + </Button> </Grid> </Grid> </Paper> ) }
import { ListItem, ListItemText, InputBase, Checkbox, ListItemSecondaryAction, IconButton }from "@material-ui/core"; import { DeleteOutlined } from "@material-ui/icons"; import React from "react"; class ToDo extends React.Component{ constructor(props){ super(props) this.state={item:props.item} render(){ const item=this.state.item; return( <ListItem> <Checkbox checked={item.done}/> <ListItemText> <InputBase inputProps={{"aria-label":"naked"}} type="text" id={item.id} name={item.id} value={item.title} multiline={true} fullWidth={true}/> </ListItemText> <ListItemSecondaryAction> <IconButton aria-label="Delete ToDo"> <DeleteOutlined/> </IconButton> </ListItemSecondaryAction> </ListItem> ) } } export default ToDo;
- 형태는 구현했으니, 버튼을 클릭했을 때, 삭제하는 메서드를 구현해야 한다
- App.js
delete=(item)=>{ const thisItems=this.state.items; // thisItems에서 item을 삭제하기 - id가 구별하는 속성 // 이러면 thisItems는 영향을 받지 않는다. const newItems=thisItems.filter((e)=>e.id!==item.id); this.setState({items:newItems},()=>{ console.log(item.id+"가 제거되었습니다."); }) }
- App.js에서 delete 함수를 ToDo.jsx로 넘기기
var todoItems=this.state.items.length>0 && ( <Paper style={{margin:16}}> <List> {this.state.items.map((item,idx)=>( <ToDo item={item} key={item.id} delete={this.delete}/> ))} </List> </Paper> )
- ToDo.jsx 파일을 삭제 아이콘을 누르면 App.js파일의 delete함수를 호출하도록 수정하기
// 위에 import는 동일하기에 생략합니다. class ToDo extends React.Component{ constructor(props){ super(props) this.state={item:props.item} this.delete=props.delete; } onButtunClick=(e)=>{ this.delete(this.state.item); } render(){ const item=this.state.item; return( <ListItem> <Checkbox checked={item.done}/> <ListItemText> <InputBase inputProps={{"aria-label":"naked"}} type="text" id={item.id} name={item.id} value={item.title} multiline={true} fullWidth={true}/> </ListItemText> <ListItemSecondaryAction> <IconButton aria-label="Delete ToDo" onClick={this.onButtunClick}> <DeleteOutlined/> </IconButton> </ListItemSecondaryAction> </ListItem> ) } } export default ToDo;
- 생성자에 delete props를 받아오고 class 내에서 버튼 클릭 이벤트 메서드(버튼 클릭 시 delete 메서드 불러옵니다) 생성 후, 해당 메서드를 IconButton에 연결(아이콘 자체에는 연결을 못하고 아이콘을 감싼 버튼에 연결을 한 것입니다.)
// material ui import import { ListItem, ListItemText, InputBase, Checkbox, ListItemSecondaryAction, IconButton }from "@material-ui/core"; import { DeleteOutlined } from "@material-ui/icons"; import React from "react"; class ToDo extends React.Component{ constructor(props){ super(props) this.state={item:props.item, readOnly:"true"} this.delete=props.delete; } // Event가 발생하면 readOnly의 값을 false로 수정하기 offReadOnlyMode=(e)=>{ // 여기서는 state의 값을 바로 변경함, 이게 가능합니까? // 한개짜리 속성을 바꿀때는 바로 바꾸기가 가능하다. this.setState({readOnly:false}) } //Enter를 눌렀을 때 동작하는 메서드 enterKeyEventHandler=(e)=>{ if(e.key==="Enter"){ this.setState({readOnly:true}); } } // input의 내용을 변경했을 때 호출될 메서드 editEventHandler=(e)=>{ const thisItem=this.state.item; thisItem.title=e.target.value; this.setState({item:thisItem}) } // 체크박스의 값을 변경할 때 호출되는 메서드 checkboxEventHandler=(e)=>{ const thisItem=this.state.item; thisItem.done=!thisItem.done; this.setState({item:thisItem}); } onButtunClick=(e)=>{ this.delete(this.state.item); } render(){든다. // this.state 쓰기 싫지? const item=this.state.item; return( <ListItem> <Checkbox checked={item.done} onChange={this.checkboxEventHandler}/> <ListItemText> <InputBase inputProps={{"aria-label":"naked", readOnly:this.state.readOnly}} type="text" id={item.id} name={item.id} value={item.title} multiline={true} fullWidth={true} onClick={this.offReadOnlyMode} onKeyPress={this.enterKeyEventHandler} onChange={this.editEventHandler}/> </ListItemText> <ListItemSecondaryAction> <IconButton aria-label="Delete ToDo" onClick={this.onButtunClick}> <DeleteOutlined/> </IconButton> </ListItemSecondaryAction> </ListItem> ) } } export default ToDo;
- 포워드 프록시
- 클라이언트 쪽에 만들어져서 클라이언트가 외부 요청을 하면 포워드 프록시가 외부 서버에 데이터를 요청하고 받은 응답을 클라이언트에게 돌려주는 방식- 리버스 프록시
- 웹 서버나 WAS(Web Application Server) 앞에 위치해서 Client가 요청을 하면 리버스 프록시가 서버에 요청해서 응답을 받아서 클라이언트에게 전송해 주는 것
- Web Server와 WAS를 분리해서 구현하면 리버스 프록시를 구현했다고 합니다.
서버가 구동중인지 확인하자
// 이거 잘 모르겠으면 23일차 fetch API 확인하기 componentDidMount(){ console.log("컴포넌트가 메모리 할당을 받음") //요청 옵션을 생성 const requestoptions={ method:"GET", headers:{"Content-Type":"application/json"} }; fetch("http://localhost:8000/todo", requestoptions) .then((response)=>response.json()) .then((response)=>{ this.setState({items:response.list}) }), (error)=>{ console.log(error) } }
- cors 오류가 난다. 이를 해결해줘야 한다.
pip install django-cors-headers
"corsheaders"
넣기"corsheaders.middleware.CorsMiddleware"
맨위에 배치CORS_ORIGIN_WHITELIST=['http://localhost:3000', 'http://127.0.0.1:3000'] CORS_ALLOW_CREDENTIALS=True
- api-config.js
let backendHost; // // // const hostname= window && window.location && window.location.hostname; if(hostname==="localhost"){ backendHost="http://localhost:8000" } export const API_BASE_URL=`${backendHost}`
import { API_BASE_URL } from "../app-config"; // 클라이언트의 요청을 처리할 함수 // 첫 번째 매개변수는 작업 // 두 번쨰 매개변수는 전송 방식 // 세 번쨰 매개변수는 파라미터 export function call(api, method, request){ let options={ headers:new Headers({ "Content-Type":"application/json" }), url:API_BASE_URL+api, method:method, }; //GET 방식일 떄의 파라미터 생성 if(request){ options.body=JSON.stringify(request); } //요청 return fetch(options.url,options) .then((response)=>response.json() .then((json)=>{ if(!response.ok){ return Promise.reject(json); } return json; }) ); }
이게 로컬에선 작동이 된다. 하지만 다른 곳에서 한다면? 소스코드를 다 들고가야 한다.
이걸 도커파일 만들어서 이미지 생성 하고 배포하면 편하다
- react 프로젝트 루트에 Dockerfile 이라고 newfile 생성 후 진행
#노드를 깔고 FROM node:alpine #경로 설정 WORKDIR /app ENV PATH /app/node_modules/.bin:$PATH COPY package.json /app/package.json # 설치좀 하고 RUN npm install --force #-- force는 react만 해당함 RUN npm install react-scripts@5.0.1 -g #포트번호 EXPOSE 3000 # 실행 명령어 CMD ["npm", "start"] #여기서 바꿀 것은 버전 및 포트번호밖에없다. #package.josn에서 react-scripts 버전 확인하고 작성하기
- react 프로젝트 루트에 docker-compose.yml 파일 만들고 진행
- 들여쓰기 및 띄어쓰기 주의합니다.
version: "3.7" -이 줄 띄우기 services: sample: container_name: todo-react-app build: context: . dockerfile: Dockerfile volumes: - ".:/app" - "/app/node_modules" ports: - "3000:3000" environment: - NODE_ENV=development stdin_open: true tty: true
docker-compose up -d --build
git --version
git config --global init.defaultBranch main
git init
git add 파일명 나열 또는 . (현재 디렉의 모든 파일)
git commit -m "메시지"
개발자로서 성장하는 데 큰 도움이 된 글이었습니다. 감사합니다.