用于使用 TypeScript 构建灵活组件的有用 React API

已发表: 2022-03-10
快速总结↬ React with JSX 是制作易于使用的组件的绝佳工具。 Typescript 组件让开发人员将您的组件集成到他们的应用程序中并探索您的 API 是一种绝对的乐趣。 在本文中了解三个鲜为人知的 React API,它们可以让您的组件更上一层楼,并帮助您构建更好的 React 组件。

你曾经直接使用过React.createElement吗? React.cloneElement呢? React 不仅仅是将您的 JSX 转换为 HTML。 更多,并帮助您提高对 React 库附带的鲜为人知(但非常有用)的 API 的了解。 我们将介绍其中的一些以及它们的一些用例,这些用例可以极大地增强组件的集成度和实用性。

在本文中,我们将介绍一些不太为人所知但对 Web 开发人员非常有用的有用的 React API。 读者应该熟悉 React 和 JSX 语法,Typescript 知识很有帮助但不是必需的。 在 React 应用程序中使用 React 组件时,读者将了解他们需要了解的所有内容。

React.cloneElement

大多数开发人员可能从未听说过或从未使用过cloneElement 。 它是最近才引入的,用于替换现已弃用的cloneWithProps函数。 cloneElement克隆一个元素,它还允许您将新道具与现有元素合并,修改它们或在您认为合适的时候覆盖它们。 这为为功能组件构建世界级 API 开辟了极其强大的选择。 看看签名。

 function cloneElement( element, props?, ...children)

这是精简的 Typescript 版本:

 function cloneElement( element: ReactElement, props?: HTMLAttributes, ...children: ReactNode[]): ReactElement

您可以获取一个元素,对其进行修改,甚至覆盖其子元素,然后将其作为新元素返回。 看看下面的例子。 假设我们要创建一个链接的TabBar组件。 这可能看起来像这样。

 export interface ITabbarProps { links: {title: string, url: string}[] } export default function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => <a key={i} href={e.url}>{e.title}</a> )} </> ) }

TabBar 是一个链接列表,但我们需要一种方法来定义两个数据,链接的标题和 URL。 所以我们需要一个带有这些信息的数据结构。 所以我们的开发人员会让我们的组件像这样。

 function App() { return ( <Tabbar links={[ {title: 'First', url: '/first'}, {title: 'Second', url: '/second'}] } /> ) }

这很好,但是如果用户想要渲染button元素而a元素怎么办? 好吧,我们可以添加另一个属性来告诉组件要渲染什么类型的元素。

但是你可以看到这很快就会变得笨拙,我们需要支持越来越多的属性来处理各种用例和边缘情况,以获得最大的灵活性。

这是一个更好的方法,使用React.cloneElement

我们将首先更改我们的接口以引用ReactNode类型。 这是一个泛型类型,包含 React 可以呈现的任何内容,通常是 JSX 元素,但也可以是字符串,甚至是null 。 这对于指定您希望接受 React 组件或 JSX 作为内联参数很有用。

 export interface ITabbarProps { links: ReactNode[] }

现在我们要求用户给我们一些 React Elements,我们将按照我们的意愿渲染它们。

 function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => e // simply return the element itself )} </> ) }

这是完全有效的,并且会渲染我们的元素。 但是我们忘记了一些事情。 一方面, key ! 我们想要添加键,以便 React 可以有效地呈现我们的列表。 我们还想改变我们的元素以进行必要的转换,以便它们适合我们的样式,例如className等。

我们可以用React.cloneElement来做这些,另一个用于检查参数的函数React.isValidElement是否符合我们的期望!

跳跃后更多! 继续往下看↓

React.isValidElement

如果元素是有效的 React 元素并且 React 可以渲染它,则此函数返回true 。 这是修改上一个示例中的元素的示例。

 function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => isValidElement(e) && cloneElement(e, {key: `${i}`, className: 'bold'}) )} </> ) }

在这里,我们为传入的每个元素添加了一个 key prop,同时使每个链接变为粗体! 我们现在可以接受任意 React Elements 作为 props,如下所示:

 function App() { return ( <Tabbar links={[ <a href='/first'>First</a>, <button type='button'>Second</button> ]} /> ) }

我们可以覆盖元素上设置的任何道具,并轻松接受各种元素,从而使我们的组件更加灵活和易于使用。

这里的好处是如果我们想为我们的按钮设置一个自定义的onClick处理程序,我们可以这样做。 接受 React 元素本身作为参数是赋予组件设计灵活性的强大方法。

useState设置器函数

使用钩子! useState钩子非常有用,并且是一个出色的 API,可用于将状态快速构建到组件中,如下所示:

 const [myValue, setMyValue] = useState()

由于 JavaScript 运行时,它可能会出现一些问题。 还记得闭包吗?

