[英]TypeScript - how should I type these React components?
Suppose I have two items: A and B. The items have different options.假设我有两个项目:A 和 B。这些项目有不同的选项。 I want to create a form that allows me to select one of the two items and to set the options for that item.
我想创建一个表单,允许我选择两个项目之一并设置该项目的选项。 When I'm done setting the options, I want to have a handler that does something with the saved options.
完成选项设置后,我想要一个处理程序来处理保存的选项。
I'm having trouble typing it properly because the options of A and B don't intersect, so when I pass the options into the specific item form component it gives me a TypeScript error.我无法正确输入它,因为 A 和 B 的选项不相交,所以当我将选项传递到特定的项目表单组件时,它给了我一个 TypeScript 错误。
How would I go about fixing this or better yet how would I modify my approach to this problem?我将如何解决这个问题或更好,但我将如何修改我解决这个问题的方法?
I've created a sample app here: https://codesandbox.io/s/patient-bush-4emyo?file=/src/ItemCreator.tsx我在这里创建了一个示例应用程序: https : //codesandbox.io/s/patient-bush-4emyo?file=/src/ItemCreator.tsx
The error "Type 'Options' is not assignable to type 'OptionsA & OptionsB'."错误“类型 'Options' 不可分配给类型 'OptionsA & OptionsB'。” occurs in this line.
发生在这一行。
<Form options={options} setOptions={setOptions} />
This is the main form creator:这是主要的表单创建者:
import React, { useState, FC, useEffect } from "react";
import { OptionsAForm, OptionsA, defaultOptionsA } from "./OptionsAForm";
import { OptionsBForm, OptionsB, defaultOptionsB } from "./OptionsBForm";
type Options = OptionsA | OptionsB;
export const ItemCreator: FC = () => {
const [item, setItem] = useState<string>("A");
const [options, setOptions] = useState<Options>(defaultOptionsA);
useEffect(() => {
if (item === "A") {
setOptions(defaultOptionsA);
} else if (item === "B") {
setOptions(defaultOptionsB);
}
}, [item]);
let Form;
if (item === "A") {
Form = OptionsAForm;
} else if (item === "B") {
Form = OptionsBForm;
}
const handleSubmit = () => {
// do stuff with Options here
console.log(options);
};
return (
<div style={{ display: "flex", flexDirection: "column" }}>
<select value={item} onChange={(e) => setItem(e.target.value)}>
<option value="" disabled>
Select an item
</option>
<option value="A">A</option>
<option value="B">B</option>
</select>
{Form && options != null && (
<Form options={options} setOptions={setOptions} />
)}
<button onClick={handleSubmit}>Submit</button>
</div>
);
};
These are the forms for the options这些是选项的表格
import React, { FC } from "react";
export const defaultOptionsA: OptionsA = {
name: "",
color: ""
};
export interface OptionsA {
name: string;
color: string;
}
interface FormProps {
setOptions: (options: OptionsA) => void;
options: OptionsA;
}
export const OptionsAForm: FC<FormProps> = ({ options, setOptions }) => {
return (
<>
<input
placeholder="Name"
value={options.name}
onChange={(e) => setOptions({ ...options, name: e.target.value })}
/>
<input
placeholder="Color"
value={options.color}
onChange={(e) => setOptions({ ...options, color: e.target.value })}
/>
</>
);
};
import React, { FC } from "react";
export const defaultOptionsB: OptionsB = {
name: "",
weight: 0
};
export interface OptionsB {
name: string;
weight: number;
}
interface FormProps {
setOptions: (options: OptionsB) => void;
options: OptionsB;
}
export const OptionsBForm: FC<FormProps> = ({ options, setOptions }) => {
return (
<>
<input
placeholder="Name"
value={options.name}
onChange={(e) => setOptions({ ...options, name: e.target.value })}
/>
<input
type="number"
placeholder="weight"
value={options.weight}
onChange={(e) =>
setOptions({ ...options, weight: parseInt(e.target.value, 10) })
}
/>
</>
);
};
Updated ItemCreator.tsx
file with added generic type Option
使用添加的通用类型
Option
更新了ItemCreator.tsx
文件
import React, { useState, FC, useEffect } from "react";
import { OptionsAForm, OptionsA, defaultOptionsA } from "./OptionsAForm";
import { OptionsBForm, OptionsB, defaultOptionsB } from "./OptionsBForm";
type OptionTypes = "A" | "B";
interface OptionsMap {
A: OptionsA;
B: OptionsB;
}
type Options<T = OptionTypes> = T extends OptionTypes ? OptionsMap[T] : null;
const isObjectEmpty = (obj: Record<string, any>) =>
Object.keys(obj).length === 0;
export const ItemCreator: FC = () => {
const [item, setItem] = useState<OptionTypes | "">("");
const [options, setOptions] = useState<Options | {}>({});
useEffect(() => {
if (item === "A") {
setOptions(defaultOptionsA);
} else if (item === "B") {
setOptions(defaultOptionsB);
}
}, [item]);
const handleSubmit = () => {
// do stuff with Options here
console.log(options);
};
return (
<div style={{ display: "flex", flexDirection: "column" }}>
<select
value={item}
onChange={(e) => setItem(e.target.value as OptionTypes)}
>
<option value="" disabled>
Select an item
</option>
<option value="A">A</option>
<option value="B">B</option>
</select>
{item === "A" && !isObjectEmpty(options) && (
<OptionsAForm
options={options as Options<"A">}
setOptions={setOptions}
/>
)}
{item === "B" && !isObjectEmpty(options) && (
<OptionsBForm
options={options as Options<"B">}
setOptions={setOptions}
/>
)}
<button onClick={handleSubmit}>Submit</button>
</div>
);
};
Update: using useReducer
because there were still errors更新:使用
useReducer
因为仍然有错误
import React, { FC } from "react";
import { OptionsAForm, OptionsA, defaultOptionsA } from "./OptionsAForm";
import { OptionsBForm, OptionsB, defaultOptionsB } from "./OptionsBForm";
type OptionTypes = "A" | "B";
interface OptionsMap {
A: OptionsA;
B: OptionsB;
}
type Options<T = OptionTypes> = T extends OptionTypes ? OptionsMap[T] : null;
const isObjectEmpty = (obj: Record<string, any>) =>
Object.keys(obj).length === 0;
type Action =
| {
type: "SET_ITEM";
item: OptionTypes;
}
| {
type: "SET_OPTIONS";
options: Options;
};
interface IState {
item: OptionTypes | "";
options: Options | {};
}
const initialState: IState = {
item: "",
options: {}
};
function reducer(state: IState, action: Action): IState {
switch (action.type) {
case "SET_ITEM": {
const { item } = action;
return {
...state,
item,
options: item === "A" ? defaultOptionsA : defaultOptionsB
};
}
case "SET_OPTIONS": {
return {
...state,
options: action.options
};
}
default:
return state;
}
}
export const ItemCreator: FC = () => {
const [state, dispatch] = React.useReducer(reducer, initialState);
const { item, options } = state;
const setItem = (selectedItem: OptionTypes) =>
dispatch({ type: "SET_ITEM", item: selectedItem });
const setOptions = (options: Options) =>
dispatch({ type: "SET_OPTIONS", options });
const handleSubmit = () => {
// do stuff with Options here
console.log(options);
};
return (
<div style={{ display: "flex", flexDirection: "column" }}>
<select
value={item}
onChange={(e) => setItem(e.target.value as OptionTypes)}
>
<option value="" disabled>
Select an item
</option>
<option value="A">A</option>
<option value="B">B</option>
</select>
{item === "A" && !isObjectEmpty(options) && (
<OptionsAForm
options={options as Options<"A">}
setOptions={setOptions}
/>
)}
{item === "B" && !isObjectEmpty(options) && (
<OptionsBForm
options={options as Options<"B">}
setOptions={setOptions}
/>
)}
<button onClick={handleSubmit}>Submit</button>
</div>
);
};
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.