2023-08-01
Python django와 react를 활용한 어플리케이션 이전 부분을 보시려먼
2023.07.28 - [LG 헬로비전 DX DATA SCHOOL/Web Programming] - Django & React
Django & React
2023/07/28 ajax 다시보기 비동기적으로 서버에 데이터를 요청하는 것 다른 부분을 영향을 주지 않고 UI를 실시간으로 갱신하는 것이 목적 최근에는 웹 서버와 웹 클라이언트를 별개의 프로그램으로
dxdata.tistory.com
확인하세요!
9. 클라이언트 프로그램 만들기
9.0)react를 사용하기 위한 사전 준비
- node.js 설치
- yarn 설치 : npm install --location=global yarn
9.1) react 프로젝트 생성
yarn create react-app 앱이름(svngapp)
9.2) 프로젝트 시작
yarn start
- 브라우저가 자동으로 실행되고 localhost:3000 으로 접속이 되면 리액트 로고가 출력
9.3) UI 작업을 편리하게 하기 위한 패키지를 설치
material-ui 패키지
- npm install --save --legacy-peer-deps @material-ui/core
- npm install --save --legacy-peer-deps @material-ui/icons
9.4) 메인 화면 만들기
- 메인 화면으로 사용할 컴포넌트 생성 - 일반적인 확장자는 jsx(js를 사용할 수 있고 타입스크립트를 사용하면 tsx 도 가능) : src 디렉토리에 ToDo.jsx 생성
//react.js 파일에서 export한 객체를 React로 받아서 사용
//{이름} 의 경우는 export 한 객체에서 이름에 해당하는 것만 받아서 사용
import React from "react"
class ToDo extends React.Component{
render(){
return(
<div className = "ToDo">
<input type="checkbox" id="todo0"
name="todo0" value="todo0"/>
<label for="todo0"> ToDo 컴포넌트 만들기</label>
</div>
)
}
}
export default ToDo;
- App.js 를 수정하여 ToDo 컴포넌트를 출력
import './App.css';
import React from 'react';
import ToDo from './ToDo';
function App() {
return (
<div className='App'>
<ToDo/>
</div>
);
}
export default App;
- 재실행 하고 메인 화면이 수정되었는지 확인
뭐가 없긴 하지만 이게 잘 나온겁니다.. ㅎ
리액트를 왜하는가? -> CBD, SPA,
(Component Based Development)
컴포넌트 기반 소프트웨어 공학, 컴포넌트 기반 개발은 기존의 시스템이나 소프트웨어를 구성하는 컴포넌트를 조립해서 하나의 새로운 응용 프로그램을 만드는 소프트웨어 개발방법론이다.
싱글 페이지 애플리케이션(SPA)은 서버로부터 완전한 새로운 페이지를 불러오지 않고 현재의 페이지를 동적으로 다시 작성함으로써 사용자와 소통하는 웹 애플리케이션이나 웹사이트를 말한다.
React 에서 사용하는 변수의 개념 :
- State는 내부 (자신 컴포넌트)에서 생성하고 데이터를 변경할 수 있음(읽고 쓰기 가능)
- Props는 외부(상위 컴포넌트)에서 상속 받는 데이터이며, 데이터를 변경할 수 없음(읽기 전용)
리액트는 원본 데이터를 수정하지 않음.
복사본을 만들어서 수정한 후 다시 대입하는 형태로 작업을 수행합니다.
데이터를 주고받는 것은 React 가 아니라 자바스크립트가 한다.
react는 화면 출력만 담음.
9.5) 메인화면에 출력할 내용을 수정
//react.js 파일에서 export한 객체를 React로 받아서 사용
//{이름} 의 경우는 export 한 객체에서 이름에 해당하는 것만 받아서 사용
import React from "react"
class ToDo extends React.Component{
constructor(props){
super(props);
//상위 컴포넌트로부터 넘겨받은 데이터를 나의 props에 저장
//props는 읽기전용이라서, 수정할 경우 state에 복사하여 사용해야 함
this.state = {item:props.item}
}
render(){
return(
<div className = "ToDo">
<input type="checkbox"
id={this.state.item.id}
name={this.state.item.id}
value={this.state.item.done}/>
<label id={this.state.item.done}>{this.state.item.title}</label>
</div>
)
}
}
export default ToDo;
- Apps.js에서 샘플 데이터를 생성해서 ToDo.jsx를 이용해서 출력
import './App.css';
import React from 'react';
import ToDo from './ToDo';
class App extends React.Component {
constructor(props){
super(props);
//하나의 객체를 생성해서 state에 item 이라는 이름으로 저장
this.state = {item:{id:0, title:"Hello React", done:true}};
}
render(){
return(
<div className='App'>
<ToDo item={this.state.item} />
</div>
)
}
}
export default App;
08/01
App.js 수정
import './App.css';
import React from 'react';
import ToDo from './ToDo';
class App extends React.Component {
constructor(props){
super(props);
//하나의 객체를 생성해서 state에 item 이라는 이름으로 저장
this.state = {items:[{id:0, title:"Hello React", done:true},
{id:1, title:"Im SVNG", done:true},
{id:2, title:"Your Master", done:false}]};
}
render(){
// map:데이터의 ㅗ임을 순회하면서 함수를 적용해 함수의 리턴 값을 가지고 데이터의
//모임을 만들어주는 함수
//데이터 변환에 활용
var todoItems = this.state.items.map((item, idx) => (
<ToDo item={item} key={item.id} />
));
return(
<div className='App'>
{todoItems}
</div>
)
}
}
export default App;
- ToDo.jsx에 material ui를 사용하기
//react.js 파일에서 export한 객체를 React로 받아서 사용
//{이름} 의 경우는 export 한 객체에서 이름에 해당하는 것만 받아서 사용
import React from "react"
import {
ListItem,
ListItemText,
InputBase,
Checkbox
} from "@material-ui/core";
class ToDo extends React.Component{
constructor(props){
super(props);
//상위 컴포넌트로부터 넘겨받은 데이터를 나의 props에 저장
//props는 읽기전용이라서, 수정할 경우 state에 복사하여 사용해야 함
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.name}
value={item.title}
multiline={true}
fullWidth={true}/>
</ListItemText>
</ListItem>
)
}
}
export default ToDo;
JS의 특성
조건 && 실행문
=> 조건의 boolean에 따라 실행여부 결정
원래는
if(조건){
실행문
}
- App.js 에도 material design 사용
import './App.css';
import React from 'react';
import ToDo from './ToDo';
import {Paper, List} from "@material-ui/core";
class App extends React.Component {
constructor(props){
super(props);
//하나의 객체를 생성해서 state에 item 이라는 이름으로 저장
this.state = {items:[{id:0, title:"Hello React", done:true},
{id:1, title:"Im SVNG", done:true},
{id:2, title:"Your Master", done:false}]};
}
render(){
// map:데이터의 모임을 순회하면서 함수를 적용해 함수의 리턴 값을 가지고 데이터의
//모임을 만들어주는 함수
//데이터 변환에 활용
var todoItems = this.state.items.length > 0 && (
<Paper style = {{margine:16}}>
<List>
{this.state.items.map((item,idx) =>
<ToDo item={item} />
)}
</List>
</Paper>
);
return(
<div className='App'>
{todoItems}
</div>
)
}
}
export default App;
- 실행시 항목 하나의 디자인이 변경되었고, 전체를 하나의 카드 형태로 만들어서 음영을 적용
1.7) 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);
//입력받은 내용을 저장할 state를 생성
this.state = {item:{title:""}}
}
render(){
return(
<Paper style = {{margin:16, padding:16}}>
<Grid contatiner>
<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;
- App.js 에서 AddToDo를 출력
import './App.css';
import React from 'react';
import ToDo from './ToDo';
import {Paper, List, Container} from "@material-ui/core"; // import 시 {} 안의 내용들의 spelling은 틀려서는 안됨. 그리고 꼭 from 뒤에 내용에 들어가있는 내용이여야 함
import AddToDo from './AddToDo';
class App extends React.Component {
constructor(props){
super(props);
//하나의 객체를 생성해서 state에 item 이라는 이름으로 저장
this.state = {items:[{id:0, title:"Hello React", done:true},
{id:1, title:"Im SVNG", done:true},
{id:2, title:"Your Master", done:false}]};
}
render(){
// map:데이터의 모임을 순회하면서 함수를 적용해 함수의 리턴 값을 가지고 데이터의
//모임을 만들어주는 함수
//데이터 변환에 활용
var todoItems = this.state.items.length > 0 && (
<Paper style = {{margine: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;
- 실행결과
1.8) 이벤트 처리
- 일반적인 프로그래밍 언어에서는 이벤트 처리를 할 때 이벤트 처리를 수행하는 함수 나 메서드를 소유한 별도의 파일(클래스)을 만들어서 처리하는 경우가 많습니다.
이렇게 이벤트 처리를 담당하는 객체를 Event Handler / Listener 라고 표현함.
이러한 방식을 Delegation 이라고도 합니다.
- React 에서는 App.js 파일에 이벤트 처리함수를 만든 후 각 컴포넌트에게 넘겨주는 방식을 많이 사용합니다.
React 에서는 이벤트 처리 함수가 대부분 데이터를 다루기 때문에 데이터를 가져오는 코드가 존재하는 App.js의 데이터를 다루기 쉽다.
1.9) AddToDo 컴포넌트에서 추가 버튼을 누르면 입력한 내용을 가지고 item을 만들어서 items에 추가하기
- App.js 파일에 추가를 위한 함수 작성
(React의 특성상, 새로 받은 데이터를 원본 데이터에 바로 넣는 것은 불가 -> 데이터 복제본을 만든 후, commit 하는 방식으로 진행)
import './App.css';
import React from 'react';
import ToDo from './ToDo';
import {Paper, List, Container} from "@material-ui/core"; // import 시 {} 안의 내용들의 spelling은 틀려서는 안됨. 그리고 꼭 from 뒤에 내용에 들어가있는 내용이여야 함
import AddToDo from './AddToDo';
class App extends React.Component {
constructor(props){
super(props);
//하나의 객체를 생성해서 state에 item 이라는 이름으로 저장
this.state = {items:[{id:0, title:"Hello React", done:true},
{id:1, title:"Im SVNG", done:true},
{id:2, title:"Your Master", done:false}]};
}
//데이터 추가를 위한 함수
//Item 한개를 받아서 items 에 추가하는 함수
add = (item) => {
//기존 items를 thisItems에 복제
const thisItems = this.state.items;
//추가할 item을 생성
item.id = "ID-" + thisItem.length;
item.done = false; //done 값은 미리 설정 -> 입력받는 것이 불가해서?
//복제본에 데이터를 추가
thisItems.push(item);
//items에 복제본을 추가
//react 에서는 props 나 state 가 변경되면 컴포넌트를 자동 재출력
this.setState({items:thisItems});
}
render(){
// map:데이터의 모임을 순회하면서 함수를 적용해 함수의 리턴 값을 가지고 데이터의
//모임을 만들어주는 함수
//데이터 변환에 활용
var todoItems = this.state.items.length > 0 && (
<Paper style = {{margine: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;
- App.js 파일에서 AddToDo에게 함수를 넘겨주기
<AddToDo add = {this.add}/>
- AddToDo.jsx 파일의 생성자에서 넘겨준 Props를 변수에 대입
class AddToDo extends React.Component{
constructor(props){
super(props);
//입력받은 내용을 저장할 state를 생성
this.state = {item:{title:""}}
//넘겨준 데이터를 변수에 대입
this.add = props.add;
}
- AddToDo.jsx 파일에 3개의 이벤트 처리 메서드 생성 - 입력 내용이 변경될 때 title을 수정하는 메서드 와 추가를 눌렀을 때 데이터를 추가하는 메서드(대부분은 Enter 나 +를 누를 때 입력 내용을 읽어오지만 리액트에서는 입력 내용이 변경될 때 state를 변경해서 빠르게 재출력을 하는 것을 권장)와 Enter를 눌렀을 때 데이터를 추가하는 메서드
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:""}}
//넘겨준 데이터를 변수에 대입
this.add = props.add;
}
//입력 내용이 변경될 때 title을 수정하는 메서드
onInputChange = (e) => {
//item 속성을 복사
const thisItem = this.state.item;
//복제된 객체의 title의 값을 입력한 내용으로 수정
thisItem.title = e.target.value;
//복제된 객체를 다시 item으로 복사
this.setState({item:thisItem});
}
// 버튼을 누를 때 호출되는 메서드
onButtonClick = (e) => {
this.add(this.state.item); // 데이터 추가
//title을 초기화
this.setState({item:{title:""}});
}
//엔터를 눌렀을 때 호출되는 메서드
enterKeyEnterHangler = (e) => {
//엔터를 눌르면 onButtonClick을 누른 것과 같은 효과
if(e.key === "Enter"){
this.onButtonClick();
}
}
- AddToDo.jsx 파일에서 이벤트와 이벤트 핸들러 연결
render(){
return(
<Paper style = {{margin:16, padding:16}}>
<Grid contatiner>
<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"
onClick={this.onButtonClick}>
추가
</Button>
</Grid>
</Grid>
</Paper>
)
}
}
export default AddToDo;
React 에서 사용하는 변수의 개념 :
- State는 내부 (자신 컴포넌트)에서 생성하고 데이터를 변경할 수 있음(읽고 쓰기 가능)
- Props는 외부(상위 컴포넌트)에서 상속 받는 데이터이며, 데이터를 변경할 수 없음(읽기 전용)
1.10) 삭제 구현
- 삭제 icon 출력하도록 ToDo.jsx 수정
//react.js 파일에서 export한 객체를 React로 받아서 사용
//{이름} 의 경우는 export 한 객체에서 이름에 해당하는 것만 받아서 사용
import React from "react"
import {
ListItem,
ListItemText,
InputBase,
Checkbox,
ListItemSecondaryAction,
IconButton
} from "@material-ui/core";
//Icon 가져오기
import DeleteOutlined from "@material-ui/icons/DeleteOutlined";
class ToDo extends React.Component{
constructor(props){
super(props);
//상위 컴포넌트로부터 넘겨받은 데이터를 나의 props에 저장
//props는 읽기전용이라서, 수정할 경우 state에 복사하여 사용해야 함
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.name}
value={item.title}
multiline={true}
fullWidth={true}/>
</ListItemText>
<ListItemSecondaryAction>
<IconButton aria-label="Delete ToDo">
<DeleteOutlined />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
)
}
}
export default ToDo;
- App.js 파일에 삭제 아이콘을 눌렀을 때 삭제를 하는 기능을 구현
import './App.css';
import React from 'react';
import ToDo from './ToDo';
import {Paper, List, Container} from "@material-ui/core"; // import 시 {} 안의 내용들의 spelling은 틀려서는 안됨. 그리고 꼭 from 뒤에 내용에 들어가있는 내용이여야 함
import AddToDo from './AddToDo';
class App extends React.Component {
constructor(props){
super(props);
//하나의 객체를 생성해서 state에 item 이라는 이름으로 저장
this.state = {items:[{id:0, title:"Hello React", done:true},
{id:1, title:"Im SVNG", done:true},
{id:2, title:"Your Master", done:false}]};
}
//데이터 추가를 위한 함수
//Item 한개를 받아서 items 에 추가하는 함수
add = (item) => {
//기존 items를 thisItems에 복제
const thisItems = this.state.items;
//추가할 item을 생성
item.id = "ID-" + thisItems.length;
item.done = false; //done 값은 미리 설정 -> 입력받는 것이 불가해서?
//복제본에 데이터를 추가
thisItems.push(item);
//items에 복제본을 추가
//react 에서는 props 나 state 가 변경되면 컴포넌트를 자동 재출력
this.setState({items:thisItems});
}
//데이터를 삭제하는 함수
delete = (item) => {
const thisItems = this.state.items; //item 복사
//thisItems에서 item을 삭제하기 - id가 구별하는 속성
const newItems = thisItems.filter((e) => e.id !== item.id); // 해당 id와 다른 것만 골라내는 것임. 똑같은 건 제외한다는 의미 그것을 newItems에 대입
//state를 변경해서 데이터를 재출력
this.setState({items:newItems}, ()=>{
console.log(item.id + "가 제거되었습니다.");
})
}
render(){
// map:데이터의 모임을 순회하면서 함수를 적용해 함수의 리턴 값을 가지고 데이터의
//모임을 만들어주는 함수
//데이터 변환에 활용
var todoItems = this.state.items.length > 0 && (
<Paper style = {{margine:16}}>
<List>
{this.state.items.map((item,idx) =>
<ToDo item={item} />
)}
</List>
</Paper>
);
return(
<div className='App'>
<Container maxWidth="md">
<AddToDo add = {this.add}/>
{todoItems}
</Container>
</div>
)
}
}
export default App;
- App.js 에서 delete 함수를 ToDo.jsx 파일에 넘겨주도록 출력하는 부분을 수정
var todoItems = this.state.items.length > 0 && (
<Paper style = {{margine:16}}>
<List>
{this.state.items.map((item,idx) =>
<ToDo item={item} key={item.id} delete = {this.delete}/>
- ToDo.jsx 파일을 삭제 아이콘을 누르면 App.js의 delete 함수를 호출하도록 수정
//react.js 파일에서 export한 객체를 React로 받아서 사용
//{이름} 의 경우는 export 한 객체에서 이름에 해당하는 것만 받아서 사용
import React from "react"
import {
ListItem,
ListItemText,
InputBase,
Checkbox,
ListItemSecondaryAction,
IconButton
} from "@material-ui/core";
//Icon 가져오기
import DeleteOutlined from "@material-ui/icons/DeleteOutlined";
class ToDo extends React.Component{
constructor(props){
super(props);
//상위 컴포넌트로부터 넘겨받은 데이터를 나의 props에 저장
//props는 읽기전용이라서, 수정할 경우 state에 복사하여 사용해야 함
this.state = {item:props.item}
this.delete = props.delete //props로 상위 객체(delete)를 받아옴
}
//삭제 icon 누를시 호출될 함수
deleteHandler = (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.name}
value={item.title}
multiline={true}
fullWidth={true}/>
</ListItemText>
<ListItemSecondaryAction>
<IconButton aria-label="Delete ToDo"
onClick = {this.deleteHandler}>
<DeleteOutlined />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
)
}
}
export default ToDo;
결과 확인
9.11) 체크 박스를 클릭했을 때 done의 값을 토글 - 수정
- ToDo.jsx 파일에 state를 추가하고 Event Handler를 추가
//react.js 파일에서 export한 객체를 React로 받아서 사용
//{이름} 의 경우는 export 한 객체에서 이름에 해당하는 것만 받아서 사용
import React from "react"
import {
ListItem,
ListItemText,
InputBase,
Checkbox,
ListItemSecondaryAction,
IconButton
} from "@material-ui/core";
//Icon 가져오기
import DeleteOutlined from "@material-ui/icons/DeleteOutlined";
class ToDo extends React.Component{
constructor(props){
super(props);
//상위 컴포넌트로부터 넘겨받은 데이터를 나의 props에 저장
//props는 읽기전용이라서, 수정할 경우 state에 복사하여 사용해야 함
this.state = {item:props.item, readOnly:true}
this.delete = props.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의 내용을 변경했을 때 호출될 메서드
editEvnetHandler = (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});
}
//삭제 icon 누를시 호출될 함수
deleteHandler = (e) => {
this.delete(this.state.item);
}
render(){
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.name}
value={item.title}
multiline={true}
fullWidth={true}
onClick={this.offReadOnlyMode}
onKeyPress={this.enterKeyEventHandler}
onChange={this.editEvnetHandler}/>
</ListItemText>
<ListItemSecondaryAction>
<IconButton aria-label="Delete ToDo"
onClick = {this.deleteHandler}>
<DeleteOutlined />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
)
}
}
export default ToDo;
- 토글이란 하나의 설정 값으로부터 다른 값으로 전환하는 것이다.
- 실행 후 브라우저에서 체크 박스가 토글 되는지 확인 및 문자열 수정 되는지 확인 후 Enter 시 그대로 저장되는지 확인
10. 클라이언트와 서버 간의 데이터 주고 받기
10.1) SOP와 CORS
- SOP(Same Origin Policy - 동일 출처 정책)
웹 클라이언트(브라우저 - ajax 와 fetch API, Web Socket 등)와 웹 서버는 동일한 도메인(IP와 포트 번호 또는 URL)을 사용하는 경우에만 데이터를 주고 받도록 하는 정책
- CORS(Cross-Origin Resource Sharing)
리소스를 제공하는 도메인과 요청하는 도메인이 다르더라도 요청을 허락해주는 웹 보안 방침
서버에서 설정
- proxy
컴퓨터 네트워크에서 서버와 클라이언트 사이에서 요청과 응답을 중계해주는 역할
포워드 프록시 : 클라이언트 쪽에 만들어져서 클라이언트가 외부 요청을 하면 포워드 프록시가 외부 서버에 데이터를 요청하고 받은 응답을 클라이언트에게 돌려주는 방식
리버스 프록시 : 웹 서버 나 WAS(Web Application Server) 앞에 위치해서 클라이언트가 요청을 하면 리버스 프록시가 서버에 요청해서 응답을 받아서 클라이언트에게 전송해주는 것
WEB Server 와 WAS를 분리해서 구현하면 리버스 프록시를 구현했다고 합니다.
- 서버에 직접 구현하는 경우는 서버에 CORS 설정을 추가하는 것이 일반적이므로
2) SOP 문제로 데이터를 가져올 수 없는 상황
- App.js 에서 기본 데이터를 제거
- 서버가 구동중인지 확인 : http://localhost:8000/todo
- App.js 파일에 componentDidMount() 메서드
import './App.css';
import React from 'react';
import ToDo from './ToDo';
import {Paper, List, Container} from "@material-ui/core"; // import 시 {} 안의 내용들의 spelling은 틀려서는 안됨. 그리고 꼭 from 뒤에 내용에 들어가있는 내용이여야 함
import AddToDo from './AddToDo';
class App extends React.Component {
constructor(props){
super(props);
//하나의 객체를 생성해서 state에 item 이라는 이름으로 저장
this.state = {items:[]};
}
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)
}
}
- 실행한 후 콘솔 창을 확인하면 corcors오류가 발생
3) djnago 에서 django-cors-headers 설치
- settings.py 에 MiddleWare로 CorsMiddleware 를 등록
MIDDLEWARE = [
"corsheaders.moddleware.CorsMiddleware",
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
- settings.py 파일에 허용할 list 설정
MIDDLEWARE = [
"corsheaders.middleware.CorsMiddleware",
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
CORS_ORIGIN_WHITELIST = ['http://localhost:3000', 'http://127.0.0.1:3000']
CORS_ALLOW_CREDENTIALS = True
ROOT_URLCONF = "todoproject.urls"
- 기존 MySQL DATA
- MySQL에 있던 데이터를 가져오는데 성공하였다.
4) 클라이언트 수정
- 가져오기, 삽입, 삭제, 수정을 편리하게 수행하도록 하기 위해서 커스터마이징
- src 디렉토리에 서버의 URL을 저장할 app-config.js 파일을 추가
let backendHost;
// 이전에 IE 와 다른 브라우저들이 자바스크립트에서 BOM(Browser Object Model)이 달라서
// 존재하는 값을 대입하기 위해서 사용하던 문법(Cross Browsing)
// 이를 해결하기 위해서 등장했던 것이 jquery
const hostname =
window && window.location && windows.location.hostname;
if(hostname === "localhost"){
backendHost = "http://localhost:8000"
}
// 백틱을 이용해서 변수의 내용을 문자열로 출력
export const API_BASE_URL = `${backendHost}`
- 실제 작업을 수행할 함수를 소유한 파일을 생성하고 작성 - src/service/API_Service.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;
})
}
- ToDo.jsx 파일에서 수정
11. 클라이언트 애플리케이션 Docker Image로 배포
11.1) Dockerfile 만들어서 배포 - 이미지가 생성
- 현재 프로젝트에 Dockerfile 파일을 생성하고 작성 - manage.py
11.2) docker-compose.yml 을 만들어서 배포 - 컨테이너까지 생성
FROM node:alpine
#경로 설정
WORKDIR /app
ENV PATH /app/node_modules/.bin:$PATH
COPY package.json /app/package.json
RUN npm install --force
RUN npm install react -scripts@5.0.1 -g
EXPOSE 3000
CMD ["npm", "start"]
- docker-compose 명령 수행
docker-compose up -d --build
12. Git Hub에 소스 코드 업로드
12.1) git 버전 확인
git --version
12.2) git의 기본 브랜치 이름을 main 변경
git config --global init.defaultBranch main
12.3) git 에서 관리할 디렉토리를 설정
- 디렉토리로 프롬프트를 옮겨서 git init
디렉토리 안에 .git 이라는 디렉토리가 생성됨
이 디렉토리를 삭제하면 git 연동이 해제 됨.
기본적으로 숨김 디렉토리 임.
4)관리할 파일을 설정
git add 파일명 나열 또는 .(현재 디렉토리의 모든 파일)
- 디렉토리 안에 .gitignore 파일이 있으면 파일에 기록된 내용은 업로드 하지 않음
5) 변경 내용이 발생한 경우 로컬에 반영하고 메시지 작성
git commit -m "메세지"
파일 변공 혹은 추가시 4번 부터 다시
6)업로드할 repository 생성
7) 원격 저장소를 등록 - 한 번만 수행
git remote add 저장소 이름 url
8) git push origin 저장소 이름(main)
'LG 헬로비전 DX DATA SCHOOL > Web Programming' 카테고리의 다른 글
Django & React (0) | 2023.07.28 |
---|---|
JavaScript 기초 3 (0) | 2023.07.04 |
JavaScript 기초 2 (0) | 2023.07.03 |
JavaScript 기초 (0) | 2023.06.30 |
LG DX DATA SCHOOL 1기 xhtml (0) | 2023.06.29 |