技术文档 活跃维护

react

typescript-cheatsheets/react

针对有经验的 React 开发者开始使用 TypeScript 的备忘清单

47098
Stars 标星
4270
Forks 分支
395
Watchers 关注
0
Open Issues
TypeScript
主要语言
MIT
开源协议
3.6 MB
仓库大小
1 个月前
最后推送
一键安装扩展 / 插件指令
dsh plugin --profile web add github:typescript-cheatsheets/react
git clone https://github.com/typescript-cheatsheets/react.git
git clone git@github.com:typescript-cheatsheets/react.git
README.md main

机器翻译正文由机器翻译自项目原始文档(英文),排版经程序统一处理,可能存在偏差,请以原项目仓库为准。

React TypeScript 备忘单

使用 TypeScript 的 React 备忘单。


网络文档 | 贡献! | 提问!

:wave: 这个仓库由 @eps1lon 和 @filiptammergard 维护。我们很高兴你想尝试使用 TypeScript 的 React!如果你发现任何错误或遗漏,请 提交一个问题! : 1:


|

备忘单 旨在帮助 React 开发者高效使用 TypeScript:

  • 提供有主见的最佳实践和可直接复制粘贴的示例。
  • 涵盖基础的 TS 类型和设置,以及针对编写可重用类型工具和 React TS 库 的高级泛型类型使用。
  • 提供参与 DefinitelyTyped 的建议。

备忘单

简介

先决条件

你可以在任何技能水平下使用这张备忘单作为参考,但假设你对 React 和 TypeScript 有基本的了解。以下是先决条件列表:

在这张备忘单中,我们假设你使用的是最新版的 React 和 TypeScript。

React 和 TypeScript 入门工具包

React 提供了 如何启动新 React 项目 的文档,支持一些最流行的框架。以下是使用 TypeScript 启动它们的方法:

  • Next.js:npx create-next-app@latest --ts
  • Remix:npx create-remix@latest
  • Gatsby: npm init gatsby --ts
  • Expo: npx create-expo-app -t with-typescript

如果你只想要一个无框架的客户端单页应用,Vite 是最常见的选择:

  • Vite: npm create vite@latest my-app -- --template react-ts

在线尝试 React 和 TypeScript

有一些工具可以让你在线运行 React 和 TypeScript,这对调试或创建可共享的复现非常有帮助。

入门

功能组件

这些可以写成普通函数,接收一个 props 参数并返回一个 JSX 元素。

// Declaring type of props - see "Typing Component Props" for more examples
type AppProps = {
  message: string;
}; /* use `interface` if exporting so that consumers can extend */

// Easiest way to declare a Function Component; return type is inferred.
const App = ({ message }: AppProps) => <div>{message}</div>;

// You can choose to annotate the return type so an error is raised if you accidentally return some other type
const App = ({ message }: AppProps): React.JSX.Element => <div>{message}</div>;

// You can also inline the type declaration; eliminates naming the prop types, but looks repetitive
const App = ({ message }: { message: string }) => <div>{message}</div>;

// Alternatively, you can use `React.FunctionComponent` (or `React.FC`), if you prefer.
// With latest React types and TypeScript 5.1. it's mostly a stylistic choice, otherwise discouraged.
const App: React.FunctionComponent<{ message: string }> = ({ message }) => (
  <div>{message}</div>
);
// or
const App: React.FC<AppProps> = ({ message }) => <div>{message}</div>;

提示:你可以使用 Paul Shen 的 VS Code 插件 来自动生成类型解构声明(包括 键盘快捷键)。

为什么 React.FC 不需要?那 React.FunctionComponent / React.VoidFunctionComponent 呢?

你可能会在很多 React TypeScript 代码库中看到这个:

const App: React.FunctionComponent<{ message: string }> = ({ message }) => (
  < 除以 > {message} < /div >
);

然而,如今的普遍共识是 React.FunctionComponent(或简写 React.FC)并非必须。如果你仍在使用 React 17 或低于 5.1 的 TypeScript,甚至 不推荐使用。这当然是一个细微的观点,但如果你同意并希望从代码库中移除 React.FC,你可以使用 这个 jscodeshift codemod。

与 " 普通函数 " 版本的一些区别:

  • React.FunctionComponent 对返回类型是显式的,而普通函数版本则是隐式的(否则需要额外的注解)。

  • 它为静态属性提供类型检查和自动完成功能,如 displayName、propTypes 和 defaultProps。

    • 请注意,在 React.FunctionComponent 中使用 defaultProps 时存在一些已知问题。详情请参见 此问题。我们还维护了一个单独的 defaultProps 部分,你也可以查阅。
  • 将来,它可能会自动将 props 标记为 readonly,不过如果在参数列表中对 props 对象进行了结构分解,这一点就无关紧要了。

在大多数情况下,使用哪种语法几乎没有太大区别,但你可能更喜欢 React.FunctionComponent 更明确的特性。

钩子

从 v16.8 开始,@types/react 支持 Hooks。

useState

类型推断对于简单值非常有效:

const [state, setState] = useState(false);
// `state` is inferred to be a boolean
// `setState` only takes booleans

如果你需要使用一个你依赖推断的复杂类型,你可以使用 typeof 来获取推断的类型。

然而,许多钩子初始化时使用的是 null 类默认值,你可能会想如何提供类型。明确声明类型,并使用联合类型:

const [user, setUser] = useState<User | null>(null);

// later...
setUser(newUser);

如果状态在设置后不久被初始化,并且之后总是有一个值,你也可以使用类型断言:

const [user, setUser] = useState<User>({} as User);

// later...
setUser(newUser);

这会暂时“欺骗” TypeScript 编译器,让它认为 {} 的类型是 User。你应该接着设置 user 状态——如果不这样做,你代码的其他部分可能会依赖 user 是 User 类型,这可能导致运行时错误。

useCallback

你可以像对待其他函数一样来输入 useCallback。

const memoizedCallback = useCallback(
  (param1: string, param2: number) => {
    console.log(param1, param2)
    return { ok: true }
  },
  [...],
);
/**
 * VSCode will show the following type:
 * const memoizedCallback:
 *  (param1: string, param2: number) => { ok: boolean }
 */

请注意,对于 React < 18,useCallback 的函数签名默认将类型化参数为 any[]:

