componentDidMount(){
//ajax 요청 객체 생성
let request = new XMLHttpRequest()
//요청 준비
request.open('GET', "http://127.0.0.1/todo?userid=Django")
//요청
request.send('')
//응답 처리
request.addEventListener('load', () => {
//json 데이터 출력
console.log(request.responseText)
})
}
구동 http://localhost:3000/
CORS Policy 에러가 출력
서버 애플리케이션은 127.0.0.1:80으로 실행 중이고 클라이언트 애플리케이션은 localhost(127.0.0.1):3000으로 실행중
SOP (동일 출처 정책)
ajax나 fetch api는 브라우저의 동일 출처 정책에 따라 자신과 동일한 도메인의 데이터만 받아올 수 있다.
동일 도메인이란 IP주소와 포트번호까지 일치하는 것을 의미한다.
서버 애플리케이션과 클라이언트 애플리케이션을 따로 구현하면 SOP 정책에 따라 ajax나 fetch api를 이용해서는 서버의 데이터를 사용할 수 없다.
서버 애플리케이션에서 요청이 가능한 클라이언트 애플리케이션의 도메인을 등록
서버 애플리케이션을 만들 때 CORS 설정을 확인해두자!
* React의 경우는 react의 설정 파일에 설정을 해도 CORS 설정이 가능하다.
* react가 node.js 플랫폼 위에서 동작하는데 node.js가 C/C++이라서 proxy를 이용해 react에 데이터를 넘겨줄 수 있다.
componentDidMount(){
//ajax 요청 객체 생성
let request = new XMLHttpRequest()
//요청 준비
request.open('GET', "http://127.0.0.1/todo?userid=Django")
//요청
request.send('')
//응답 처리
request.addEventListener('load', () => {
//json 데이터 출력
//JSON 문자열을 데이터로 변환
let data = JSON.parse(request.responseText);
console.log(data)
//서버에서 받아온 데이터를 state에 대입해서 화면에 출력
this.setState({items:data.list})
})
}
이전의 애플리케이션들은 여러 개의 HTML 파일을 만들어서 화면 전환을 이용해서 여러 콘텐츠를 출력했는데 -> 화면 전환을 하게 되면 이전 내용을 삭제하고 새로운 내용을 출력해야 하기 때문에 깜빡임이 발생하고 -> 네트워크 에러가 발생하게 되면 화면 전체가 에러 페이지를 출력 -> 모바일의 경우는 에러 페이지를 보는 횟수가 증가
해결 방법으로 로컬에 데이터를 저장해서 네트워크 에러가 발생한 경우 로컬의 데이터를 출력 하나의 화면을 여러 컴포넌트로 분할하고 분할한 컴포넌트 별로 별도로 데이터를 요청해서 출력
SPA를 만들기 위한 프레임워크로 널리 사용되는 것이 angular, react, vue 입니다.
angular는 구글이 만든 것으로 공식적으로 더 이상 업데이트를 하지 않는다고 발표를 했습니다.
현재 국내 플랫폼 기업이나 중소 기업은 react를 선호하는데 vue를 사용하는 곳도 있습니다.
2) React
2013년, Facebook에서 발표한 SPA를 구현하기 위한 오픈소스 자바스크립트 프레임워크
새로운 문법 사용: 가상 DOM과 JSX
출력이 빠름: 게임 엔진의 출력 방식을 이용, 가상의 DOM을 메모리에 배치하기 떄문
컴포넌트 기반
태그 화면에 보여지는 개체 <div>
컴포넌트 하나의 영역을 나타내기 위한 태그와 코드의 집합 자체적으로 데이터를 가질 수 있고, 데이터가 변경되면 자동으로 재출력 (새로고침 필요 X) 미리 구조를 만들어두고 다시 출력할 때는 변경된 부분만 다시 출력
프레임워크가 제공하는 기능을 그대로 사용
클래스가 충분한 기능을 제공해주기 때문에 클래스를 이용해서 인스턴스를 만들어서 바로 사용
ex. 프레임워크가 Template라는 클래스를 제공하면 template = new Template()
프레임워크가 제공하는 기능을 상속받아서 재정의해서 사용
클래스가 충분한 기능을 제공해주지 않아서 내가 기능을 추가해서 사용
상속을 받으면 상위 클래스의 모든 것을 하위 클래스가 물려받음
React에서는 Component라는 클래스가 Component의 기능은 전부 가지고 있는데 화면에 아무것도 출력하지 않는다. React.Component로부터 상속을 받고 render라는 메서드를 재정의해서 return 값을 출력하도록 설계
React-native
- 모바일 앱을 만들 수 있다.
- 안드로이드와 애플 환경에서 모두 사용할 수 있게 코드를 바꿔준다.
Figma
- 디자인을 리액트 코드로 바꿔준다.
3) 설치
node.js 설치
- node.js를 자바스크립트로 애플리케이션을 개발하기 위한 플랫폼이다. (프레임워크 아님)
- 설치확인: node --version
패키지 관리자: npm과 yarn
- package.json 파일에 의존성을 설정하고 node.modules 디렉토리에 패키지 저장
- git hub에 업로드 할 때 node_modules는 할 필요 없음. package.json만 있으면 빌드를 다시 해서 설치 가능
npm install --location=global yarn
- 설치 확인: yarn --version
4)React 프로젝트 생성 및 실행
생성
yarn create react-app 앱이름(myapp)
실행
#myapp 디렉토리에서
yarn start
yarn 없이 실행: npm start
5) Comonent를 화면에 출력
Component: 화면에 출력되는 독립적인 개체
src 디렉토리에 ToDo.jsx
확장자는 js 도 상관없지만 react 컴포넌트 파일이라는 것을 알려주기 위해서 jsx 로 설정
import React from "react";
//react에서 컴포넌트 클래스를 만들기 위해 Component로부터 상속 받기
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>
)
}
}
id: 자바스크립트에서 태그를 구별해서 가져오기 위한 값 (중복 불가) document.getElementById
class: react에선 className. css에서 동일한 디자인을 적용하기 위한 이름 (중복 가능) document.getElementByClassName
name: 클라이언트가 서버로 전송할 때 이름 (중복 가능. checkbox, select 등)
value: 값
label: for="id" 글자를 눌러도 체크가 된다.
메인 페이지의 역할을 수행하는 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;
export default App; 이 없을 경우 에러 ERROR in ./src/index.js 12:33-36 export 'default' (imported as 'App') was not found in './App' (module has no exports)
javascript는 기본적으로 private이라서 그렇다.
react는 App.js 파일이 전체 화면을 출력하는 파일 App.js에 작성한 내용이 출력 react는 반드시 하나로 묶어서 출력해야 함
import React from "react"
class Sample extends React.Component{
render(){
return(
<div className="Sample">
<p>안녕하세요</p>
</div>
)
}
}
export default Sample;
App.js
import './App.css';
import React from 'react';
import ToDo from './ToDo';
import Sample from './Sample';
function App() {
return (
<div className="App">
<ToDo />
<Sample/>
</div>
);
}
export default App;
6) 컴포넌트에서 데이터를 사용하는 방법
props 속성 : 상위 컴포넌트에서 넘겨주는 데이터 큰 화면에서 작은 화면으로 넘어갈 때 전달해주는 데이터 내가 쓸 데이터를 내가 만들어서 준다.
state 상태 : 컴포넌트 내부에서 사용하는 데이터 react는 props나 state에 변화가 생기면 화면을 다시 출력
context : 글로벌·전역변수, 모든 컴포넌트에서 사용할 수 있는 데이터 ex. 로그인 기능
redux : 전역 데이터를 만드는 외부 라이브러리
Entry 포인트 : React는 시작점이 App.js
7) props와 state 사용
ToDo.jsx
import React from "react"
class ToDo extends React.Component{
//생성자
constructor(props){
super(props); //상위 클래스에서 넘겨준 모든 props를 현재 클래스에 저장
//state 생성 - item이라는 이름으로 props 중에서 item이라는 값을 state로 저장
//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.id}>{this.state.item.title}</label>
</div>
)
}
}
export default ToDo;
그런데 현재 상위 컴포넌트가 없는 상태라서 item.id, item.done, item.title을 묶을 수 있는 클래스가 필요하다.
App.js 수정 > ToDo.jsx 파일에 props 전달
import './App.css';
import React from 'react';
import ToDo from './ToDo';
class App extends React.Component {
constructor(props){
super(props)
//3개의 객체를 가진 배열을 생성
//this.state.item = {item:{id:0, "title":"Hello React", "done":true}}
this.state = {item:{id:0, "title":"Hello React", "done":true}}
}
render(){
return(
<div>
{/* Todo에게 item이라는 이름으로 데이터 전달 */}
<ToDo item={this.state.item} />
</div>
)
}
}
export default App;
App.js에 this.state.item이라는 이름으로 데이터 생성
props의 데이터를 ToDo에게 전달
상위 클래스: React.Component 상위 컴포넌트(부모): App 하위 컴포넌트(자식): Todo 하위 클래스: App
배열이나 리스트를 순회하면서 작업을 수행하는 경우
map
데이터를 변환해주는 함수
함수를 대입해서 함수에 데이터를 순서대로 대입해서 리턴한 결과를 모아 다시 배열이나 리스트로 리턴해주는 함수
filter
데이터를 필터링해주는 함수
boolean을 리턴하는 함수를 대입해서 데이터를 순서대로 대입하고 return 값이 true인 데이터만 모아 다시 배열이나 리스트로 리턴해주는 함수
reduce
계산하고 리턴해주는 함수
연산 후 리턴하는 함수를 대입해서 데이터를 순서대로 대입,
return 값을 가지고 다음 함수 호출의 매개변수로 활용해서 결과를 하나의 값으로 리턴해주는 함수
App.js
import './App.css';
import React from 'react';
import ToDo from './ToDo';
// import Sample from './Sample';
class App extends React.Component {
constructor(props){
super(props)
//this.state.time = {item:{id:0, "title":"Hello React", "done":true}}
this.state = {items:[ {id:0, "title":"Hello React", "done":true},
{id:1, "title":"vue", "done":false},
{id:2, "title":"angular", "done":false}]
}
}
render(){
//배열을 순회하면서 출력할 내용을 생성
//item은 배열을 순회할 때 각각의 데이터이고 idx는 인덱스
//배열을 순회하면서 출력물을 만들 때는 key를 설정
//key를 설정하지 않으면 출력에는 문제가 없지만 콘솔에 에러가 출력
let display = this.state.items.map((item, idx) => ( //(아이템, 인덱스)
<ToDo item={item} key={item.id} />
));
return(
<div className="App">
{display}
</div>
)
}
}
export default App;
클라이언트(웹 브라우저)의 요청을 서버가 받아서 처리한 후 서버가 출력하는 코드를 만들어서 클라이언트에게 전송을 하고 클라이언트는 이 코드를 받아서 파싱해서 출력하는 방식
서버 코드 와 클라이언트 코드를 하나의 프로젝트에 전부 작성
서버 코드를 수정할 때 클라이언트 코드가 영향을 받을 수 있고 클라이언트 코드를 수정할 때 서버 코드가 영향을 받을 수 있습니다.
이 방식으로 서비스를 만들려면 템플릿 언어라는 것을 학습을 해야 합니다.
최근에는 이 방식을 선호하지 않습니다.
클라이언트 사이드 랜더링
서버는 클라이언트 요청을 받아서 데이터(XML -> JSON)를 응답으로 전송하고 클라이언트는 응답을 받아서 직접 데이터를 해석해서 출력하는 코드를 만들고 화면에 랜더링하는 방식입니다.
이 방식에서는 서버 프로그램(Python의 Django, Java의 Spring, JavaScript의 express.js 등) 과 클라이언트 프로그램(vue.js, react.js->next.js, android-java 나 kotlin, ios-swift, 플로터-dart 등) 을 별도로 작성합니다.
서버 코드 가 클라이언트 코드에 영향을 주지 않고 클라이언트 코드가 서버 코드에 영향을 주지 않습니다.
이렇게 통신만으로 서로 연동되는 경우를 느슨한 결합(loosly coupling)이라고 합니다.
최근에는 프로그램에 유지보수가 자주 발생하므로 이러한 느슨한 결합의 형태로 프로그램을 만들기를 권장합니다.
프로그래밍 언어에서의 객체
{name:"아담", age:53} - dict
person = Person() person.name = "아담" person.age = 53
rest_framework 에서는 serializer를 정의하면 클라이언트가 전송한 매개변수를 가지고 모델 클래스로 자동 변환이 가능
apiapp 디렉토리에 serializers.py 파일을 만들고 클라이언트의 매개변수를 Book
from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
#클라이언트에서 입력할 데이터만 나열
#아래 나열한 데이터를 입력하면 Book 클래스의 인스턴스를 자동 생성
fields = ['bid', 'title', 'author', 'publisheddate']
데이터 삽입(POST) 처리와 전체 조회(GET)하는 처리를 views.py 파일에 작성
from rest_framework import status
from rest_framework.generics import get_object_or_404
from .models import Book
from .serializers import BookSerializer
@api_view(['GET', 'POST'])
def booksAPI(request):
#GET 방식의 처리 - 조회를 요청하는 경우
if request.method == 'GET':
#테이블의 데이터를 전부 가져오기
books = Book.objects.all()
#출력하기 위해서 브라우저의 형식으로 데이터를 변환
serializer = BookSerializer(books, many=True)
#출력
return Response(serializer.data)
#POST 방식의 처리 - 삽입하는 경우
elif request.method == 'POST':
serializer = BookSerializer(data = request.data)
serializer.save()
return Response(serializer.data)
apiapp 디렉토리의 urls.py 파일에 url과 이전에 만든 함수를 연결
from django.urls import path
from .views import hello, booksAPI
urlpatterns = [
path("hello/", hello),
path("books/", booksAPI),
]
서버를 구동하고 브라우저에 127.0.0.1
데이터를 삽입할 때 에러가 발생
rest_framework를 사용할 때는 데이터를 save할 때 is_valid()를 호출해서 유효성 검사에 통과했을 때만 삽입되도록 해야 함.
...
elif request.method == 'POST':
#클라이언트에서 전송된 데이터를 가지고 Model 인스턴스 생성
serializer = BookSerializer(data = request.data)
#유효성 검사를 수행해서 통과하면 삽입, 실패하면 이유 출력
if serializer.is_vallid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors)
bid는 프라이머리 키기 떄문에, bid에 똑같은 정보를 입력하면
7) 데이터 1개 가져오기
데이터 1개를 가져오려면 클라이언트에서 기본키 값을 서버에 제공해야 하고
서버는 이 값을 읽어서 하나의 데이터를 찾아 넘겨줘야 한다.
views.py
@api_view(['GET'])
def oneBookAPI(request, bid):
#Book 테이블에서 bid 컬럼의 값이 bid인 값을 찾아온다.
book = get_object_or_404(Book, bid=bid)
#출력할 수 있도록 변환
serializer = BookSerializer(book)
return Response(serializer.data)
urls.py
from django.urls import path
from .views import hello, booksAPI, oneBookAPI
urlpatterns = [
...
path("onebook/<int:bid>", oneBookAPI),
]
git add 파일or디렉토리_이름_나열
git add . # 디렉토리의 모든 파일 과 디렉토리 관리
현재까지 변경 내용을 로컬 git에 반영
git commit -m "메시지"
변경한 내역이 없으면 commit 하지 않음
이메일이나 이름을 등록한 적이 없으면 이메일과 이름을 등록하라는 메시지 출력
이 작업을 수행하고 커밋하면 됨
돌아가려면 commit된 id로 돌아가야 한다.
git reset --soft id
git reset --hard id
soft: 이전 내용을 저장은 해놓지만 코드를 수정하지는 않는다.
hard: 이전 작업 내역을 날려버리고 코드 수정
원격 git 저장
1. 원격에 저장할 때 파이썬 프로젝트에서 수행할 작업
.gitignore 파일을 만들어서 가상환경 디렉토리 등록해서 업로드가 안되도록 함
업로드 하지 않을 디렉토리나 파일을 등록
pip freeze > requirements.txt
수행해서 설치한 패키지 목록을 저장
2. Github에서 repository를 생성 (1번만 수행)
3. 로컬 git과 원격 repository를 연결
git remote add 이름 repositoryurl
git remote add origin https://github.com/YachaeMoon/Djnago.git
맨처음 연결하는 경우는 이름은 origin을 관습적으로 사용
4. 연결 확인
git remote -v
원격 git과 로컬 git이 연결됨
repository는 큰 집, branch는 방. 하나의 repository에서 각자 branch를 만들고 코드를 넣으면 merge해서 전체가 하나가 될 수 있다. 그때마다 코드를 push하면 알림을 준다. 코드가 바뀐 걸 확인하면 들어가서 confirm을 하면 merge가 자동적으로 변경된다.
MIDDLEWARE 부분은 장고 애플리케이션이 요청을 처리하기 전에 수행하고자 하는 작업이 있을 때 작업을 기재하는 곳입니다. 필터의 역할을 수행하는 것입니다.
DATABASES 부분은 사용할 데이터베이스를 설정하는 부분인데 기본 코드는 python 안에 있는 sqlite3에 접속하도록 설정되어 있습니다.
TIME_ZONE은 시간 대역을 설정하는 부분인데 우리는 'Asia/Seoul' 로 변경해야 합니다.
TIME_ZONE = "Asia/Seoul"
urls.py
사용자의 요청(url)을 처리할 함수나 클래스를 설정하는 파일
views.py
실제로 요청을 처리할 함수나 클래스를 작성하는 곳
models.py
사용할 데이터베이스 모델을 만드는 곳
5) 프로젝트의 모델 변경 내용을 데이터베이스에 반영
python manage.py migrate
이 명령을 맨 처음 실행시키면 유저 정보나 로그인에 관련된 테이블을 자동으로 생성합니다.
admin, auth, session 관련 테이블을 생성
authentication(인증): 로그인 authorization(인가): 권한
session: 클라이언트의 정보를 서버에 저장하는 것 대부분의 경우 session에 로그인한 유저 정보를 저장해두는 경우가 많습니다. 세션은 브라우저 창을 닫으면 자동으로 소멸됩니다.
서버가 구동중이면 Ctrl + C로 서버 중지하고 수행!
6) 관리자 계정 생성 및 접속
python manage.py createsuperuser
수행하고 필요한 정보(username, email, password)를 입력
127.0.0.1/admin으로 접속하면
7) 메인 페이지 작성
myapp > views.py 파일에 작성
from django.shortcuts import render
from django.http import HttpResponse
def index(request):
return HttpResponse('<h1>야채입니당</h1>') #HTML
index 나 request는 사용자 설정!
myproject > urls.py 파일에 작성
from django.contrib import admin
from django.urls import path
#myapp 디렉토리에 있는 views 파일의 내용을 가져오기
from myapp import views
urlpatterns = [
path("admin/", admin.site.urls),
path("user/", admin.site.urls),
path("", views.index)
]
"" 를 입력하면 myapp/views.py 파일의 index 함수를 호출해서 처리
브라우저에 IP를 입력하면 이제
8) 요청이 오면 HTML 파일을 출력 (별로 안 중요)
def menu(request):
#menu.html 파일을 출력하는데 파일에 message 라는 이름으로 data를 전달
return render(request, 'menu.html', {'message':'data'})
출력할 HTML 파일을 저장하기 위한 templates 디렉토리를 애플리케이션 디렉토리(myapp)에 생성
templates 디렉토리에 menu.html 파일을 만들고 출력 내용을 작성
서버 애플리케이션에 html 파일을 만들어서 출력하는 방식을 서버 사이드 랜더링이라고 하고 최근에는 잘 사용하지 않음
SELECT <열 목록>
FROM <첫 번째 테이블>
INNER JOIN <두 번째 테이블>
ON <조인될 조건>
[WHERE 검색 조건]
USE market_db;
SELECT *
FROM buy
INNER JOIN member
ON buy.mem_id = member.mem_id
WHERE buy.mem_id = 'GRL';
3) 별칭
USE market_db;
SELECT mem_id --에러! buy.mem_id
FROM buy
INNER JOIN member
ON buy.mem_id = member.mem_id;
Error Code: 1052. Column 'mem_id' in field list is ambiguous
SELECT할 때 겹치는 열 이름은 어떤 테이블의 열인지 명확하게 표현해야 함!
그런데 이러면 코드가 너무 길어진다는 문제가 있다.
FROM 절 뒤에 별칭을 주자!
SELECT B.mem_id, M.mem_name
FROM buy B
INNER JOIN member M
ON B.mem_id = M.mem_id;
4) 내부 조인 활용
SELECT M.mem_id, M.mem_name, B.prod_name, M.addr
FROM buy B
INNER JOIN member M
ON B.mem_id = M.mem_id
ORDER BY M.mem_id;
한번도 구매하지 않은 회원의 정보는 없다.
구매하지 않은 회원의 정보도 같이 검색되려면 외부조인을 사용해야 한다.
B. 외부 조인
1) 외부조인의 기본
SELECT <열 목록>
FROM <첫 번째 테이블(LEFT 테이블)>
<LEFT | RIGHT | FULL> OUTER JOIN <두 번째 테이블(RIGHT 테이블)>
ON <조인될 조건>
[WHERE 검색 조건]
LEFT OUTER JOIN: 왼쪽 테이블(member)의 내용은 모두 출력되어야 한다.
RIGHT OUTER JOIN: 반대. 오른쪽에 있는 회원테이블을 기준으로 외부조인.
FULL OUTER JOIN: 한쪽에 들어 있는 내용이면 출력. 자주 사용되진 않음.
SELECT M.mem_id, M.mem_name, B.prod_name, M.addr
FROM member M
LEFT OUTER JOIN buy B
ON M.mem_id = B.mem_id
ORDER BY M.mem_id;
2) 외부 조인의 활용
SELECT DISTINCT M.mem_id, M.mem_name, B.prod_name, M.addr
FROM member M
LEFT OUTER JOIN buy B
ON M.mem_id = B.mem_id
WHERE B.prod_name IS NULL
ORDER BY M.mem_id;
C. 기타 조인
1) 상호조인 (cartesian product)
한쪽 테이블의 모든 행과 다른쪽 테이블의 모든 행을 조인시키는 기능
그래서 상호 조인 결과의 전체 행 개수는 두 테이블의 각 행의 개수를 곱한 개수가 된다.
SELECT *
FROM buy
CROSS JOIN member;
ON 구문은 사용할 수 없다.
결과의 내용은 의미가 없다. 랜덤으로 조인하기 때문이다.
상호 조인의 주 용도는 테스트하기 위해 대용량의 데이터를 생성할 때!
2) 자체조인 (self join)
자신이 자신과 조인한다는 의미
1개의 테이블만 사용
실무에서는 많이 사용하지 않지만, 회사의 조직 관계가 대표적
직원의 직속 상관은 또한 회사의 직원이기도 하다.
그래서 직원의 직속 상관의 정보를 조회할 때 사용한다.
SELECT <열 목록>
FROM <테이블> 별칭A
INNER JOIN <테이블> 별칭B
ON <조인될 조건>
[WHERE 검색 조건]
create table emp_table (emp CHAR(4), manager CHAR(4), phone VARCHAR(8));
insert into emp_table values('대표', NULL, '0000');
insert into emp_table values('관리이사', '대표', '1111');
insert into emp_table values('경리부장', '관리이사', '1122');
insert into emp_table values('인사부장', '관리이사', '1133');
SELECT A.emp "직원", B.emp "직속상관", B.phone "직속상관연락처"
FROM emp_table A
INNER JOIN emp_table B
ON A.manager = B.emp
WHERE A.emp = '경리부장';