Backend
home

2024. 6. 28 - React

miniblog 구성

page, list, ui 폴더 구성
page
BlogMain.js
import React from "react"; import styled from "styled-components"; import data from "../data.json" import PageLayOut from "./PageLayout"; import Button from "../ui/Button"; import PostList from "../list/PostList"; import { useNavigate } from "react-router-dom"; import { useState } from "react"; import { getPostList } from "./postAPI"; import { useEffect } from "react"; function BlogMain({query}) { const navigate = useNavigate(); const [data, setData] = useState([]) useEffect(() => { getPostList() .then(result => { setData(result) console.log() }) .catch(console.log) .finally(() => console.log('finally...')) }, [query]) return ( <PageLayOut> <Button title="글 작성" onClick={() => navigate('/post-write')}></Button> <PostList posts={data} onClickItem={(item) => navigate(`post/${item.id}`)}></PostList> </PageLayOut> ) } export default BlogMain
JavaScript
복사
PageLayout.js
import React from "react"; import styled from "styled-components" const Wrapper = styled.div` padding: 16px; width: cal(100%-32px); display: flex; flex-direction: column; align-items: center; justify-content: center; ` const Container = styled.div` width: 100%; max-width: 720px; & > * { :not(:lastChild) { margin-bottom: 16px } } ` function PageLayOut(props) { return ( <Wrapper> <Container> {props.children} </Container> </Wrapper> ) } export default PageLayOut
JavaScript
복사
postAPI.js
import axios from "axios" export const getPostList = async () => { const res = await axios.get('http://127.0.0.1:5500/data.json') return res.data // javascript Object }
JavaScript
복사
list
CommentList.js
import React from "react"; import CommentListItem from "./CommentListItem"; import styled from "styled-components"; const Wrapper = styled.div` display: flex; flex-direction: column; align-items: flex-start; justify-content: center; margin-bottom: 10px; & > * { :not(:last-child) { margin-bottom: 16px; } } ` // CommentListItem 반복 출력 // 댓글 배열 : comments function CommentList({comments}) { return ( <Wrapper> { comments.map((comment, index) => { return ( <CommentListItem key={index} comment={comment} /> ) }) } </Wrapper> ) } export default CommentList
JavaScript
복사
CommentListItem.js
import React from "react"; import styled from "styled-components"; const Wrapper = styled.div` width: 90%; padding: 8px 16px; display: flex; flex-direction: column; align-items: flex-start; border: 10px solid gray; border-radius: 8px; cursor: pointer; background: white; :hover {background: lightgray;} ` const TitleText = styled.p` font-size: 16px; white-space: pre-wrap; ` function CommentListItem({comment}) { return ( <Wrapper> <TitleText>{comment.content}</TitleText> </Wrapper> ) } export default CommentListItem
JavaScript
복사
PostList.js
import React from "react"; import styled from "styled-components"; import PostListItem from "./PostListItem"; const Wrapper = styled.div` margin-top: 10px; display: flex; flex-direction: column; align-items: flex-start; justify-content: center; & > * { :not(:lastChild) { margin-bottom: 16px; } } ` function PostList({posts, onClickItem}) { return ( <Wrapper> { posts.map((post, index) => { return ( <PostListItem key={index} post={post} onClick={() => onClickItem(post)} /> ) }) } </Wrapper> ) } export default PostList
JavaScript
복사
PostListItem.js
import React from "react"; import styled from "styled-components" const Wrapper = styled.div` width: calc(100%); padding: 16px; display: flex; flex-direction: column; align-items: flex-start; border: 1px solid gray; border-radius: 8px; cursor: pointer; background-color: white; :hover {background: lightgray} ` const TitleText = styled.p` font-size: 20px; font-weight: bold; ` function PostListItem({post, onClick}) { return ( <Wrapper onClick={onClick}> <TitleText>{post.title}</TitleText> </Wrapper> ) } export default PostListItem
JavaScript
복사
PostViewPage.js
import React, { useState } from "react"; import styled from "styled-components"; import PageLayOut from "../page/PageLayout"; import Button from "../ui/Button"; import TextInput from "../ui/TextInput"; import CommentList from "./CommentList"; import data from "../data.json"; import { useNavigate, useParams } from "react-router-dom"; const PostContainer = styled.div` margin-top: 10px; padding: 8px 16px; border: 1px solid #aaa; border-radius: 8px; ` const TitleText = styled.p` font-size: 28px; font-weight: 500; ` const ContentText = styled.p` font-size: 20px; line-height: 32px; ` const Label = styled.p` font-size: 16px; ` function PostViewPage() { const [comment, setComment] = useState('') const navigate = useNavigate() const {postId} = useParams() // /post/3 => 3 추출 const post = data.find((item) => item.id == postId) return ( <PageLayOut> <Button title="뒤로가기" onClick={() => navigate("/")}></Button> <PostContainer> <TitleText> {post.title} </TitleText> <ContentText> {post.content} </ContentText> </PostContainer> <Label>댓글 목록</Label> <CommentList comments={post.comments}></CommentList> <Label>댓글 작성</Label> <TextInput height={40} value={comment} onChange={(event) => setComment(event.target.value)} /> <Button title="댓글 작성" onClick={() => navigate("/")} /> </PageLayOut> ) } export default PostViewPage
JavaScript
복사
ui
Button.js
import React from "react"; import styled from "styled-components" const StyledButton = styled.button` padding: 8px 16px; font-size: 16px; border-width: 1px; border-radius: 8px; cursor: pointer; ` // 버튼 이름, 이벤트 핸들러 function Button({ title, onClick }){ return ( <StyledButton onClick={onClick}> {title || '버튼'} </StyledButton> ) } export default Button
JavaScript
복사
TextInput.js
import React from "react"; import styled from "styled-components" const StyledTextarea = styled.textarea` width: calc(100% - 32px); ${props => props.height && `height: ${props.height}px;`} padding: 16px; font-size: 16px; line-height: 20px; ` // 높이, 기본 값, onChange 핸들러 function TextInput({ height, value, onChange }) { return ( <StyledTextarea height={height} value={value} onChange={onChange} /> ); } export default TextInput
JavaScript
복사

Ajax

자바스크립트를 이용해서 브라우저에서 서버에 비동기식으로 데이터를 요청하고 서버로부터 수신한 데이터를 사용하여 웹페이지의 화면을 동적으로 갱신하는 프로그래밍.
Web API를 이용하여 비동기 통신 - XMLHttpRequest

JSON

클라이언트와 서버간 HTTP 통신을 위한 텍스트 기반의 데이터 포맷.
특정 플랫폼이나 언어에 종속적이지 않음.
값은 객체 리터럴 그대로 사용하고, 문자열은 반드시 큰따옴표를 사용.
{ "name": "MINSUNG", "age": 30, "alive": true, "hobby": [ "Campping", "game" ] }
JavaScript
복사

XMLHttpRequest

자바스크립트를 사용하여 HTTP 요청을 하기 위해서는 XMLHttpRequest를 사용
요청 전송과 응답 수신을 위한 메소드와 프로퍼티 제공.
XMLHttpRequest
// 객체 생성 const xhr = new XMLHttpRequest();
JavaScript
복사
Http 요청
open(method, url, [ async ] ): async 값은 기본값이 true 로 비동기 방식으로 동작.
setRequestHeader() : Header값을 설정, open 메소드 호출 이후 설정
send(): 설정된 HTTP 요청을 서버로 전송
// 1. 객체 생성 const xhr = new XMLHttpRequest(); // 2. HTTP 요청 초기화 xhr.open('GET', '/users'); // 3. HTTP 요청 헤더 설정 // 클라이언트가 서버로 전송할 데이터의 MIME 타입 지정: json xhr.setRequestHeader('content-type', 'application/json'); // 4. HTTP 요청 전송 xhr.send();
JavaScript
복사
XMLHttpRequest
Http 응답처리
서버에서 전송한 응답을 처리하려면 XMLHttpRequest 객체가 생성하는 이벤트를 확인해야 함.
XMLHttpRequest 의 이벤트 핸들러
onreadystatechange, onload, onerror
onreadystatechange
http 요청 - 현재 상태를 나타내는 readystate 프로퍼티 값이 변경된 경우 이벤트 발생
const xhr = new XMLHttpRequest(); // https://jsonplaceholder.typicode.com은 Fake REST API를 제공하는 서비스다. xhr.open('GET', 'https://jsonplaceholder.typicode.com/todos/1'); xhr.send(); // readystatechange 이벤트는 readyState(HTTP 요청의 현재 상태) 프로퍼티가 변경될 때마다 발생 xhr.onreadystatechange = () => { // readyState 프로퍼티 값이 4(XMLHttpRequest.DONE)가 아니면 서버 응답이 완료되지 상태다. // 만약 서버 응답이 아직 완료되지 않았다면 아무런 처리를 하지 않는다. if (xhr.readyState !== XMLHttpRequest.DONE) return; // status 프로퍼티는 응답 상태 코드를 나타낸다. // status 프로퍼티 값이 200이면 정상적으로 응답된 상태이고 200이 아니면 에러가 발생한 상태다. // 정상적으로 응답된 상태라면 response 프로퍼티에 서버의 응답 결과가 담겨 있다. if (xhr.status === 200) { console.log(JSON.parse(xhr.response)); // {userId: 1, id: 1, title: "delectus aut autem", completed: false} } else { console.error('Error', xhr.status, xhr.statusText); } };
JavaScript
복사

프로미스

도입 배경
자바스크립트는 비동기 처리 위한 방법으로 콜백함수 사용
가독성이 좋지 않음.
비동기 처리 중 에러 처리가 어려움.
여러 개의 비동기 처리를 한번에 처리하기도 한계가 있음.
ES6 에서는 비동기통신의 처리를 위해 프로미스 도입
콜백 패턴이 가진 단점을 보완: 처리 시점을 명확히 확인하여 처리.
Promise 생성자 함수를 new 연산자와 함께 호출하여 객체 생성
콜백 함수를 인자로 받음: 콜백 함수는 resolve, reject 를 받음
// 프로미스 생성 const promise = new Promise((resolve, reject) => { // Promise 함수의 콜백 함수 내부에서 비동기 처리를 수행한다. if (/* 비동기 처리 성공 */) { resolve('result'); } else { /* 비동기 처리 실패 */ reject('failure reason'); } });
JavaScript
복사
get 방식 요청하는 비동기 통신을 하는 코드를 프로미스로 구현
// GET 요청을 위한 비동기 함수 const promiseGet = url => { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.send(); xhr.onload = () => { if (xhr.status === 200) { // 성공적으로 응답을 전달받으면 resolve 함수를 호출한다. resolve(JSON.parse(xhr.response)); } else { // 에러 처리를 위해 reject 함수를 호출한다. reject(new Error(xhr.status)); } }; }); }; // promiseGet 함수는 프로미스를 반환한다. promiseGet('https://jsonplaceholder.typicode.com/posts/1');
JavaScript
복사
프로미스 상태
프로미스의 상태정보
의미
상태변경 조건
pending
비동기 처리가 아직 수행되지 않은 상태
프로미스가 생성된 직후 상태
fulfilled
비동기 처리가 수행된 상태(성공)
resolve 함수 호출
rejected
비동기 처리가 수행된 상태(실패)
reject 함수 호출
프로미스 후속 메소드
.then()
두 개의 콜백 함수를 전달 받음
첫 번째 콜백 함수: fulfilled 상태가 되면 호출
이 때 콜백 함수의 인자는 프로미스 실행 결과를 받음
두 번째 콜백 함수: rejected 상태가 되면 호출
프로미스 에러를 인수로 받음
프로미스 반환
.catch()
한 개의 콜백 함수를 전달 받음
프로미스 rejected 상태인 겨웅에만 호출
프로미스 반환
.finally()
한 개의 콜백 함수를 전달 받음
프로미스의 상태와 상관 없이 무조건 한번 호출, 공통적으로 꼭 실행해야 하는 처리 수행
프로미스 반환
에러처리
- then() 메소드에서 처리하는 것보다는 catch() 메소드에서 처리하는 것이 좋음
비동기 처리 에러 뿐만 아니라 then() 메소드 내부에서 처리되는 에러도 캐치 가능
promiseGet('https://jsonplaceholder.typicode.com/todos/1') .then(res => console.log(res)) .catch(err => console.error(err)); // TypeError: console.xxx is not a function
JavaScript
복사
프로미스 체이닝
const url = 'https://jsonplaceholder.typicode.com'; // id가 1인 post의 userId를 취득 promiseGet(`${url}/posts/1`) // 취득한 post의 userId로 user 정보를 취득 .then(value => { console.log('userId' , value.userId) return promiseGet(`${url}/users/${value.userId}`) }) .then(userInfo => console.log(userInfo)) .catch(err => console.error(err));
JavaScript
복사
프로미스의 상태정보
의미
상태변경 조건
then
promiseGet 함수가 반환한 프로미스가 resolve 한 값 (id가 1인 post)
콜백 함수가 반환한 프로미스
then
첫 번째 then 메소드가 반환한 프로미스가 resolve한 값 (post의 userId로 취득한 user 정보)
콜백 함수가 반환한 값을 resolve 한 프로미스
catch
promiseGet 함수 또는 후속 처리 메소드에서 반환한 프로미스의 rejected한 값
콜백 함수가 반환한 값을 resolve 한 프로미스

fetch

HTTP 요청 전송 기능을 제공하는 Web API
HTTP 요청 URL과 HTTP요청 메소드, HTTP 요청 헤더, 페이로드(쿼리스트링) 등을 설정한 객체를 전달.
const promise = fetch(url [, options]) fetch('https://jsonplaceholder.typicode.com/todos/1') .then(response => console.log(response));
JavaScript
복사
fetch('https://jsonplaceholder.typicode.com/todos/1') // response는 HTTP 응답을 나타내는 Response 객체이다. // json 메서드를 사용하여 Response 객체에서 HTTP 응답 몸체를 취득하여 역직렬화한다. .then(response => response.json()) // json은 역직렬화된 HTTP 응답 몸체이다. .then(json => console.log(json)); // {userId: 1, id: 1, title: "delectus aut autem", completed: false}
JavaScript
복사
const request = { get(url) { return fetch(url); }, post(url, payload) { return fetch(url, { method: 'POST', headers: { 'content-Type': 'application/json' }, body: JSON.stringify(payload) }); }, delete(url) { return fetch(url, { method: 'DELETE' }); } };
JavaScript
복사
request.get('https://jsonplaceholder.typicode.com/todos/1') .then(response => { if (!response.ok) throw new Error(response.statusText); return response.json(); }) .then(todos => console.log(todos)) .catch(err => console.error(err)); // {userId: 1, id: 1, title: "delectus aut autem", completed: false}
JavaScript
복사
request.post('https://jsonplaceholder.typicode.com/todos', { userId: 1, title: 'JavaScript', completed: false }).then(response => { if (!response.ok) throw new Error(response.statusText); return response.json(); }) .then(todos => console.log(todos)) .catch(err => console.error(err)); // {userId: 1, title: "JavaScript", completed: false, id: 201}
JavaScript
복사
request.delete('https://jsonplaceholder.typicode.com/todos/1') .then(response => { if (!response.ok) throw new Error(response.statusText); return response.json(); }) .then(todos => console.log(todos)) .catch(err => console.error(err));
JavaScript
복사

Axios

브라우저와 node.js에서 사용할 수 있는 Promise 기반 HTTP 클라이언트 라이브러리
Axios는 node.js 브라우저를 위한 Promise 기반 HTTP 클라이언트
특징
브라우저를 위해 XMLHttpRequests 생성
Promise API를 지원
요청 및 응답 데이터 변환
JSON 데이터 자동 변환
설치
jsDelivr CDN 사용
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
JavaScript
복사
unpkg CDN 사용
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
JavaScript
복사
Axios API 레퍼런스
axios에 해당 config 를 전송하면 요청이 가능
axios(config)
// POST 요청 전송 axios({ method: 'post', url: '/user/12345', data: { firstName: 'Fred', lastName: 'Flintstone' } });
JavaScript
복사
axios(url[, config])
// GET 요청 전송 (기본값) axios('/user/12345');
JavaScript
복사
Axios API Reference
요청 메소드 명령어: 편의를 위해 지원하는 모든 요청 메소드의 명령어를 제공
– axios.request(config) – axios.get(url[, config]) – axios.delete(url[, config]) – axios.head(url[, config]) – axios.options(url[, config]) – axios.post(url[, data[, config]]) – axios.put(url[, data[, config]]) – axios.patch(url[, data[, config]])
JavaScript
복사
명령어 메소드를 사용시 ‘url’, ‘method’, ‘data’. 속성을 config에서 지정할 필요가 없음
기본 예제
GET 요청
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
JavaScript
복사
axios.get('/todos') .then(function (response) { // 성공 핸들링 console.log(response); }) .catch(function (error) { // 에러 핸들링 console.log(error); }) .then(function () { // 항상 실행되는 영역 }); // 선택적으로 위의 요청은 다음과 같이 수행될 수 있습니다. axios.get('/todos', { params: { userId: 1 } }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); }) .then(function () { // 항상 실행되는 영역 });
JavaScript
복사
POST 요청
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
JavaScript
복사
axios.post('/todos', { userId: 5, title: 'Java', completed: false}) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });
JavaScript
복사
PUT 요청
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
JavaScript
복사
axios.put('/todos/5', { id: 5, content: 'VUE', completed: true }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });
JavaScript
복사
DELETE 요청
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
JavaScript
복사
axios.delete('/todos/5') .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });
JavaScript
복사