function useCallback<T extends (...args: any[]) => any>(
  callback: T,
  deps: DependencyList,
): T;

在 React 18 及更高版本中,useCallback 的函数签名更改为以下形式:

function useCallback<T extends Function>(callback: T, deps: DependencyList): T;

因此,以下代码将在 React >= 18 中产生 "Parameter 'e' implicitly has an 'any' type." 错误,但在 React 17 中不会产生 "<"。

// @ts-expect-error Parameter 'e' implicitly has 'any' type.
useCallback((e) => {}, []);
// Explicit 'any' type.
useCallback((e: any) => {}, []);

useReducer

你可以将 判别联合 用于 reducer 的动作。不要忘记定义 reducer 的返回类型,否则 TypeScript 会自己推断。

import { useReducer } from "react";

const initialState = { count: 0 };

type ACTIONTYPE =
  | { type: "increment"; payload: number }
  | { type: "decrement"; payload: string };

function reducer(
  state: typeof initialState,
  action: ACTIONTYPE,
): typeof initialState {
  switch (action.type) {
    case "increment":
      return { count: state.count + action.payload };
    case "decrement":
      return { count: state.count - Number(action.payload) };
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: "decrement", payload: "5" })}>
        -
      </button>
      <button onClick={() => dispatch({ type: "increment", payload: 5 })}>
        +
      </button>
    </>
  );
}

在 TypeScript Playground 中查看

与 redux 的 Reducer 一起使用

如果你使用 redux 库来编写 reducer 函数,它提供了一个方便的格式化辅助工具 Reducer<State, Action>,它会为你处理返回类型。

所以上面的 reducer 示例变为:

从 'redux' 导入 { Reducer };

导出函数 reducer: Reducer < AppState, Action > () {}
为 useReducer 提供显式类型

在大多数情况下,useReducer 的类型推断应该能可靠地工作。当推断失败时,可以使用以下语法显式提供状态和动作类型,其中动作类型被包裹在一个单元素元组中。

const [state, dispatch] = useReducer<typeof initialState, [ACTIONTYPE]>(
  reducer,初始状态,
);

useEffect / useLayoutEffect

useEffect 和 useLayoutEffect 都用于执行 副作用,并返回一个可选的清理函数,这意味着如果它们不处理返回值,就不需要类型。在使用 useEffect 时,要注意不要返回除函数或 undefined 以外的任何值,否则 TypeScript 和 React 都会报错。在使用箭头函数时,这一点可能比较微妙:

function DelayedEffect(props: { timerMs: number }) {
  const { timerMs } = props;

  useEffect(
    () =>
      setTimeout(() => {
        /* do stuff */
      }, timerMs),
    [timerMs],
  );
  // bad example! setTimeout implicitly returns a number
  // because the arrow function body isn't wrapped in curly braces
  return null;
}
上述示例的解决方案
函数 DelayedEffect(props: { timerMs: number }) {
  const { timerMs } = props;

  useEffect(() => {
    setTimeout(() => {
      /* do stuff */
    }, timerMs);
  }, [timerMs]);
  // 更好;使用 void 关键字确保返回 undefined return null;
}

useRef

useRef 在当前的 @types/react 中总是返回一个 RefObject<T>。需要一个初始值,并且返回的 .current 是基于该值类型化的。(MutableRefObject 已被弃用,仅为向后兼容而保留。)

选项 1:DOM 元素引用

要访问 DOM 元素: 将元素类型作为泛型提供,并将 null 作为初始值。React 会为你管理 .current,TypeScript 期望你将此 ref 传递给元素的 ref 属性:

function Foo() {
  // - If possible, prefer as specific as possible. For example, HTMLDivElement
  //   is better than HTMLElement and way better than Element.
  // - Technical-wise, this returns RefObject<HTMLDivElement>
  const divRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    // Note that ref.current may be null. This is expected, because you may
    // conditionally render the ref-ed element, or you may forget to assign it
    if (!divRef.current) throw Error("divRef is not assigned");

    // Now divRef.current is sure to be HTMLDivElement
    doSomethingWith(divRef.current);
  });

  // Give the ref to an element so React can manage it for you
  return <div ref={divRef}>etc</div>;
}

如果你确定 divRef.current 永远不会为 null,也可以使用非空断言操作符 !:

const divRef = useRef<HTMLDivElement>(null!);
// Later... No need to check if it is null
doSomethingWith(divRef.current);

请注意,您在这里选择放弃类型安全——如果在渲染时忘记将 ref 分配给某个元素,或者 ref 指向的元素是有条件渲染的,您将会遇到运行时错误。

提示:选择使用哪个 HTMLElement

Refs 需要具体类型 —— 仅仅指定任意 HTMLElement 是不够的。如果你不知道所需元素类型的名称,可以查看 lib.dom.ts,或者故意制造一个类型错误,让语言服务告诉你:

选项 2:可变值引用

在渲染过程中保持可变值而不在更改时重新渲染:传递你想要的初始值 — React 并不会为你管理 .current,你需要手动写入它。

function Foo() {
  const intervalRef = useRef<number | null>(null);

  useEffect(() => {
    intervalRef.current = window.setInterval(() => {
      /* ... */
    }, 1000);
    return () => {
      if (intervalRef.current !== null) clearInterval(intervalRef.current);
    };
  }, []);

  return (
    <button
      onClick={() => {
        /* clearInterval the ref */
      }}
    >
      Cancel timer
    </button>
  );
}
另请参阅

useImperativeHandle

在 React 19 中,ref 是函数组件的普通属性,所以 useImperativeHandle 直接用 ref 属性调用 —— 不需要 forwardRef。

// Countdown.tsx
import { useImperativeHandle, Ref } from "react";

export type CountdownHandle = {
  start: () => void;
};

type CountdownProps = {
  ref?: Ref<CountdownHandle>;
};

const Countdown = ({ ref }: CountdownProps) => {
  useImperativeHandle(ref, () => ({
    // start() has type inference here
    start() {
      alert("Start");
    },
  }));

  return <div>Countdown</div>;
};
// The component using the Countdown component
import { useEffect, useRef } from "react";
import Countdown, { CountdownHandle } from "./Countdown.tsx";

function App() {
  const countdownEl = useRef<CountdownHandle>(null);

  useEffect(() => {
    if (countdownEl.current) {
      // start() has type inference here as well
      countdownEl.current.start();
    }
  }, []);

  return <Countdown ref={countdownEl} />;
}

