๐Ÿง‘โ€๐Ÿ’ป ์งง์€ํ˜ธํก/React

[React] ์ œ๋„ค๋ฆญ ์กฐ๊ฑด๋ถ€ ํƒ€์ž… ํ™œ์šฉํ•˜๊ธฐ - ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ปค์Šคํ…€ํ›…์œผ๋กœ ์ œ๊ณต

ํ•œ๊ทœ์ง„ 2023. 6. 2. 02:51

ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์„ ํ•˜๋‹ค๋ณด๋ฉด ํ”ํžˆ ๋งˆ์ฃผํ•  ์ˆ˜ ์žˆ๋Š” ์ƒํ™ฉ์ด๋‹ค. Select, Toggle ๋˜๋Š” DatePicker ๋“ฑ์˜ ์ธํ’‹ ์ปดํฌ๋„ŒํŠธ๋“ค์„ ์„ค๊ณ„ํ•  ๋•Œ, ๊ทธ ์ปดํฌ๋„ŒํŠธ๋“ค์„ ํ•ธ๋“ค๋งํ•  ์ปค์Šคํ…€ํ›…๋„ ๊ฐ™์ด ์ž‘์„ฑํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ณดํ†ต์€ useToggle, useSelect ๋“ฑ์˜ ์ด๋ฆ„์œผ๋กœ ์“ฐ๊ณ  [value, onChange] ๊ฐ™์€ ๋ฐฐ์—ด์„ returnํ•ด ์‚ฌ์šฉํ•œ๋‹ค. ์™ธ์ฃผ ๊ฐœ๋ฐœ์„ ํ•˜๋ฉด์„œ ์ดˆ๊ธฐ์— ์ด๋Ÿฐ ์ปดํฌ๋„ŒํŠธ ์ž‘์—…์„ ํ•ด์•ผํ•  ์ผ์ด ๋งŽ์•˜๋‹ค.

 

import useSelect from '~hooks/useSelect';
import Select from '~components/Select';

const App = () => {
  const [value, handleSelectValue] = useSelect();

  return (
    <>
      <Select 
          value={value}
        onClick={handleSelectValue} 
        placeholder={'๋“ฑ๊ธ‰ ์„ ํƒ'}
      />
    </>
  );
};

์œ„์™€ ๊ฐ™์ด ๋งค๋ฒˆ ์ปดํฌ๋„ŒํŠธ ๋”ฐ๋กœ ํ›… ๋”ฐ๋กœ ๋งŒ๋“ค๊ณ  ์‚ฌ์šฉํ•  ๋•Œ๋„ ์ปดํฌ๋„ŒํŠธ ๋”ฐ๋กœ ํ›… ๋”ฐ๋กœ ์ž„ํฌํŠธํ•ด์™€, ํ›…์—์„œ ๋ฆฌํ„ดํ•œ ๊ฐ’๋“ค์„ ์ปดํฌ๋„ŒํŠธ์— prop์œผ๋กœ ๋„˜๊ธฐ๋Š” ํŒจํ„ด์ด ์ผ๋ฐ˜์ ์ด๋‹ค. ์กฐ๊ธˆ๋” ๊ฐœ์„ ํ•ด๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค.

 

์šฐ๋ฆฌ๊ฐ€ ์•Œ๊ณ  ์‹ถ์€๊ฑด input ๊ฐ’์ด ๋ฐ”๋€Œ์—ˆ์„๋•Œ ํ•ธ๋“ค๋งํ•˜๋Š” onChange ํ•จ์ˆ˜๊ฐ€ ์•„๋‹ˆ๋ผ, ์ง€๊ธˆ input์— ๋“ค์–ด์žˆ๋Š” value๊ฐ€ ๊ถ๊ธˆํ•œ๊ฑฐ๋‹ˆ๊นŒ ํ•œ๋ฒˆ ๋” ์ถ”์ƒํ™”๋ฅผ ํ•ด๋ณผ ์ˆ˜ ์žˆ๊ฒ ๋‹ค ์‹ถ์–ด ๊ณ ๋ฏผ์„ ํ•ด๋ณด์•˜๋‹ค. ์ถ”๊ฐ€๋กœ ํ•ด๋‹น input์ด ์—ฌ๋Ÿฌ ํƒ€์ž…์˜ value๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค๋ฉด ํƒ€์ž… ์ถ”๋ก ์€ ์–ด๋–ค์‹์œผ๋กœ ํ•ด์•ผํ• ์ง€์— ๋Œ€ํ•œ ๊ณ ๋ฏผ๊นŒ์ง€.

 

