[DRF] 3. Mini-Project : Create Blog(1)에서 만든 api에 리액트를 연결시켜보자.
리액트 자료도 포스팅 해야 되는데 이건 또 어느 세월에 올려야 되는 걸까.
기존의 drf 폴더를 빠져나와서 아무데나 react 폴더를 하나 만들어서 디렉토리에 들어간 후에
npx create-react-app blogapi .를 입력해준다.
그러면 blogapi라는 폴더가 만들어지는데 안에 들어가서 npm start를 하면 서버가 실행되는 것을 볼 수 있다.
DRF도 어려운데 내가 React까지 배워야 하나요?
그럴 필요 없지만 BE의 비애라고 한다면 열심히 만들어놓고 결과물을 확인 못 한다는 것.
물론 postman 같은 sw를 활용하면 뒤에 데이터가 어떻게 흘러가는지 testing하고 다 할 수 있긴 한데
어쨌든 공부는 풀스택으로 하는 게 맞다. 그래야 전체적인 흐름이 이해가 간다.
src에 components라는 폴더를 만들고 여기에 js 파일을 생성한다.
react가 어떤 원리로 돌아가는 지 간략하게 정리하자면
요소들을 블럭처럼 만들어 놓고, 얘네를 조립식으로 통채로 쑤셔넣었다가 뺐다가 하는 방식으로 구성한다.
Header.js - Contents.js - Footer.js 의 구조로 이루어진 페이지가 있다면, 헤더의 네비바를 누를 때마다
해당 요소의 컴포넌트가 기존의 요소를 빼버리고 Contents.js를 통해 화면에 출력시키는 그런 원리다.
사실 얘기만 들어보면 성능 이슈가 있지 않겠느냐는 이의 제기가 충분히 들어올 법한데,
이걸 또 어떻게 Virtual DOM으로 최적화를 시켜버렸다고 한다;;
자세한 건 훗날 리액트 카테고리에서 다루도록 하고, 바로 실전으로 찢어보자.
1. src > components 디렉토리 생성 후, Header.js, Posts.js, PostLoading.js, Footer.js, Content.js 생성
2. 터미널에 npm install react-router-dom , npm install react-bootstrap bootstrap입력하여 라우터 설치
3. 각각 .js 파일에 아래와 같이 입력하기
App.js
import React from 'react';
import './App.css';
import { BrowserRouter, Routes, Route} from 'react-router-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import Header from './components/Header';
import Content from './components/Content'
import Footer from './components/Footer';
function App() {
return (
<div className="App">
<BrowserRouter>
<Header />
</BrowserRouter>
</div>
);
}
export default App;
Header.js
import React from 'react';
import {Link} from 'react-router-dom'
import styles from "./Header.module.css";
export default function Header() {
const contents = [
{id: 1, title: 'PROJECT', url: 'introduce'},
{id: 2, title: 'STORY', url: 'story'},
{id: 3, title: 'PLAN', url: 'plan'},
{id: 4, title: 'TEAM', url: 'team'},
{id: 5, title: 'ROADMAP', url: 'roadmap'},
{id: 6, title: 'PARTNER', url: 'partner'},
];
return (
<>
<nav id={styles.navbar} className="navbar navbar-expand-md navbar-light">
<div className="container-fluid">
<Link className="navbar-brand" to="/">
<img src="#" alt=""></img>
</Link>
<button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="true" aria-label="Toggle navigation">
<span className="navbar-toggler-icon"></span>
</button>
<div className={`collapse navbar-collapse ${styles.navbarSupportedContent}`} id="navbarSupportedContent">
<ul className="navbar-nav me-auto mb-2 mb-lg-0">
{contents.map((content) =>
<li className="nav-item" key={content.id}>
<Link className="nav-link" activeClass="active" to={content.url}>{content.title}</Link>
</li>
)}
</ul>
</div>
</div>
</nav>
</>
);
}
Header.module.css (만들어줘야됨)
#navbar {
max-width: 1200px;
height: 100px;
margin: 0 auto;
}
.navbarSupportedContent {
width: 100%;
justify-content: flex-end;
z-index: 300;
}
#navSocial {
margin-left: 10px;
margin-right: 40px;
}
.socialIcon {
color: rgba(0,0,0,.5);
padding-left: 10px;
}
.socialIcon:hover {
color: rgba(0,0,0,.7);
}
리액트를 공부해보지 않았다면 굉장히 당황스러운 내용이지만, 라우터가 뭔지는 잠시 건너뛰고
그냥 [헤더] - [컨텐츠] - [푸터] 구조를 만들어주었을 뿐이다.
이제 각각의 요소에서 return시킨 블럭을 짜맞춰주기만 하면 된다.
헤더만 만들었으니까 현재 어떤 식으로 출력되는지 확인해보자.
이렇게 상단에 정상적으로 네비바가 보이는 것을 확인할 수 있다.
리액트 공부가 아니므로 나머진 코드만 올리도록 함.
App.js
import React from 'react';
import './App.css';
import { BrowserRouter, Routes, Route} from 'react-router-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import Header from './components/Header';
import Content from './components/Content'
import Footer from './components/Footer';
function App() {
return (
<div className="App">
<BrowserRouter>
<div className='container'>
<Header />
<Routes>
<Route path="/" element={<Content/>} />
</Routes>
<Footer />
</div>
</BrowserRouter>
</div>
);
}
export default App;
footer.js
import React from 'react';
import {Link} from 'react-router-dom';
export default function Footer() {
const footers = [
{
title: 'Company',
description: [
'Team',
'History',
'Contact us',
'Locations']
},
{
title: 'Features',
description: [
'Cool stuff',
'Random feature',
'Team feature',
'Developer stuff',
'Another one',
]
},
{
title: 'Resources',
description: [
'Resource',
'Resource name',
'Another resource',
'Final resource',
]
},
{
title: 'Legal',
description: ['Privacy policy', 'Terms of use']
}
]
return (
<footer className="py-5 bg-dark mt-auto">
<div className="row">
{footers.map((footer) => (
<div className="col" key={footer.title}>
<h4 style={{color : "white"}}>
{footer.title}
</h4>
<ul>
{footer.description.map((item) =>
<li key={item}>
<Link to="/">{item}</Link>
</li>
)}
</ul>
</div>
))}
</div>
</footer>
);
}
여기까지 하면 이런 모습의 페이지가 나타날 것이다
https://pypi.org/project/django-cors-headers/2.2.0/
이제 React도 대충 만들어놨으니 이전에 만든 Django API와 연결시켜주어야 한다.
잊지 말자. DRF는 8000번 포트, React는 3000번 포트에서 작업 중이었다.
그것을 django-cors-headers를 이용해 다른 출처에서 우리가 만든 API에 연결할 수 있다.
미들웨어 세팅시에 해당 줄은 가능한 저 위치에 써주는 것이 좋다.
그렇지 않으면 스크립트가 꼬일 수도 있음.
다음으로 setting.py에 접근을 허용할 출처를 명시해준다.
그럼 DRF에서 보낸 DB 정보를 React에서 받아야 하므로
Content.js로 돌아가 해당 코드를 입력하자.
지금부터 React와 DRF 서버 모두 가동시킨다. (노트북: 죽여줘)
import React from "react";
export default class connectionExample extends React.Component {
componentDidMount() {
const apiUrl = 'https://127.0.0.1:8000/api/'; // 새 구성 요소 생성
fetch(apiUrl)
.then((response) => response.json()) // REST에서 DB data가 json 형태로 넘어옴
.then((data) => console.log(data)) // 데이터 분석용
}
render() {
return (
<div>
Example connection
</div>
);
}
}
이제 실행만 하면 끝인가? 싶었는데 실행시키면 에러가 난다.
아까 Content.js에서 apiUrl을 작성할 때, 프로토콜 버전을 https:// 시크릿 버전으로 했지만
실제 DRF는 그렇지 않을 수 있다. 따라서 본인이 현재 사용하는 버전에 맞게 수정해야 한다.
Content.js의 apiUrl에서 http:// 로 수정해주자.
대충 읽어보면 정보를 가져오려는데 엑세스가 실패했다.
핵심 정책에 의해서 차단되었으며, 이 데이터에서 DB에 엑세스하는 것을 차단했단다.
사실 굉장히 간단한 문제다.
localhost가 곧 127.0.0.1을 의미하는 것은 맞지만 컴퓨터는 멍청해서 그걸 인지하지 못한다.
따라서 다시 setting.py로 가서 이번엔 http://localhost:3000d=으로 바꾸고 오자.
그러면 짜잔~
드디어!!!!!!!!! 우리가 만든 DRF API에서 데이터를 받아오는 것에 성공했음을 알 수 있다.
이제 받아온 정보를 토대로 화면에 출력시키면 된다.
하지만 실제로 데이터를 받자마자 화면에 출력시키기엔 API와 정보를 주고받는 과정에서 몇 초 정도 시간이 걸릴 수 있다.
따라서 그 사이에는 로딩화면을 띄우도록 하자.
예전에 어떤 개발자분께 들은 말이지만 로딩화면에 나오는 퍼센트는 아무 의미가 없다고 한다.
데이터가 얼마나 오고 가는지 그걸 수치로 표현한다는 게 가능하겠냐고. 하지만 소비자 입장에서 그게 있고 없고 차이가 커서 구현한다는 말이 있었다.
여기서 부터는 리액트를 설명하면서 넘어가기엔 너무 깊이 있으므로 모두 코드로 대체.
PostLoading.js
import React from "react";
export default function PostLoading(Component) {
return function PostLoadingComponent( {isLoading, ...props }) {
// 조건문이 들어갈 예정
return (
<p style={{ fontsize:'25px' }}>
We are waiting for the data to load...!
</p>
);
};
}
Content.js
import React, { useEffect, useState } from "react";
import Posts from './Posts';
import PostLoadingComponent from './PostLoading';
export default function Content() {
const PostLoading = PostLoadingComponent(Posts);
const [appState, setAppState] = useState({
loading: false, // 데이터를 수집하는 동안, loading이 화면에 출력됨
posts: null, // API에서 반환하는 모든 데이터 저장
})
useEffect(() => {
setAppState({ loading: true });
const apiUrl = `http://127.0.0.1:8000/api/a`; // 잠시 block하기 위해 고의로 틀림
fetch(apiUrl)
.then((data) => data.json())
.then((posts) => {
setAppState({ loading: false, posts: posts });
});
}, [setAppState]);
return (
<div className="App">
<h1>Latest Posts</h1>
<PostLoading isLoading={appState.loading} posts={appState.posts} />
</div>
)
}
여기까지하고 실행해보면 로딩화면이 떠야한다.
이제 Post를 만들면 된다!
진짜 마무리 작업.
PostLoading.js (if 조건문 추가)
import React from "react";
export default function PostLoading(Component) {
return function PostLoadingComponent( {isLoading, ...props }) {
if (!isLoading) return <Component {...props} />;
return (
<p style={{ fontsize:'25px' }}>
We are waiting for the data to load...!
</p>
);
};
}
Posts.js
import React from "react";
const Posts = (props) => {
const { posts } = props;
if (!posts || posts.length === 0) return <p>Can not find any posts, sorry</p>
return (
<div className='container grid'>
{posts.map((post) => {
return (
<div key={post.id}>
{post.title.substr(0, 50)}...
<div>
{post.excerpt.substr(0, 60)}...
</div>
</div>
)
})}
</div>
)
}
export default Posts;
비록 아무런 css 효과도 넣지 않아 단촐하지만
드디어 DRF API로부터 정보를 받아서 화면에 출력하는 걸 성공했다.