원지의 개발
article thumbnail
728x90

 

Ajax

  • AJAX ( Asynchronous Javascript and XML)는 웹 페이지의 이동없이 필요한 데이터만 전송하는 기술

웹 어플리케이션에서 데이터를 가져올 때 서버쪽 데이터가 필요할 때 ajax기법을 사용

이 작업은 시간이 오래 걸릴 수도 있기 때문에 비동기적으로 처리하게 됨

비동기(asynchronous): 요청이 끝날 때 까지 기다리는 것이 아니라, 동시에 여러 작업을 수행, 순서 보장X

 

나중에 react에서는 다른 서버의 REST API와 통신을 이용하여 데이터베이스 데이터를 가져올 수 있음

 

2023.01.06 - [클라이언트/JavaScript] - [JavaScript] AJAX, API, fetch, Promise, then, json, xml

 

[JavaScript] AJAX, API, fetch, Promise, then, json, xml

ES5 ▶ XMLHttpRequest, fetch(), ajax(), Axois 등 XMLHttpRequest: ajax 프로그램에 사용할 수 있는 객체 AJAX (비동기 통신) Asynchronous Javascript ans XML 웹페이지의 이동 없이 필요한 데이터만 전송하는 기술 ex) 구글

j-won950101.tistory.com


ES6의 fetch를 이용 - 리액트에서 데이터 처리하기

Promise = fetch(요청주소)

1. 이벤트 클릭시 처리

  • 데이터를 가져와서 useState()에 저장하는 작업
  • 극단적으로 표현해 데이터 통신에 100초 가 소요되면 useState는 100초 간 undefined 상태가 됨
  • 렌더링 시에 에러를 나타내기 때문에, undefined에 관한 처리를 동시에 진행

3항 연산자로 undefined 상태를 처리해줌

import { Fragment, useEffect, useState } from "react"


const App = () => {
    
    /* 
        Ajax를 이용해서 외부데이터 가져오가
        1. Promise = fetch()
    */

    //클릭해서 데이터 가져오기
    const [raw, setRaw] = useState();
    const handleClick = () => {
        fetch("https://raw.githubusercontent.com/yopy0817/data_example/master/hi.json")
        .then( response => response.json() )
        .then( data => setRaw(data)) // 클릭 이벤트를 넣지 않고 그냥 하면 계속 실행되서 무한루프 돈다. 절대 하면 안됨
    }

    return (
        <Fragment>

            <button onClick={handleClick}>데이터 가져오기</button>
            { //JSX 문법 - 3항 연산자
                raw === undefined ? //state는 비어있기 때문에 빈 화면에 대한 처리를 해줘야 함
                <div>
                    데이터준비중
                </div>
                :
                <div>
                    아이디: {raw.userId},
                    비밀번호: {raw.userPw},
                    이름: {raw.userName}
                </div>
            }

            {/*
                가져온 데이터: {raw.userId}, {raw.userPw}, {raw.userName}
                우선 화면을 바로 뿌려주는데 버튼을 누르지 않으면 state값이 없어서 바로 찍으면 annot read properties of undefined 뜸. 그래서 다른 화면을 보여줘야함
            */}

        </Fragment>
    )
}

export default App;
  • 반복문, 조건문, 중첩된 함수 내에서 Hook 호출하지x
  • 리액트에서 data fetching을 훅없이 사용하면 무한루프에 빠짐
    state변경 - rerendering - data fetching - (state 값 변경으로 인한) rerenderting - 다시 data fetching 반복
  • 무한루프를 막기 위해 3항 연산자를 사용하여 state값이 없는 상태에서의 처리를 해주고, 클릭 이벤트 실행시 데이터를 가져오게끔 만들어줌

2. 화면 렌더링 완료시 데이터 처리 - useEffect()훅 사용

  • 비동기 작업을 컴포넌트에 바로 쓰고 state를 변경하면, 무한루프에 빠지게 됨
  • 그래서 useEffect() 훅을 이용하여 첫번째 렌더링 완료시만 데이터만 가져오도록 처리