์ด๋ฒˆ ๊ธ€์—์„  ๊ทธ ๊ณ ๋ฏผ์— ๋Œ€ํ•œ ๊ฒฐ๊ณผ๋ฅผ ์ ์–ด๋ณด์•˜๋‹ค.

 


 

์ปดํฌ๋„ŒํŠธ๋„ ์ปค์Šคํ…€ํ›…์œผ๋กœ ์ œ๊ณตํ•˜๊ธฐ

Select ์ปดํฌ๋„ŒํŠธ๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•˜๋„๋ก ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

import useSelect from '~hooks/useSelect';

const RANK_OPTIONS: Option[] = [
  {value: 'white', label: 'white'},
  {value: 'silver', label: 'silver'},
  {value: 'gold', label: 'gold'},
  {value: 'dia', label: 'dia'},
];

const App = () => {
  const [rank, renderSelectRank] = useSelect(RANK_OPTIONS, 'silver');

  return (
    <>
      {renderSelectRank({placeholder: '๋“ฑ๊ธ‰ ์„ ํƒ'})}
    </>
  );
};

useSelect Hook์˜ ์ธ์ž๋กœ ์˜ต์…˜ ๋ฐฐ์—ด๊ณผ initialValue๋ฅผ ๋„˜๊ฒจ์ค€๋‹ค. ์ปดํฌ๋„ŒํŠธ์™€ ์ง์ ‘์ ์œผ๋กœ ๊ด€๋ จ๊ดธ renderSelectRank์—๋Š” placeholder๋ฅผ ๋„ฃ์–ด์ฃผ์–ด ์‚ฌ์šฉํ•œ๋‹ค.

 

const useSelectInput = (
  options: Option[],
  initialValue: selectOption = null,
) => {
  const isMulti = isMultiSelect(initialValue);
  const [value, setValue] = useState<selectOption>(initialValue);

  const renderSelectInput = ({placeholder, width}: SelectInputProps) => (
    <SelectStyles width={width}>
      <Select
        isMulti={isMulti}
        onChange={selectOption => setValue(selectOption)}
        defaultValue={initialValue}
        options={options}
        placeholder={placeholder}
      />
    </SelectStyles>
  );

  return [value, renderSelectInput] as const;
};

export default useSelectInput;

๊ฐ„๋žตํ•˜๊ฒŒ ํ‘œํ˜„๋œ useSelect Hook์˜ ๊ตฌํ˜„์ด๋‹ค. ํ›…์ด renderํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š”๊ฒŒ ๋‚ฏ์„ค ์ˆ˜๋„ ์žˆ์ง€๋งŒ (์ปดํฌ๋„ŒํŠธ๋ฅผ ์ ์ ˆํ•œ ์œ„์น˜์— ๋ Œ๋”๋งํ•˜๊ณ  ์„ ํƒ๋œ ๊ฐ’์„ ๋‚ด๋ณด๋‚ธ๋‹ค๋Š”) ๋”ฑ ํ•„์š”ํ•œ ์ •๋ณด๋งŒ App์—์„œ ๋ณผ ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค. render ํ•จ์ˆ˜์— ์ธ์ž๋กœ ๋„˜๊ฒจ์ค€ ๊ฐ’์ด ์‹ค์ œ ์ปดํฌ๋„ŒํŠธ์˜ props์œผ๋กœ ๋“ค์–ด๊ฐ„๋‹ค๋Š” ์ ์—์„œ, ๋น„์Šทํ•˜๊ฒŒ ๊ฐ์ฒด์˜ ํ˜•ํƒœ๋กœ ๋„˜๊ธฐ๋„๋ก ์„ค๊ณ„ํ–ˆ๋‹ค๋Š” ์ ๋„ ๋งค๋ ฅ์žˆ๋‹ค.

 

 

์ปดํฌ๋„ŒํŠธ๊ฐ€ ์—ฌ๋Ÿฌ ํƒ€์ž…์˜ value๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค๋ฉด?

 

isMulti๋ฅผ true๋กœ ์ฃผ๋ฉด ์œ„์˜ ์‚ฌ์ง„์ฒ˜๋Ÿผ ๊ฐ’์„ ์—ฌ๋Ÿฌ๊ฐœ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค. ํ˜„์žฌ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” react-select๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” value์— Option ๋˜๋Š” Option[] ํƒ€์ž…์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

 

