TypeScriptで柔軟なコンポーネントを構築するための便利なReactAPI
公開: 2022-03-10 React.createElement
を直接使用したことがありますか? React.cloneElement
はどうですか? Reactは、JSXをHTMLに変換するだけではありません。 さらに、Reactライブラリに付属しているあまり知られていない(しかし非常に便利な)APIの知識をレベルアップするのに役立ちます。 コンポーネントの統合と有用性を大幅に向上させることができる、それらのいくつかとそれらのユースケースのいくつかについて説明します。
この記事では、あまり一般的には知られていないが、Web開発者にとって非常に役立ついくつかの便利なReactAPIについて説明します。 読者はReactとJSX構文の経験が必要です。Typescriptの知識は役に立ちますが、必須ではありません。 読者は、Reactアプリケーションで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の2つのデータを定義する方法が必要です。 したがって、この情報とともに渡されるデータ構造が必要になります。 したがって、開発者はコンポーネントをそのように作成します。
function App() { return ( <Tabbar links={[ {title: 'First', url: '/first'}, {title: 'Second', url: '/second'}] } /> ) }
これは素晴らしいことですが、ユーザーが要素でa
なくbutton
要素をレンダリングしたい場合はどうでしょうか。 さて、レンダリングする要素のタイプをコンポーネントに指示する別のプロパティを追加することができます。
しかし、これがすぐに扱いにくくなることがわかります。最大限の柔軟性を実現するには、さまざまなユースケースやエッジケースを処理するために、ますます多くのプロパティをサポートする必要があります。
React.cloneElement
を使用するより良い方法があります。
まず、 ReactNode
タイプを参照するようにインターフェイスを変更します。 これは、Reactがレンダリングできるすべてのもの、通常はJSX要素を含むジェネリック型ですが、文字列やnull
の場合もあります。 これは、ReactコンポーネントまたはJSXを引数としてインラインで受け入れるように指定する場合に役立ちます。
export interface ITabbarProps { links: ReactNode[] }
ここで、ユーザーにReact要素を提供するように依頼し、希望どおりにレンダリングします。
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'}) )} </> ) }
ここでは、渡す各要素にキープロップを追加し、すべてのリンクを同時に太字にします。 これで、次のような小道具として任意のReact要素を受け入れることができます。
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ループの一般的なイベントや非同期イベントなど、変数が存在するコンテキストが原因で、変数が正しい値にならない場合があります。 これは、字句スコープが原因です。 新しい関数が作成されると、字句スコープが保持されます。 新しい関数がないため、 newVal
の字句スコープは保持されません。したがって、値は実際には使用されるまでに逆参照されます。
setTimeout(() => { setMyValue(newVal) // this will not work }, 1000)
あなたがする必要があるのは、関数としてセッターを利用することです。 新しい関数を作成することにより、変数参照は字句スコープに保持され、currentValはReactuseStateフック自体によって渡されます。
setTimeout(() => { setMyValue((currentVal) => { return newVal }) }, 1000)
これにより、setter関数が正しいコンテキストで呼び出されるため、値が正しく更新されます。 ここでReactが行うことは、React状態の更新が発生するように、正しいコンテキストで関数を呼び出すことです。 これは、現在の値に基づいて動作することが役立つ他の状況でも使用できます。Reactは、最初の引数を現在の値として関数を呼び出します。
注:非同期とクロージャのトピックに関する追加情報については、KentC.Doddsによる「 useState
」を読むことをお勧めします。
JSXインライン関数
JSXインライン関数のCodepenデモは次のとおりです。
正確にはReactAPIごとではありません。
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
要素の1つまたは2つのプロパティをオーバーライドしたいが、開発者にボタンに他のプロパティを追加するオプションを提供したいとします。 type='button'
またはtype='submit'
設定など。 ボタン要素全体を再作成するのではなく、既存のプロパティを拡張し、さらに1つの小道具を追加するだけです。
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> ) }
ここでは、Button要素に渡されたprop className
を取得し、それを挿入し直します。propがundefined
の場合の安全性チェックを行います。
結論
Reactは非常に強力なライブラリであり、急速に人気を博しているのには十分な理由があります。 パフォーマンスが高く、保守が容易なWebアプリを構築するための優れたツールセットを提供します。 非常に柔軟性があり、同時に非常に厳格であるため、使用方法を知っていると非常に便利です。 これらは注目に値するAPIのほんの一部であり、ほとんど見過ごされています。 次のプロジェクトで試してみてください!
最新のReactAPI、フックについてさらに読むには、useHooks()を読むことをお勧めします。 Typescriptチートシートには、ReactおよびTypescriptフックに関する優れた情報も含まれています。