본문 바로가기

리액트

[리액트] 개인 코드 정리(리덕스 툴킷 타입스크립트 설정)

반응형

 useSelector 와 useDispatch 를 대신하여 사용할 hook을 지정

// app/store.ts

import { configureStore } from '@reduxjs/toolkit'
import searchSlice from './slice/searchSlice'


/* 스토어 생성 */
const store = configureStore({
    reducer: {
        search: searchSlice.reducer
    }
})


// store의 getState는 각 레듀서가 반환하는 state 값을 담고 있다.
// 해당 반환값을 typeof 로 검사하여 반환된 타입으로
// 반환되는 state의 타입을 동적으로 결정한다.
export type RootState = ReturnType<typeof store.getState>

// 추론된 타입: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch

export default store

 

 useSelector 와 useDispatch 를 대신하여 사용할 hook을 지정

AppDispatch 는 store 가 반환하는 dispatch 로 부터 타입을 유추하여 타입을 자동으로 지정해준다.

예를 들어,   {posts: PostsState, comments: CommentsState, users: UsersState}이런 형식이다.

// app/hooks.ts
import { useDispatch, useSelector } from "react-redux";
import type { TypedUseSelectorHook } from "react-redux";
import type { RootState, AppDispatch } from "./store";


export const useAppDispatch: () => AppDispatch = useDispatch

// 기존의 useSelector 훅을 대신하는 훅으로
// 리듀서에서 반환되는 값의 타입을 자신의 타입으로 한다.
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

 

 스토어의 루트 리듀서에 도달하기 전에 처리하고자 하는 요청이 있을 때

스토어의 루트 리튜서에 actions 을 전달하기 전에 우선적으로 처리하고자 하는 작업이 있다면 createAsyncThunk 미들웨어를 사용한다. 이를 이용하면 비동기적으로 처리하고자 하는 네트워크 요청이 있다면 요청처리 전, 처리 후, 처리가 불가능한 상태에 따른 다양한 상태에 대한 처리를 실시할 수 있다.

// app/slice/ searchSlice

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";
import type { PayloadAction } from "@reduxjs/toolkit";
import type { RootState } from "../store";

const asyncGetSearchData = createAsyncThunk(
  "searchSlice/asyncGetSearchData",
  async (item:string) => {
    const response = await axios.get(
      `http://apis.data.go.kr/1471000/FoodNtrIrdntInfoService1/getFoodNtrItdntList1?servicekey=${process.env.REACT_APP_BUSAN_KEY}&type=json&desc_kor=${item}`
    );
    const data = await response.data;

    return data.body.items;
  }
);

// 초기 state 의 타입을 지정한다.
interface InitialState {
  value: string[];
}

// 초기 state 를 지정한다.
const initialState: InitialState = {
  value: [],
};

// 슬라이스를 생성한다.
const searchSlice = createSlice({
  name: "search",
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder.addCase(asyncGetSearchData.pending, (state, action) => {
      state.value = ['데이터를 준비중 입니다.'];
    });
    builder.addCase(
      asyncGetSearchData.fulfilled,
      (state, action: PayloadAction<string[]>) => {
        state.value = action.payload;
      }
    );
    builder.addCase(asyncGetSearchData.rejected, (state, action) => {
      state.value = ['요청이 처리되지 않았습니다.'];
    });
  },
});

// 사용한 레듀서를 내보낸다.

export {asyncGetSearchData}

// store로 전달할 슬라이스를 내보낸다.
export default searchSlice;

 

 처리가 완료된 요청에 대한 결과를 스토어에서 가져오려면

useSelector() 선택자를 사용하여 스토어에서 dispatch 한 actions 에 대한 처리 결과인 state 을 사용할 수 있다.

// src / Search.tsx

import styles from "./Search.module.css";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons";
import { useNavigate } from "react-router-dom";
import { useAppDispatch } from "../../app/hooks";
import { asyncGetSearchData } from "../../app/slice/searchSlice";
import Header from "./Header";

interface SearchType {
  fixed: boolean;
}

function Search({ fixed }: SearchType) {
  const navigate = useNavigate();
  const dispatch = useAppDispatch();

  return (
    <>
      <Header isStyle={true}></Header>
      <article
        className={styles.search}
        style={
          fixed
            ? {
                position: "relative",
                top: "8rem",
                maxWidth: "600px",
              }
            : { position: "fixed" }
        }
      >
        <div className={styles.search_container}>
          <label className={styles.search_icon} htmlFor="search">
            <FontAwesomeIcon
              style={{ color: "black" }}
              icon={faMagnifyingGlass}
            />
          </label>
          <input
            onKeyUp={(e) => {
              if (e.code === "Enter") {
                // sendURL(e.currentTarget.value);
                if (e.currentTarget.value !== "")
                  dispatch(asyncGetSearchData(e.currentTarget.value));
                e.currentTarget.value = "";
                return navigate("/busan_item_map/search");
              }
            }}
            placeholder="음식명을 입력 후 [Enter] 를 눌러주세요!!"
            type="text"
            id="search"
            className={styles.search_input}
            style={fixed ? { border: '1px solid rgba(28, 99, 231,0.1)', boxShadow:'inset 0 0 5px 2px rgba(55,55,55,0.5)' } : {}}
          />
        </div>
      </article>
    </>
  );
}

export default Search;
반응형