type selectOption = SingleValue<Option> | MultiValue<Option>;

const isMultiSelect = (value: selectOption): value is MultiValue<Option> => {
  return (value as MultiValue<Option>)?.length !== undefined;
};

export interface SelectInputProps {
  placeholder?: string;
  width?: number;
}

ํ•ด๋‹น Select ์ปดํฌ๋„ŒํŠธ์—๊ฒŒ multi์ธ์ง€ single์ธ์ง€ ์—ฌ๋ถ€๋ฅผ ๋”ฐ๋กœ ๋ฐ›์ง€ ์•Š๊ณ , ์ธ์ž๋กœ ๋ฐ›์€ initialValue์˜ ํƒ€์ž…์„ ๋ณด๊ณ  ๊ฒฐ์ •ํ•˜๋„๋ก ํ–ˆ๋‹ค.

 

const [rank, renderSelectRank] = useSelect(RANK_OPTIONS, []);

์ดˆ๊ธฐ๊ฐ’์œผ๋กœ ๋นˆ ๋ฐฐ์—ด์„ ๋„ฃ์–ด์ฃผ๋ฉด MultiSelect ์ปดํฌ๋„ŒํŠธ๋กœ์จ ๋™์ž‘ํ•œ๋‹ค.

 

 

ํƒ€์ž…์ถ”๋ก  ๋ฌธ์ œ

๋ฌธ์ œ๋Š” ์—ฌ๊ธฐ๋‹ค. useSelect ์ปค์Šคํ…€ํ›…์—์„œ ๋‚˜์˜จ value(์—ฌ๊ธฐ์„  rank)์˜ ํƒ€์ž…์ด ๋‚ด๊ฐ€ ์ƒ๊ฐํ•˜๋Š”๋Œ€๋กœ ๋‚˜์˜ค์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. SingleValue์™€ MultiValue์˜ ์œ ๋‹ˆ์˜จ์œผ๋กœ state์˜ ์ƒํƒœ๋ฅผ ์ง€์ •ํ•ด์ฃผ์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ปค์Šคํ…€ํ›…์—์„œ ๋ฐ˜ํ™˜๋ ๋•Œ๋„ ๊ทธ ํƒ€์ž…(selectOption)์œผ๋กœ ๋‚˜์˜จ๋‹ค.

 

if (isMulti){
    return [value as MultiValue<Option>, renderSelectInput] as const;
} else {
    return [value as SingleValue<Option>, renderSelectInput] as const;
}

์ด๋ ‡๊ฒŒ ํƒ€์ž… ๊ฐ€๋“œ๋ฅผ ์‚ฌ์šฉํ•ด ๋‹จ์–ธ์„ ํ•ด์ฃผ๋ฉด ๋ ๊นŒํ•˜๊ณ  ์ˆ˜์ •ํ•ด๋ณด์•˜๋”๋‹ˆ

 

๋ฌด์–ธ๊ฐ€ ์ ์šฉ์ด ๋˜๊ธดํ•˜์ง€๋งŒ, ์›ํ•˜๋Š”๋Œ€๋กœ ์ •ํ™•ํ•˜๊ฒŒ ํƒ€์ž…์ด ์ถ”๋ก ๋˜์ง€๋Š” ์•Š์•˜๋‹ค.


์–ด๋–ป๊ฒŒ ํ•˜๋ฉด ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์„๊นŒ!

 


 

์ œ๋„ค๋ฆญ ์กฐ๊ฑด๋ถ€ ํƒ€์ž… ํ™œ์šฉํ•˜๊ธฐ

useSelect ์ปค์Šคํ…€ํ›…์—์„œ๋Š” single๊ณผ multi select๋ฅผ ๋ชจ๋‘ ์ฒ˜๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ทธ์— ๋งž๊ฒŒ ๋ฐ˜ํ™˜ ํƒ€์ž…์„ ์ˆ˜์ •ํ•ด์•ผ ํ•œ๋‹ค. ํ•˜์ง€๋งŒ useState์˜ ํƒ€์ž…์„ ํ†ตํ•ด ํƒ€์ž… ์ถ”๋ก ์„ ์™„๋ฒฝํ•˜๊ฒŒ ํ•˜๋ ค๋ฉด ์œ„์™€ ๊ฐ™์€ ์–ด๋ ค์›€์ด ์žˆ์—ˆ๋‹ค

 

