본문 바로가기

WINK-(Web & App)/React.js 스터디

[2024 여름방학 React.js 스터디] 김민서 #6주차

반응형
API 연동의 기본

 

API를 호출하기 위한 axios 라이브러리를 설치한다

 

- REST API

  1. URI는 정보의 자원을 표현해야 한다 (리소스명은 동사보다 명사를 사용)
  2. 자원에 대한 행위는 HTTP Method(GET, POST, PUT, DELETE 등)로 표현한다
Method 역할
POST POST를 통해 해당 URI를 요청하면 리소스를 생성한다
GET GET를 통해 해당 리소스를 조회한다
PUT PUT를 통해 해당 리소스를 수정한다
DELETE DELETE를 통해 리소스를 삭제한다

 

URI 설계 시 주의사항

  • 슬래시 구분자(/)는 계층 관계를 나타내는 데 사용
http://restapi.example.com/houses/apartments
  • URI 마지막 문자로 슬래시를 포함하지 않는다
http://restapi.example.com/houses/apartments/ (X)
  • 하이픈(-)은 URI 가독성을 높이는데 사용
  • 밑줄(_)은 URI에 사용하지 않는다
  • URI 경로에는 소문자가 적합하다 (대소문자에 따라 다른 리소스로 인식해서)
  • 파일 확장자는 URI에 포함시키지 않는다 (대신 Accept header 사용)
    http://restapi.example.com/members/soccer/345/photo.jpg (X)
GET / members/soccer/345/photo HTTP/1.1 Host: restapi.example.com Accept: image/jpg

 

- HTTP 응답 상태 코드

상태코드  
200 클라이언트의 요청을 정상적으로 수행함
201 클라이언트가 어떠한 리소스 생성을 요청, 해당 리소스가 성공적으로 생성됨(POST를 통한 리소스 생성 작업 시)
상태코드  
400 클라이언트의 요청이 부적절 할 경우 사용
401 클라이언트가 인증되지 않은 상태에서 보호된 리소스를 요청했을 때 사용 (로그인 하지 않은 유저가 로그인 했을 때, 요청 가능한 리소스를 요청했을 때)
403 유저 인증상태와 관계 없이 응답하고 싶지 않은 리소스를 클라이언트가 요청했을 때 사용 (403 보다는 400이나 404를 권고, 403은 리소스가 존재한다는 뜻이기 때문)
405 클라이언트가 요청한 리소스에서는 사용 불가능한 Method를 이용했을 경우 사용
상태코드  
301 클라이언트가 요청한 리소스에 대한 URI가 변경 되었을 때 사용 (응답 시 Location header에 변경된 URI를 적어 줘야 한다)
500 서버에 문제가 있을 경우 사용

 

- axios의 사용법

import axios from 'axios';
axios.get('/users/1'); //get이 위치한 자리에는 메서드 이름을 소문자로 넣는다
axios.post('/users', {
  username: 'Qnd',
  name: 'Qnd'
  }); // 두번째 파라미터에 등록하고자 하는 정보를 넣을 수 있다

 

- useState와 useEffect로 데이터 로딩하기

총 3가지 상태 관리 필요

  1. 요청의 결과
  2. 로딩 상태
  3. 에러
// Users.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';

const Users = () => {
  const [users, setUsers] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => { //첫번째 파라미터로 등록하는 함수에는 async 사용 불가
    const fetchUsers = async () => { //함수 내부에서 async라는 새로운 함수 선언
      try {
        // 요청이 시작 할 때에는 error 와 users 를 초기화하고
        setError(null);
        setUsers(null);
        // loading 상태를 true 로 바꿉니다.
        setLoading(true);
        const response = await axios.get(
          'https://jsonplaceholder.typicode.com/users'
        );
        setUsers(response.data); // 데이터는 response.data 안에 들어있습니다.
      } catch (e) {
        setError(e);
      }
      setLoading(false);
    };

    fetchUsers();
  }, []);

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return null;
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>
          {user.username} ({user.name})
        </li>
      ))}
    </ul>
  );
}

export default Users;
  • useEffect에 첫 번째 파라미터로 등록하는 함수에는 async 사용 불가 (useEffect가 반환하는 것이 cleanup 함수 또는 undefined여야하는데 비동기 함수는 Promise를 반환하기 때문)
useEffect(async () => {
  // 비동기 함수로 작성한 경우
}, []);



useEffect(() => {
  // useEffect 내부에서 비동기 함수를 선언하고 호출
  const fetchUsers = async () => {
    // 비동기 작업 수행
  };
  fetchUsers();
}, []);
  • 로딩 상태가 활성화 됐을 땐 로딩중.. 문구가 보여짐
  • users의 값이 없을 때에는 null이 보여짐
  • 마지막에는 users 배열 렌더링

화면 녹화 어떻게 하는 지 모르겠어서 스터디 자료에서 가져왔습니다

주소를 이상하게 바꾸면

 

- 버튼으로 API 재요청하기

버튼 클릭으로 API를 재요청하려면 fetchUsers 함수가 useEffect 외부에서도 호출될 수 있어야 한다

=>fetchUsers 함수를 바깥으로 꺼내주기

import React, { useState, useEffect } from 'react';
import axios from 'axios';
const Users=()=>{
  const [users, setUsers] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const fetchUsers = async () => {
    try {
      // 요청이 시작 할 때에는 error 와 users 를 초기화하고
      setError(null);
      setUsers(null);
      // loading 상태를 true 로 바꿉니다.
      setLoading(true);
      const response = await axios.get(
        'https://jsonplaceholder.typicode.com/users'
      );
      setUsers(response.data); // 데이터는 response.data 안에 들어있습니다.
    } catch (e) {
      setError(e);
    }
    setLoading(false);
  };

  //컴포넌트가 처음 렌더링될 때 fetchUsers 호출
  useEffect(() => {
    fetchUsers();
  }, []);

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return null;
  return (
    <>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={fetchUsers}>다시 불러오기</button>
    </>
  );
}

export default Users;

 

 

useReducer로 요청 상태 관리하기

 

LOADING, SUCCESS, ERROR 액션에 따라 다르게 처리

import React, { useEffect, useReducer } from 'react';
import axios from 'axios';

const reducer = (state, action) => {
  switch (action.type) {
    case 'LOADING':
      return {
        loading: true,
        data: null,
        error: null
      };
    case 'SUCCESS':
      return {
        loading: false,
        data: action.data,
        error: null
      };
    case 'ERROR':
      return {
        loading: false,
        data: null,
        error: action.error
      };
    default:
      throw new Error(`Unhandled action type: ${action.type}`);
  }
}

const Users = () => {
  const [state, dispatch] = useReducer(reducer, {
    loading: false,
    data: null,
    error: null
  });

  const fetchUsers = async () => {
    dispatch({ type: 'LOADING' });
    try {
      const response = await axios.get(
        'https://jsonplaceholder.typicode.com/users'
      );
      dispatch({ type: 'SUCCESS', data: response.data });
    } catch (e) {
      dispatch({ type: 'ERROR', error: e });
    }
  };

  useEffect(() => {
    fetchUsers();
  }, []);

  const { loading, data: users, error } = state; // state.data 를 users 키워드로 조회

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return null;
  return (
    <>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={fetchUsers}>다시 불러오기</button>
    </>
  );
}

export default Users;

장점

  • useState의 setState 함수를 여러번 사용하지 않아도 된다
  • 다른 곳에서도 쉽게 재사용 가능

 

useAsync 커스텀 Hook 만들어서 사용하기

 

커스텀 Hook (리액트의 상태 관리나 로직을 쉽게 재사용하기 위해 만든 함수 = 레시피) 을 만들면 요청 상태 관리 로직을 쉽게 재사용 할 수 있다

//useAsync.js
import { useReducer, useEffect } from 'react';

const reducer = (state, action) => {
  switch (action.type) {
    case 'LOADING':
      return {
        loading: true,
        data: null,
        error: null
      };
    case 'SUCCESS':
      return {
        loading: false,
        data: action.data,
        error: null
      };
    case 'ERROR':
      return {
        loading: false,
        data: null,
        error: action.error
      };
    default:
      throw new Error(`Unhandled action type: ${action.type}`);
  }
}

const useAsync = (callback, deps = []) => { // callback은 API 요청을 시작하는 함수, deps는 해당 함수 안에서 사용하는 useEffect의 deps로 설정
  const [state, dispatch] = useReducer(reducer, {
    loading: false,
    data: null,
    error: false
  });

  const fetchData = async () => { 
    dispatch({ type: 'LOADING' });
    try {
      const data = await callback();
      dispatch({ type: 'SUCCESS', data });
    } catch (e) {
      dispatch({ type: 'ERROR', error: e });
    }
  };

  useEffect(() => {
    fetchData();
    // eslint 설정을 다음 줄에서만 비활성화
    // eslint-disable-next-line
  }, deps);

  return [state, fetchData]; // fetchData 함수를 반환해 나중에 데이터를 쉽게 리로딩 해줄 수 있다
}

export default useAsync;

deps 값은 나중에 사용할 비동기 함수에서 파라미터가 필요하고, 그 파라미터가 바뀔 때 새로운 데이터를 불러오고 싶은 경우 활용할 수 있다 (현재는 불필요, 기본값은 [])

//Users.js
import React from 'react';
import axios from 'axios';
import useAsync from './useAsync';

const getUsers = async() => {
  const response = await axios.get(
    'https://jsonplaceholder.typicode.com/users'
  );
  return response.data;
}

const Users = () => {
  const [state, refetch] = useAsync(getUsers, []);

  const { loading, data: users, error } = state; // state.data 를 users 키워드로 조회

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return null;
  return (
    <>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={refetch}>다시 불러오기</button>
    </>
  );
}

export default Users;

 

- 데이터 나중에 불러오기 (특정 버튼을 눌렀을 때만 API 요청)

// useAsync.js
import { useReducer, useEffect } from 'react';

const reducer = (state, action) => {
  switch (action.type) {
    case 'LOADING':
      return {
        loading: true,
        data: null,
        error: null
      };
    case 'SUCCESS':
      return {
        loading: false,
        data: action.data,
        error: null
      };
    case 'ERROR':
      return {
        loading: false,
        data: null,
        error: action.error
      };
    default:
      throw new Error(`Unhandled action type: ${action.type}`);
  }
}

const useAsync = (callback, deps = [], skip = false) => { //skip의 값이 true면 useEffect에서 아무런 작업도 하지 않도록 설정
  const [state, dispatch] = useReducer(reducer, {
    loading: false,
    data: null,
    error: false
  });

  const fetchData = async () => {
    dispatch({ type: 'LOADING' });
    try {
      const data = await callback();
      dispatch({ type: 'SUCCESS', data });
    } catch (e) {
      dispatch({ type: 'ERROR', error: e });
    }
  };

  useEffect(() => {
    if (skip) return;
    fetchData();
    // eslint 설정을 다음 줄에서만 비활성화
    // eslint-disable-next-line
  }, deps);

  return [state, fetchData];
}

export default useAsync;
// Users.js
import React from 'react';
import axios from 'axios';
import useAsync from './useAsync';

const getUsers = async () => {
  const response = await axios.get(
    'https://jsonplaceholder.typicode.com/users'
  );
  return response.data;
}

const Users = () => {
  const [state, refetch] = useAsync(getUsers, [], true);

  const { loading, data: users, error } = state; // state.data 를 users 키워드로 조회

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return <button onClick={refetch}>불러오기</button>;
  return (
    <>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={refetch}>다시 불러오기</button>
    </>
  );
}

export default Users;

 

- API 에 파라미터가 필요한 경우

User 컴포넌트 만들고 id 값을 props로 받아 와서

https://jsonplaceholder.typicode.com/users/1 이렇게 맨 뒤에 id를 넣어 API를 요청하자

