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
복사