如果你仍然维护针对 React < 19 的代码,请参阅forwardRef 部分了解使用 forwardRef<CountdownHandle, CountdownProps> 的传统方法。

自定义钩子

如果你在自定义 Hook 中返回一个数组,你会想要避免类型推断,因为 TypeScript 会推断为联合类型(而实际上你希望数组中每个位置是不同的类型)。相反,可以使用 TS 3.4 const 断言 :

import { useState } from "react";

export function useLoading() {
  const [isLoading, setState] = useState(false);
  const load = (aPromise: Promise<any>) => {
    setState(true);
    return aPromise.finally(() => setState(false));
  };
  return [isLoading, load] as const; // infers [boolean, typeof load] instead of (boolean | typeof load)[]
}

在 TypeScript Playground 中查看

这样,当你进行解构时,你实际上会根据解构位置得到正确的类型。

替代方案:断言元组返回类型

如果你在使用 const 断言时遇到问题,你也可以断言或定义函数的返回类型:

从 "react" 导入 { useState };

export function useLoading() {
  const [isLoading, setState] = useState(false);
  const load = (aPromise: Promise < any > ) = >  { 设置状态(true);
    return aPromise.finally(() => > 设置状态(false));
  };
  return [isLoading, load] as [
    boolean,
    (aPromise: Promise<any>) => Promise<any>,
  ];
}

如果你编写大量自定义钩子,一个自动为元组类型的辅助函数也会很有帮助:

函数 tuplify < T 扩展任何[] > (...元素: T) {
  返回元素;
}

function useArray() {
  const numberValue = useRef(3).current;
  const functionValue = useRef(() => {}).current;
  return [numberValue, functionValue]; // type is (number | (() => void))[]
}

function useTuple() {
  const numberValue = useRef(3).current;
  const functionValue = useRef(() => {}).current;
  return tuplify(numberValue, functionValue); // 类型是 [number, () = >  void]
}

请注意,React 团队建议,自定义 Hook 如果返回两个以上的值,应使用适当的对象而不是元组。

更多 Hooks TypeScript 阅读:

如果你正在编写一个 React Hooks 库,不要忘记你还应该为用户公开你的类型以供使用。

Example React Hooks TypeScript Library:

类组件

在 TypeScript 中,React.Component 是一个泛型类型(也就是 React.Component<PropType, StateType>),因此你需要为它提供(可选的)属性和状态类型参数:

type MyProps = {
  // using `interface` is also ok
  message: string;
};
type MyState = {
  count: number; // like this
};
class App extends React.Component<MyProps, MyState> {
  state: MyState = {
    // optional second annotation for better type inference
    count: 0,
  };
  render() {
    return (
      <div>
        {this.props.message} {this.state.count}
      </div>
    );
  }
}

在 TypeScript Playground 中查看

不要忘记,你可以导出/导入/扩展这些类型/接口以便重用。

为什么要对 state 注释两次?

给 state 类属性添加注解不是严格必要的,但它可以在访问 this.state 时提供更好的类型推断,也有助于初始化状态。

这是因为它们以两种不同的方式工作,第二个泛型类型参数将允许 this.setState() 正确工作,因为该方法来自基类,但是在组件内部初始化 state 会覆盖基类实现,所以你必须确保告诉编译器你实际上并没有做任何不同的事情。

在这里查看 @ferdaber 的评论。

无需 只读

你经常会看到示例代码中使用 readonly 来标记 props 和 state 为不可变:

type MyProps = {
  只读消息: 字符串;
};
类型 MyState = { 只读 count: 数字;
};

这是没有必要的,因为 React.Component<P,S> 已经将它们标记为不可变的。(查看 PR 和讨论!)

类方法:像平常一样操作,但要记住,你的函数的任何参数也需要指定类型:

class App extends React.Component<{ message: string }, { count: number }> {
  state = { count: 0 };
  render() {
    return (
      <div onClick={() => this.increment(1)}>
        {this.props.message} {this.state.count}
      </div>
    );
  }
  increment = (amt: number) => {
    // like this
    this.setState((state) => ({
      count: state.count + amt,
    }));
  };
}

在 TypeScript Playground 中查看

类属性:如果你需要声明类属性以供以后使用,请直接在类的body中定义它们,而无需初始赋值:

class App extends React.Component<{
  message: string;
}> {
  pointer: number; // like this
  componentDidMount() {
    this.pointer = 3;
  }
  render() {
    return (
      <div>
        {this.props.message} and {this.pointer}
      </div>
    );
  }
}

在 TypeScript Playground 中查看

输入 getDerivedStateFromProps

在你开始使用 getDerivedStateFromProps 之前,请先阅读 文档 和 你很可能不需要派生状态。派生状态可以使用 hooks 来实现,这也有助于设置记忆化。

这里有几种方式可以注释 getDerivedStateFromProps

  1. 如果您明确地键入了派生状态,并且想要确保 getDerivedStateFromProps 的返回值符合它。
class Comp extends React.Component<Props, State> {
  static getDerivedStateFromProps(
    props: Props,
    state: State,
  ): Partial<State> | null {
    //
  }
}
  1. 当你希望函数的返回值决定你的状态时。
class Comp extends React.Component<
  Props,
  ReturnType<(typeof Comp)["getDerivedStateFromProps"]>
> {
  static getDerivedStateFromProps(props: Props) {}
}
  1. 当你想要使用其他状态字段和记忆化来获取派生状态时
type CustomValue = any;
interface Props {
  propA: CustomValue;
}
interface DefinedState {
  otherStateField: string;
}
type State = DefinedState & ReturnType<typeof transformPropsToState>;
function transformPropsToState(props: Props) {
  return {
    savedPropA: props.propA, // save for memoization
    derivedState: props.propA,
  };
}
class Comp extends React.PureComponent<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = {
      otherStateField: "123",
      ...transformPropsToState(props),
    };
  }
  static getDerivedStateFromProps(props: Props, state: State) {
    if (isEqual(props.propA, state.savedPropA)) return null;
    return transformPropsToState(props);
  }
}

在 TypeScript Playground 中查看

函数组件

