[Velopert리액트4]API연동

Yongrok·2021년 12월 29일
0

React_Velopert

목록 보기
5/9

사용교재 : 벨로퍼트와 함께하는 모던 리액트
범위 : 4.1 ~ 4.5
노션 용량문제로 Migration
3장은 todoList 실습파트로 스킵

4. API 연동

1. API 연동의 기본

axios

axios.요청 메서드("API 주소", {요청 바디})

1) 데이터 요청

  • 에러 상태
  • 현재 요청 정보의 상태
  • 로딩 관리
  • 데이터 요청 : axios
const [users, setUsers] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);

useEffect(() => {
  const fetchUsers = async () => {
    try {
      setError(null);
      setUsers(null);
      setLoading(true);
      const response = await axios.get(
        'https://jsonplaceholder.typicode.com/users'
      );
      setUsers(response.data);
    } catch (e) {
      setError(e);
    }
    setLoading(false);
  };

  fetchUsers();
}, []);

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

(!) useEffect 의 첫번째 파라미터 함수에는 async 사용불가
→ 내부 함수에서 async 를 쓰는 함수를 새로 선언

useEffect , fetchUsers 는 분리가능!

const fetchUsers = async () => {
  try {
    setError(null);
    setUsers(null);
    setLoading(true);
    const response = await axios.get(
      'https://jsonplaceholder.typicode.com/users'
    );
    setUsers(response.data);
  } catch (e) {
    setError(e);
  }
  setLoading(false);
};

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

2. useReducer

요청 상태 관리 : LOADING , SUCCESS , ERROR

function 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}`);
  }
}

function 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;
//...
}

vs useState

간결함 : ↔setState 로 매번 상태변화를 줘야함

재사용성: 별로로 로직을 분리하기 때문에 다른 컴포넌트에서도 재사용 가능

3. 커스텀 훅 useAsync

API 요청 상태 관리 로직

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

function 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}`);
  }
}

function useAsync(callback, 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();
  }, deps);

  return [state, fetchData];
}

export default useAsync;

useAsync 의 두번째 파라미터 deps 는 해당 함수를 호출할 때 할당한다. → 동적으로 함수 내부의 useEffect 사용가능

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

function Users() {
  const [state, refetch] = useAsync(getUsers, []);
//...
}

useAsync(callback, deps, skip)

skip = true, false

useEffect(() => { if ( skip ) return; ... }, deps)

前) 렌더링 시 자동으로 API 요청

改) skip 파라미터가 true 일 때만, API 요청

API요청의 파라미터?

id 등 API 요청에 파라미터가 필요한 경우, API 주소가 들어간 함수에서 파라미터를 받게 만든다 → 여기에선 User.jsgetUsers( id )

4. react-async

요청 상태를 관리하는 react 라이브러리
앞선 커스텀 훅 useAsync 와 유사함
반환값: 객체

사용법 : useAsync(파라미터)

  • 파라미터: { promiseFn, customerId ...}
    • promiseFn : 호출할 함수
    • customerId :
    • watch : 값이 바뀔 때마다 promiseFn 재호출
    • 그 외 다양
  • 반환값 : { data, error, isLoading, run ... }

5. Context

API에 요청한 외부 데이터들을 다른 컴포넌에서도 전역적으로 사용하기 위함

// UserContext.js
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
});

function 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}`);
  }
}

const UsersStateContext = createContext(null);
const UsersDispatchContext = createContext(null);

export function UsersProvider({ children }) {
  const [state, dispatch] = useReducer(usersReducer, initialState);
  return (
    <UsersStateContext.Provider value={state}>
      <UsersDispatchContext.Provider value={dispatch}>
        {children}
      </UsersDispatchContext.Provider>
    </UsersStateContext.Provider>
  );
}

export function useUsersState() {
  const state = useContext(UsersStateContext);
  if (!state) {
    throw new Error('Cannot find UsersProvider');
  }
  return state;
}

export function useUsersDispatch() {
  const dispatch = useContext(UsersDispatchContext);
  if (!dispatch) {
    throw new Error('Cannot find UsersProvider');
  }
  return dispatch;
}

API 처리 함수

// UserContext.js
export async function getUsers(dispatch) {
  dispatch({ type: 'GET_USERS' });
  try {
    const response = await axios.get(
      'https://jsonplaceholder.typicode.com/users'
    );
    dispatch({ type: 'GET_USERS_SUCCESS', data: response.data });
  } catch (e) {
    dispatch({ type: 'GET_USERS_ERROR', error: e });
  }
}

export async function getUser(dispatch, id) {
  dispatch({ type: 'GET_USER' });
  try {
    const response = await axios.get(
      `https://jsonplaceholder.typicode.com/users/${id}`
    );
    dispatch({ type: 'GET_USER_SUCCESS', data: response.data });
  } catch (e) {
    dispatch({ type: 'GET_USER_ERROR', error: e });
  }
}

반복 코드 줄이기 api.js

// api.js
import axios from 'axios';

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

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

액션 타입 결정 및 함수 반환

// asyncActionUtils.js
export default function createAsyncDispatcher(type, promiseFn) {
  const SUCCESS = `${type}_SUCCESS`;
  const ERROR = `${type}_ERROR`;

  async function actionHandler(dispatch, ...rest) {
    dispatch({ type }); // 요청 시작됨
    try {
      const data = await promiseFn(...rest);
      dispatch({
        type: SUCCESS,
        data
      }); // 성공함
    } catch (e) {
      dispatch({
        type: ERROR,
        error: e
      }); // 실패함
    }
  }

  return actionHandler; // 만든 함수를 반환합니다.
}

⇒ 改 UsersContext.js

// UsersContext.js
export const getUsers = createAsyncDispatcher('GET_USERS', api.getUsers);
export const getUser = createAsyncDispatcher('GET_USER', api.getUser);

0개의 댓글