import { Fragment, useEffect, useState } from "react"


const App = () => {

    //화면이 mount이후 데이터 가져오기 = 화면 렌더링 완료시 데이터 처리하기
    const [data, setData] = useState();
    useEffect( () => { //첫번째 렌더링 완료시만 데이터만 가져오도록
        fetch("https://raw.githubusercontent.com/yopy0817/data_example/master/hi.json")
        .then( response => response.json() )
        .then( data => setData(data))
    }, []);

    return (
        <Fragment>

            <h3>mount이후 데이터 가져오기</h3>
            {
                data && <div> {/* 처음에는 state값 없으므로 조건을 줘야함 */}
                    아이디: {data.userId}
                    비밀번호: {data.userPw}
                    이름: {data.userName}
                </div>
            }

        </Fragment>
    )
}

export default App;
  • 똑같이 무한루프에 빠지게 되므로 이번에는 mount(생성) 이후 데이터를 가져오게 하기위해 useEffect 훅을 이용하여 첫번째 렌더링 이후에만 데이터를 가져오도록 만듦
  • 처음에는 state값이 없으므로 undefined 처리를 해줘야 하는데 여기서 && 연산자 이용
    data && <div> data값 </div> - 앞의 data 전체가 true이면 뒤의 값도 확인
                                                    앞의 data 전체가 false이면 뒤의 값 확인x = undefined이면 false 판별

Axios

  • Axios는 비동기를 더 편하게 처리하는 라이브러리
  • get(), post() 함수를 제공하고, 사용했을 때 리턴은 Promise
엑시오스 설치
npm add axios

엑시오스 사용

Promise(반환) = axios.get(요청주소).then 사용가능

 

axios 사용 비동기 코드

import axios from "axios"
import { Fragment, useState } from "react"



const App = () => {

    /* 
        Axios는 비동기를 편하게 처리하는 라이브러리 입니다. (fetch로 사용해도 무방합니다.)
        - 설치 npm add axios
        - Axios는 get(), post() 함수를 제공하고, 사용했을 때 리턴은 Promise
    */

    //비동기는 순서를 보장하지 않음
    const handleClick = () => {
        axios.get("https://raw.githubusercontent.com/yopy0817/data_example/master/hi.json")
        // console.log(result); Promise 객체 나옴
        .then(Response => {
            console.log(Response.data);
            console.log(1);
        })

        console.log(2);

        axios.get("https://raw.githubusercontent.com/yopy0817/data_example/master/by.json")
        .then(Response => {
            console.log(Response.data)
            console.log(3);
        })

        console.log(4);

        axios.get("https://raw.githubusercontent.com/yopy0817/data_example/master/hello.json")
        .then(Response => {
            console.log(Response.data);
            console.log(5);
        })
    }
   

    return (
        <Fragment>
            <h3>엑시오스로 데이터 가져오기</h3>

            <button onClick={handleClick}>데이터 가져오기</button>

            {
                data && <div>
                    아이디: {data.userId},
                    비밀번호: {data.userPw},
                    이름: {data.userName}
                </div>
            }
        </Fragment>
    )
}

export default App;

 

axios만 사용하여 동기적으로 사용하고 싶다면 함수 안에 함수를 넣어서 사용 가능
🔥🔥🔥🔥
콜백지옥 🔥🔥🔥🔥

 //순서를 보장받고 싶다면? 콜백함수의 지옥

    const handleClick = () => {
        axios.get("https://raw.githubusercontent.com/yopy0817/data_example/master/hi.json")
        .then(Response => {
            console.log(Response.data);
            console.log(1);

            axios.get("https://raw.githubusercontent.com/yopy0817/data_example/master/by.json")
            .then(Response => {
                console.log(Response.data)
                console.log(3);

                axios.get("https://raw.githubusercontent.com/yopy0817/data_example/master/hello.json")
                .then(Response => {
                    console.log(Response.data);
                    console.log(5);
                });

            });

        });
    }

axios 사용 동기 코드 ▼ async, await

import axios from "axios"
import { Fragment, useState } from "react"

const App = () => {

    /* 
        Axios는 비동기를 편하게 처리하는 라이브러리 입니다. (fetch로 사용해도 무방합니다.)
        - 설치 npm add axios
        - Axios는 get(), post() 함수를 제공하고, 사용했을 때 리턴은 Promise
    */

    const [data, setData] = useState();

    const handleClick = async () => {

        let result = await axios.get("https://raw.githubusercontent.com/yopy0817/data_example/master/hi.json");
        console.log(result.data);

        console.log(1);

        let result2 = await axios.get("https://raw.githubusercontent.com/yopy0817/data_example/master/by.json");
        console.log(result2.data);

        console.log(2);

        let result3 = await axios.get("https://raw.githubusercontent.com/yopy0817/data_example/master/hello.json");
        console.log(result3.data);

        console.log(3);

    }

    return (
        <Fragment>
            <h3>엑시오스로 데이터 가져오기</h3>

            <button onClick={handleClick}>데이터 가져오기</button>

            {
                data && <div>
                    아이디: {data.userId},
                    비밀번호: {data.userPw},
                    이름: {data.userName}
                </div>
            }
        </Fragment>
    )
}

export default App;

async(이거 비동기임) , await(기다려) 적용

  • ES6의 문법으로 비동기 코드를 간결하게 작성 가능
  • 비동기 작업을 할 함수 앞에 async를 붙여 비동기 작업을 마치 동기 작업 처럼 작성할 수 있

규칙

  1. async 함수 안에서 await을 사용
  2. function 앞에 async 키워드를 추가, 함수는 언제나 Promise를 반환함
  3. 리턴이 Promise라면 await을 적용하고 then절을 없앨 수 있음

Axios는 이미 Promise를 반환하므로 Axios앞에 await을 사용할 수 있고, then() 절을 생략 할  수 있음
Axios를 호출하는 부모함수에는 await을 반드시 달아줌

 

장점

  1. 코드의 간결성
  2. 어싱크, 어웨잇은 동기적방식(순서를) 보장

async, await 적용하여 변경하기

const handleClick = async () => {
    let response = await axios.get('https://raw.githubusercontent.com/yopy0817/data_example/master/hi.json')
    console.log(response.data);
    setData( response.data );

    console.log(1);

    let response2 = await axios.get('https://raw.githubusercontent.com/yopy0817/data_example/master/hello.json')
    console.log(response2.data);

    console.log(2);

    let response3 = await axios.get('https://raw.githubusercontent.com/yopy0817/data_example/master/by.json')
    console.log(response3.data);

    console.log(3);

}

JS ES6 문법

Promise

  • 자바스크립에 내장된 내장객체
  • 상태(state) - pending(수행중), fullfilled(성공적 완료), rejected(실패)
  • Producer - 정보를 제공하는 제공자 (즉, Promise)
    Consumer - 사용자 (즉, 호출하는 사람 = 나)

  • Promise 객체에는 executer라는 콜백 함수로 resolve, reject 라는 두개의 함수를 매개변수로 가지고 있음

Promise 생성

  • executor콜백함수를 전달해야 하며 executor콜백함수는 다시 resolve함수와 reject함수를 받음

Promise 사용 - then(), catch()

Promise 적용

  • 결과가 언제 돌아갈지 모르지만, 내가 이것을 완료되면 Promise를 전달해 줄테니
    너는 then함수만 이용해서 결과를 받아서 처리해!

 

ajax fetch()의 내부모습 예시

//자바스크립트로 구현되어있는 fetch라고 가정
function myfetch(req) { 
    //비동기적 실행.. 10초..
    return new Promise( (success, fail) => {
        success("data....");
    });
}

//fetch와 같은 실행구조를 보인다.
myfetch("http://localhost:5502/~~~~~").then( response => console.log(response) );

실습

동기적 vs 비동기적 콜백

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
    <script>
        //동기적 콜백 vs 비동기적 콜백

        //동기적 콜백
        function aaa(func) {
            func("sucess");
        }

        //비동기적 콜백
        function bbb(func) {
            setTimeout(function() {
                func("sucess");
            },2000);
        }


        //결과: 1 sucess 2 3 5 sucess 4
        console.log(1);
        //동기적 콜백의 반환
        aaa(function(result) {
            console.log(result); //sucess
            console.log(2);
        })

        console.log(3);

        //비동기적 콜백의 반환
        bbb(function(result) {
            console.log(result); //sucess
            console.log(4);
        })

        console.log(5);

    </script>

</body>
</html>
  • aaa는 동기적으로 실행되는 함수이고, bbb는 setTImeOut으로 비동기적으로 실행되는 함수
  • 1 success 2 3 까지는 동기적으로 실행되지만 bbb를 만나서 2초의 딜레이가 걸리면서 두번째 success와 4보다 5가 더 빨리 찍힘

Promise 객체의 실패 (성공은 주석)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
    <script>
        //프로미스 객체 - 자바스크립트 내장객체 (특별한 패턴을 제공해줍니다.)

        //excuter라는 콜백함수를 전달받고, excuter 첫번째는 성공시킬 함수, 두번째는 실패일 때 실행시켜줄 함수
        var promise = new Promise(function(success, fail) {
            
            //성공
            // setTimeout( function() {
            //     success("성공"); //3초 이후 실행
            // }, 3000);

            //실패
            fail (new Error("에러남"));
        });

        console.log(promise);

        promise.then(function(response) {
                console.log(response);
        }).catch(function(err) {
                console.log(err);
        });

    </script>

</body>
</html>
  • Promise 객체를 생성해서 fail되게 한 다음 Error로 "에러남"이라는 텍스트를 던지면서 promise라는 변수에 담고, 콘솔에 찍어보면 Promise 객체가 반환됨을 알 수 있음
  • 여기에는
    state: reject,
    result: 
    1. message: "에러남"
    2. stack: "Error: 에러남\n at http://127.0.0.1:5502/08async_await/index02.html:23:19\n at new Promise (<anonymous>)\n at http://127.0.0.1:5502/08async_await/index02.html:15:23" 등의 상태가 뜸
  • Promise.catch로 reject된 상태가 받아서 에러남 이라는 텍스트가 콘솔에 출력됨

async, await으로 then없이 간결하게 만들기

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
    <script>
        //예시) myFetch

        function myFetch(url) {
            //url을 가지고 처리
            return new Promise(function(success, fail) {
                success("data...");
            });

        }

        var result = myFetch("http://~~"); 
        console.log(result); //Promise 나옴

        myFetch("http://~~")
        .then(response => {
            console.log(response); //data... 나옴
        })

    </script>

</body>
</html>
  • myFetch 함수로 Promise 객체가 생성되었을 때, then으로 받아줄 success, fail 함수가 들어갈 매개변수가 없으므로 Promise 객체가 나옴 (?)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
    <script>
        //예시) myFetch

    //async(이거 비동기야) - function 앞에 선언하게 되면 return 값을 자동으로 Promise로 변형해줍니다.

    async function myFetch(url) {
        return "data...";
    }

    //await(기다려) - 리턴이 Promise라면 await을 적용할 수 있고, then절을 생략할 수 있습니다. (단, async 함수 안에서만 사용할 수 있음)

    (async () => { //즉시실행 함수

        let result = await myFetch("http://...."); // await을 붙이면 .then(response => console.log(response)); 이 구문을 자동으로 해줌
        console.log(result);

    }) ();


    </script>