//User.js
import React from 'react';
import axios from 'axios';
import useAsync from './useAsync';

const getUser = async (id) => {
  const response = await axios.get(
    `https://jsonplaceholder.typicode.com/users/${id}`
  );
  return response.data;
}

const User = ({ id }) => {
  const [state] = useAsync(() => getUser(id), [id]); //id 가 바뀔 때마다 재호출
  const { loading, data: user, error } = state;

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!user) return null;

  return (
    <div>
      <h2>{user.username}</h2>
      <p>
        <b>Email:</b> {user.email}
      </p>
    </div>
  );
}

export default User;
//Users.js
import React, { useState } from 'react';
import axios from 'axios';
import useAsync from './useAsync';
import User from './User';

const getUsers = async () => {
  const response = await axios.get(
    'https://jsonplaceholder.typicode.com/users'
  );
  return response.data;
}

const Users = () => {
  const [userId, setUserId] = useState(null); //초깃값은 null, 클릭한 사용자의 id를 userId 값으로 설정
  const [state, refetch] = useAsync(getUsers, [], true);

  const { loading, data: users, error } = state; // state.data 를 users 키워드로 조회

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return <button onClick={refetch}>불러오기</button>;
  return (
    <>
      <ul>
        {users.map(user => (
          <li
            key={user.id}
            onClick={() => setUserId(user.id)}
            style={{ cursor: 'pointer' }}
          >
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={refetch}>다시 불러오기</button>
      {userId && <User id={userId} />}
    </>
  );
}

export default Users;

 

 

react-async 로 요청 상태 관리하기

 

react-async : useAsync와 비슷한 함수가 들어있는 라이브러리, 매번 직접 요청 상태 관리를 위한 커스텀 Hook을 만들기 귀찮을 때 사용할 수 있다. 커스텀 Hook은 결과물을 배열로 반환하지만 이 Hook은 객체 형태로 반환한다

 

import { useAsync } from "react-async"

const loadCustomer = async ({ customerId }, { signal }) => {
  const res = await fetch(`/api/customers/${customerId}`, { signal })
  if (!res.ok) throw new Error(res)
  return res.json()
}

const MyComponent = () => {
  const { data, error, isLoading } = useAsync({ promiseFn: loadCustomer, customerId: 1 })
  if (isLoading) return "Loading..."
  if (error) return `Something went wrong: ${error.message}`
  if (data)
    return (
      <div>
        <strong>Loaded some data:</strong>
        <pre>{JSON.stringify(data, null, 2)}</pre>
      </div>
    )
  return null
}

useAsync를 사용 할 때 파라미터로 넣은 옵션 객체에는 호출 할 함수 promiseFn, 파라미터도 필드 이름과 함께 customerId 넣어줘야 한다

 

- User 컴포넌트를 react-async의 useAsync로 전환해보자

//User.js
import React from 'react';
import axios from 'axios';
import { useAsync } from 'react-async';

const getUser = async({ id }) => { //프로미스를 반환하는 함수의 파라미터를 객체 형태로 작성 (id 값을 따로 받아와서 사용하기 위해)
  const response = await axios.get(
    `https://jsonplaceholder.typicode.com/users/${id}`
  );
  return response.data;
}

const User = ({ id }) => {
  const { data: user, error, isLoading } = useAsync({
    promiseFn: getUser,
    id,
    watch: id //watch 값에 특정 값을 넣어주면 그 값이 바뀔 때마다 getUser를 다시 호출해준다
  });

  if (isLoading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!user) return null;

  return (
    <div>
      <h2>{user.username}</h2>
      <p>
        <b>Email:</b> {user.email}
      </p>
    </div>
  );
}

export default User;

 

- Users 컴포넌트를 react-async의 useAsync로 전환하기

//Users.js
import React, { useState } from 'react';
import axios from 'axios';
import { useAsync } from 'react-async';
import User from './User';

const getUsers = async() => {
  const response = await axios.get(
    'https://jsonplaceholder.typicode.com/users'
  );
  return response.data;
}

const Users = () => {
  const [userId, setUserId] = useState(null);
  const { data: users, error, isLoading, reload } = useAsync({
    promiseFn: getUsers
  });

  if (isLoading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return <button onClick={reload}>불러오기</button>;
  return (
    <>
      <ul>
        {users.map(user => (
          <li
            key={user.id}
            onClick={() => setUserId(user.id)}
            style={{ cursor: 'pointer' }}
          >
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={reload}>다시 불러오기</button>
      {userId && <User id={userId} />}
    </>
  );
}

export default Users;

컴포넌트를 렌더링하는 시점부터 데이터를 불러오게 된다

 

렌더링하는 시점에 API 호출 - promiseFn, reload 사용

사용자의 특정 인터랙션에 따라 API 호출 - deferFn, run 사용

 

 

Context 와 함께 사용하기

 

특정 데이터들은 다양한 컴포넌트에서 필요하게 될 때는 (현재 로그인된 사용자의 정보, 설정 등) Context 를 사용하면 개발이 편해진다

// UsersContext.js
import React, { createContext, useReducer, useContext } from 'react';

// UsersContext 에서 사용 할 기본 상태
const initialState = {
  users: {
    loading: false,
    data: null,
    error: null
  },
  user: {
    loading: false,
    data: null,
    error: null
  }
};

// 로딩중일 때 바뀔 상태 객체
const loadingState = {
  loading: true,
  data: null,
  error: null
};

// 성공했을 때의 상태 만들어주는 함수
const success = data => ({
  loading: false,
  data,
  error: null
});

// 실패했을 때의 상태 만들어주는 함수
const error = error => ({
  loading: false,
  data: null,
  error: error
});

// 위에서 만든 객체 / 유틸 함수들을 사용하여 리듀서 작성
const usersReducer = (state, action) => {
  switch (action.type) {
    case 'GET_USERS':
      return {
        ...state,
        users: loadingState
      };
    case 'GET_USERS_SUCCESS':
      return {
        ...state,
        users: success(action.data)
      };
    case 'GET_USERS_ERROR':
      return {
        ...state,
        users: error(action.error)
      };
    case 'GET_USER':
      return {
        ...state,
        user: loadingState
      };
    case 'GET_USER_SUCCESS':
      return {
        ...state,
        user: success(action.data)
      };
    case 'GET_USER_ERROR':
      return {
        ...state,
        user: error(action.error)
      };
    default:
      throw new Error(`Unhanded action type: ${action.type}`);
  }
}

// State 용 Context 와 Dispatch 용 Context 따로 만들어주기
const UsersStateContext = createContext(null);
const UsersDispatchContext = createContext(null);

// 위에서 선언한 두가지 Context 들의 Provider 로 감싸주는 컴포넌트
export function UsersProvider({ children }) {
  const [state, dispatch] = useReducer(usersReducer, initialState);
  return (
    <UsersStateContext.Provider value={state}>
      <UsersDispatchContext.Provider value={dispatch}>
        {children}
      </UsersDispatchContext.Provider>
    </UsersStateContext.Provider>
  );
}

// State 를 쉽게 조회 할 수 있게 해주는 커스텀 Hook
export function useUsersState() {
  const state = useContext(UsersStateContext);
  if (!state) {
    throw new Error('Cannot find UsersProvider');
  }
  return state;
}

// Dispatch 를 쉽게 사용 할 수 있게 해주는 커스텀 Hook
export function useUsersDispatch() {
  const dispatch = useContext(UsersDispatchContext);
  if (!dispatch) {
    throw new Error('Cannot find UsersProvider');
  }
  return dispatch;
}

 

id를 가지고 특정 사용자의 정보를 가져오는 API를 호출하고 싶을 땐 (요청이 시작 했을 때 액션을 디스패치, 성공하거나 실패했을 때 또 다시 디스패치)

dispatch({ type: 'GET_USER' });
try {
  const response = await getUser();
  dispatch({ type: 'GET_USER_SUCCESS', data: response.data });
} catch (e) {
  dispatch({ type: 'GET_USER_ERROR', error: e });
}

 

- 만든 Context 사용하기

//Users.js
import React, { useState } from 'react';
import { useUsersState, useUsersDispatch, getUsers } from './UsersContext';
import User from './User';

const Users = () => {
  const [userId, setUserId] = useState(null);
  const state = useUsersState();
  const dispatch = useUsersDispatch();

  const { data: users, loading, error } = state.users;
  const fetchData = () => {
    getUsers(dispatch);
  };

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return <button onClick={fetchData}>불러오기</button>;

  return (
    <>
      <ul>
        {users.map(user => (
          <li
            key={user.id}
            onClick={() => setUserId(user.id)}
            style={{ cursor: 'pointer' }}
          >
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={fetchData}>다시 불러오기</button>
      {userId && <User id={userId} />}
    </>
  );
}

export default Users;

useUsersState()  useUsersDispatch() 로 state  dispatch 를 가져오고, 요청을 시작 할 때에는 getUsers() 함수 안에 dispatch 를 넣어서 호출해준다

// User.js
import React, { useEffect } from 'react';
import { useUsersState, useUsersDispatch, getUser } from './UsersContext';

const User = ({ id }) => {
  const state = useUsersState();
  const dispatch = useUsersDispatch();
  useEffect(() => { // id 값이 바뀔 때마다 getUser() 호출
    getUser(dispatch, id);
  }, [dispatch, id]);

  const { data: user, loading, error } = state.user;

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!user) return null;
  return (
    <div>
      <h2>{user.username}</h2>
      <p>
        <b>Email:</b> {user.email}
      </p>
    </div>
  );
}

export default User;

getUser() 함수를 호출 할 때에는 두번째 파라미터에 현재 props 로 받아온 id 값을 넣어준다

 

 

추가자료

 

- 라우팅

웹 애플리케이션에서의 라우팅 - 사용자가 요청한 URL에 따라 알맞는 페이지를 보여주는 것

리액트에서 라우트 시스템을 구축하기 위해 리액트 라우터 또는 Next.js를 사용할 수 있다

 

- 싱글 페이지 애플리케이션

한 개의 페이지로 이루어진 애플리케이션 (html을 한번만 받아와서 웹 애플리케이션을 실행시킨 후에 그 이후에는 필요한 데이터만 받아와서 화면에 업데이트 해주는 것)

=> 사용자가 경험하기에는 여러 페이지가 존재하는 것처럼 느낄 수 있다

 

- 리액트 라우터 사용하기

react-router-dom 라이브러리 설치
src/index.js 파일에서 react-router-dom에 내장되어 있는 BrowserRouter라는 컴포넌트를 사용하여 감싸주기
웹 사이트에 들어왔을 때 가장 먼저 보여질 Home, 웹 사이트를 소개하는 About 만들기

Route 컴포넌트를 통해 라우트 설정을 해주어야 한다

<Route path="주소규칙" element={보여 줄 컴포넌트 JSX} />

Routes 컴포넌트 내부에서 Route 컴포넌트 사용

Link 컴포넌트를 사용해 다른 페이지로 이동하는 링크를 보여주자

<Link to="경로">링크 이름</Link>

a 태그를 사용하면 페이지를 새로 불러오게 되지만 Link 컴포넌트에는 페이지를 새로 불러오는 것을 막고 History API를 통해 브라우저 주소의 경로만 바꾸는 기능이 내장되어 있다!

 

소개를 누르면
About 페이지가 보여짐

 

- URL 파라미터와 쿼리스트링

  • URL 파라미터 : 주소 경로에 유동적인 값을 넣는 형태 / ID 또는 이름을 사용해 특정 데이터를 조회할 때 사용 (ex. /profile/wink)
  • 쿼리스트링 : 주소의 뒷부분에 ? 이후에 key=value 로 값을 정의하며 & 로 구분하는 형태 / 키워드 검색, 페이지네이션, 정렬 방식 등 데이터 조회에 필요한 옵션을 전달할 때 사용 (ex. /articles?**page=1&keyword=react)

URL 파라미터를 사용해보자

URL 파라미터는 useParams 라는 Hook을 사용해 객체 형태로 조회할 수 있다
경로에 : 를 사용하여 설정한다
Profile 페이지로 이동할 수 있도록 Home 페이지에 Link 만들기
URL 파라미터를 확인할 수 있다

 

 

쿼리스트링을 사용해보자

URL 파라미터와 다르게 Route 컴포넌트를 사용할 때 별도로 설정해야하는 것이 없다

useLocation 사용

useLocation Hook이 반환하는 객체에 있는 값들

  • pathname: 현재 주소의 경로 (쿼리스트링 제외)
  • search: 맨 앞의 ? 문자 포함한 쿼리스트링 값
  • hash: 주소의 # 문자열 뒤의 값 (주로 History API 가 지원되지 않는 구형 브라우저에서 클라이언트 라우팅을 사용할 때 쓰는 해시 라우터에서 사용합니다.)
  • state: 페이지로 이동할때 임의로 넣을 수 있는 상태 값
  • key: location 객체의 고유 값, 초기에는 default 이며 페이지가 변경될때마다 고유의 값이 생성됨

주소창에 http://localhost:3000/about?detail=true&mode=1 를 직접 입력

?detail=true&mode=1에서 key와 value를 파싱하는 작업을 해야한다

useSearchParams로 쿼리스트링을 쉽게 파싱해보자

  • useSearchParams 는 배열 타입의 값을 반환한다
  • 첫번째 원소는 쿼리파라미터를 조회하거나 수정하는 메서드들이 담긴 객체를 반환한다
  • get 메서드를 통해 특정 쿼리파라미터를 조회할 수 있고, set 메서드를 통해 특정 쿼리파라미터를 업데이트 할 수 있다. 쿼리파라미터가 존재하지 않는다면 null 로 조회된다
  • 두번째 원소는 쿼리파라미터를 객체형태로 업데이트할 수 있는 함수를 반환한다

* 쿼리파라미터를 조회할 때 값은 무조건 문자열 타입이다!

 

- 중첩된 라우트

//App.js
import { Route, Routes } from 'react-router-dom';
import About from './pages/About';
import Article from './pages/Article';
import Articles from './pages/Articles';
import Home from './pages/Home';
import Profile from './pages/Profile';

const App = () => {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
      <Route path="/profiles/:username" element={<Profile />} />
      <Route path="/articles" element={<Articles />}>
        <Route path=":id" element={<Article />} />
      </Route>
    </Routes>
  );
};

export default App;
// Articles.js
import { Link, Outlet } from 'react-router-dom';

const Articles = () => {
  return (
    <div>
      <Outlet />
      <ul>
        <li>
          <Link to="/articles/1">게시글 1</Link>
        </li>
        <li>
          <Link to="/articles/2">게시글 2</Link>
        </li>
        <li>
          <Link to="/articles/3">게시글 3</Link>
        </li>
      </ul>
    </div>
  );
};

export default Articles;

Outlet 컴포넌트가 사용된 자리에 중첩된 라우트가 보여지게 된다

게시글 하단에 게시글 목록이 잘 나타난다

 

 

- 리액트 라우터 부가기능

 

1. useNavigate - Link 컴포넌트를 사용하지 않고 다른 페이지로 이동을 해야 하는 상황에 사용하는 Hook

2. NavLink - 링크에서 사용하는 경로가 현재 라우트의 경로와 일치하는 경우 특정 스타일 또는 CSS 클래스를 적용하는 컴포넌트 (style또는 className을 설정할 때 { isActive: boolean } 을 파라미터로 전달받는 함수 타입의 값을 전달한다)

3. Navigate 컴포넌트 - 컴포넌트를 화면에 보여주는 순간 다른 페이지로 이동 하고 싶을 때 사용하는 컴포넌트

 

반응형