수업 25일차

galoo·2023년 7월 31일
0

HelloVision Dx Data School

목록 보기
25/72
post-thumbnail

✔ ToDo List

개발 시 주의사항

개발을 할 때는 Back, Front를 각각 만들고 각각 테스트를 해서 완성해야 한다.
이후, 둘을 연결하는 작업을 합니다.
한번에 하려고 하면 Back, Front, 둘의 통신 중에서 어디서 문제가 생긴지 모릅니다.

App.js에서 여러 개의 데이터를 생성해서 출력하기

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)는 죽어도 기억해야 한다.
  • 모양이 별로인 것 같다.
    - ToDo.jsx를 수정해보자.

material ui를 적용한 ToDo.jsx

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;

JS 의 신기한 문법

  • 조건 && 실행문
    - 조건이 false이면 아무 일도 하지 않고, true이면 뒤의 실행문을 수행함
  • Java boolean 은 true, flase 만 가능 \rarr and, or에는 무조건 true, false
  • 하지만 Python, JS 에서 Falsy(False처럼 동작하는 애)가 있다고 했다.
    - 0, null(js, undefined), null(python), [], ''
    - 나머지는 Truth(true 처럼 취급되는 친구들)
  • &&는 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에는 아무것도 들어가지 않을 것이다.

ToDo 추가 화면 출력

  • 예전에는 목록을 출력하는 화면과 데이터를 추가하는 화면이 있을 때, 별도의 페이지로 구성을 해서 출력하는 것이 일반적이었지만, 최근에는 복잡하지 않다면 하나의 화면에 2개의 컴포넌트를 배치해서 출력하기도 하고, 컴포넌트를 교체하는 방식으로 출력하기도 합니다.
    - 이러한 방식을 SPA(Single Page Application)라고 합니다.

추가화면을 위한 컴포넌트를 생성합시다. - AddToDo.jsx

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;

이제 AddToDo 만든것을 App.js에 넣어 만들자

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;

Event 처리

  • React에서는 event 처리를 다른 방식으로 하는 경향이 있습니다.
  • 일반적인 프로그래밍 언어에서는 이벤트 처리를 할 때, 처리를 수행하는 함수는 메서드를 소유한 별도의 파일(클래스)을 만들어서 처리하는 경우가 많다.
  • 이벤트 처리를 담당하는 객체를 Event Handler(Listener)라고 합니다.
    - 이러한 방식을 Delegation이라고도 합니다.
    - 좌석 예약을 할 때, 50좌석에 대한 이벤트를 다 만드는 것이 아닌, Linstener 하나만 만들어서 처리하는 것이 더 편리하다.

React나 모바일에서는 좀 다르게 한다.

  • React에서는 App.js파일에 이벤트 처리 함수를 만든 후, 각 컴포넌트에게 넘겨주는 방식을 많이 사용합니다.
  • React에서는 이벤트 처리 함수가 대부분 데이터를 다루기 때문에, 데이터를 가져오는 코드가 존재하는 App.js의 데이터를 다루기 쉽기 때문이다.

AddToDo Component에서 + 버튼을 누르면 입력한 내용으로 Item 만들어서 items에 추가하기

App.js 파일에 추가를 위함 메서드 작성

//데이터 추가를 위한 함수
    // 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 파일에서 AddToDo에게 함수를 넘기기

  • 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.jsx에 3개의 이벤트 처리 메서드 생성

  • 입력 내용이 변경될 때, title을 수정하는 메서드
    - 대부분 +나 엔터를 누를 때 입력 내용을 불러오지만, React에서는 입력 내용이 변경될 때 state를 변경해서 빠르게 재출력을 하는 것을 권장함
  • +를 눌렀을 때, 데이터를 추가하는 메서드
  • Enter를 눌렀을 때 데이터를 추가하는 메서드
  • 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.jsx 파일에서 이벤트와 이벤트 핸들러를 연결

  • 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>
        )
    }

삭제 구현하기

ToDo.jsx 파일에서 삭제 아이콘 출력하도록 수정

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 파일에 삭제 아이콘을 눌렀을 때, 실제로 데이터 삭제하기

  • 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에 연결(아이콘 자체에는 연결을 못하고 아이콘을 감싼 버튼에 연결을 한 것입니다.)

체크박스 기능 수정 - done의 값을 토글

ToDo.jsx 파일에 state를 추가하고 이벤트 핸들러를 추가

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

클라이어느와 서버 간의 데이터 주고 받기

SOP와 CORS

