为了使系统方便维护,我们会在项目中引入 Typescript,通过使用 TypeScript,可以更好地定义对象和函数的类型,减少错误,提高代码的可读性和可维护性。然而大部分新手刚接触 Typescript 或者 React ,不知道如何声明Props,类组件,函数组件等。接下来让我们通过例子来走进 React 和 Typescript 世界。

常见的Props类型声明

下面是常见的类型声明,我们通过 type 关键字来声明,当然你也可以通过 interface 来声明。

type Props = {
  name: string; // 姓名
  age: number; // 年龄
  
  disabled: boolean; // 是否禁用
  
  students: Array<{
    name: string; // 姓名
    age: number; // 年龄
  }>; // 学生列表

  people: {
    name: string; // 姓名
    age: number; // 年龄
  },
  
  obj2: object; // 对象类型
  obj3: {}; // 对象类型

  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void; // 原生事件类型
  onClick: (e: React.MouseEvent<HTMLButtonElement>) => void; // 原生事件类型
  
  onSubmit: (val: string) => void; // 自定义事件
 
  optional?: string; // 可选属性
}

在 TypeScript 中,object 作为非原始类型,可能会引起误解。实际上,object 并不是指“任何对象”,而是指“任何非原始类型”。也就是说,object 表示的并不是数字、字符串、布尔值、符号、null 或 undefined。通常在声明对象时,我们不会用 object,而是会使用具体的对象的属性,例如上述的 people ,如果需要声明无法确定的键值对,可以用 Record<string, any>

类组件声明

上述声明一些常用的组件属性,我们来声明一个类组件,通常需要声明组件的 PropsState

interface Props {
  message: string;
};
interface State {
  count: number; 
};
class App extends React.Component<Props, State> {
  state: State = {
    count: 0,
  };
  render() {
    const {
      message,
    } = this.props;
    const {
      count,
    } = this.state;
    return (
      <div>
        {message} {count}
      </div>
    );
  }
}

函数组件声明

我们都知道函数组件只需要一个 Props ,所以声明函数组件跟类组件还是有区别的,最简单的函数组件声明如下:

type Props = {
  name: string;
};
// 或者
// interface Props {
//   name: string;
// }

const App = (props: Props) => {
  return props.name;
};
// 或者
const App: React.FC<Props> = (props) => {
  return props.name;
}
// 或者
const App: React.FC<Props> = ({ name }): string => {
  return name;
}

组件children的类型

在 React 可以通过 children 来自定义子元素的渲染,我们可以这样声明:

interface Props {
  children: React.ReactNode;
}
// 或者
interface Props {
  children: JSX.Element;
}
// 或者
interface Props {
  children: React.ReactElement;
}
// 或者
interface Props {
  children: React.ReactNode | JSX.Element;
}

ReactNode和JSX.Element,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;
// ============== ReactElement声明
type Key = string | number
interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
  type: T;
  props: P;
  key: Key | null;
}
// ============== JSX.Element声明
declare global {
  namespace JSX {
    interface Element extends React.ReactElement<any, any> { }
  }
}

从上述定义看 JSX.Element 继承了 ReactElement ,只不过在 Props 和 State 声明指定为 any,更加通用,然而 ReactNode 更全面,通常我们 React 组件不一定是 JSX 元素,也可能是字符串,数字,空等类型,所以 children 用 ReactNode 来定义就对了。

hooks声明

useState 声明 state 时,会接受一个泛型,如果我们不指定类型,通常会根据初始值自动推断出类型,下面的例子中,isDone 会被推断为 boolean 类型。

const [isDone, setDone] = useState(false);

如果 useState 参数是空,ts 会推断这个 state 是 undefined,接下来我们看下面的例子,我们手动设置 name 为 "",ts 会抛出警告。「Argument of type '""' is not assignable to parameter of type 'SetStateAction<undefined>'.」

function App() {
  const [name, setName] = useState();

  return (
    <div>
      <button 
        onClick={() => {
          setName("");
        }}>
          click
        </button>
      { name }
    </div>
  )
}

所以我们需要手动声明 state 的类型,即:

const [name, setName] = useState<string>();  // name = [string | undefined]

下面是一些常见的 useState 声明:

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


setUser(newUser); // newUser 可以是 User 或者 null

const [user, setUser] = useState<User>({}); // 报错 Argument of type '{}' is not assignable to parameter of type 'User | (() => User)'.
const [user, setUser] = useState<User>({} as User); // 正常
本文为原创,未经授权,禁止任何媒体或个人自媒体转载
商业侵权必究,如需授权请联系[email protected]