ํ๋ก ํธ์๋ ๊ฐ๋ฐ์ ํ๋ค๋ณด๋ฉด ํํ ๋ง์ฃผํ ์ ์๋ ์ํฉ์ด๋ค. 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์ ๋ํดํธ๋ ์ง์ ํด์ฃผ์๋ค.
๋๋์ด ๋ด๊ฐ ์๊ฐํ๋๋๋ก ํ์ ์ถ๋ก ์ด ๋๋๊ฒ์ ํ์ธํด ๋ณผ ์ ์๋ค.