Skip to content

pre-onboading-2team/pre-onboarding-7th-3-1-2

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

10 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Week 3-1. ๊ฒ€์ƒ‰์ฐฝ & ๊ฒ€์ƒ‰์–ด ์ถ”์ฒœ ๊ธฐ๋Šฅ ๊ตฌํ˜„


  1. ํŒ€ ์†Œ๊ฐœ ๐Ÿ‘ซ
  2. ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ ๐Ÿš€
  3. ๊ธฐ์ˆ  ์Šคํƒ ๐Ÿ› 
  4. ๊ตฌํ˜„ ๊ธฐ๋Šฅ ๐Ÿ“
  5. ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ๐Ÿ—‚
  6. Best Practice ์„ ์ •๊ณผ์ •๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ
  7. ํ”„๋กœ์ ํŠธ ์„ค์น˜ ๋ฐ ์‹คํ–‰ โœจ

1. ํŒ€ ์†Œ๊ฐœ ๐Ÿ‘ซ


2. ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ ๐Ÿš€

  • ๊ฐœ์š” : ์›ํ‹ฐ๋“œ ํ”„๋ก ํŠธ์—”๋“œ ํ”„๋ฆฌ์˜จ๋ณด๋”ฉ 7๊ธฐ 2ํŒ€ ๊ณผ์ œ 3-1 ์ค‘ Best Practice
  • ์ฃผ์ œ : ๊ฒ€์ƒ‰์ฐฝ & ๊ฒ€์ƒ‰์–ด ์ถ”์ฒœ ๊ธฐ๋Šฅ ๊ตฌํ˜„
  • ๊ธฐ๊ฐ„ : 2022.11.8 ~ 2022.11.11

3. ๊ธฐ์ˆ  ์Šคํƒ ๐Ÿ› 

  • Typescript
  • React
  • Axios
  • Styled-Components

4. ๊ตฌํ˜„ ๊ธฐ๋Šฅ ๐Ÿ“

  • ๊ตฌํ˜„์‚ฌํ•ญ
    • ์งˆํ™˜๋ช… ๊ฒ€์ƒ‰์‹œ API ํ˜ธ์ถœ ํ†ตํ•ด์„œ ๊ฒ€์ƒ‰์–ด ์ถ”์ฒœ ๊ธฐ๋Šฅ ๊ตฌํ˜„
      • ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ํ…์ŠคํŠธ์™€ ์ผ์น˜ํ•˜๋Š” ๋ถ€๋ถ„ ๋ณผ๋“œ์ฒ˜๋ฆฌ
      • ๊ฒ€์ƒ‰์–ด๊ฐ€ ์—†์„ ์‹œ โ€œ๊ฒ€์ƒ‰์–ด ์—†์Œโ€ ํ‘œ์ถœ
    • API ํ˜ธ์ถœ ์ตœ์ ํ™”
      • API ํ˜ธ์ถœ๋ณ„๋กœ ๋กœ์ปฌ ์บ์‹ฑ ๊ตฌํ˜„ (๋ฏธ๊ตฌํ˜„)
      • ์ž…๋ ฅ๋งˆ๋‹ค API ํ˜ธ์ถœํ•˜์ง€ ์•Š๋„๋ก API ํ˜ธ์ถœ ํšŸ์ˆ˜๋ฅผ ์ค„์ด๋Š” ์ „๋žต ์ˆ˜๋ฆฝ ๋ฐ ์‹คํ–‰
      • ํ‚ค๋ณด๋“œ๋งŒ์œผ๋กœ ์ถ”์ฒœ ๊ฒ€์ƒ‰์–ด๋“ค๋กœ ์ด๋™ ๊ฐ€๋Šฅํ•˜๋„๋ก ๊ตฌํ˜„

5. ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ๐Ÿ—‚

src
 โ”ฃ api // ๋น„๋™๊ธฐ ํ†ต์‹  ๊ด€๋ จ ๋กœ์ง ๊ด€๋ฆฌ
 โ”ฃ components // ๊ณต์šฉ ์ปดํฌ๋„ŒํŠธ
 โ”ฃ constant // ์ƒ์ˆ˜ ๋ณ€์ˆ˜ ๊ด€๋ฆฌ
 โ”ฃ data // ๋”๋ฏธ ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ
 โ”ฃ hooks // ์ปค์Šคํ…€ ํ›… ๊ด€๋ฆฌ
 โ”ฃ pages // ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ
 โ”ฃ style // ๊ธ€๋กœ๋ฒŒ ์Šคํƒ€์ผ ๊ด€๋ฆฌ
 โ”ฃ types // ๊ณต์šฉ ํƒ€์ž… ๊ด€๋ฆฌ
 โ”— utils // ๊ณต์šฉ ์œ ํ‹ธ ํ•จ์ˆ˜ ๊ด€๋ฆฌ

6. Best Practice ์„ ์ •๊ณผ์ •๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ

Debounce ์ปค์Šคํ…€ ํ›…

// src/hooks/useDebounce.ts

import { useEffect, useState } from "react";

export const useDebounce = (value: string, delay: number) => {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(timer);
    };
  }, [value, delay]);

  return debouncedValue;
};

// src/components/SearchForm.ts

