React中的类型

330 阅读4分钟

创建React元素

创建 React 元素可以通过 JSX 扩展语法 或者 createElement api 两种方式。

createElement

jsx 标签

zh-hans.react.dev/learn/writi…

JSX是对HTML标签的一种语法扩展,JSX 语法更加严格并且相比 HTML 有更多的规则,开发组件时一般将渲染逻辑和标签存在同一个地方-组件。

function Greeting({ name }) {
  return (
    <h1 className="greeting">
      你好<i>{name}</i>,欢迎!
    </h1>
  );
}

export default function App() {
  return <Greeting name="泰勒" />;
}

createElement

如果不喜欢JSX,可以使用 createElement 作为替代方案。createElement返回一个React 元素。

API详情:createElement(type, props, ...children)

import { createElement } from 'react';  

function Greeting({ name }) {  
    return createElement(  
        'h1',  
        { className: 'greeting' },  
        '你好',  
        createElement('i', null, name),  
        '。欢迎!'  
    );  

}

export default function App() {
  return createElement(Greeting, { name: '泰勒' });
}

React 元素

React 元素是用于描述用户界面的轻量级结构。

类似于

{
  type: Greeting,
  props: {
    name: '泰勒'
  },
  key: null,
  ref: null,
}

创建这个对象不会渲染组件或创建DOM元素,它告诉React如何渲染组件。

React内置类型

React.ReactElement

React.ReactElement 是一个接口,对应了一个对象的结构,包括 type、props、key 三个属性值。 通常情况下,函数组件会返回 React.ReactElement 类型的值。

 type Key = string | number

 interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
    type: T;
    props: P;
    key: Key | null;
}

JSXElementConstructor

JSXElementConstructor 表示一个函数式组件 或 类式组件。

type JSXElementConstructor<P> =
    | ((props: P) => ReactElement<any, any> | null)
    | (new (props: P) => Component<any, any>);

JSX.Element

declare global {
  namespace JSX {
    interface Element extends React.ReactElement<any, any> { }
  }
}

可以看出 JSX.Element 继承自React.ReactElement<any, any>。JSX 是一个全局的namespace。 该类型的变量值只能是 ReactElement 实例。 JSX.Element 可以通过执行 React.createElement 或是转译 JSX 获得:

const jsx = <div>hello</div>
const ele = React.createElement("div", null, "hello");

React.ReactNode

ReactNode 是一个联合类型,可以是 string、number、ReactElement、ReactFragment、boolean、null、undefined、或者是ReactNode的数组集合。 因此,ReactElement类型的变量可以赋值给ReactNode类型的变量,反之不行。

type ReactText = string | number;
type ReactChild = ReactElement | ReactText;

interface ReactNodeArray extends Array<ReactNode> {}
type ReactFragment = {} | ReactNodeArray;
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;

React Hooks

useRef

在函数组件的顶层使用useRef函数后,会返回一个仅有一个current属性的可变对象,current对象是我们设置的初始值。 下面看下定义:

  • 第一种定义
/**
* `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument
* (`initialValue`). The returned object will persist for the full lifetime of the component.
*
* Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable
* value around similar to how you’d use instance fields in classes.
*
* Usage note: if you need the result of useRef to be directly mutable, include `| null` in the type
* of the generic argument.
*
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#useref
*/
  // 第一种类型定义:返回一个可变对象
 function useRef<T>(initialValue: T): MutableRefObject<T>;

从第8号代码可以看出,如果希望返回一个可变对象,可以在泛型中添加null。 比如:

const inputRef = useRef<HTMLInputElement|null>(null); // 将返回一个可变对象
  • 第二种定义
 // convenience overload for refs given as a ref prop as they typically start with a null value
 // 第二种类型定义:返回一个不可变对象
 // 重载:下面这种使用方式,将返回一个不可变的引用对象,也就是返回的对象的current属性是只读的
 function useRef<T>(initialValue: T|null): RefObject<T>;
const inputRef = useRef<HTMLInputElement>(null); // 将返回一个不可变对象
  • 第三种定义
 // convenience overload for potentially undefined initialValue / call with 0 arguments
 // has a default to stop it from defaulting to {} instead
 function useRef<T = undefined>(): MutableRefObject<T | undefined>;
const inputRef = useRef(); // 将返回一个可变对象
  • 可变对象和不可变对象
// 可变对象
interface MutableRefObject<T> {
    current: T;
}

// 不可变对象
interface RefObject<T> {
    readonly current: T | null;
}

useState

/**
 * Returns a stateful value, and a function to update it.
 *
 * @version 16.8.0
 * @see https://reactjs.org/docs/hooks-reference.html#usestate
 */
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
// convenience overload when first argument is omitted
/**
 * Returns a stateful value, and a function to update it.
 *
 * @version 16.8.0
 * @see https://reactjs.org/docs/hooks-reference.html#usestate
 */
function useState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];

从useState的类型定义可以看出有两种形式,一种是有初始值,一种是没有初始值。useState会返回状态值以及一个更新函数。更新函数的定义如下:

Dispatch表示setState的类型,是一个没有返回值的方法,SetStateAction定义了setState的入参类型,是S或者返回S类型值的一个函数。

type Dispatch<A> = (value: A) => void;

// Unlike the class component setState, the updates are not allowed to be partial
type SetStateAction<S> = S | ((prevState: S) => S);

forwardRef

function forwardRef<T, P = {}>(render: ForwardRefRenderFunction<T, P>): ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>>;

/** Ensures that the props do not include ref at all */
type PropsWithoutRef<P> =
    // Pick would not be sufficient for this. We'd like to avoid unnecessary mapping and need a distributive conditional to support unions.
    // 条件类型
    // see: https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types
    // https://github.com/Microsoft/TypeScript/issues/28339
    P extends any ? ('ref' extends keyof P ? Pick<P, Exclude<keyof P, 'ref'>> : P) : P;

interface RefAttributes<T> extends Attributes {
    ref?: Ref<T> | undefined;
}

// will show `ForwardRef(${Component.displayName || Component.name})` in devtools by default,
// but can be given its own specific name
interface ForwardRefExoticComponent<P> extends NamedExoticComponent<P> {
    defaultProps?: Partial<P> | undefined;
    propTypes?: WeakValidationMap<P> | undefined;
}

interface NamedExoticComponent<P = {}> extends ExoticComponent<P> {
    displayName?: string | undefined;
}

// TODO: similar to how Fragment is actually a symbol, the values returned from createContext,
 // forwardRef and memo are actually objects that are treated specially by the renderer; see:
 // https://github.com/facebook/react/blob/v16.6.0/packages/react/src/ReactContext.js#L35-L48
 // https://github.com/facebook/react/blob/v16.6.0/packages/react/src/forwardRef.js#L42-L45
 // https://github.com/facebook/react/blob/v16.6.0/packages/react/src/memo.js#L27-L31
 // However, we have no way of telling the JSX parser that it's a JSX element type or its props other than
 // by pretending to be a normal component.
 //
 // We don't just use ComponentType or FunctionComponent types because you are not supposed to attach statics to this
 // object, but rather to the original function.
 interface ExoticComponent<P = {}> {
     /**
      * **NOTE**: Exotic components are not callable.
      */
     (props: P): (ReactElement|null);
     readonly $$typeof: symbol;
 }

 interface ForwardRefRenderFunction<T, P = {}> {
     (props: PropsWithChildren<P>, ref: ForwardedRef<T>): ReactElement | null;
     displayName?: string | undefined;
     // explicit rejected with `never` required due to
     // https://github.com/microsoft/TypeScript/issues/36826
     /**
      * defaultProps are not supported on render functions
      */
     defaultProps?: never | undefined;
     /**
      * propTypes are not supported on render functions
      */
     propTypes?: never | undefined;
 }

ForwardRefRenderFunctionForwardRefExoticComponent之前的区别

参考

When to use JSX.Element vs ReactNode vs ReactElement?

如何优雅地在 React 中使用TypeScript,看这一篇就够了!