reactjs - React props - กำลังประสบปัญหากับการแยกประเภทเชิงสหภาพ
ฉันมีคอมโพเนนต์สองตัวที่มี props คล้ายกัน แต่มีความแตกต่างที่สำคัญ หนึ่งในคอมโพเนนต์ที่เรียกว่า TabsWithState รับเพียง prop เดียวคือ tabs ซึ่งเป็นอาร์เรย์ของอ็อบเจ็กต์ที่มีรูปแบบดังนี้:
interface ItemWithState {
name: string;
active: boolean;
}
interface WithStateProps {
tabs: ItemWithState[];
};
ส่วนประกอบที่คล้ายกันอีกตัวหนึ่ง เรียกว่า TabsWithRouter ต้องการรูปแบบของรายการแตกต่างออกไป:
interface ItemWithRouter {
name: string;
path: string;
}
interface WithRouterProps {
tabs: ItemWithRouter[];
};
ฉันกำลังพยายามสร้างคอมโพเนนต์ทั่วไป Tabs ซึ่งจะครอบคลุมทั้งสองสถานการณ์ ฉันต้องการที่จะสามารถเขียนคอมโพเนนต์ <Tabs /> โดยหากส่งพร็อพ withRouter เข้ามา คุณสมบัติ tabs ต้องเป็นชนิด ItemWithRouter[] แต่ถ้าไม่ส่งพร็อพ withRouter เข้ามา มันต้องเป็นชนิด ItemWithState[] นอกจากนี้ หากส่ง withRouter เข้ามา Tabs ก็ควรยอมรับพร็อพ baseUrl ที่เป็นทางเลือกได้ด้วย
ฉันพยายามสร้างชนิดยูเนียนแบบจำแนกประเภท แบบนี้:
type WithStateProps = {
withRouter?: never;
baseUrl?: never;
tabs: ItemWithState[];
};
type WithRouterProps = {
withRouter: boolean;
baseUrl?: string;
tabs: ItemWithRouter[];
};
type TabsProps = WithStateProps | WithRouterProps;
ในคอมโพเนนต์ Tabs แบบทั่วไปของฉัน ฉันต้องการแสดง TabsWithRouter หากมี withRouter และแสดง TabsWithState หากไม่มี withRouter:
const Tabs = (props: TabsProps) => {
const { withRouter } = props;
if (withRouter) {
return <TabsWithRouter {...props} />;
}
return <TabsWithState {...props} />;
};
ตอนแรกฉันพยายามกำหนด TabsWithRouter และ TabsWithState ให้เป็นคอมโพเนนต์ฟังก์ชันที่รับ WithRouterProps และ WithStateProps ตามลำดับ:
const TabsWithRouter: React.FC<WithRouterProps> = (props: WithRouterProps) => { ... }
const TabsWithState: React.FC<WithStateProps> = (props: WithStateProps) => { ... }
แต่ฉันได้รับข้อผิดพลาด Types of property 'withRouter' are incompatible. Type 'undefined' is not assignable to type 'boolean'. ตามที่เห็นได้ใน ts playground นี้
ดังนั้นฉันจึงพยายามพิมพ์ TabsWithRouter และ TabsWithState เป็นการรับ TabsProps เป็น props ของพวกมัน:
const TabsWithRouter: React.FC<TabsProps> = (props: TabsProps) => {
const { tabs } = props;
console.log(tabs[0].path)
return null
}
const TabsWithState: React.FC<TabsProps> = (props: TabsProps) => {
const { tabs } = props;
console.log(tabs[0].active)
return null
}
แต่ในกรณีนี้ การพยายามเข้าถึง tabs[x].path หรือ tabs[x].active ทำให้เกิดข้อผิดพลาด Property 'active' does not exist on type 'ItemWithState | ItemWithRouter'. Property 'active' does not exist on type 'ItemWithRouter' ดังที่เห็นใน this ts playground
ที่น่าสนใจคือ ในทั้งสองกรณี เมื่อฉันพยายามที่จะ ใช้ คอมโพเนนต์จริงๆ prop ก็ทำงานได้ถูกต้อง ซึ่งสามารถเห็นได้จากตัวอย่างบางส่วนที่ด้านล่างของ playground ts แต่ละอัน
ผมรู้สึกว่าผมใกล้จะได้แล้ว แต่ผมกำลังมีปัญหาในการทำให้ประเภท union ที่มีการแยกชนิดทำงานตามที่ผมต้องการเพื่อให้ TypeScript หยุดเกิดข้อผิดพลาด ผมได้อ่านโพสต์หลาย ๆ อันที่ถามคำถามคล้าย ๆ กัน แต่ดูเหมือนผมจะนำไปใช้กับสิ่งที่ผิดพลาดในสถานการณ์ของผมไม่ได้
แก้ไข :
ตามคำขอ นี่คือไฟล์ tsconfig.json ของฉัน:
{
"extends": "./tsconfig.paths.json",
"compilerOptions": {
"baseUrl": "src",
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"types": [
"cypress",
"cypress-file-upload",
"jest"
],
"downlevelIteration": true,
"noFallthroughCasesInSwitch": true
},
"include": [
"src"
]
}
แก้ไข2
สนามเด็กเล่น TS นี้แสดงให้เห็นว่าโซลูชันของกัปตัน-โยซซาเรียนไม่ได้บังคับใช้ประเภทที่ฉันต้องการบังคับใช้กับคอมโพเนนต์ Tabs
คำตอบ #1
จะเป็นอย่างไรกับสิ่งที่เรียบง่ายแบบนี้... การให้ TypeScript เบาะแสโดยใช้ as WithRouterProps หรือ as WithStateProps ตามความเหมาะสม?
const Tabs = (props: TabsProps) => {
const { withRouter } = props;
if (withRouter !== undefined) {
return <TabsWithRouter {...props as WithRouterProps} />;
}
return <TabsWithState {...props as WithStateProps} />;
};
คำตอบ #2
การควบคุมการไหลของเงื่อนไขที่มีนามแฝงสามารถใช้ได้ตั้งแต่ [email เป็นต้นไป ดังนั้น การใช้ตัวแปร withRouter แบบ destructured จะไม่สามารถนำข้อมูลชนิดของตัวแปรมาใช้เมื่อถูกใช้ภายในคำสั่ง if ก่อน [email .
จากนั้นก็มีปัญหาเล็กน้อยในโค้ดของคุณ prop withRouter ของคุณมีประเภท boolean และที่นี่:
const Tabs = (props: TabsProps) => {
const { withRouter } = props;
if (withRouter) { // withRouter === true
return <TabsWithRouter {...props} />;
}
return <TabsWithState {...props} />;
};
คุณกำลังจำกัดชนิดของมันเป็น true ไม่ใช่ boolean (true | false)
อีกปัญหาหนึ่งคือประเภทแบบเลือกได้จะนำค่่า undefined เข้าสู่ union เสมอ ดังนั้นคุณต้องตรวจสอบกรณีดังกล่าวอย่างชัดเจน การตรวจสอบนี้จะตรวจสอบทั้งค่าที่ระบุชัดเจนว่า 'undefined' หรือ กรณีที่ !('withRouter' in props):
const Tabs = (props: TabsProps) => {
if (props.withRouter === undefined) {
return <TabsWithState {...props} />;
}
return <TabsWithRouter {...props} />;
};
ลิงก์สนามเด็กเล่น
ตั้งแต่ [email คุณสามารถใช้ withRouter แบบแยกโครงสร้างเป็นเงื่อนไขที่มีนามแฝงได้:
const Tabs = (props: TabsProps) => {
const { withRouter } = props
if (withRouter === undefined) {
return <TabsWithState {...props} />;
}
return <TabsWithRouter {...props} />;
};
ตรวจสอบสนามเด็กเล่น
