์๋ก
๊ธฐ๋ณธ์ ์ธ Form ์ปดํฌ๋ํธ๊ฐ ์๋๋ฐ, ์ด๋ฆ, ์ฃผ์, ํธ๋ํฐ ๋ฒํธ๋ฅผ input์ผ๋ก ๋ฐ์ ์ ์๊ณ , ์ ์ฅ๋ฒํผ์ ๋๋ฅด๋ฉด input ์ ๋ณด๊ฐ ์ ์ฅ๋๋ค.
์ด Form ์ปดํฌ๋ํธ๋ ์์ฑ์, ์์ ์ ์ฌ์ฌ์ฉํ ์ ์๋ ์ปดํฌ๋ํธ์ด๋ค.
์์ฑ์
๊ธฐ๋ณธ input ์ด๊ธฐ ์ํ๊ฐ์ด ๋น string์ ๊ฐ์ง๋ค.
์ ์ฅ ์ด๋ฒคํธ ๋ฐ์์ input ๋ฐ์ดํฐ ๊ฐ์ ๋ชจ์์ ์์ฑ์์ฒญ์ ๋ณด๋ด๋ ํจ์์ธ onCreateInfo๋ผ๋ ํจ์๋ฅผ ํธ์ถํ๋ค.
์์ ์
์์ ์ปดํฌ๋ํธ์์ ์ ๋ฌ ๋ฐ์ ๋ฐ์ดํฐ value๊ฐ์ ์ด๊ธฐ๊ฐ์ผ๋ก ๊ฐ์ง๋ค.
์ ์ฅ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ฉด input ๋ฐ์ดํฐ๋ฅผ ์๋ฒ์ ์์ ์์ฒญ์ ๋ณด๋ด๋ onEditInfo๋ผ๋ ํจ์๋ฅผ ํธ์ถํ๋ค.
interface Props {
data?: Data; //์์ ์์๋ง ์กด์ฌ
onCreateInfo?: (inputs: Data) => void;
onUpdateInfo?: (inputs: Data) => void;
}
export const InfoForm = ({ data, onCreateInfo, onUpdateInfo}: Props) => {
const initialFormValue: Data = {
name: '',
address: '',
phone: '',
};
const [formValue, setFormValue] = useState<Data>(
data
? {
name: data.name,
address: data.address,
phone: data.phone
}
: initialFormValue,
);
const handleSave = () => {
//data๊ฐ ์์ผ๋ฉด ์์ ํ๊ธฐ
if (data) {
onUpdateInfo?.(formValue);
}
// ์์ผ๋ฉด ์์ฑํ๊ธฐ
else {
onCreateInfo?.(formValue);
}
};
const handleChangeFormValue = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target; //name์ ์ธํ์์์ ๋ฐ์ธ๋ฉ์ํจ 'name' 'address' 'phone'
setFormValue({ ...formValue, [name]: value });
};
return (
<>
<TextField
name='name'
value={name}
onChange={handleChangeFormValue} />
<TextField
name='address'
value={address}
onChange={handleChangeFormValue} />
<TextField
name='phone'
value={phone}
onChange={handleChangeFormValue} />
<Button onClick={handleCancel}>
์ทจ์
</Button>
<Button onClick={handleSave}>
์ ์ฅ
</Button>
</>
);
}
์๋๋ Form ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ๋ ๋ถ๋ชจ ์ปดํฌ๋ํธ์ด๋ค.
์์ฑ ์ปดํฌ๋ํธ
export const CreationContainer = () => {
const [postSiteInfo] = usePostSiteInfoMutation();
const handleCreateInfo = (inputs: Data) => {
postSiteInfo(inputs)
.unwrap()
.then((res: Data) => {
//detail view ํ์ด์ง๋ก ์ด๋
})
.catch((err: ErrorReturnType) => alert(err.data.error.message));
};
return (
<>
<Typography variant='h5'>
์์ฑ
</Typography>
<InfoForm
onCreateInfo={handleCreateInfo}></InfoForm>
</>
);
};
์์ ์ปดํฌ๋ํธ
export const EditContainer = () => {
const { data, isSuccess } = useGetSiteInfoQuery(siteId);
const [updateSiteInfo] = useUpdateSiteInfoMutation();
const handleUpdateInfo = (inputs: Data) => {
updateSiteInfo({ id: siteId, body: inputs })
.unwrap()
.then((res) => {
alert('์ ๋ณด๊ฐ ์์ ๋์์ต๋๋ค');
})
.catch((err: ErrorReturnType) => alert(err.data.error.message));
};
return (
<>
<Typography variant='h5'>
์์
</Typography>
<InfoForm
data={data}
onUpdateInfo={handleUpdateInfo}}></InfoForm>
</>
);
};
๊ธฐ์กด ๋ด๊ฐ ์ฝ๋๋ฅผ ์งค๋ ์๊ฐํ๋ ๋ก์ง์ ์ด๋ ๋ค.
์์ฑ, ์์ ์ ๊ณตํต์ผ๋ก ์ฌ์ฉํ ์ ์๋ Form ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ๋๋ฐ ์์ฑ์์๋ onCreateInfo๋ผ๋ props๋ง ์ ๋ฌํด์ฃผ๋ฉด ๋๊ณ , ์์ ์์๋ ์๋์ ๊ฐ์ data ๊ฐ์ฒด์ onEditInfo๋ผ๋ props๋ฅผ ์ ๋ฌํด์ฃผ๋ฉด ๋๋ค.
{
name: '์๋ ๊ฐ์ง๊ณ ์๋ ์ด๋ฆ',
address: '์ฃผ์',
phone: 'ํฐ๋ฒํธ'
}
์ด๋ ๊ฒ ์ฝ๋๋ฅผ ์์ฑํ๋ค๋ณด๋ ์์ ๊ฐ์ ์กฐ๊ฑด์ผ๋ก typescript๋ก ์ฝ๋๋ฅผ ์ง๋ ค๋ฉด Form ์ปดํฌ๋ํธ๋ ์๋์ ๊ฐ์ด ๋ชจ๋ ํ๋๊ฐ optional์ด ๋์ด์ผ ์๋ํ์๋ค.
interface Props {
data?: Data; //์์ ์์๋ง ์กด์ฌ
onCreateInfo?: (inputs: Data) => void;
onEditInfo?: (inputs: Data) => void;
}
ํ์ง๋ง ๋ชจ๋ ์ปดํฌ๋ํธ๊ฐ optional์ธ props๋ฅผ ๊ฐ์ง๋ ์ปดํฌ๋ํธ๋ ๊ณผ์ฐ ๊ด์ฐฎ์๊ฑธ๊น?
์ด ์ฝ๋์ ๋ฌธ์ ๋ ์๋์ ๊ฐ์๋ค.
- ๋ชจ๋ props๊ฐ optional์ด๋ผ Form ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ๋ ์ ์ฅ์์๋ ์์ฑํ ๋ ํ์ํ props์ ์์ ํ ๋ ํ์ํ props๊ฐ ์ด๋ค๊ฑด์ง ์๊ธฐ ํ๋ค๋ค.
- ๋ง์ฝ Form ์ปดํฌ๋ํธ ์ฌ์ฉ์ ์๋ฌด๋ฐ props๋ฅผ ์ ๋ฌํ์ง ์๊ฑฐ๋, ์์ฑํ ๋ data๋ฅผ props๋ก ์ ๋ฌํ๋ค๋ฉด?
์ปดํฌ๋ํธ๊ฐ ์๋ํ๋ ๋ฐ์ ๋ค๋ฅด๊ฒ ๋์ํ๋ค.
๋ฐ๋ผ์, ์์ ๊ฐ์ ๋ฌธ์ ๋ฅผ ๋ง๊ธฐ ์ํด ํด๊ฒฐ์ฑ ์ ๊ณ ๋ฏผํด์ผ ํ๋ค.
- ์์ ์์๋ data, onUpdateInfo ๋ง ์ ๋ฌํ๊ธฐ
- ์์ฑ์์๋ onCreateInfo ๋ง ์ ๋ฌํ๊ธฐ
๊ธ์์ผ๋ถํฐ ์ฃผ๋ง๊น์ง ๊ณ์ ๋๋ฅผ ๊ณ ๋ฏผ์ ๋น ๋จ๋ฆฐ ๋ฌธ์ ์๋๋ฐ, ๊ณ ๋ฏผ ๋์ discriminated unions ํ์ ์ผ๋ก ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์๋ค.
Discriminated unions ๊ตฌ๋ณ๋ ์ ๋์จ
union (ํฉ์งํฉ) ํ์ ์ผ๋ก A, B ํ์ ๋์ ํฉ์น ๊ฒฐ๊ณผ๋ก A, B ํ์ ๋ชจ๋ ์ฌ์ฉํ ์ ์๋๋ฐ, ์ด ๋ ํ์ ์ด ์๋ก ๊ตฌ๋ณ๋ ์ ์๋ ๊ณตํต๋ ํ๋กํผํฐ(ํ๋ณ์/๊ตฌ๋ณ์)๋ฅผ ๊ฐ์ง๊ณ ์๋ค. ๋ฐ๋ผ์ ๊ทธ ๊ณตํต์ ์ธ ํ๋กํผํฐ๋ก A, B ํ์ ์ ์ฝ๊ฒ ๊ตฌ๋ณํ ์ ์๊ฒ ํ๋ค.
type ConditionalProps =
| {
type: 'edit';
data: Data;
onUpdateInfo: (inputs: Data) => void;
}
| {
type: 'create';
onCreateInfo: (inputs: Data) => void;
};
์ฌ๊ธฐ์๋ type ์ด๋ผ๋ ์์ฑ ๊ฐ์ผ๋ก edit์ ํ์ํ props์ create์ ํ์ํ props๋ฅผ ๊ตฌ๋ถํ๋ค.
์ฆ, Conditional Props๋ ๊ฒฐ๊ตญ Union ํ์ ์ด๋ผ ์์ ๋ช ์๋ ๋ชจ๋ ์์ฑ๊ฐ type, data, onUpdateInfo, onCreatInfo ์ ๋ค ์ฌ์ฉ ๊ฐ๋ฅํ๋ค. ํ์ง๋ง ์ฐ๋ฆฌ๊ฐ ์ํ๋ ๊ฑด ๊ฐ type ์ ๋ฐ๋ผ ์ฌ์ฉํ ์ ์๋ ์์ฑ๊ฐ์ ๋๋์ด ์ฃผ๋ ๊ฒ์ด๋ค.
๋ฐ๋ผ์ ์๋ก๋ฅผ ๊ตฌ๋ถํ ์ ์๋ ๊ณตํต์ ์ธ ํ๋กํผํฐ์ธ type๋ฅผ ์ฃผ์ด edit ์ ํ์ํ ์์ฑ๊ฐ๊ณผ create์ ํ์ํ ์์ฑ๊ฐ์ ๊ตฌ๋ถํด์ค๋ค.
export const InfoForm = (props: ConditionalProps) => {
const initialFormValue: Data = {
name: '',
address: '',
phone: '',
};
const [formValue, setFormValue] = useState<Data>(
props.type === 'edit' //<----- props.type์ผ๋ก ๊ตฌ๋ถ
? {
name: props.data.name,
address: props.data.address,
phone: props.data.phone
}
: initialFormValue,
);
const handleSave = () => {
//props.type๊ฐ edit ์ด๋ฉด ์์ ํ๊ธฐ
if (props.type === 'edit') {
props.onUpdateInfo(formValue);
}
else {
props.onCreateInfo(formValue);
}
};
//..์ดํ ์ฝ๋๋ ๋งจ ์ฒ์ ์์ ์ฝ๋์ ๋์ผ
}
์ฌ์ฉํ ๋๋ type์ narrowing downํด์ฃผ์ด props.type๊ฐ 'edit'์ธ ๊ฒฝ์ฐ ์ธ ์ ์๋ ํ๋กํผํฐ์ธ data ์ onUpdateInfo ๋ฅผ ํ์ฉํด์ ๋ก์ง์ ์์ฑํ๊ณ , ๊ทธ ์ด์ธ ์์ฑ์์๋ onCreateInfo ํ๋กญ์ค๋ฅผ ํ์ฉํด์ ์์ฑ์ ํ์ํ ํจ์๋ฅผ ํธ์ถํ๋ค.
์ฌ๊ธฐ์ ์ ๋จน์๋ ๋ถ๋ถ์ด ์๋๋ฐ, ์ต๊ด์ฒ๋ผ ์๋์ ๊ฐ์ด props๋ฅผ ๊ตฌ์กฐ ๋ถํด ํ ๋นํ๋๋ ..
export const InfoForm = (props: ConditionalProps) => {
const { type, data, onUpdateInfo, onCreateInfo } = props
Property 'data' does not exist on type 'ConditionalProps'.ts(2339)
์ด๋ ๊ฒ ts ์๋ฌ๊ฐ ๋ฌ๋ค.
discriminated unions๋ฅผ ์ด๋ฐ ์์ ์ฒ๋ผ ์ฌ์ฉํ ๋์๋ props๋ฅผ ํ๋ณ์์ ๋ฐ๋ผ ๊ตฌ๋ณ๋๊ฒ ์ฌ์ฉํ๋๋ก ํ์ ์ ์ขํ๋ ๊ฒ์ด๋ฏ๋ก ๋ชจ๋ ํ๋กํผํฐ๋ฅผ ๊ตฌ์กฐ๋ถํดํ ๋นํ ์๋ ์๋ ๋ฏํ๋ค.
๋ฐ๋ผ์ ๊ตฌ์กฐ๋ถํดํ ๋นํ์ง ์๊ณ ๊ทธ๋๋ก props ๋ณ์๋ฅผ ๊ฐ์ง๊ณ ์์ ์ฌ์ฉํ๋ฉด ๋๋ค.
๊ฒฐ๋ก
๊ตฌ๋ณ๋ ์ ๋์จ์ ์ฌ์ฉํด์ Form ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ๋ฉด, ์ด์ ์์ฑ์ปดํฌ๋ํธ์์ ์์ ํ ๋ ์ฐ์ด๋ data props๋ฅผ ์ฌ์ฉํ๋ ค๊ณ ํ๋ฉด IDE์์ ์๋ฌ๋ฅผ ๋ด์ค๋ค.
๋ง์ฐฌ๊ฐ์ง๋ก? ์์ ์ปดํฌ๋ํธ์์ data props๊ฐ ๋น ์ง๋ค๋ฉด ์๋ฌ๋ฅผ ๋ด์ฃผ๊ณ , ์์ฑ์ ํ์ํ onCreateInfo๋ฅผ props๋ก ์ฌ์ฉํ๋ค๋ฉด ๋ง์ฐฌ๊ฐ์ง๋ก ์๋ฌ๋ฅผ ๋ด์ค๋ค.
์ด์ ํ์ํ ๋ ์ฌ์ฉํ ์ ์๋ type์ผ๋ก narrowing down ํด์ค์ผ๋ก์จ ๋ ์์ ์ ์ธ ํ๋ก๊ทธ๋๋ฐ์ ํ ์ ์๋ค. ๋ ๋์ ์ฝ๋๊ฐ ๋์๋ค.
๐๐ฝ๐๐ฝ๐๐ฝ
References
https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions