React 小技巧
3 min read
目录

isValidElement

官方解释

判断是不是 React 元素:只有通过 createElement 返回的 JSX 标签 和 对象 会被认为是 React 元素。)

例如,即使像 42 这样的数字是一个有效的 React 节点(并且能从一个组件中返回),但它仍然不是有效的 React 元素。使用 createPortal 创建的数组和 portal 也 不被 认为是 React 元素。

// JSX
console.log(isValidElement(<p />)); // true  
console.log(isValidElement(<MyComponent />)); // true

// createElement 创建的
console.log(isValidElement(createElement('p'))); // true  
console.log(isValidElement(createElement(MyComponent))); // true
console.log(isValidElement(null)); // false  
console.log(isValidElement(25)); // false  
console.log(isValidElement('Hello')); // false  
console.log(isValidElement({ age: 42 })); // false  
console.log(isValidElement([<div />, <div />])); // false  
console.log(isValidElement(MyComponent)); // false

场景

需要明确传入的参数必须是可作为组件的 react 元素,比如下面是一个简单的案例:常见的 table 组件中会有大量的筛选组件,这里的 appliedfilters 一个包含多个筛选组件对象的数组:

export interface AppliedFilters {
  key: string
  filter: ReactNode
}
const appliedfilters: AppliedFilters[] = []

if (table.getColumn("status")) {
appliedfilters.push({
  key: "status",
  filter: (
    <FilterCheckbox
      column={table.getColumn("status")}
      title="状态"
      options={statusOptions}
    />
  ),
})
}

渲染每一个 filter 需要确认时 react 元素

{filters.map((f) => {
    if (isValidElement(f.filter)) {
      const Comp = f.filter
      return <Fragment key={f.key}>{Comp}</Fragment>
    }
    // other code
})}

关于 filter 类型声明 ReactNode 保证其可扩展性和灵活性;当然 filter 还可能有很多种情况,所声明基础类型接口的时候,泛用性尽可能的好,然后在具体组件可以进一步约束类型。
这对于维护和开发个人认为是一个不错的方式。当然技术没有银弹,方案也不是绝对的,这里仅仅作为参考

React 中的常用类型

props 类型的声明

常见的也是最直观简单的方式是给函数组件的 props 直接声明每一个属性的类型,比如下面封装了一个简单的基础组件 Show ,它接收三个属性,这里可以分别定义:

import React from "react"

interface ShowProps {
  when?: boolean
  fallback?: React.ReactNode
  children?: React.ReactNode
}

export const Show = ({ when, fallback, children }: ShowProps) => {
  return when ? <>{children}</> : <>{fallback}</>
}

PropsWithChildren

在上面的组件中甚至日常封装的众多组件都包含了 children 属性,反复声明让代码有点冗余,当声明的类型足够多的时候,是否含有 children 属性就略显麻烦,所以 react 提供了一个工具类 PropsWithChildren

type PropsWithChildren<P = unknown> = P & { children?: ReactNode | undefined };

现在改进一下 Show 组件可以如下样子:

import React, { PropsWithChildren } from "react"

interface ShowProps {
  when?: boolean
  fallback?: React.ReactNode
}

export const Show = ({ when, fallback, children }: PropsWithChildren<ShowProps>) => {
  return when ? <>{children}</> : <>{fallback}</>
}

现在看 ShowProps 的类型就更加有灵活性,而且也不用每次都需要声明 children 类型了。

如果是一个容器组件有且只有一个属性 children ,可以直接声明 PropsWithChildren 而不需要传递类型参数。

…props 剩余传参

有时我们希望将所有剩余的 props 全部传递到某些底层组件中,除了组件,在原生的 html 元素中这种方式也有效。

import { ComponentProps } from 'react'

import ProductTile from './ProductTile'

type Props = {
  color: string
} & ComponentProps<typeof ProductTile>

function ProminentProductTile({ color, ...props }: Props) {
  return (
    <div style={{ background: color }}>
      <ProductTile {...props} />
    </div>
  )
}

更多阅读

关于 useState、useReducer、useRef 和常见的事件类型 Robin Weser | Clean React with TypeScript