简体   繁体   English

TypeScript - 我应该如何输入这些 React 组件?

[英]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.

相关问题 打字稿:如何键入 React 抽象组件? - Typescript: How to type React abstract components? Typescript ESLint 错误 - 使用 React 功能组件时如何输入子项? - Typescript ESLint Error - How can I type children when using react functional components? React/Typescript:如何通过多个组件传递和“输入”道具 - React/Typescript: How do I pass and "type" props down through multiple components 我应该如何为样式组件React Native组件指定子类型? - How should I specify type of children for styled-components React Native component? 我应该在 React 中使用什么类型的组件:函数式组件还是类基组件? - What type of components should I use in React: functional components or class base components? 我应该在反应 typescript 中将什么类型传递给我的 imageIcon 道具? - What type i should pass to my imageIcon props in react typescript? 我应该如何构建一个通用的 React/Typescript 表单组件,其字段可以作为类型传入? - How should I structure a generic React/Typescript form component whose fields can be passed in as a type? 我应该将反应组件渲染为组件数组吗 - Should I Render react components as an array of components 反应,打字稿 - 无状态和普通组件的类型 - react, typescript - a type for both stateless and normal components Typescript 类型用于传递函数和反应组件 - Typescript type for passing functions and react components
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM