[React+Spring] Axios 를 이용한 API 통신

HJ·2024년 1월 19일
0

React+Spring

목록 보기
3/11

아래 내용은 코드로 배우는 React with 스프링부트 API서버 강의와 함께 설명이 부족한 부분에 대해서 조사하여 추가한 내용입니다.


Axios 설정


Ajax 통신을 위해 npm install axios 를 통해 설치가 필요하며, Axios 는 자동 JSON 데이터 변환을 지원합니다. fetch() 의 경우 json() 을 이용하여 변환이 필요합니다.




async, await


asyncpromise 가 반환되도록 합니다. Promise 란 프로미스가 생성된 시점에는 알려지지 않았을 수도 있는 값을 위한 대리자로, 비동기 연산이 종료된 이후에 결과 값과 실패 사유를 처리하기 위한 처리기를 연결할 수 있습니다.

Promise 를 사용하면 비동기 메서드에서 마치 동기 메서드처럼 값을 반환할 수 있습니다. 다만 최종 결과를 반환하는 것이 아니고, 미래의 어떤 시점에 결과를 제공하겠다는 약속을 반환합니다. ( 참고 )

자바스크립트는 await 키워드를 만나면 promise 가 처리될 때까지 기다립니다. promise 가 처리되면 그 결과가 함께 실행이 재개되며, promise 를 기다리는 동안 다른 스크립트를 실행하는 등의 역할을 할 수 있기 때문에 CPU 리소스가 낭비되지 않습니다. awaitasync 함수 안에서만 동작합니다.


export const getList = async(pageParam) => {
    const {page, size} = pageParam;
    // config 의 인자로 params 와 값을 지정하면 ? 로 시작하는 쿼리 스트링으로 변환된다
    const res = await axios.get(`${prefix}/list`, {params:{
        page: page,
        size: size
    }});
    return res.data;
}

GET 요청을 수행하기 위해 axios.get() 을 사용하며, 비동기로 처리하기 위해 await 키워드를 사용합니다. config 의 인자로 params 라는 키 값에 page 와 size 를 전달하면 ?page=X&size=X 형태로 쿼리 스트링이 완성되게 됩니다.




API 통신


const initState = {
    toDoNo: 0,
    title: '',
    content: '',
    dueDate: '',
    complete: false
}


function ReadComponent({ tno }) {
    const [toDo, setToDo] = useState(initState);

    useEffect(() => {
        getSingleData(tno).then(data => {
            setToDo(data)
        })
    }, [tno]);

    return (
        {makeDiv('No', toDo.toDoNo)}
        ...
    )
}

initState 라는 객체를 만들고, useState() Hook 을 통해 ToDo 객체의 초기 상태를 지정합니다. 특정 데이터를 조회할 때 필요한 번호를 useEffect() 의 의존성 배열에 전달하고, 이 값이 변경되면 getSingleData() 를 호출해 다시 API 통신을 해서 데이터를 받아온 후 state 를 수정하여 다시 렌더링 될 수 있도록 합니다.

/todo/read/11 을 호출했을 때 스프링 서버와의 통신이 정상적으로 이루어진 것을 확인할 수 있습니다.




Custom Hook 생성


페이징 처리를 할 때 pagesize 가 필요한데 만약 pagesize 가 지정된 상태로 페이지를 이동한다고 했을 때, 쿼리 스트링으로 지정된 pagesize 가 없어진다면 default 값으로 다시 변하게 됩니다. Custom Hook 을 만들어 페이지 이동 시, 해당 값들을 계속 유지해줄 수 있습니다.


const useMove = () => {
    ...
    const page = getValue(searchParam.get('page'), 1);
    const size = getValue(searchParam.get('size'), 10);
    const queryDefault = createSearchParams({page, size}).toString()

    const moveToList = (pageParam) => {
        let queryStr = '';

        if(pageParam) {
            const pageNum = getValue(pageParam.page, 1);
            const sizeNum = getValue(pageParam.size, 10);
            queryStr = createSearchParams({page: pageNum, size:sizeNum}).toString();
        } else {
            queryStr = queryDefault;
        }

        navigate({
            pathname: '../list',
            search: queryStr
        })
    }
    return {moveToList}
}

위의 예시가 앞서 말한 쿼리 파라미터를 유지해주기 위해 만든 Custom Hook 입니다. moveToList() 를 호출할 때 전달된 pageParam 이 있다면 해당 값들을 쿼리 파라미터를 자동으로 생성해주는 createSearchParams() 로 전달하고. 없다면 기본으로 생성된 값을 반환해주는 역할을 수행합니다.

위의 Hook 에는 List 로 이동하는 것 뿐만 아니라 상세 조회화면, 수정 화면과 같은 이동 함수들을 함께 정의하여 수정이 끝났을 때, 삭제가 끝났을 때 페이지 이동을 쉽게 구현할 수 있습니다.




목록 생성하기