从 React 19 开始,defaultProps 不再支持函数组件。请直接在参数列表中使用解构默认值 —— TypeScript 会自动将该 prop 推断为可选:

type GreetProps = { age?: number };

const Greet = ({ age = 21 }: GreetProps) => {
  // ...
};

如果您更愿意单独声明默认值(例如,为了在组件之间共享它们),可以将它们提取到一个常量中,并在解构时展开它们:

type GreetProps = { age?: number };

const defaultProps = { age: 21 } satisfies GreetProps;

const Greet = ({ age = defaultProps.age }: GreetProps) => {
  // ...
};

在 React 19 中,对函数组件设置 Greet.defaultProps = { age: 21 } 是无效的——该值在运行时会被忽略,并且 FunctionComponent 不再为其进行类型标注。

类组件

类组件仍然支持 static defaultProps。推荐的方法是将带有默认键的 props 类型设为必填,并让 LibraryManagedAttributes(由 JSX 自动应用)在调用处将它们设为可选:

type GreetProps = {
  age: number;
};

class Greet extends React.Component<GreetProps> {
  static defaultProps = {
    age: 21,
  };

  render() {
    return <div>Hello, I am {this.props.age}</div>;
  }
}

// Type-checks — `age` is optional at the call site thanks to defaultProps.
const el = <Greet />;
React.JSX.LibraryManagedAttributes 对库作者的细微差别

如果你导出 GreetProps 给消费者,age 将会显示为必需,尽管 defaultProps 在调用时使其可选。GreetProps 是 内部 合同——有一个单独的 外部 合同,JSX 通过 React.JSX.LibraryManagedAttributes 计算。你可以显式计算它:

// 内部合约 — 不导出
类型 GreetProps = { age: 数字;
};

class Greet 扩展自 React.Component < GreetProps > { static defaultProps = { age: 21 } }
}

// 外部合约导出类型 ApparentGreetProps = React.JSX.LibraryManagedAttributes<
  typeof Greet,
  问候属性
>;

对于大多数应用程序,这不是必需的——只有那些重新导出 props 类型的库作者才容易遇到这个问题。

输入组件属性

这是为熟悉 TypeScript 的 React 开发者提供的基本入门指南和参考。

基本属性类型示例

在 React TypeScript 应用中你可能会使用的 TypeScript 类型列表:

type AppProps = {
  message: string;
  count: number;
  disabled: boolean;
  /** array of a type! */
  names: string[];
  /** string literals to specify exact string values, with a union type to join them together */
  status: "waiting" | "success";
  /** an object with known properties (but could have more at runtime) */
  obj: {
    id: string;
    title: string;
  };
  /** array of objects! (common) */
  objArr: {
    id: string;
    title: string;
  }[];
  /** any non-primitive value - can't access any properties (NOT COMMON but useful as placeholder) */
  obj2: object;
  /** an interface with no required properties - (NOT COMMON, except for things like `React.Component<{}, State>`) */
  obj3: {};
  /** a dict object with any number of properties of the same type */
  dict1: {
    [key: string]: MyTypeHere;
  };
  dict2: Record<string, MyTypeHere>; // equivalent to dict1
  /** function that doesn't take or return anything (VERY COMMON) */
  onClick: () => void;
  /** function with named prop (VERY COMMON) */
  onChange: (id: number) => void;
  /** function type syntax that takes an event (VERY COMMON) */
  onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
  /** alternative function type syntax that takes an event (VERY COMMON) */
  onClick(event: React.MouseEvent<HTMLButtonElement>): void;
  /** any function as long as you don't invoke it (not recommended) */
  onSomething: Function;
  /** an optional prop (VERY COMMON!) */
  optional?: OptionalType;
  /** when passing down the state setter function returned by `useState` to a child component. `number` is an example, swap out with whatever the type of your state */
  setState: React.Dispatch<React.SetStateAction<number>>;
};
object 作为非原始类型

object 是 TypeScript 中常见的误解来源。它并不意味着“任何对象”,而是意味着“任何非原始类型”,也就是说它表示除了 number、bigint、string、boolean、symbol、null 或 undefined 之外的任何东西。

在 React 中,给“任何非原始值”指定类型很可能不是你应该经常做的事情,这意味着你可能不会经常使用 object。

空接口,{} 和 Object

一个空接口、{} 和 Object 都表示“任何非空值”——而不是你可能认为的“一个空对象”。使用这些类型是常见的误解来源,不推荐使用。

interface AnyNonNullishValue {} // equivalent to `type AnyNonNullishValue = {}` or `type AnyNonNullishValue = Object`

let value: AnyNonNullishValue;

// these are all fine, but might not be expected
value = 1;
value = "foo";
value = () => alert("foo");
value = {};
value = { foo: "bar" };

// these are errors
value = undefined;
value = null;

有用的 React 属性类型示例

适用于接受其他 React 组件作为 props 的组件。

export declare interface AppProps {
  children?: React.ReactNode; // best, accepts everything React can render
  childrenElement: React.JSX.Element; // A single React element
  style?: React.CSSProperties; // to pass through style props
  onChange?: React.FormEventHandler<HTMLInputElement>; // form events! the generic parameter is the type of event.target
  //  more info: https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/patterns_by_usecase/#wrappingmirroring
  props: Props & React.ComponentPropsWithoutRef<"button">; // to impersonate all the props of a button element and explicitly not forwarding its ref
  props2: Props & React.ComponentPropsWithRef<MyButtonWithForwardRef>; // to impersonate all the props of MyButtonForwardedRef and explicitly forwarding its ref
}
React.JSX.Element 与 React.ReactNode 区别?

引用 @ferdaber:更技术性的解释是,有效的 React 节点并不等同于 React.createElement 返回的内容。无论组件最终渲染什么,React.createElement 总是返回一个对象,该对象符合 React.JSX.Element 接口,但 React.ReactNode 是组件所有可能返回值的集合。

  • React.JSX.Element -> React.createElement 的返回值
  • React.ReactNode -> 组件的返回值

更多讨论:ReactNode 与 React.JSX.Element 不重叠的情况

类型还是接口?

你可以使用类型(Types)或接口(Interfaces)来定义 Props 和 State,所以自然而然地会产生一个问题——你应该使用哪个?

摘要

在需要类型之前使用接口 - orta。

更多建议