在某些情况下,变量可能不是正确的值,因为它所在的上下文,例如在常见的 for 循环或异步事件中。 这是因为词法作用域。 当创建一个新函数时,词法范围被保留。 因为没有 new 函数,所以不会保留newVal的词法范围,因此该值实际上在使用时被取消引用。

 setTimeout(() => { setMyValue(newVal) // this will not work }, 1000)

您需要做的是将 setter 用作函数。 通过创建一个新函数,变量引用保留在词法范围内,并且 currentVal 由 React useState Hook 本身传入。

 setTimeout(() => { setMyValue((currentVal) => { return newVal }) }, 1000)

这将确保您的值正确更新,因为在正确的上下文中调用了 setter 函数。 React 在这里所做的是在正确的上下文中调用您的函数以发生 React 状态更新。 这也可以用于其他有助于对当前值进行操作的情况,React 使用第一个参数作为当前值调用您的函数。

注意关于异步和闭包主题的更多阅读,我建议阅读 Kent C. Dodds 的“ useState Lazy Initialization And Function Updates”。

JSX 内联函数

这是 JSX 内联函数的 Codepen 演示:

请参阅 Gaurav Khanna 的 Pen [Hello World in React](https://codepen.io/smashingmag/pen/QWgQQKR)。

请参阅 Gaurav Khanna 的笔 Hello World in React。

不完全是一个 React API。

JSX 确实支持内联函数,并且它对于使用内联变量声明简单逻辑非常有用,只要它返回一个 JSX 元素。

这是一个例子:

 function App() { return ( <> {(() => { const darkMode = isDarkMode() if (darkMode) { return ( <div className='dark-mode'></div> ) } else { return ( <div className='light-mode'></div> ) // we can declare JSX anywhere! } })()} // don't forget to call the function! </> ) }

这里我们在 JSX 中声明代码,我们可以运行任意代码,我们所要做的就是返回一个 JSX 函数来渲染。

我们可以让它有条件,或者简单地执行一些逻辑。 注意内联函数周围的括号。 特别是在我们调用这个函数的地方,如果我们愿意,我们甚至可以从周围的上下文中将一个参数传递给这个函数!

 })()}

这在您希望以比标准.map允许在 JSX 元素内部更复杂的方式对集合数据结构进行操作的情况下很有用。

 function App() { return ( <> {(() => { let str = '' for (let i = 0; i < 10; i++) { str += i } return (<p>{str}</p>) })()} </> ) }

在这里,我们可以运行一些代码来循环一组数字,然后将它们内联显示。 如果您使用 Gatsby 等静态站点生成器,则此步骤也将被预先计算。

component extends type

对于创建自动完成友好的组件非常有用,此功能允许您创建扩展现有HTMLElements或其他组件的组件。 对于在 Typescript 中正确键入元素界面最有用,但对于 JavaScript,实际应用程序是相同的。

这是一个简单的例子,假设我们想要覆盖一个button元素的一个或两个属性,但仍然让开发人员可以选择向按钮添加其他属性。 比如设置type='button'type='submit' 。 我们显然不想重新创建整个 button 元素,我们只想扩展它现有的属性,也许再添加一个 prop。

 import React, { ButtonHTMLAttributes } from 'react'

首先我们导入 React 和ButtonHTMLAttributes类,这是一个包含HTMLButtonElement属性的类型。 您可以在此处阅读此类接口的源代码:

你可以看到 React 团队已经在 TypeScript 中重新实现了所有 Web 的 API,因此可以进行类型检查。

接下来,我们像这样声明我们的接口,添加我们的status属性。

 interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' }

最后,我们做了几件事。 我们使用 ES6 解构来提取我们关心的道具( statuschildren ),并将任何其他属性声明为rest 。 在我们的 JSX 输出中,我们返回一个按钮元素,使用 ES6 结构向该元素添加任何其他属性。

 function Button(props: ButtonProps) { const { status, children, ...rest } = props // rest has any other props return ( <button className={`${status}`} {...rest} // we pass the rest of the props back into the element > {children} </button> ) }

因此,现在开发人员可以添加type道具或按钮通常具有的任何其他道具。 我们在className中提供了一个额外的属性来设置按钮的样式。

这是整个示例:

 import React, { ButtonHTMLAttributes } from 'react' export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' } export default function Button(props: ButtonProps) { const { status, children, ...rest } = props return ( <button className={`${status}`} {...rest} > {children} </button> ) }

这提供了一种创建符合您的样式准则的可重用内部组件的好方法,而无需重新构建整个 HTML 元素! 您可以简单地覆盖整个道具,例如根据状态设置className或允许传入其他类名。

 import React, { ButtonHTMLAttributes } from 'react' export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' } export default function Button(props: ButtonProps) { const { status, children, className, ...rest } = props return ( <button className={`${status || ''} ${className || ''}`} {...rest} > {children} </button> ) }

在这里,我们将 prop className传递给 Button 元素,并将其重新插入,并在 prop 为undefined的情况下进行安全检查。

结论

React 是一个非常强大的库,它迅速流行是有充分理由的。 它为您提供了一个强大的工具集来构建高性能且易于维护的 Web 应用程序。 它非常灵活,同时又非常严格,如果您知道如何使用它,这将非常有用。 这些只是一些值得注意且在很大程度上被忽视的 API。 在您的下一个项目中尝试一下!

要进一步了解最新的 React API、钩子,我建议阅读 useHooks()。 Typescript Cheatsheet 也有一些关于 React 和 Typescript Hooks 的重要信息。