虽然这决不能解决问题React.forwardProps
,但另一种方法是解决它并使用innerRef
属性。然后,您可以对innerRef
属性强制执行类型。实现您想要的相同结果,但键入灵活、开销更少且无需实例化。
工作演示:
组件/标签/index.tsx
import * as React from "react";
import { FC, LabelProps } from "~types";
/*
Field label for form elements
@param {string} name - form field name
@param {string} label - form field label
@returns {JSX.Element}
*/
const Label: FC<LabelProps> = ({ name, label }) => (
<label className="label" htmlFor={name}>
{label}:
</label>
);
export default Label;
组件/字段/index.tsx
import * as React from "react";
import Label from "../Label";
import { FC, InputProps, TextAreaProps } from "~types";
/*
Field elements for a form that are conditionally rendered by a fieldType
of "input" or "textarea".
@param {Object} props - properties for an input or textarea
@returns {JSX.Element | null}
*/
const Field: FC<InputProps | TextAreaProps> = (props) => {
switch (props.fieldType) {
case "input":
return (
<>
<Label name={props.name} label={props.label} />
<input
ref={props.innerRef}
name={props.name}
className={props.className}
placeholder={props.placeholder}
type={props.type}
value={props.value}
onChange={props.onChange}
/>
</>
);
case "textarea":
return (
<>
<Label name={props.name} label={props.label} />
<textarea
ref={props.innerRef}
name={props.name}
className={props.className}
placeholder={props.placeholder}
rows={props.rows}
cols={props.cols}
value={props.value}
onChange={props.onChange}
/>
</>
);
default:
return null;
}
};
export default Field;
组件/表单/index.tsx
import * as React from "react";
import Field from "../Fields";
import { FormEvent, FC, EventTargetNameValue } from "~types";
const initialState = {
email: "",
name: "",
background: ""
};
const Form: FC = () => {
const [state, setState] = React.useState(initialState);
const emailRef = React.useRef<HTMLInputElement>(null);
const nameRef = React.useRef<HTMLInputElement>(null);
const bgRef = React.useRef<HTMLTextAreaElement>(null);
const handleChange = React.useCallback(
({ target: { name, value } }: EventTargetNameValue) => {
setState((s) => ({ ...s, [name]: value }));
},
[]
);
const handleReset = React.useCallback(() => {
setState(initialState);
}, []);
const handleSubmit = React.useCallback(
(e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const alertMessage = Object.values(state).some((v) => !v)
? "Must fill out all form fields before submitting!"
: JSON.stringify(state, null, 4);
alert(alertMessage);
},
[state]
);
return (
<form className="uk-form" onSubmit={handleSubmit}>
<Field
innerRef={emailRef}
label="Email"
className="uk-input"
fieldType="input"
type="email"
name="email"
onChange={handleChange}
placeholder="Enter email..."
value={state.email}
/>
<Field
innerRef={nameRef}
label="Name"
className="uk-input"
fieldType="input"
type="text"
name="name"
onChange={handleChange}
placeholder="Enter name..."
value={state.name}
/>
<Field
innerRef={bgRef}
label="Background"
className="uk-textarea"
fieldType="textarea"
rows={5}
name="background"
onChange={handleChange}
placeholder="Enter background..."
value={state.background}
/>
<button
className="uk-button uk-button-danger"
type="button"
onClick={handleReset}
>
Reset
</button>
<button
style={{ float: "right" }}
className="uk-button uk-button-primary"
type="submit"
>
Submit
</button>
</form>
);
};
export default Form;
类型/index.ts
import type {
FC,
ChangeEvent,
RefObject as Ref,
FormEvent,
ReactText
} from "react";
// custom utility types that can be reused
type ClassName = { className?: string };
type InnerRef<T> = { innerRef?: Ref<T> };
type OnChange<T> = { onChange: (event: ChangeEvent<T>) => void };
type Placeholder = { placeholder?: string };
type Value<T> = { value: T };
// defines a destructured event in a callback
export type EventTargetNameValue = {
target: {
name: string;
value: string;
};
};
/*
Utility interface that constructs typings based upon passed in arguments
@param {HTMLElement} E - type of HTML Element that is being rendered
@param {string} F - the fieldType to be rendered ("input" or "textarea")
@param {string} V - the type of value the field expects to be (string, number, etc)
*/
interface FieldProps<E, F, V>
extends LabelProps,
ClassName,
Placeholder,
OnChange<E>,
InnerRef<E>,
Value<V> {
fieldType: F;
}
// defines props for a "Label" component
export interface LabelProps {
name: string;
label: string;
}
// defines props for an "input" element by extending the FieldProps interface
export interface InputProps
extends FieldProps<HTMLInputElement, "input", ReactText> {
type: "text" | "number" | "email" | "phone";
}
// defines props for an "textarea" element by extending the FieldProps interface
export interface TextAreaProps
extends FieldProps<HTMLTextAreaElement, "textarea", string> {
cols?: number;
rows?: number;
}
// exporting React types for reusability
export type { ChangeEvent, FC, FormEvent };
索引.tsx
import * as React from "react";
import { render } from "react-dom";
import Form from "./components/Form";
import "uikit/dist/css/uikit.min.css";
import "./index.css";
render(<Form />, document.getElementById("root"));