</body>
</html>
  • async을 사용하면 리턴값을 자동으로 Promise로 변형해 줌
  • 리턴값이 Promise 라면 await을 사용할 수 있고, await을 이용하면 then절 생략가능
  • 그래서 위의 코드와 똑같이 result로만 받았어도 결과는 ...data로 정상적으로 나옴

전체코드

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
    <script>
        //예시) myFetch
    /* 
        function myFetch(url) {
            //url을 가지고 처리
            return new Promise(function(success, fail) {
                success("data...");
            });

        }

        // var result = myFetch("http://~~");
        // console.log(result); //Promise 나옴

        myFetch("http://~~")
        .then(response => {
            console.log(response); //data... 나옴
        })

        myFetch("http://~~")
        .then(response => response)
        .then(data => data)
        .then(data => console.log(data)); //data... 나옴
    */



    //async(이거 비동기야) - function 앞에 선언하게 되면 return 값을 자동으로 Promise로 변형해줍니다.

    async function myFetch(url) {
        return "data...";
    }

    // var result = myFetch("http://~~");
    // console.log(result);

    // myFetch("http://~~")
    // .then(response => console.log(response));

    //await(기다려) - 리턴이 Promise라면 await을 적용할 수 있고, then절을 생략할 수 있습니다. (단, async 함수 안에서만 사용할 수 있음)

    (async () => { //즉시실행 함수

        let result = await myFetch("http://...."); // await을 붙이면 .then(response => console.log(response)); 이 구문을 자동으로 해줌
        console.log(result);

    }) ();


    </script>

</body>
</html>

Ajax 실습 - news API

App.js

import { Fragment } from "react"
import { Route, Routes } from "react-router-dom";
import Header from "./layout/Header";
import NewsHome from "./component2/NewsHome";

const App = () => {

    return (
        
        <Routes>
            <Route element={<Header/>}> {/* 헤더 사용하려고 */}
                <Route path="/" element={<NewsHome/>}/> {/* 본문 내용 */}
                <Route path="/:category" element={<NewsHome/>}/> {/* 본문 내용 */}
            </Route>
        </Routes>

    )
}

export default App;
  • 라우터의 사용과 각 주소를 연결하는 기본 틀을 만드려고 App.js 사용
  • 라우터로 NewsHome에 연결하고 header 사용
  • <Route path="/:category" element={<NewsHome/>}/> {/* 본문 내용 */}
    카테고리 값이 바뀌면 그에 따라서 article이 바뀌게 하기 위해서 설정

NewsHome.js - 기본 구성

import { Fragment } from "react";
import NewsCategory from "./NewsCategory";
import NewsList from "./NewsList";

const NewsHome = () => {

    return (
        <Fragment>
            
            <NewsCategory/>
            <NewsList/>
            
        </Fragment>
    )
}

export default NewsHome;
  • NewsHome에서는 카테고리와 리스트 컴포넌트를 넣음
  • 결과적으로 아래 사진처럼 배치

NewsList.js

import axios from "axios";
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import NewsArticle from "./NewsArticle";

import styled from './NewsList.module.css'; //css


