用于使用 TypeScript 构建灵活组件的有用 React API
已发表: 2022-03-10 你曾经直接使用过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 演示:
不完全是一个 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 解构来提取我们关心的道具( status
和children
),并将任何其他属性声明为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 的重要信息。