export const SearchForm = () => {
  const [value, onChange, reset] = useInput("");
  const debouncedValue = useDebounce(value, 500);
  const [{ loading, data, error }] = useSickAsync(debouncedValue, [
    debouncedValue,
  ]);

  // ...
  • ์ž…๋ ฅ๋งˆ๋‹ค API ํ˜ธ์ถœํ•˜์ง€ ์•Š๋„๋ก API ํ˜ธ์ถœ ํšŸ์ˆ˜๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•ด Debounce ํ•จ์ˆ˜ ์ ์šฉ
  • ์ปค์Šคํ…€ ํ›…์œผ๋กœ ๋ถ„๋ฆฌ (useDebounce)
  • ์‚ฌ์šฉ์ž ์ž…๋ ฅ๊ฐ’์„ Debounce๋œ ๊ฐ’์œผ๋กœ api ์š”์ฒญ(useSickAsync)

Flux ํŒจํ„ด์„ ์ ์šฉํ•œ ๋น„๋™๊ธฐ ํ†ต์‹  ์ปค์Šคํ…€ ํ›… ํ™œ์šฉ

// src/hooks/useSickAsync.ts

import { AxiosError, AxiosResponse } from "axios";
import { DependencyList, useEffect, useReducer } from "react";

import { httpClient } from "../api/api";
import { SickServiceImp } from "../api/SickService";

export type AsyncData = AxiosResponse<any, any> | null;
export type AsyncError = AxiosError | Error | null | boolean;

export type RequestState = {
  loading: boolean;
  data: AsyncData;
  error: AsyncError;
};

export type Action =
  | { type: "LOADING" }
  | { type: "SUCCESS"; data: AsyncData }
  | { type: "ERROR"; error: AsyncError };

type Cache = Record<string, AsyncData>;

const cache: Cache = {};

function asyncReducer(state: RequestState, action: Action): RequestState {
  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`);
  }
}

const initialState: RequestState = {
  loading: false,
  data: null,
  error: false,
};

const sickService = new SickServiceImp(httpClient);

export const useSickAsync = (
  value: string,
  deps: DependencyList
): [RequestState, () => Promise<void>] => {
  const [state, dispatch] = useReducer(asyncReducer, initialState);
  const fetchData = async () => {
    dispatch({ type: "LOADING" });
    try {
      const data = await sickService.getSickList({ q: value });
      dispatch({ type: "SUCCESS", data });
    } catch (e: any) {
      dispatch({ type: "ERROR", error: e });
    }
  };

  useEffect(() => {
    fetchData();
  }, deps);

  return [state, fetchData];
};



// src/components/SearchForm.ts

export const SearchForm = () => {
  const [value, onChange, reset] = useInput("");
  const debouncedValue = useDebounce(value, 500);
  const [{ loading, data, error }] = useSickAsync(debouncedValue, [
    debouncedValue,
  ]);

  const items = data?.data;
  const [activeIdx, handleKeyArrow] = useKeyArrow(items || []);

  return (
    <S.SearchFormContainer>
      <S.SearchFormTitle>
        ๊ตญ๋‚ด ๋ชจ๋“  ์ž„์ƒ์‹œํ—˜ ๊ฒ€์ƒ‰ํ•˜๊ณ  ์˜จ๋ผ์ธ์œผ๋กœ ์ฐธ์—ฌํ•˜๊ธฐ
      </S.SearchFormTitle>
      <SearchInput
        value={value}
        onChange={onChange}
        reset={reset}
        onKeyDown={handleKeyArrow}
      />
      {value && (
        <S.SearchResultBlock>
          {loading && <S.SearchItemLabel>๊ฒ€์ƒ‰ ์ค‘ ...</S.SearchItemLabel>}
          {error && <S.SearchItemError>์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค</S.SearchItemError>}
          {items && (
            <SearchList
              value={debouncedValue}
              items={items}
              activeIdx={activeIdx}
            />
          )}
        </S.SearchResultBlock>
      )}
    </S.SearchFormContainer>
  );
};

// 
  • Flux ํŒจํ„ด์„ ์ ์šฉํ•ด ๋น„๋™๊ธฐ ํ†ต์‹  ์ƒํƒœ ๊ด€๋ฆฌ(loading,error,success)๋ฅผ ์ปค์Šคํ…€ ํ›…์œผ๋กœ ๋ถ„๋ฆฌ

์ผ์น˜ํ•˜๋Š” ํ…์ŠคํŠธ ๋ณผ๋“œ ์ฒ˜๋ฆฌ

// src/utils

export const divideByKeyword = (target: string, keyword: string) => {
  const preIdx = target.indexOf(keyword);
  const postIdx = preIdx + keyword.length;
  const prefix = target.slice(0, preIdx);
  const postfix = target.slice(postIdx);

  return [prefix, postfix];
};


// src/components/SearchItem.tsx

interface SearchItemProps {
  value: string;
  keyword: string;
  isActive?: boolean;
}

export const SearchItem = ({
  value,
  keyword,
  isActive = false,
}: SearchItemProps) => {
  const [prefix, postfix] = divideByKeyword(value, keyword);

  return (
    <S.SearchItemBlock isActive={isActive}>
      <S.SearchItemIcon>
        <HiOutlineSearch size="1.8rem" color="#a6afb7" />
      </S.SearchItemIcon>
      <S.SearchItemText>{prefix}</S.SearchItemText>
      <S.SearchItemMatched>{keyword}</S.SearchItemMatched>
      <S.SearchItemText>{postfix}</S.SearchItemText>
    </S.SearchItemBlock>
  );
};
  • ๊ฒ€์ƒ‰์–ด(keyword)๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ „์ฒด ํ…์ŠคํŠธ(target) string์„ ๋ถ„๋ฆฌํ•˜๋Š” ์œ ํ‹ธ ํ•จ์ˆ˜ ๊ตฌํ˜„

7. ํ”„๋กœ์ ํŠธ ์„ค์น˜ ๋ฐ ์‹คํ–‰ โœจ


  1. Git Clone
$ git clone https://github.com/pre-onboading-2team/pre-onboarding-7th-3-1-2.git
  1. ํ”„๋กœ์ ํŠธ ํŒจํ‚ค์ง€ ์„ค์น˜
$ npm install
  1. ํ”„๋กœ์ ํŠธ ์‹คํ–‰
$ npm start

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published