const NewsList = () => {

    //1. API 가져오기
    //8eeb2d41ecd540b38900f5df80334afc

    //5. 라우터로 들어오는 값에 처리
    const {category} = useParams();

    //category가 없거나 undefined이면 all
    const query = (category || 'all') === 'all' ? '' : `&category=${category}`; //이 조건은 변할 수 있음!
    // console.log(query);


    //2. useEffect에서 화면 로딩시 데이터처리

    const [data, setData] = useState(); //이렇게 하면 첫번쨰 데이터는 undefined고, 두번째부터 20개의 articles 들어옴


    //useEffect는 내장 함수이기 때문에 그대로 유지하고, 함수를 하나 걸어서 async 해주기
    useEffect( () => {

        (async() => { //즉시 실행함수의 매개변수로 async
            const url = `https://newsapi.org/v2/top-headlines?country=kr${query}&apiKey=8eeb2d41ecd540b38900f5df80334afc`;
            console.log(url);

            let {data: {articles}} = await axios.get(url); //data를 가져와서 그 안의 articles, 구조분해 할당
            
            setData(articles);
            setLoding(true); //로딩완료

        }) (); /* 즉시실행함수 괄호 빼먹지 말자 */
    }, [query] ); //6. 변화가 일어날 때마다 재실행할 변수
    
    // console.log(data);

    //3. 데이터 로딩처리 (데이터가 오기전에 state는 undefined)
    const [loading, setLoding] = useState(false);
    if(loading === false) {
        return <div>로딩중</div>
    }

    //4. li태그를 컴포넌트로 변경


    return (
        <div className={styled.news_container}>
            <h3>오늘의 헤드라인</h3>
            <ul className={styled.news_wrap}>
                {
                    /* 1. url, urlToImage, title, author, description, publishedAt */
                    data.map( (item, index) => <NewsArticle key={index+1} item={item}/>)
                }
            </ul>
        </div>
    )
}

export default NewsList;

NewsCategory.js

import { NavLink } from "react-router-dom";



const NewsCategory = () => {

    /* 
        business, entertainment, general, health, science, sports, technology
        
        1. 카테고리 맵 회전 - 카테고리는 고정으로 있는 값 (state로 관리할 필요x)
        2. 라우터 설정 = App.js 가보면 /여도 NewsHome, /:category여도 NewsHome
        3. NewsList에서는 라우터값을 처리
    */

    const category = [
        {name: "all", topic: "전체"},
        {name: "business", topic: "비즈니스"},
        {name: "entertainment", topic: "연예"},
        {name: "general", topic: "일반"},
        {name: "health", topic: "건강"},
        {name: "science", topic: "과학"},
        {name: "sports", topic: "스포츠"},
        {name: "technology", topic: "기술"}
    ];

    const myStyle = {
        color : "pink",
        borderBottom : "3px solid red"
    }

    return (
        <ul>
            {
                category.map( (item, index) => <li key={index}>
                    {/* 3항 연산자 부분: 돌아가는 주소 부분이 all이면 공백으로 바꿔주고, 나머지는 링크에 걸린 이름 나오게 */}
                    <NavLink to={item.name === 'all' ? '/' : `/${item.name}`} style={({isActive}) => isActive ? myStyle : undefined}>{item.topic}</NavLink>
                </li>
                )
            }
        </ul>
    )
}

export default NewsCategory;

NewsArticle.js



const NewsArticle = ( /* props */ {item} ) => { //props 확인해 보고 해석하기

    // console.log(props);
    /* 1. url, urlToImage, title, author, description, publishedAt */

    const {url, urlToImage, title, author, description, publishedAt} = item;

    //날짜 데이터 형식 수정
    let date = new Date(publishedAt);
    let year = date.getFullYear();
    let month = date.getMonth() + 1;
    let day = date.getDate();

    return (
        <li>
            
            <a href={url}>
                <img src={urlToImage} alt={title} />
                <div>
                    <p>{author === null? "기자없음" : author}</p>
                    <p>{`${year}년 ${month}월 ${day}일`}</p>
                    <p>{title}</p>
                    <p>{description}</p>
                </div>
            </a>
           
        </li>
    )

}

export default NewsArticle;

NewsList.module.js

.news_container {
    padding: 50px 40px;
}

.news_wrap {
    display: flex;
    flex-direction: column;
}

.news_wrap li {
    position: relative;
    min-height: 150px; /* 최소넓이 */
    padding-left: 200px;
}

.news_wrap li img {
    position: absolute;
    top: 0;
    left: 0;
    width: 150px;
    height: 100px;
}

.news_wrap li a {
    color: black;
    text-decoration: none;
}

오늘 하루

더보기

기억에 남는 부분

 

어려운 부분

 

문제 해결 부분

 

728x90
profile

원지의 개발

@원지다

250x250