์กฐ๊ธˆ์˜ ์‚ฝ์งˆ ๋์—, ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์˜ ์ œ๋„ค๋ฆญ์„ ํ†ตํ•ด ์ปค์Šคํ…€ํ›… ํ•จ์ˆ˜์— ์ „๋‹ฌ๋˜๋Š” initialValue์˜ ํƒ€์ž…์— ๋”ฐ๋ผ ๋ฐ˜ํ™˜ ํƒ€์ž…์ด ๋‹ฌ๋ผ์ง€๋„๋ก ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

 

 

์กฐ๊ฑด๋ถ€ ํƒ€์ž…

ํƒ€์ž…์€ ๊ฐ€๋Šฅํ•œ ๊ฐ’๋“ค์˜ ์ง‘ํ•ฉ์ด๋‹ค

์กฐ๊ฑด๋ถ€ ํƒ€์ž…์— ๋Œ€ํ•œ ์ด์•ผ๊ธฐ์— ์•ž์„œ ์ด ๊ฐœ๋…์„ ํ•œ๋ฒˆ ๋” ์ •๋ฆฌํ•˜๊ณ  ๊ฐ€๋ฉด ์ข‹๋‹ค. ์ดํŽ™ํ‹ฐ๋ธŒ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์Šคํ„ฐ๋””๋ฅผ ์ฒ˜์Œ ์‹œ์ž‘ํ• ๋•Œ ๊ฐ€์žฅ ํ—ท๊ฐˆ๋ ธ๋˜ ๋ถ€๋ถ„ ์ค‘ ํ•˜๋‚˜์ด๋‹ค. T extends U ์ด๋ฉด T๋Š” U์˜ ๋ถ€๋ถ„์ง‘ํ•ฉ์ด๋ผ๊ณ  ๋งํ•  ์ˆ˜ ์žˆ๋‹ค.

 

T๊ฐ€ U๋ฅผ ํ™•์žฅํ–ˆ๋‹ค๋Š” ๊ฒƒ์€, U์— ์žˆ๋Š” ๋ชจ๋“  ์†์„ฑ๊ณผ ๋ฉ”์†Œ๋“œ๋“ค์ด T์—๋„ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๋ณด์žฅํ•œ๋‹ค. ๋‹ค๋ฅด๊ฒŒ ๋งํ•˜๋ฉด 'T๋Š” U์— ํ• ๋‹น ๊ฐ€๋Šฅํ•˜๋‹ค' ๋ผ๋Š” ์˜๋ฏธ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. (U๋กœ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ๊ฒƒ๋“ค์„ T๋กœ๋„ ํ•  ์ˆ˜ ์žˆ๋‹ค โ†’ T๋Š” U์— ํ• ๋‹น ๊ฐ€๋Šฅํ•˜๋‹ค)

 

T extends U ? X : Y

 

์ด๋ฅผ ํ†ตํ•ด ์•„๋ฌด๋Ÿฐ ํƒ€์ž…์˜ ๊ฐ’์ด๋‚˜ ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ๋Š” ์ œ๋„ค๋ฆญ T์— ์ œ์•ฝ์„ ๊ฑธ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ ๊ฐœ๋…์„ ์ด์šฉํ•ด ์กฐ๊ฑด๋ถ€ ํƒ€์ž…์„ ๋‚˜ํƒ€๋‚ผ ์ˆ˜ ์žˆ๋‹ค. ์‚ผํ•ญ์—ฐ์‚ฐ์ž์™€ ๋น„์Šทํ•˜๊ฒŒ ์ƒ๊ฒผ๋‹ค. T๊ฐ€ U๋ฅผ ํ™•์žฅํ•˜๋ฉด(True๋ฉด) ํƒ€์ž… X๋ฅผ, False๋ฉด ํƒ€์ž… Y๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

 

 

์ ์šฉํ•ด๋ณด๊ธฐ

๊ทธ๋ ‡๋‹ค๋ฉด ์ง€๊ธˆ useSelect ์ปดํฌ๋„ŒํŠธ์—์„  ์ด๋ ‡๊ฒŒ ๊ตฌํ˜„์„ ํ•ด๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

type SingleSelect = SingleValue<Option>;
type MultiSelect = MultiValue<Option>;

const useSelectInput = <T extends SingleSelect | MultiSelect>(
  options: Option[],
  initialValue: T,
) => {
  const isMulti = isMultiSelect(initialValue);
  const [value, setValue] = useState<T>(initialValue);
  //...

  return [value, renderSelectInput] as SelectReturnType<T, typeof renderSelectInput>;
};

T๋Š” SingleSelect ๋˜๋Š” MultiSelect์— ํ• ๋‹น ๊ฐ€๋Šฅํ•œ ๊ฐ์ฒด๋“ค์˜ ์ง‘ํ•ฉ์ด๋‹ค. ์ธ์ž๋กœ ๋ฐ›์€ initialValue์™€ state์˜ ํƒ€์ž…์€ ๋‹น์—ฐํžˆ T์ด๊ฒ ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ปค์Šคํ…€ํ›…์€ SelectReturnType<T>๋ผ๋Š” ํƒ€์ž…์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

 

type SelectReturnType<T, RenderType> = T extends MultiSelect
  ? [MultiSelect, RenderType]
  : [SingleSelect, RenderType];

์—ฌ๊ธฐ์„œ ์กฐ๊ฑด๋ถ€ ํƒ€์ž…์ด ๋“ฑ์žฅํ•œ๋‹ค. T๊ฐ€ MultiSelect์— ํ• ๋‹น ๊ฐ€๋Šฅํ•˜๋ฉด, MultiSelect์— ๋งž๋Š” ๋ฆฌํ„ด ํƒ€์ž…์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

 

์กฐ๊ฑด๋ถ€ ํƒ€์ž…์€ ์ปดํŒŒ์ผ ํƒ€์ž„์— ํƒ€์ž…์„ ๊ฒฐ์ •ํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋œ๋‹ค. ํ•˜์ง€๋งŒ isMultiSelect ๊ฐ™์€ ํƒ€์ž… ๊ฐ€๋“œ๋Š” ๋Ÿฐํƒ€์ž„์—์„œ ์‹ค์ œ ๊ฐ’์— ๋”ฐ๋ผ ๋™์ž‘ํ•œ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์•„๊นŒ ์œ„์—์„œ ํƒ€์ž…๊ฐ€๋“œ์—์„œ ๋‚˜์˜จ ๊ฒฐ๊ณผ๋ฅผ ์กฐ๊ฑด์œผ๋กœ ํƒ€์ž…์„ ๋‹จ์–ธํ•ด ๋ฐ˜ํ™˜ํ–ˆ์„ ๋• ์ œ๋Œ€๋กœ ์ถ”๋ก ์ด ๋˜์ง€ ์•Š๋Š”๊ฒƒ์ด๋ผ๊ณ  ๊ฒฐ๋ก ์„ ๋‚ด๋ ธ๋‹ค.

 

 

+ initialValue๋ฅผ ์ƒ๋žตํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด

const [rank, renderSelectRank] = useSelect(RANK_OPTIONS, []); //Multi
const [rank, renderSelectRank] = useSelect(RANK_OPTIONS, null); //Single

initialValue์˜ ํƒ€์ž…์„ ๋ณด๊ณ  select state์˜ ํƒ€์ž…์„ ๊ฒฐ์ •ํ•˜๊ณ  ์žˆ๋‹ค. SingleSelect์ด๊ณ  ์ดˆ๊ธฐ๊ฐ’์ด ์—†๋Š” ๊ฒฝ์šฐ์—๋„ ์œ„์™€ ๊ฐ™์ด null์„ ํ•„์ˆ˜์ ์œผ๋กœ ์ฃผ์–ด์•ผ ํ•œ๋‹ค. ๊ทธ๋Ÿด ๋•Œ initialValue(๋‘๋ฒˆ์งธ ์ธ์ž)๋ฅผ ์ƒ๋žตํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด initialValue์— ๋””ํดํŠธ ๊ฐ’ null์„ ๋„ฃ์–ด๋ณด์•˜๋‹ค.

 

const useSelectInput = <T extends SingleSelect | MultiSelect = SingleSelect>(
  options: Option[],
  initialValue: T = null as T,
) => {

์ด๋ ‡๊ฒŒ. initialValue๊ฐ€ null์ผ๋•Œ SingleSelect๊ฐ€ ๋‚˜์˜ฌ ์ˆ˜ ์žˆ๋„๋ก T์˜ ๋””ํดํŠธ๋„ ์ง€์ •ํ•ด์ฃผ์—ˆ๋‹ค.

 

๋“œ๋””์–ด ๋‚ด๊ฐ€ ์ƒ๊ฐํ•˜๋Š”๋Œ€๋กœ ํƒ€์ž… ์ถ”๋ก ์ด ๋˜๋Š”๊ฒƒ์„ ํ™•์ธํ•ด ๋ณผ ์ˆ˜ ์žˆ๋‹ค.