ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • React RestAPI - 1
    Front-End/React 2024. 9. 3. 14:02
    728x90
    반응형

    React RestAPI - 1

     

     

     

    우선 값을 주고받기 위해 JSX에 태그를 추가하자.

     

    1. 체크박스, 버튼 생성

    Day.js

    export default function Day() {
        const day = useParams().day;
        const wordList = dummy.words.filter(word =>
            word.day === Number(day)
        );
        console.log(wordList);
    
        return <>
            <h2>Day {day}</h2>
            <table>
                <tbody>
                {
                    wordList.map(word => (
                            <tr key={word.id}>
                                <td>
                                    <input type="checkbox"/>
                                </td>
                                <td>
                                    {word.eng}
                                </td>
                                <td>
                                    {word.kor}
                                </td>
                                <td>
                                    <button>뜻보기</button>
                                    <button className="btn_del">삭제</button>
                                </td>
                            </tr>
                        )
                    )
                }
                </tbody>
            </table>
        </>
    }

     

    화면

     

    이제 우측의 버튼들의 기능을 만들어볼건데,

    버튼을 눌렀을 때 발생하는 이벤트들은 표의 영단어에만 한정되는 것이니까,

    새로 컴포넌트를 만드는 게 좋아보인다.

     

    Word.js

    Day.js에 있던걸 긁어와서 만들었다.

    export default function Word({ word }) {
        return (
            <>
                <tr>
                    <td>
                        <input type="checkbox"/>
                    </td>
                    <td>
                        {word.eng}
                    </td>
                    <td>
                        {word.kor}
                    </td>
                    <td>
                        <button>뜻보기</button>
                        <button className="btn_del">삭제</button>
                    </td>
                </tr>
            </>
        )
    }

     

    Day.js 수정

    export default function Day() {
        const day = useParams().day;
        const wordList = dummy.words.filter(word =>
            word.day === Number(day)
        );
        console.log(wordList);
    
        return <>
            <h2>Day {day}</h2>
            <table>
                <tbody>
                {
                    wordList.map(word => (
                        <Word word={word} key={word.id}></Word>
                        )
                    )
                }
                </tbody>
            </table>
        </>
    }

     

    우선, 뜻 부분을 상황에 따라 보여줄지 말지 정하게 만들어야 한다.

    그 다음 setIsShow()를 통해 뜻 보기 버튼이 작동하게 만들면 된다.

     

    Word.js

    export default function Word({ word }) {
        const [isShow, setIsShow] = useState(false);
        
        function toggleShow() {
            setIsShow(!isShow);
        }
    
        return (
            <>
                <tr>
                    <td>
                        <input type="checkbox"/>
                    </td>
                    <td>
                        {word.eng}
                    </td>
                    <td>
                        {isShow && word.kor}
                    </td>
                    <td>
                        <button onClick={toggleShow}>뜻보기</button>
                        <button className="btn_del">삭제</button>
                    </td>
                </tr>
            </>
        )
    }

    isShow라는 state를 만들어서, 뜻 부분이 값에 따라 보일지 말지 정하고 있다.

    toggleShow()를 통해 boolean타입의 값을 버튼 누를 때마다 반전시켜주고 있다.

     

    이제 체크버튼을 이용해서 외운 단어와 외우지 못한 단어를 구분해주겠다.

    data.json의 첫 단어의 isDone을 true로 바꿔주겠다.

    "words": [
      {
        "id": 1,
        "day": 1,
        "eng": "book",
        "kor": "책",
        "isDone": true
      },

     

    그 다음 체크박스에 checked 속성을 이용한 코드를 적는다.

    <input type="checkbox" checked={word.isDone}/>

    이러면 화면 상으로 외운 단어는 체크가 되어있다.

     

    그리고 css에 맞게 className을 추가해주자.

    <tr className={word.isDone ? 'off' : ''}>

     

    화면

    화면 상으로는 문제가 없다. 하지만 콘솔창을 보자

    JSX에 적힌 코드대로, 항상 그 값이 고정되어 있기 때문에 onchange함수가 없으면 읽기 전용이라는 뜻이다.

    그래서 onchange를 만들어주자.

     

    Word.js

    export default function Word({ word }) {
        const [isShow, setIsShow] = useState(false);
        const [isDone, setIsDone] = useState(word.isDone);
        
        function toggleShow() {
            setIsShow(!isShow);
        }
    
        function toggleDone() {
            setIsDone(!isDone);
        }
    
        return (
            <>
                <tr className={isDone ? 'off' : ''}>
                    <td>
                        <input type="checkbox" checked={isDone} onChange={toggleDone}/>
                    </td>
                    <td>
                        {word.eng}
                    </td>
                    <td>
                        {isShow && word.kor}
                    </td>
                    <td>
                        <button onClick={toggleShow}>뜻보기</button>
                        <button className="btn_del">삭제</button>
                    </td>
                </tr>
            </>
        )
    }

    isDone을 state에 추가했으므로, 기존에 word.isDone이던 부분들을 isDone으로 바꿔주었다.

    함수를 통해 바꿔주는 건 isDone이지 word.isDone이 아니기 때문이다.

    그래서 체크상태에 변화를 줘도 새로고침을 하면 항상 같은 화면으로 되돌아온다.

     

    참고로 className 대신에 class로 적어도 경고문만 뜨고 동작은 한다고 한다.
    다만 권장하지는 않을 뿐이다.

     

     

     

    2. RestAPI

    스프링 공부할 때 RestAPI를 계속 다뤘으니 따로 설명은 안한다.

    json을 이용할 것이기 때문에 설치를 해야 한다.

    터미널에 npm install -g json-server 입력

    설치가 완료되면 json-server --watch ./src/db/data.json --port 3001까지 입력해준다.

     

    다 완료되면 터미널이 귀여운 이모티콘 하나를 날려주는데 ♡⸜(˶˃ ᵕ ˂˶)⸝♡

    제공받은 리소스로 접근해보자.

     

    http://localhost:3001/days

     

    http://localhost:3001/words?day=1

    이렇게 조건을 추가할 수도 있다.

    이제 각 파일에서 API를 이용해서 더미로 작업했던 부분을 바꿔주겠다. 

     

     

     

    3. useEffect

    이 함수가 호출되는 타이밍은 렌더링 결과가 실제 DOM에 반영되고 난 직후이다.

    useEffect(() => {
        console.log(1);
    });

    이렇게 적으면 렌더링될 때마다 실행된다.

    useEffect(() => {
        console.log(1);
    }, []);

    이렇게 적으면 첫 렌더링될 때만 실행된다.

     

    DayList.js

    export default function DayList() {
        const [days, setDays] = useState([]);
        const [count, setCount] = useState(0);
        
        function onClick() {
            setCount(count + 1);
        }
    
        useEffect(() => {
            console.log("count Change");
        });
    
        return (
            <>
                <ul className="list_day">
                    {days.map(day =>
                        <li key={day.id}>
                            <Link to={`/day/${day.day}`}>Day {day.day}</Link>
                        </li>
                    )}
                    <li></li>
                </ul>
                <button onClick={onClick}>{count}</button>
            </>
    
        );
    }

     

    화면

    정상적으로 횟수에 맞게 출력된다

    처음 count Change가 두 번 출력되는 것은

    React18부터  StrictMode가 개발 모드에서 두 번 렌더링을 유도한다고 한다.

    불필요한 부작용을 방지하기 위해,

    컴포넌트가 예상치 못한 상태 변경 없이 안전하게 렌더링될 수 있도록 돕기 위함이다.

    이 경우, useEffect 훅이 두 번 호출되는 것을 볼 수 있다고 한다.

     

    버튼을 새로 만들고, 버튼을 누르면 날짜 버튼을 새로 생성해주는 기능도 만들어보자

    function onClick2() {
        setDays([
            ...days,
            {
                id : Math.random(),
                day : 1
            }
        ])
    }
    <button onClick={onClick2}>Day Change</button>

     

    화면

    안에 문구는 수정을 안했는데 일단 문구는 중요한게 아니니까,

    분명 날짜를 새로 만드는 중인데 콘솔창에 계속 출력이 된다.

    아까 말했듯 렌더링 될 때마다 useEffect가 작동하기 때문에 이 부분도 수정해야 한다.

     

    useEffect(() => {
        console.log("count Change");
    }, [count]);

    이렇게 두 번째 매개변수로 배열을 만들고 count를 적으면 count가 변경될 때만 실행된다.

    이 것을 의존성 배열이라고 한다.

    이제 정상작동할 것이다.

     

     

     

    4. fetch(), then()

    이제 우리 data.json에서 실제로 가져와보자.

     

    DayList.js에 코드 추가

    useEffect(() => {
        fetch('http://localhost:3001/days')
            .then(res => {
                return res.json();
            })
            .then(data => {
                setDays(data);
            })
    }, []);

    fetch()

    promise 객체를 반환한다.

    promise는 비동기 처리에 활용되는 객체다.

     

    예를 들어 비동기 로직인 A, B, C가 있을 때,

    그냥 실행하면 로직이 먼저 끝나는 순서대로 출력되지만,

    Promise를 사용하면 A, B, C 순서대로 출력시킬 수 있다.

     

    then()

    프로미스 객체의 메서드로, 비동기 작업이 성공했을 때 실행할 콜백 함수를 등록하는데 사용된다.

    비동기 작업이 완료되면 해당 작업의 결과값을 인자로 받아 실행한다.

     

    첫 then()은 받아온 데이터를 json 형식으로 바꿔주고,

    두 번째 then()에서 days를 setDays를 통해 값을 변경시켜준다.

     

    그럼 나머지 파일에서도 더미데이터 대신 우리 data를 이용하도록 하자

     

    Day.js 수정

    export default function Day() {
        const day = useParams().day;
        const [words, setWords] = useState([]);
    
        useEffect(() => {
            fetch(`http://localhost:3001/words?day=${day}`)
                .then(res => {
                    return res.json();
                })
                .then(data => {
                    setWords(data);
                })
        }, [day]);
    
        return <>
            <h2>Day {day}</h2>
            <table>
                <tbody>
                {
                    words.map(word => (
                        <Word word={word} key={word.id}></Word>
                        )
                    )
                }
                </tbody>
            </table>
        </>
    }

     

    useEffect()에 의존성 배열에 day를 추가하면 day가 바뀔 때마다 재실행된다.

     

    그런데 잘 보면, fetch()를 사용하는 부분이 비슷비슷하다.

    이런 부분은 사용자가 직접 훅을 만들어서 사용할 수도 있다.

     

     

     

    5. 커스텀 훅

    지금 fetch()를 사용한 두 곳에서 다른 점은 url 주소 뿐이다.

    따라서 url을 props로 받고, 해당 url을 이용하는 컴포넌트를 생성하자.

     

    src/hooks/useFetch.js 생성

    export default function useFetch(url) {
        const [data, setData] = useState([]);
    
        useEffect(() => {
            fetch(url)
                .then(res => {
                    return res.json();
                })
                .then(data => {
                    setData(data);
                })
        }, [url]);
    
        return data;
    }

    Day.js 와 DayList.js에 있는 것을 기반으로 했지만,

    다른 곳에서도 사용할 수 있게 words를 data로 변경해주었다.

     

    이제 기존에 fetch()를 사용하던 곳에서도 바꿔주자.

     

    DayList.js

    export default function DayList() {
        const days = useFetch("http://localhost:3001/days");
    
        return (
            <>
                <ul className="list_day">
                    {days.map(day =>
                        <li key={day.id}>
                            <Link to={`/day/${day.day}`}>Day {day.day}</Link>
                        </li>
                    )}
                    <li></li>
                </ul>
            </>
        );
    }

     

    Day.js

    export default function Day() {
        const day = useParams().day;
        const words = useFetch(`http://localhost:3001/words?day=${day}`);
    
        return <>
            <h2>Day {day}</h2>
            <table>
                <tbody>
                {
                    words.map(word => (
                        <Word word={word} key={word.id}></Word>
                        )
                    )
                }
                </tbody>
            </table>
        </>
    }

     

    이렇게 해두면, 나중에 수정이 필요할 때도,

    useFetch 컴포넌트 하나만 수정하면 이용하는 모든 부분에서 수정된 상태로 적용된다.

    728x90
    반응형

    'Front-End > React' 카테고리의 다른 글

    React 스프링부트와 리액트 연동하기  (3) 2024.09.19
    React RestAPI - 2  (1) 2024.09.03
    React 라우터  (1) 2024.09.02
    React 더미 데이터 생성, map() 반복  (2) 2024.08.31
    React 각 컴포넌트 별 CSS 적용  (0) 2024.08.31
Designed by Tistory.