function ListComponent() {
    const {page, size} = useMove();
    const [serverData, setServerData] = useState(initState);

    useEffect(() => {
        getList({page, size}).then(data => {
            setServerData(data);
        });
    }, [page, size])

    return (
        {serverData.dtoList.map(todo =>
            <div key={todo.toDoNo} className="w-full min-w-[400px] p-2 m-2 rounded shadow-md" onClick={() => moveToRead(todo.toDoNo)}>
            <div className="flex ">
                <div className="p-2 w-1/12 text-lg"> {todo.toDoNo} </div>
                    ...
                </div>task_nameto
            </div>        
        )}
    )
    ...
}

export default ListComponent

useMove() 을 통해 page 와 size 를 가져옵니다. page 와 size 값이 변경된 경우, List 를 반환하는 API( getList ) 를 호출하여 다시 서버로부터 데이터를 받아옵니다. 받아온 데이터는 useState() 를 통해 상태로 관리할 수 있습니다.

반복되는 부분 상위의 최상위 div 태그에서 onClick 이벤트를 설정하여 어떠한 글씨를 클릭해도 해당 데이터를 상세하게 볼 수 있는 페이지로 이동할 수 있도록 moveToPage 를 호출하도록 설정합니다. 페이지를 이동하는 함수는 위에서 만든 useMove() 에 정의됩니다.


useMove 의 page 와 size 를 사용할 수 있는 이유

ReadComponent 에서 moveToList() 를 호출할 때, 이는 useMove() 내부의 navigate 함수를 호출하게 됩니다. 이 때, navigate 함수가 list 페이지로 이동하면서 page와 size 정보를 쿼리 파라미터로 전달합니다.

따라서 ListComponent 에서 다시 useMove 를 사용하면 해당 페이지로 이동한 정보를 가져올 수 있습니다. 이러한 방식으로 컴포넌트 간에 useMove() 에서 관리하는 정보를 공유할 수 있습니다.




customHook 을 이용한 페이지 이동


import React from 'react'
import { useState, useEffect } from 'react'
import useMove from '../../hooks/useMove';
import ResultModal from '../common/ResultModal';
import { getSingleData, modifyData } from '../../api/toDoApi';

// 1. ToDo 의 상태 초기값
const initState = {
    toDoNo: 0,
    title: '',
    content: '',
    dueDate: '',
    complete: false
}


function ModifyComponent({ tno }) {

    const { moveToRead } = useMove();   // 2. 페이지 이동을 위해 Custom Hook 인 useMove 에서 상세보기화면으로 이동하는 moveToRead 함수를 꺼낸다
    const [toDo, setToDo] = useState(initState);    //3. ToDo 객체를 상태로 관리한다
    const [result, setResult] = useState(null);     // 4. ResultModal 을 띄우는 경우를 판단하기 위한 state

    // tno 값이 변경되었을 때 tno 값으로 다시 데이터를 조회한다
    useEffect(() => {
        getSingleData(tno).then(data => {
            setToDo(data)
        })
    }, [tno]);


    // input 태그의 값이 변했을 때 컴포넌트에서 state 로 관리하는 ToDo 의 데이터를 변경해준다
    const handleChange = (event) => {
        toDo[event.target.name] = event.target.value;
        setToDo({ ...toDo });
    }

    // 수정 후 저장을 눌렀을 때, ResultModal 이 나와야 하기 때문에 판단 기준이 되는 result 의 상태를 변경
    const handleClickSave = () => {
        if(window.confirm("저장하시겠습니까?")) {
            modifyData(toDo).then(data => {
                setResult(tno);
                setToDo({...initState}) // toDo 데이터 초기화
            })
        }
    }

    // ResultModal 의 Closed 버튼을 클릭한 경우 다시 result 를 초기화하고 상세보기 페이지로 이동한다
    const handleClickClose = () => {
        setResult(null);    // null 로 변경하면 결과 Modal 이 사라진다
        moveToRead(tno);
    }   

    return (
            <div className="relative mb-4 flex w-full flex-wrap items-stretch">
                <div className="w-1/5 p-6 text-right font-bold">TITLE</div>
                <input className="w-3/5 p-6 rounded-r border border-solid border-neutral-300 shadow-md"
                        name="title" type={'text'} value={toDo.title} onChange={handleChange} ></input>
            </div>
            ...
            <button type="button"
                    onClick={() => moveToRead(toDo.toDoNo)}
                    className="rounded p-3 m-2 w-36 bg-gray-500 text-xl text-white" > Back </button>
            <button type="button"
                    onClick={handleClickSave}
                    className="rounded  p-3 m-2 w-36 bg-blue-500 text-xl text-white" > Save </button>
            ...
            {result ? <ResultModal title={'Success'} content={`${result} ToDo Modified`} callbackFn={handleClickClose} />
                    : <></>}
        </div>
    );
}

export default ModifyComponent

0개의 댓글