这里有一个有用的经验法则:

  • 在编写库或第三方环境类型定义时,总是使用 interface 来定义公共 API,因为如果某些定义缺失,这允许使用者通过 声明合并 来扩展它们。

  • 考虑为你的 React 组件的 Props 和 State 使用 type,这样更一致,也因为它限制更多。

你可以在 Interface vs Type alias in TypeScript 2.7 中阅读更多关于这一经验法则背后的推理。

TypeScript 手册现在还包括关于 类型别名和接口之间的差异 的指南。

注意:在大规模应用中,出于性能原因更倾向于使用接口(参见微软官方关于此的说明),但对此需持保留态度

类型对于联合类型很有用(例如 type MyType = TypeA | TypeB),而接口更适合声明字典的结构,然后 implementing 或 extending 它们。

类型与接口的实用表

这是一个微妙的话题,别太纠结于此。这里有一个方便的表格:

方面 类型 接口
可以描述功能 ✅ ✅
可以描述构造函数 ✅ ✅
可以描述元组 ✅ ✅
接口可以扩展它 ⚠️ ✅
类可以继承它 🚫 ✅
类可以实现它 (implements) ⚠️ ✅
可以与同类的另一个相交 ✅ ⚠️
可以与同类创建联合 ✅ 🚫
可用于创建映射类型 ✅ 🚫
可以通过映射类型进行映射 ✅ ✅
在错误消息和日志中展开 ✅ 🚫
可以增强 🚫 ✅
可以递归 ⚠️ ✅

⚠️ 在某些情况下

(来源:Karol Majewski)

从属性派生状态

在你开始使用 getDerivedStateFromProps 之前,请先浏览 文档 和 你可能不需要派生状态。派生状态可以通过 hooks 轻松实现,同时 hooks 也可以帮助轻松设置记忆化。

这里有几种方式可以注释 getDerivedStateFromProps

  1. 如果您明确地键入了派生状态,并且想要确保 getDerivedStateFromProps 的返回值符合它。
class Comp extends React.Component<Props, State> {
  static getDerivedStateFromProps(
    props: Props,
    state: State,
  ): Partial<State> | null {
    //
  }
}
  1. 当你希望函数的返回值决定你的状态时。
class Comp extends React.Component<
  Props,
  ReturnType<(typeof Comp)["getDerivedStateFromProps"]>
> {
  static getDerivedStateFromProps(props: Props) {}
}
  1. 当你想要使用其他状态字段和记忆化来获取派生状态时
type CustomValue = any;
interface Props {
  propA: CustomValue;
}
interface DefinedState {
  otherStateField: string;
}
type State = DefinedState & ReturnType<typeof transformPropsToState>;
function transformPropsToState(props: Props) {
  return {
    savedPropA: props.propA, // save for memoization
    derivedState: props.propA,
  };
}
class Comp extends React.PureComponent<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = {
      otherStateField: "123",
      ...transformPropsToState(props),
    };
  }
  static getDerivedStateFromProps(props: Props, state: State) {
    if (isEqual(props.propA, state.savedPropA)) return null;
    return transformPropsToState(props);
  }
}

在 TypeScript Playground 中查看

形式与事件

如果性能不是问题(通常也不是!),内联处理程序是最简单的,因为你可以直接使用 类型推断和上下文类型:

const el = (
  <button
    onClick={(event) => {
      /* event will be correctly typed automatically! */
    }}
  />
);

但是如果你需要单独定义你的事件处理程序,IDE 工具在这里真的很有用,因为 @type 定义提供了丰富的类型信息。输入你要查找的内容,通常自动补全会帮助你。下面是表单事件的 onChange 的示例:

type State = {
  text: string;
};
class App extends React.Component<Props, State> {
  state = {
    text: "",
  };

  // typing on RIGHT hand side of =
  onChange = (e: React.FormEvent<HTMLInputElement>): void => {
    this.setState({ text: e.currentTarget.value });
  };
  render() {
    return (
      <div>
        <input type="text" value={this.state.text} onChange={this.onChange} />
      </div>
    );
  }
}

在 TypeScript Playground 中查看

与其使用 React.FormEvent<> 和 void 来键入参数和返回值,你也可以将类型直接应用到事件处理程序本身(由 @TomasHubelbauer 贡献):

// typing on LEFT hand side of =
onChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
  this.setState({ text: e.currentTarget.value });
};
为什么有两种方法做同一件事?

第一种方法使用推断的方法签名 (e: React.FormEvent < HTMLInputElement > ): void ,而第二种方法则强制使用由 @types/react 提供的委托类型。因此 React.ChangeEventHandler < > 只是 " @types/react 所认可的 " 类型,而你可以将推断方法视为更 手工制作 。无论哪种方式,这都是一个值得了解的好模式。更多内容请参阅我们的 Github PR 。

从 React v19.2.10 开始,FormEvent 和 FormEventHandler 已被弃用,应替换为 SubmitEvent 和 SubmitEventHandler。老的事件类型仍然可以使用,但会触发弃用消息。

在表单中使用非受控组件时输入 onSubmit

如果你并不特别在意事件的类型,你可以直接使用 React.SyntheticEvent。如果你的目标表单有自定义命名的输入项,你可以使用类型断言:

<form
  ref={formRef}
  onSubmit={(e: React.SyntheticEvent) => {
    e.preventDefault();
    const target = e.target as typeof e.target & {
      email: { value: string };
      password: { value: string };
    };
    const email = target.email.value; // typechecks!
    const password = target.password.value; // typechecks!
    // etc...
  }}
>
  <div>
    <label>
      Email:
      <input type="email" name="email" />
    </label>
  </div>
  <div>
    <label>
      Password:
      <input type="password" name="password" />
    </label>
  </div>
  <div>
    <input type="submit" value="Log in" />
  </div>
</form>

在 TypeScript Playground 中查看

当然,如果你正在制作任何类型的重要表单,你应该使用 Formik 或 React Hook Form,它们是用 TypeScript 编写的。

事件类型列表
事件类型 描述
动画事件 CSS 动画。
ChangeEvent 修改 <input>、<select> 和 <textarea> 元素的值。
剪贴板事件 使用复制、粘贴和剪切事件。
CompositionEvent 由于用户间接输入文本而发生的事件(例如,根据浏览器和电脑的设置,如果您想在美式键盘上输入日语,可能会出现一个带有额外字符的弹出窗口)
拖放事件 使用指针设备(例如鼠标)进行的拖放交互。
FocusEvent 当元素获得或失去焦点时发生的事件。
FormEvent 每当表单或表单元素获得/失去焦点、表单元素的值被更改或表单被提交时发生的事件。
InvalidEvent 当输入的有效性限制失败时触发(例如 <input type="number" max="10">,有人输入数字 20 时)。
KeyboardEvent 用户与键盘的交互。每个事件描述一次单独的按键交互。
输入事件 在 <input>、<select> 和 <textarea> 的值更改之前发生的事件。
鼠标事件 由于用户与指针设备(例如鼠标)交互而发生的事件
PointerEvent 由于用户与各种指点设备(如鼠标、笔/触控笔、触摸屏)的交互而发生的事件,同时也支持多点触控。除非你为较旧的浏览器(IE10 或 Safari 12)开发,否则推荐使用指针事件。继承自 UIEvent。
TouchEvent 由于用户与触摸设备交互而发生的事件。继承自 UIEvent。
TransitionEvent CSS 过渡。并非所有浏览器完全支持。继承自 UIEvent
UIEvent 鼠标、触摸和指针事件的基础事件。
WheelEvent 在鼠标滚轮或类似输入设备上滚动。(注意:wheel 事件不应与 scroll 事件混淆)
SyntheticEvent 所有上述事件的基础事件。在不确定事件类型时应使用此事件

上下文

基本示例

这是一个创建包含活动主题的上下文的基本示例。

import { createContext } from "react";

type ThemeContextType = "light" | "dark";

const ThemeContext = createContext<ThemeContextType>("light");

通过将上下文本身渲染为提供者来包装需要上下文的组件。在 React 19 中,可以直接渲染上下文对象——你不再需要 <ThemeContext.Provider>:

import { useState } from "react";

const App = () => {
  const [theme, setTheme] = useState<ThemeContextType>("light");

  return (
    <ThemeContext value={theme}>
      <MyComponent />
    </ThemeContext>
  );
};

<ThemeContext.Provider value={theme}> 仍然有效,行为完全相同——它只是旧的写法。

阅读带有 use 的上下文:

import { use } from "react";

const MyComponent = () => {
  const theme = use(ThemeContext);

  return <p>The current theme is {theme}.</p>;
};

useContext(ThemeContext) 仍然可以使用。主要的区别在于 use 还可以解包一个 promise,并且可以在条件和循环中调用。

没有默认上下文值

如果你没有任何有意义的默认值,请指定 null:

import { createContext } from "react";

interface CurrentUserContextType {
  username: string;
}

const CurrentUserContext = createContext<CurrentUserContextType | null>(null);
const App = () => {
  const [currentUser, setCurrentUser] = useState<CurrentUserContextType>({
    username: "filiptammergard",
  });

  return (
    <CurrentUserContext value={currentUser}>
      <MyComponent />
    </CurrentUserContext>
  );
};

现在上下文的类型可以是 null,如果你尝试访问 username 属性,你会注意到会出现 'currentUser' is possibly 'null' TypeScript 错误。你可以使用可选链来访问 username :

import { use } from "react";

const MyComponent = () => {
  const currentUser = use(CurrentUserContext);

  return <p>Name: {currentUser?.username}.</p>;
};

然而,最好不要必须检查 null,因为我们知道上下文不会是 null。实现这一点的一种方法是提供一个自定义钩子来使用上下文,如果上下文未提供,则抛出错误:

import { createContext, use } from "react";

interface CurrentUserContextType {
  username: string;
}

const CurrentUserContext = createContext<CurrentUserContextType | null>(null);

const useCurrentUser = () => {
  const currentUserContext = use(CurrentUserContext);

  if (!currentUserContext) {
    throw new Error(
      "useCurrentUser has to be used within <CurrentUserContext>",
    );
  }

  return currentUserContext;
};

在这里使用运行时类型检查的好处是,当提供者未正确包裹组件时,可以在控制台打印出清晰的错误信息。现在可以在不检查 null 的情况下访问 currentUser.username:

const MyComponent = () => {
  const currentUser = useCurrentUser();

  return <p>Username: {currentUser.username}.</p>;
};
类型断言作为一种替代

避免必须检查 null 的另一种方法是使用类型断言告诉 TypeScript 你知道上下文不是 null:

import { use } from "react";

const MyComponent = () => {
  const currentUser = use(CurrentUserContext);

  return <p>Name: {currentUser!.username}.</p>;
};

另一个选项是使用一个空对象作为默认值,并将其转换为预期的上下文类型:

const CurrentUserContext = createContext<CurrentUserContextType>(
  {} as CurrentUserContextType,
);

你也可以使用非空断言来获得相同的结果:

const CurrentUserContext = createContext<CurrentUserContextType>(null!);

当你不知道该选择什么时,优先选择运行时检查和抛出,而不是类型断言。

forwardRef/createRef

关于 useRef,请查看Hooks 部分。

将 Ref 作为属性(推荐用于 React 19)

在 React 19 中,你可以在函数组件中直接将 ref 作为一个属性访问——无需 forwardRef 包装。

选项 1:继承原生元素的所有属性

使用 ComponentPropsWithRef 从原生元素继承所有属性。

import { ComponentPropsWithRef, useRef } from "react";

function MyInput(props: ComponentPropsWithRef<"input">) {
  return <input {...props} />;
}

// Usage in parent component
function Parent() {
  const inputRef = useRef<HTMLInputElement>(null);

  return <MyInput ref={inputRef} placeholder="Type here..." />;
}
选项 2:显式类型

如果你有自定义属性并且想要精细控制,你可以显式地为 ref 指定类型:

import { Ref, useRef } from "react";

interface MyInputProps {
  placeholder: string;
  ref: Ref<HTMLInputElement>;
}

function MyInput(props: MyInputProps) {
  return <input {...props} />;
}

// Usage in parent component
function Parent() {
  const inputRef = useRef<HTMLInputElement>(null);

  return <MyInput ref={inputRef} placeholder="Type here..." />;
}

阅读更多: 包装/镜像 HTML 元素

传统方法(React 19 之前)

forwardRef

对于 React 18 及更早版本,使用 forwardRef:

import { forwardRef, ReactNode } from "react";

interface Props {
  children?: ReactNode;
  type: "submit" | "button";
}
export type Ref = HTMLButtonElement;

export const FancyButton = forwardRef<Ref, Props>((props, ref) => (
  <button ref={ref} className="MyClassName" type={props.type}>
    {props.children}
  </button>
));
附注:你从 forwardRef 获取的 ref 是可变的,因此如果需要,你可以对其进行赋值。

这是故意为之。如果必须,你可以让它不可变——如果你想确保没人重新分配它,可以分配React.Ref:

从 "react" 导入 { forwardRef, ReactNode, Ref };

接口 Props { children?: ReactNode;
  类型: "提交" | "按钮";
}

export const FancyButton = forwardRef(
  (
    props: Props, ref: Ref<HTMLButtonElement>, // <-- 显式不可变 ref 类型
  ) =  >   (
    < 按钮 ref={ref} className="MyClassName" type={props.type} >
      {props.children}
    </button>
  ),
);

如果你需要获取一个转发 ref 的组件的属性,请使用 ComponentPropsWithRef。

创建引用

createRef 主要用于类组件。函数组件通常依赖 useRef。

import { createRef, PureComponent } from "react";

class CssThemeProvider extends PureComponent<Props> {
  private rootRef = createRef<HTMLDivElement>();

  render() {
    return <div ref={this.rootRef}>{this.props.children}</div>;
  }
}

带引用的通用组件

通用组件通常需要手动处理 ref,因为它们的通用性质阻止了自动类型推断。主要方法如下:

在 这篇文章 中阅读更多内容。

选项 1:包装组件

最直接的方法是通过 props 手动处理 refs:

interface ClickableListProps<T> {
  items: T[];
  onSelect: (item: T) => void;
  mRef?: React.Ref<HTMLUListElement> | null;
}

export function ClickableList<T>(props: ClickableListProps<T>) {
  return (
    <ul ref={props.mRef}>
      {props.items.map((item, i) => (
        <li key={i}>
          <button onClick={() => props.onSelect(item)}>Select</button>
          {item}
        </li>
      ))}
    </ul>
  );
}
选项 2:重新声明 forwardRef

对于带有泛型的真正 forwardRef 行为,请扩展模块声明:

// Redeclare forwardRef to support generics
declare module "react" {
  function forwardRef<T, P = {}>(
    render: (props: P, ref: React.Ref<T>) => React.ReactElement | null,
  ): (props: P & React.RefAttributes<T>) => React.ReactElement | null;
}

// Now you can use forwardRef with generics normally
import { forwardRef, ForwardedRef } from "react";

interface ClickableListProps<T> {
  items: T[];
  onSelect: (item: T) => void;
}

function ClickableListInner<T>(
  props: ClickableListProps<T>,
  ref: ForwardedRef<HTMLUListElement>,
) {
  return (
    <ul ref={ref}>
      {props.items.map((item, i) => (
        <li key={i}>
          <button onClick={() => props.onSelect(item)}>Select</button>
          {item}
        </li>
      ))}
    </ul>
  );
}

export const ClickableList = forwardRef(ClickableListInner);
选项 3:调用签名

如果你既需要通用支持,又需要具有完全类型推断的正确 forwardRef 行为,你可以使用调用签名:

// Add to your type definitions (e.g. in `index.d.ts` file)
interface ForwardRefWithGenerics extends React.FC<WithForwardRefProps<Option>> {
  <T extends Option>(
    props: WithForwardRefProps<T>,
  ): ReturnType<React.FC<WithForwardRefProps<T>>>;
}

export const ClickableListWithForwardRef: ForwardRefWithGenerics =
  forwardRef(ClickableList);

鸣谢: https://stackoverflow.com/a/73795494

:::注意 选项 1 通常就足够且更清晰。当你特别需要 forwardRef 行为时使用选项 2。对于需要同时使用泛型和完整 forwardRef 类型推断的高级库场景,使用选项 3。:::

附加资源


有东西要添加吗?提交一个问题

门户

使用 ReactDOM.createPortal:

const modalRoot = document.getElementById("modal-root") as HTMLElement;
// assuming in your html file has a div with id 'modal-root';

export class Modal extends React.Component<{ children?: React.ReactNode }> {
  el: HTMLElement = document.createElement("div");

  componentDidMount() {
    modalRoot.appendChild(this.el);
  }

  componentWillUnmount() {
    modalRoot.removeChild(this.el);
  }

  render() {
    return ReactDOM.createPortal(this.props.children, this.el);
  }
}

在 TypeScript Playground 中查看

使用钩子

同上,但使用钩子

从 "react" 导入 { useEffect, useRef, ReactNode }
从 "react-dom" 导入 { createPortal };

const modalRoot = document.querySelector("#modal-root") as HTMLElement;

类型 ModalProps = { 子元素: ReactNode;
};

function 模态框({ children }: ModalProps) {
  // 使用 ref 仅创建一次 div 元素 const elRef = useRef < HTMLDivElement | null > (null);
  if (!elRef.current) elRef.current = document.createElement("div");

  useEffect(() = >  { const el = elRef.current!; // 非空断言,因为它永远不会为 null modalRoot.appendChild(el);
    返回 () =  >   {
      modalRoot.removeChild(el);
    };
  }, []);

  返回 createPortal(children, elRef.current);
}

在 TypeScript Playground 中查看

模态组件使用示例:

import { useState } from "react";

function App() {
  const [showModal, setShowModal] = useState(false);

  return (
    <div>
      // you can also put this in your static html file
      <div id="modal-root"></div>
      {showModal && (
        <Modal>
          <div
            style={{
              display: "grid",
              placeItems: "center",
              height: "100vh",
              width: "100vh",
              background: "rgba(0,0,0,0.1)",
              zIndex: 99,
            }}
          >
            I'm a modal!{" "}
            <button
              style={{ background: "papayawhip" }}
              onClick={() => setShowModal(false)}
            >
              close
            </button>
          </div>
        </Modal>
      )}
      <button onClick={() => setShowModal(true)}>show Modal</button>
      // rest of your app
    </div>
  );
}
示例背景

此示例基于 React 文档中的 通过门户事件冒泡 示例。

错误边界

选项 1:使用 react-error-boundary

React-error-boundary - 是一个轻量级的软件包,适用于这种场景,并内置了 TS 支持。这种方法还可以让你避免使用现在不太流行的类组件。

选项 2:编写自定义错误边界组件

如果你不想为此添加新的 npm 包,你也可以自己编写 ErrorBoundary 组件。

import React, { Component, ErrorInfo, ReactNode } from "react";

interface Props {
  children?: ReactNode;
}

interface State {
  hasError: boolean;
}

class ErrorBoundary extends Component<Props, State> {
  public state: State = {
    hasError: false
  };

  public static getDerivedStateFromError(_: Error): State {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

  public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    console.error("Uncaught error:", error, errorInfo);
  }

  public render() {
    if (this.state.hasError) {
      return <h1>Sorry.. there was an error</h1>;
    }

    return this.props.children;
  }
}

export default ErrorBoundary;

Concurrent React

并发 React API(Suspense、useTransition、useDeferredValue、startTransition、use)允许你在 React 在后台渲染工作或等待数据时保持 UI 的响应性。自 React 18 起它们都很稳定,并在 React 19 中获得了额外的功能。

Suspense

Suspense 允许你在子组件等待某些内容时声明性地显示一个回退——通常是使用 use(promise) 展开的数据、一个懒加载组件,或者服务器上的流式边界。

import { Suspense } from "react";

const UserProfile = ({ userPromise }: { userPromise: Promise<User> }) => {
  const user = use(userPromise);
  return <p>Hello, {user.name}!</p>;
};

const App = ({ userPromise }: { userPromise: Promise<User> }) => (
  <Suspense fallback={<p>Loading...</p>}>
    <UserProfile userPromise={userPromise} />
  </Suspense>
);

SuspenseProps 的类型为 { children?: ReactNode; fallback?: ReactNode }。fallback 可以是任何 ReactNode,包括 null。

use

use 读取上下文或 Promise 的值。与 useContext 不同,它可以在条件语句和循环中调用,并且可以与 Promise 的 Suspense 集成。

import { use } from "react";

const Comments = ({
  commentsPromise,
}: {
  commentsPromise: Promise<Comment[]>;
}) => {
  // Suspends until the promise resolves; throws to the nearest <Suspense>.
  const comments = use(commentsPromise);
  return (
    <ul>
      {comments.map((c) => (
        <li key={c.id}>{c.text}</li>
      ))}
    </ul>
  );
};

这个 promise 通常由父组件创建并传递 —— 不要在组件内部创建,否则每次渲染都会创建一个新的 promise。

useTransition

useTransition 将状态更新标记为非紧急,因此在渲染时,React 可以保持打字、滚动和其他紧急输入的响应性。

import { useState, useTransition } from "react";

const TabSwitcher = () => {
  const [isPending, startTransition] = useTransition();
  const [tab, setTab] = useState<"posts" | "comments">("posts");

  const selectTab = (next: "posts" | "comments") => {
    startTransition(() => {
      setTab(next);
    });
  };

  return (
    <>
      <button disabled={isPending} onClick={() => selectTab("posts")}>
        Posts
      </button>
      <button disabled={isPending} onClick={() => selectTab("comments")}>
        Comments
      </button>
      {tab === "posts" ? <Posts /> : <Comments />}
    </>
  );
};
异步过渡(React 19)

在 React 19 中,传递给 startTransition 的函数可以是异步的。这是 Actions 的基础,也是 useActionState 和 <form action> 调度其挂起状态的方式。

const [isPending, startTransition] = useTransition();

const onSubmit = () => {
  startTransition(async () => {
    await saveDraft(content);
    setSavedAt(new Date());
  });
};

isPending 在异步回调的整个期间都会保持为 true,包括等待的工作。

useDeferredValue

useDeferredValue 允许你延迟重新渲染计算开销较大的 UI 部分,这样紧急的更新(输入文本时)可以优先执行。

import { useDeferredValue, useState } from "react";

const SearchPage = () => {
  const [query, setQuery] = useState("");
  const deferredQuery = useDeferredValue(query);

  return (
    <>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      {/* SearchResults re-renders with deferredQuery, lagging behind input */}
      <SearchResults query={deferredQuery} />
    </>
  );
};
initialValue(React 19)

React 19 添加了一个可选的第二个参数:在延迟值跟上之前,初始渲染时使用的值。在 SSR/流式渲染时很有用,当你想显示一个已知的初始值而不是最新值时。

const deferredQuery = useDeferredValue(query, "");

startTransition(独立使用)

startTransition 也可以直接从 react 导出,以便在组件外使用 —— 例如,在非 React 代码的事件处理器或第三方存储中使用。

import { startTransition } from "react";

store.subscribe(() => {
  startTransition(() => {
    forceRender();
  });
});

独立版本不提供 isPending 标志——如果需要该标志,请使用 hook。

另请参阅

我的问题在这里没有得到回答!

本站来源与版权声明
  • 本文标题:react - 针对有经验的 React 开发者开始使用 TypeS
  • 本文链接:https://cn121.com/manual/typescript-cheatsheets-react.html
  • 原项目:typescript-cheatsheets/react 版权归原作者 typescript-cheatsheets 及贡献者所有
  • 收录信息:本站于 2026-10-16 收录本项目,本页所列协议与仓库指标均为收录当时的状态;该日期之后原项目的版本更新与协议变更,本页不作同步。
  • 开源协议:收录时本项目采用 MIT(查看 LICENSE 原文),本站译文为其衍生内容;使用、修改、分发请以该仓库 LICENSE 原文为准。
  • 站点出处:本文首发于 OneTwoOne,收录自 GitHub 开源项目 typescript-cheatsheets/react。
  • 翻译说明:本页正文为人工智能生成内容——由机器翻译对原项目 README 初译、经程序校验排版,可能存在错漏,请以原项目文档为准。
  • 引用声明:商业转载、第三方聚合或 AI 检索训练引用时,请务必保留以上来源出处、本文永久链接,以及原项目的版权声明与许可信息。
  • 下架通道:若原项目此后变更或收紧了许可协议、或作者/权利人认为本站的收录方式(译文、排版适配、简介翻译等)超出其授权范围,请通过 xyd3302001@163.com 发送下架通知,并附上项目地址与本页链接。本站核实后将第一时间删除本页内容,或改为不复制原文的目录性收录;署名更正等其他要求可一并提出。