ํ์ผ ์ ๋ก๋ ์ปดํฌ๋ํธ์
target๊ฐ์ ๋๊ฒจ์ ์ ๋ก๋ API ์์ฒญ ์targetType์ผ๋ก ์ฌ์ฉํ๋ ค๊ณ ํ๋ค.
๋ถ๋ชจ ์ปดํฌ๋ํธ์์๋ ์๋์ฒ๋ผfile-input์ปดํฌ๋ํธ์targetprop์ ์ถ๊ฐ๋ก ๋๊ธฐ๊ณ ์์๋ค.
<file-input
v-model="salesOrderDetail.fileList"
:mode="'edit'"
:target="'ORDER'"
/>
๊ณตํต ์ปดํฌ๋ํธ์ธ file-input.vue์์๋ props๋ฅผ ์ด๋ ๊ฒ ์ ์ธํ๋ค.
const props = withDefaults(defineProps<IFileUploadProps>(), {
module: "",
fileCategory: "",
target: "",
mode: "edit",
modelValue: () => [],
clearable: false,
multiple: true,
accept: ".ppt, .pdf, .hwp, .docx, .xls, .xlsx, .png, .jpg, .jpeg",
maxSize: 10,
error: false,
errorMessage: "",
hintMessage: "",
placeholder: "ํ์ผ์ฒจ๋ถ"
});
๊ทธ๋ฆฌ๊ณ ์ ๋ก๋ ์์ฒญ์ ๋ณด๋ผ ๋ ์ด๋ ๊ฒ ์ฌ์ฉํ๊ณ ์์๋ค.
await apiClient.file<IFileResponse>("/files/upload", {
moduleCode: props.module,
fileType: "FILE",
targetType: props.target ?? null,
file
});
๊ทธ๋ฐ๋ฐ ์ด์ํ๊ฒ๋ props.target ๊ฐ์ด ๋ณด์ด์ง ์์๋ค.
โ ์ฒ์์๋ ๋ถ๋ชจ์์ ๊ฐ์ ์๋ชป ๋๊ธด ์ค ์์๋ค.
๊ทธ๋์ ํ
์คํธ์ฉ์ผ๋ก fileCategory๋ ์ถ๊ฐํด์ ๋๊ฒจ๋ดค๋ค. โ
<file-input
v-model="salesOrderDetail.fileList"
:mode="'view'"
:target="'ORDER'"
:fileCategory="'abc'"
/>
ํ์ง๋ง ์ฌ์ ํ ์ํ๋ ๊ฐ์ด props์์ ํ์ธ๋์ง ์์๋ค.
์ฝ์๋ก ์๋ฌด๋ฆฌ ์ฐ์ด๋ด๋ ๋ณด์ด์ง ์๊ธธ๋ ์ผ๋จ ์์์ v-bind="attrs`๋ฅผ ๊ฐ๊ฐ ํ์ธํ๋ค.
(์ธ์ ๋ ํญ์ ํน์๊ฐ ๋ฌธ์ ์ง..?)
const attrs = useAttrs();
console.log("props.target:", props.target);
console.log("attrs.target:", attrs.target);
console.log("attrs:", attrs);
๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ์๋ค.
props.target: undefined
attrs.target: ORDER
attrs: Proxy(Object) { target: 'ORDER' }
์ฌ๊ธฐ์ ์ค์ํ ์ ์ ๋ถ๋ชจ์์ ๊ฐ์ด ์ ๋์ด์จ ๊ฒ ์๋๋ผ๋ ๊ฒ์ด๋ค.
๋ถ๋ชจ๊ฐ ๋๊ธด target="ORDER"๋ ์ค์ ๋ก ์์๊น์ง ๋์ฐฉํ๋ค.
๋ค๋ง Vue๊ฐ ์ด ๊ฐ์ props๋ก ์ธ์ํ์ง ๋ชปํด์ $attrs๋ก ๋๊ฒจ๋ฒ๋ฆฐ ๊ฒ์ด๋ค.
์ฆ ๋ฌธ์ ๋ ๊ณตํต ์ปดํฌ๋ํธ์ defineProps ์ฒ๋ฆฌ ์ชฝ์ ์์๋ค.
์ฒ์์๋ IFileUploadProps ํ์
์ target์ด ์๋ ์ค ์์๋ค.
ํ์ง๋ง ํ์
ํ์ผ์ ํ์ธํด๋ณด๋ target์ ๋ถ๋ช
ํ ์กด์ฌํ๋ค.
interface IBaseUploadProps<T extends IFileResponse = IFileResponse> {
modelValue: T[];
mode?: "edit" | "view";
required?: boolean;
hint?: string;
hintMessage?: string;
readonly?: boolean;
disabled?: boolean;
persistentHint?: boolean;
clearable?: boolean;
multiple?: boolean;
maxSize?: number;
module: string;
fileCategory?: string;
target?: string;
}
export interface IFileUploadProps extends IBaseUploadProps<ISaveFile> {
placeholder?: string;
accept?: string;
target?: string;
}
TypeScript ๊ธฐ์ค์ผ๋ก๋ ๋ฌธ์ ๊ฐ ์์ด ๋ณด์๋ค.
IFileUploadProps๋ IBaseUploadProps<ISaveFile>๋ฅผ ์์ํ๊ณ ์๊ณ ,
target?: string๋ ์กด์ฌํ๋ค.
๊ทธ๋ฐ๋ฐ ๋ฐํ์์์๋ props.target์ด ์๋๋ผ $attrs.target์ผ๋ก ๋น ์ง๊ณ ์์๋ค.
์์ธ์ defineProps<IFileUploadProps>()์ ๋๊ธด ํ์
๊ตฌ์กฐ๊ฐ Vue SFC ์ปดํ์ผ๋ฌ๊ฐ ๋ฐํ์ props๋ก ์์ ์ ์ผ๋ก ๋ณํํ๊ธฐ์๋ ๋ณต์กํ๋ ๊ฒ์ด๋ค.
(๋ญ๊ฐ doc์ ์์ด๋ณด์ฌ์ ์ด์ฉํ๋ฉด ์ ์ด๋ฐ๊ฒ ํฐ์ง๋๊ฑฐ์ฃ ..?)
๋ฌธ์ ๊ฐ ๋ ๊ตฌ์กฐ๋ ๋ค์ ์กฐํฉ์ด์๋ค.
interface IBaseUploadProps<T extends IFileResponse = IFileResponse> {
...
target?: string;
}
export interface IFileUploadProps extends IBaseUploadProps<ISaveFile> {
...
target?: string;
}
์ฆ,
์ด ์กฐํฉ์ด ๋ค์ด๊ฐ ์์๋ค.
TypeScript๋ ์ด ํ์ ์ ์ ์์ ์ผ๋ก ์ดํดํ๋ค.
ํ์ง๋ง Vue์ defineProps<T>()๋ ์์ ํ TypeScript ํ์
์ฒด์ปค์ฒ๋ผ ๋์ํ์ง ์๋๋ค.
Vue SFC ์ปดํ์ผ๋ฌ๋ ํ์
์ ๋ณด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ฐํ์ prop ์ต์
์ ๋ง๋ค์ด์ผ ํ๋๋ฐ, ์ด ๊ณผ์ ์ ์ ํ์ ์ธ AST ๋ถ์์ ๊ฐ๊น๋ค.
๊ทธ๋์ TypeScript ์
์ฅ์์๋ target์ด ์กด์ฌํ์ง๋ง, Vue ์ปดํ์ผ๋ฌ๊ฐ ๋ฐํ์ props ๋ชฉ๋ก์ผ๋ก ๋ณํํ๋ ๊ณผ์ ์์ target์ ๋๋ฝํ ์ ์๋ค.
๊ทธ ๊ฒฐ๊ณผ Vue๋ ๋ถ๋ชจ์์ ์ ๋ฌ๋ฐ์ target์ ์ด๋ ๊ฒ ํ๋จํ๋ค.
target? ๋ด๊ฐ ์๋ prop ๋ชฉ๋ก์ ์๋ค.
๊ทธ๋ผ props๊ฐ ์๋๋ผ attrs๋ก ๋๊ธด๋ค.
๊ทธ๋์ ์ต์ข ๊ฒฐ๊ณผ๊ฐ ์ด๋ ๊ฒ ๋ ๊ฒ์ด๋ค.
props.target // undefined
attrs.target // "ORDER"
๊ฐ์ฅ ํ์คํ ํด๊ฒฐ๋ฒ์ defineProps์ ์ฌ์ฉํ๋ ํ์
์ flatํ๊ฒ ๋ง๋๋ ๊ฒ์ด๋ค.
์ฆ extends + generic ๊ตฌ์กฐ๋ฅผ ํผํ๊ณ , ํด๋น ์ปดํฌ๋ํธ์์ ์ฌ์ฉํ๋ props๋ฅผ ์ง์ ๋ช
์ํ๋ค.
interface FileInputProps {
modelValue: ISaveFile[];
mode?: "edit" | "view";
required?: boolean;
hint?: string;
hintMessage?: string;
readonly?: boolean;
disabled?: boolean;
persistentHint?: boolean;
clearable?: boolean;
multiple?: boolean;
maxSize?: number;
module: string;
fileCategory?: string;
target?: string;
placeholder?: string;
accept?: string;
error?: boolean;
errorMessage?: string;
}
const props = withDefaults(defineProps<FileInputProps>(), {
module: "",
fileCategory: "",
target: "",
mode: "edit",
modelValue: () => [],
clearable: false,
multiple: true,
accept: ".ppt, .pdf, .hwp, .docx, .xls, .xlsx, .png, .jpg, .jpeg",
maxSize: 10,
error: false,
errorMessage: "",
hintMessage: "",
placeholder: "ํ์ผ์ฒจ๋ถ"
});
์ด๋ ๊ฒ ๋ณ๊ฒฝํ๋ฉด Vue๊ฐ target์ ์ ์์ ์ผ๋ก prop์ผ๋ก ์ธ์ํ๋ค.
์ดํ API ์์ฒญ์์๋ ์ ์์ ์ผ๋ก ์ฌ์ฉํ ์ ์๋ค.
await apiClient.file<IFileResponse>("/files/upload", {
moduleCode: props.module,
fileType: "FILE",
targetType: props.target || null,
fileCategory: props.fileCategory || null,
file
});
์ฌ๊ธฐ์ ?? null๋ณด๋ค || null์ ์ฌ์ฉํ ์ด์ ๋ ๊ธฐ๋ณธ๊ฐ์ด ๋น ๋ฌธ์์ด์ผ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
props.target ?? null
์ด๋ ๊ฒ ์ฐ๋ฉด props.target์ด ""์ผ ๋ ๊ทธ๋๋ก ๋น ๋ฌธ์์ด์ด ๋ค์ด๊ฐ๋ค.
๋ฐ๋ฉด,
props.target || null
์ด๋ ๊ฒ ์ฐ๋ฉด ๋น ๋ฌธ์์ด์ผ ๊ฒฝ์ฐ null๋ก ์ฒ๋ฆฌํ ์ ์๋ค.
๊ณตํต ํ์ ์ ๊ผญ ์ฌ์ฌ์ฉํ๊ณ ์ถ๋ค๋ฉด generic์ ์ ๊ฑฐํ๋ ๊ฒ๋ ๋ฐฉ๋ฒ์ด๋ค.
export interface IBaseUploadProps {
modelValue: ISaveFile[];
mode?: "edit" | "view";
required?: boolean;
hint?: string;
hintMessage?: string;
readonly?: boolean;
disabled?: boolean;
persistentHint?: boolean;
clearable?: boolean;
multiple?: boolean;
maxSize?: number;
module: string;
fileCategory?: string;
target?: string;
}
export interface IFileUploadProps extends IBaseUploadProps {
placeholder?: string;
accept?: string;
error?: boolean;
errorMessage?: string;
}
๋ค๋ง ๊ฐ์ฅ ์์ ํ ๋ฐฉ๋ฒ์ defineProps์ ๋ค์ด๊ฐ๋ ํ์
๋งํผ์ ์ต๋ํ ๋จ์ํ๊ณ ๋ช
ํํ๊ฒ ์ ์งํ๋ ๊ฒ์ด๋ค.
๋น์ฅ ๊ตฌ์กฐ๋ฅผ ๋ฐ๊พธ๊ธฐ ์ด๋ ต๋ค๋ฉด $attrs์์ ๊ฐ์ ๊บผ๋ด ์ฌ์ฉํ ์๋ ์๋ค.
const attrs = useAttrs();
const uploadTarget = computed(() => {
return props.target || String(attrs.target || "");
});
๊ทธ๋ฆฌ๊ณ ์ ๋ก๋ ์์ฒญ์์๋ ์ด๋ ๊ฒ ์ฌ์ฉํ๋ค.
await apiClient.file<IFileResponse>("/files/upload", {
moduleCode: props.module,
fileType: "FILE",
targetType: uploadTarget.value || null,
file
});
ํ์ง๋ง ์ด ๋ฐฉ๋ฒ์ ๊ทผ๋ณธ์ ์ธ ํด๊ฒฐ์ฑ ์ ์๋๋ค.
target์ ๋ช
ํํ ์ปดํฌ๋ํธ์ prop์ผ๋ก ์ฌ์ฉ๋๋ ๊ฐ์ด๊ธฐ ๋๋ฌธ์ $attrs์์ ๊บผ๋ด ์ฐ๋ ๊ฒ๋ณด๋ค defineProps์์ ์ ์์ ์ผ๋ก ์ธ์๋๋๋ก ๊ณ ์น๋ ๊ฒ์ด ๋ง๋ค.
defineProps<T>()์ ํ์
์ ๋ฃ์ ๋๋ TypeScript์์ ๋ฌธ์ ๊ฐ ์๋ค๊ณ ํด์ Vue ๋ฐํ์์์๋ ํญ์ ์ ์ ๋์ํ๋ค๊ณ ๋ฏฟ์ผ๋ฉด ์ ๋๋ค.
ํนํ ์๋์ ๊ฐ์ ํ์ ๊ตฌ์กฐ๋ ์กฐ์ฌํด์ผ ํ๋ค.
interface BaseProps<T> {
value: T;
}
interface ChildProps extends BaseProps<SomeType> {
target?: string;
}
์ด๋ฐ ๊ตฌ์กฐ๋ TypeScript์์๋ ์ ์์ด์ด๋ Vue SFC ์ปดํ์ผ๋ฌ๊ฐ ๋ฐํ์ props๋ก ๋ณํํ๋ ๊ณผ์ ์์ ์ผ๋ถ prop์ ๋๋ฝํ ์ ์๋ค.
์์ผ๋ก๋ ๋ค์ ๊ธฐ์ค์ ์งํค๋ ๊ฒ์ด ์ข๋ค.
defineProps์ ๋ฃ๋ ํ์
์ ์ต๋ํ flatํ๊ฒ ์์ฑํ๊ธฐinterface ComponentProps {
modelValue: SomeType[];
target?: string;
mode?: "edit" | "view";
}
$attrs๋ฅผ ๋ฐ๋ก ํ์ธํ๊ธฐconst attrs = useAttrs();
console.log("props.target:", props.target);
console.log("attrs.target:", attrs.target);
๋ง์ฝ props์๋ ์๊ณ $attrs์ ์๋ค๋ฉด ๋ถ๋ชจ ์ ๋ฌ ๋ฌธ์ ๋ผ๊ธฐ๋ณด๋ค ์์์ prop ์ ์ธ ๋ฌธ์ ์ผ ๊ฐ๋ฅ์ฑ์ด ๋๋ค.
console.log(props)๋ง ๋ฏฟ์ง ์๊ธฐVue์ props๋ reactive proxy์ด๊ธฐ ๋๋ฌธ์ ์ฝ์์์ ํผ์ณ ๋ณด๋ ๊ฒ๋ง์ผ๋ก๋ ํท๊ฐ๋ฆด ์ ์๋ค.
๊ฐ๋ฅํ๋ฉด ๊ฐ๋ณ ๊ฐ์ ์ง์ ์ฐ๋ ๊ฒ์ด ์ข๋ค.
console.log("target:", props.target);
console.log("module:", props.module);
console.log("mode:", props.mode);
๊ณตํต ํ์
์ ์ฌ์ฌ์ฉํ๋ฉด ์ฝ๋๊ฐ ๊น๋ํด ๋ณด์ด์ง๋ง, defineProps์์๋ ์คํ๋ ค ๋ฐํ์ ๋ณํ ๋ฌธ์ ๊ฐ ์๊ธธ ์ ์๋ค.
ํนํ ๊ณตํต ํ์ ์ด generic, extends, imported type์ ํฌํจํ๊ณ ์๋ค๋ฉด props ์ ์ธ์๋ flat type์ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ ์์ ํ๋ค.
์ด๋ฒ ๋ฌธ์ ๋ ๋ถ๋ชจ ์ปดํฌ๋ํธ์์ prop์ ์๋ชป ๋๊ธด ๋ฌธ์ ๊ฐ ์๋์๋ค.
๋ถ๋ชจ์์ ๋๊ธด ๊ฐ์ ์์๊น์ง ์ ์์ ์ผ๋ก ๋์ฐฉํ๋ค.
๋ค๋ง Vue๊ฐ ํด๋น ๊ฐ์ prop์ผ๋ก ์ธ์ํ์ง ๋ชปํด์ $attrs๋ก ๋๊ฒจ๋ฒ๋ฆฐ ๊ฒ์ด ๋ฌธ์ ์๋ค.
ํต์ฌ ์์ธ์ defineProps<IFileUploadProps>()์ ์ฌ์ฉํ ํ์
์ด extends + generic ๊ตฌ์กฐ์๊ณ , Vue SFC ์ปดํ์ผ๋ฌ๊ฐ ์ด๋ฅผ ๋ฐํ์ props๋ก ์์ ํ ๋ณํํ์ง ๋ชปํ ๊ฒ์ด์๋ค.
ํด๊ฒฐ์ defineProps์ ์ฌ์ฉํ๋ ํ์
์ flatํ๊ฒ ๋ฐ๊พธ๋ ๊ฒ.
interface FileInputProps {
modelValue: ISaveFile[];
module: string;
target?: string;
fileCategory?: string;
mode?: "edit" | "view";
}
์ด๋ ๊ฒ ๋จ์ํ๊ฒ ์ ์ธํ๋ฉด props.target์ผ๋ก ์ ์ ์ ๊ทผํ ์ ์๋ค.
์ด๋ฒ ์ผ์ ํตํด ์๊ฒ ๋ ์ ์ ํ๋๋ค.
TypeScript๊ฐ ์ดํดํ๋ ํ์ ๊ณผ Vue๊ฐ ๋ฐํ์ props๋ก ๋ณํํ ์ ์๋ ํ์ ์ ๋ค๋ฅผ ์ ์๋ค.