تعیین تایپ صحیح children prop در React در ابتدا می‌تواند مشکلاتی را ایجاد کند. اگر بخواهیم آن‌ها را به‌عنوان تایپ خاصی از JSX در نظر بگیریم، ممکن است در رندر کامپوننت‌های child با مشکلاتی مواجه شویم. همچنین مشکل پارادوکس انتخاب نیز مطرح است، زیرا گزینه‌های متعددی برای تایپ children prop وجود دارد و همین موضوع ممکن است باعث شود تا هنگام تصمیم‌گیری دچارخستگی شویم. در این مقاله قصد داریم نحوه استفاده از children prop با استفاده از تایپ اسکریپت را باهم بررسی کنیم.

Children در JSX

هنگامی که یک عبارت JSX را با تگ‌های باز و بسته می‌نویسیم، محتوای ارسال شده بین آن‌ها “child” نامیده می‌شود. به عنوان مثال:

<Border> Hey, I represent the JSX children! </Border>

در این مثال، رشته Hey, I represent the JSX children!به child رندر شده در Borderاشاره دارد.

برعکس، برای دسترسی به محتوای ارسال شده بین تگ‌های باز و بسته JSX، React از props.childrenاستفاده می‌کند. به عنوان مثال، Borderمی‌تواند children prop را به صورت زیر دریافت کند:

const Border = ({children}) => {
   return <div style={{border: "1px solid red"}}>
      {children}
   </div>
}

Border به این صورت children prop را می‌پذیرد، سپس childrenرا در یک divبا border  استایل ۱px solid redرندر می‌کند.

این مفهوم، یعنی دریافت و دستکاری محتوای ارسال شده در تگ‌های باز و بسته یک عبارت JSX کاربرد اصلی children prop است.

بررسی تایپ‌های مختلف children که پشتیبانی می‌شوند

به طور دقیق، تعداد انگشت شماری از انواع محتوای پشتیبانی شده وجود دارد که می‌توانند در تگ‌های باز و بسته عبارت JSX مورد استفاده قرار بگیرند. در ادامه تعدادی از پرکاربردترین آن‌ها را باهم بررسی خواهیم کرد.

Strings

همانطور که در مثال زیر می‌بینیم رشته‌ها جزء تایپ‌‌های معتبر children هستند:

<YourComponent> This is a valid child string </YourComponent />

باید به این نکته توجه داشته باشیم که رشته This is a valid child stringدر Your Componentای که داریم props.childrenخواهد بود.

JSX

همچنین می‌توانیم سایر عناصر JSX را به عنوان childهای معتبر منتقل کنیم. این کار معمولاً هنگام ترکیب اجزای مختلف تودرتو مفید است. به عنوان مثال:

<Wrapper>
  <YourFirstComponent />
  <YourSecondComponent />
</Wrapper>

همانطور که در ادامه می‌بینیم، ترکیب تایپ‌های مختلف childها کاملاً قابل قبول است:

<Wrapper>
  I am a valid string child
  <YourFirstComponent />
  <YourSecondComponent />
</Wrapper>

عبارات جاوااسکریپت

عبارات جاوااسکریپت به همان اندازه جزو تایپ‌های معتبر childها به‌شمار می‌آیند. به عنوان مثال:

<YourFirstComponent> {myScopedVariableReference} </YourFirstComponent>

باید به این نکته توجه داشته باشیم که عبارات در JSXداخل پرانتز نوشته می‌شوند.

توابع

توابع نیز همانند مواردی که بررسی کردیم جزو تایپ‌های معتبر هستند. مثلا:

<YourFirstComponent> 
  {() => <div>{myScopedVariableReference}</div>} 
</YourFirstComponent>

همانطور که می‌بینیم children prop را می‌توانیم با طیف گسترده‌ای از انواع داده‌ها نشان دهیم. ممکن است تمایل داشته باشیم آن‌ها را به صورت دستی تایپ کنیم، مانند:

type Props = {
  children: string | JSX.Element | JSX.Element[] | () => JSX.Element
}

const YourComponent = ({children} : Props) => {
  return children
}

این موضوع ممکن است ایده خوبی به نظر برسد، اما children prop را به طور کامل نشان نمی‌دهد. همچنین در مورد fragmentها، portalها و مقادیر رندر نادیده گرفته شده، مانند undefined، null، trueیا falseنیز سوالاتی مطرح می‌شود.

یک مثال کامل ممکن است چیزی شبیه به کد زیر باشد:

type ReactText = string | number;
type ReactChild = ReactElement | ReactText;

interface ReactNodeArray extends Array<ReactNode> {}
type ReactFragment = {} | ReactNodeArray;
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;

type Props = {
  children: ReactNode
}

// source: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/d076add9f29db350a19bd94c37b197729cc02f87/types/react/index.d.ts

تایپ‌های extend شده برای ReactPortal و ReactElement را می‌بینیم که ممکن است کمی پیچیده به نظر برسند. اما نکته‌ای که وجود دارد این است که در عمل نمی‌خواهیم children prop را به صورت دستی تایپ کنیم. در عوض پیشنهاد می‌شود تا از تایپ‌های رسمی پشتیبانی شده که در ادامه با آن‌ها آشنا خواهیم شد استفاده کنیم.

استفاده از تایپ PropsWithChildren

تایپ React.PropsWithChildrenاینجا component prop را می‌گیرد و یک تایپ union که مناسب تایپ children prop است را برمی‌گرداند. در مثال زیر تعریف تایپ PropsWithChildrenرا بررسی می‌کنیم:

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

اکنون باید به این سوال پاسخ دهیم که تایپ propsWithChildrenچگونه کار می‌کند.

فرض کنید یک کامپوننت Fooبا props FooPropsداریم:

type FooProps = {
  name: 'foo'
}

export const Foo = (props: FooProps) => {
    return null
}

می‌توانیم children prop را به صورت زیر تعریف کنیم:

import { PropsWithChildren } from 'react'

type FooProps = {
  name: 'foo'
}

export const Foo = (props: PropsWithChildren<FooProps>) => {
    return props.children
}

وقتی که PropsWithChildrenرا به کامپوننت FooPropsمنتقل می‌کنیم، در حقیقت children prop را به صورت داخلی تایپ می‌کنیم.

توسعه‌دهندگان اکثرا استفاده از این روش برای تایپ children prop را توصیه می‌‌کنند. زیرا به boilerplate کم‌تری نیاز دارد و children prop به طور ضمنی تایپ شده است.

استفاده صریح از تایپ ReactNode

در مواردی که باید صریحاً children prop را تایپ کنیم، می‌توانیم از تایپ ReactNodeاستفاده کنیم. تعریف تایپ PropsWithChildrenبه صورت زیر است:

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

propsWithChildrenاز تایپ reactNodeاستفاده می‌کند.

همچنین می‌توانیم به‌ جای استفاده از PropsWithChildren، مستقیماً children prop را نیز تایپ کنیم:

import { ReactNode } from 'react'

type FooProps = {
  name: 'foo'
  // look here 👇
  children: ReactNode
}

export const Foo = (props: FooProps) => {
    return props.children
}

استفاده از تایپ FunctionComponent (یا FC)

interface عمومی FunctionComponentهمچنین ممکن است به عنوان تایپ مناسب children prop مورد استفاده قرار گیرد. از لحاظ داخلی، این رابط به PropsWithChildrenمتکی است.

در ادامه نحوه استفاده از آن را بررسی می‌کنیم:

import { FunctionComponent } from 'react'

type FooProps = {
  name: 'foo'
}

export const Foo: FunctionComponent<FooProps> = (props) => {
    return props.children
}

توجه به این نکته لازم است که تایپ FCیک نام مستعار برای FunctionComponentاست. همانطور که در ادامه می‌بینیم موارد استفاده از آن‌ها شبیه هم می‌باشد:

import { FC } from 'react'

type FooProps = {
  name: 'foo'
}

export const Foo: FC<FooProps> = (props) => {
    return props.children
}

استفاده از تایپ Component برای کلاس کامپوننت‌ها

اکثر پایگاه‌های کد React مدرن دیگر از کلاس کامپوننت‌ها استفاده نمی‌کنند، مگر در موارد استفاده خاص. اگر متوجه شویم که نیاز به تایپ children prop در یک کلاس کامپوننت داریم، می‌توانیم از پراپ Componentاستفاده کنیم. به عنوان مثال:

import { Component } from 'react'

type FooProps = {
  name: 'foo'
}

class Foo extends Component<FooProps> {
  render() {
    return this.props.children
  }
}

مشابه interface FunctionComponentو نام مستعار FCآن، تایپ Componentبه طور خودکار شامل children prop می‌شود.

جمع‌بندی

در این مقاله سعی کردیم نحوه استفاده از children prop با استفاده از تایپ اسکریپت را بررسی کنیم. به طور کلی پیشنهاد می‌شود در صورت امکان، از تایپ PropsWithChildrenدر پروژه‌های خود استفاده کنیم.