SOP - Same Origin Policy - 동일 출처 정책

  • 웹 클라이언트(브라우저 - ajax와 fetch API, Web Socket 등)와 웹 서버는 동일한 도메인(ip와 포트번호 혹은 URL)을 사용하는 경우에만 데이터를 주고 받도록 하는 정책

CORS - Cross-Origin Resource Sharing

  • 리소스를 제공하는 도메인과 요청하는 도메인이 다르더라도 요청을 허락해주는 웹 보안 방침
  • 서버에서 설정합니다.

Proxy

  • 컴퓨터 네트워크에서 서버와 클라이언트 사이에서 요청과 응답을 중계해주는 역할
  • 포워드 프록시
    - 클라이언트 쪽에 만들어져서 클라이언트가 외부 요청을 하면 포워드 프록시가 외부 서버에 데이터를 요청하고 받은 응답을 클라이언트에게 돌려주는 방식
  • 리버스 프록시
    - 웹 서버나 WAS(Web Application Server) 앞에 위치해서 Client가 요청을 하면 리버스 프록시가 서버에 요청해서 응답을 받아서 클라이언트에게 전송해 주는 것
    - Web Server와 WAS를 분리해서 구현하면 리버스 프록시를 구현했다고 합니다.
  • 서버를 직접 구현하는 경우는 서버에 CORS 설정을 추가하는 것이 일반적이고, 서버를 직접 구현한 경우가 아니라면 클라이언트 쪽에 Proxy 서버를 별도로 만들거나, react 같은 경우는 Proxy 설정을 추가합니다.

서버가 구동중인지 확인하자

// 이거 잘 모르겠으면 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 오류가 난다. 이를 해결해줘야 한다.

Django에서 CORS 설정

패키지 설치

  • pip install django-cors-headers

APP 등록

  • settings.py에 앱 등록해주기
    - "corsheaders" 넣기
  • settings.py에 MiddleWare로 CorsMiddleware 를 사용할 수 있도록 등록
  • settings.py에 허용할 List 설정

MiddleWare!

  • 미들웨어는 순서가 매우 중요하다.
  • 어떤 요청을 처리하기 전 (Pre-Processing)
  • Filter, Wrapper
  • "corsheaders.middleware.CorsMiddleware" 맨위에 배치

White List

  • 내 안에 들어와도 되는 애들 리스트
CORS_ORIGIN_WHITELIST=['http://localhost:3000',
'http://127.0.0.1:3000']
CORS_ALLOW_CREDENTIALS=True

Client 수정

  • 가져오기, 삽입, 삭제, 수정을 편리하게 수행하도록 하기 위해서 커스터마이징
  • 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}`

src에 new folder (service)에 ApiService.JS 생성

App.js 수정

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

이게 로컬에선 작동이 된다. 하지만 다른 곳에서 한다면? \rarr 소스코드를 다 들고가야 한다.
\rarr 이걸 도커파일 만들어서 이미지 생성 하고 배포하면 편하다

✔ 클라이언트 애플리케이션 Docker Image로 배포하기

Dockerfile 만들어서 배포 - 이미지가 생성

  • 현재 프로젝트에 Dockerfile 파일을 생성하고 작성
  • 프로젝트 디렉토리에서 작업
  • 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 버전 확인하고 작성하기

docker-compose.yml을 만들어서 배포 - 컨테이너까지 생성

  • 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
  • dockerfile은 이미지까지만, dockercompose는 컨테이너까지 만드는 것

  • 이미지 & 컨테이너 생성을 확인하였다.

✔ Git Hub에 소스 코드 업로드

git version 확인

  • git --version

git의 기본 branch이름 변경

  • git config --global init.defaultBranch main
  • master->main으로 branch이름 변경하는 명령어
  • 예전 이름 master여서 git hub 처음 연동을 한다면 브랜치 이름이 틀렸다고 오류가 발생함
  • git은 master git hub는 main이라 오류가 발생함

git에서 관리할 디렉토리를 설정

  • 디렉토리로 프롬프트를 옮겨서 git init
  • 하나의 git repo에는 여러 프로젝트 x, 큰 root direc을 만들어야함
  • 디렉토리 안에 .git 이라는 디렉토리가 생성됨
  • 해당 디렉토리를 삭제하며 git 연동이 해제됨

관리할 파일 설정

  • git add 파일명 나열 또는 . (현재 디렉의 모든 파일)

변경 내용이 발생한 경우 로컬에 반영하고 메시지 작성

  • git commit -m "메시지"
profile
밀가루 귀여워요

1개의 댓글

comment-user-thumbnail
2023년 8월 1일

개발자로서 성장하는 데 큰 도움이 된 글이었습니다. 감사합니다.

답글 달기