حل مشکل تکی بودن عنصر ریشه‌ای در return

Solving the Problem of Single Element Root in Return

23 بهمن 1399
حل مشکل تکی بودن عنصر ریشه ای در return

حل مشکل تکی بودن عنصر ریشه ای در return

قبلا برایتان توضیح داده بودم که در قسمت JSX کدهای هر کامپوننت فقط یک عنصر ریشه ای وجود خواهد داشت و تمامی عناصر دیگر درون آن قرار خواهند گرفت:

    render() {
        console.log('[Person.js rendering...');
        return (
            <div className={classes.Person}>
                <p onClick={this.props.click}>I'm {this.props.name} and I am {this.props.age} years old!</p>
                <p>{this.props.children}</p>
                <input type="text" onChange={this.props.changed} value={this.props.name} />
            </div>
        )
    }

در اینجا فقط یک div داریم و تگ های p و input درون آن قرار گرفته اند اما نمی توانیم این کد را به شکل زیر بنویسیم:

    render() {
        console.log('[Person.js rendering...');
        return (
            <div className={classes.Person}>
                <p onClick={this.props.click}>I'm {this.props.name} and I am {this.props.age} years old!</p>
                <p>{this.props.children}</p>
                <input type="text" onChange={this.props.changed} value={this.props.name} />
            </div>
            <div>Hello</div>
        )
    }

اگر چنین کدی بنویسیم و به مرورگر برویم با خطای failed to compile مواجه خواهیم شد:

Adjacent JSX elements must be wrapped in an enclosing tag

این خطا به ما می گوید که تگ های برادر (تگ هایی که کنار هم هستند) باید درون یک تگ واحد (تگ پدر) قرار داشته باشند.

البته راه هایی برای دور زدن این مشکل وجود دارد. اگر به کامپوننت Persons.js نگاه کنید متوجه کد زیر خواهید شد:

    render() {
        console.log('[Persons.js] rendering...');

        return this.props.persons.map((person, index) => {
            return (
                <Person
                    click={() => this.props.clicked(index)}
                    name={person.name}
                    age={person.age}
                    key={person.id}
                    changed={(event) => this.props.changed(event, person.id)} />
            );
        });
    }

ما در این کد آرایه ای از افراد مختلف را برگردانده ایم نه یک فرد و هیچ عنصر پدری نیز وجود ندارد! از نظر فنی آرایه یک شیء حساب می شود که درون آن اعضای مختلفی وجود دارند. react هم به ما اجازه می دهد که آرایه ای از اعضا را نمایش دهیم (البته تا زمانی که هر کدام یک key داشته باشند (کد بالا).

برای انجام این کار به فایل Person.js بروید:

    render() {
        console.log('[Person.js rendering...');
        return (
            <div className={classes.Person}>
                <p onClick={this.props.click}>I'm {this.props.name} and I am {this.props.age} years old!</p>
                <p>{this.props.children}</p>
                <input type="text" onChange={this.props.changed} value={this.props.name} />
            </div>
        )
    }

div اصلی و ریشه ای ما برای استایل دهی استفاده می شود اما فعلا آن را حذف می کنیم تا عناصر درون آن کنار هم باشند. حالا می توانیم به جای آنکه آن ها را به شکل عادی با ()return برگردانیم آن ها را به شکل یک آرایه (با علامت براکت) و با متد return برمی گردانیم:

    render() {
        console.log('[Person.js rendering...');
        return [
            <p onClick={this.props.click}>I'm {this.props.name} and I am {this.props.age} years old!</p>,
            <p>{this.props.children}</p>,
            <input type="text" onChange={this.props.changed} value={this.props.name} />
        ]
    }

در کد بالا به جای پرانتزهای return از علامت براکت استفاده کرده ایم تا کد را به شکل یک آرایه در بیاوریم. سپس بین هر دو عنصر از ویرگول استفاده کرده ایم به طوری که انگار اعضای آرایه را از هم جدا کرده ایم (به ویرگول های بین تگ ها دقت کنید).

حالا اگر به مرورگر برویم و Toggle Persons را کلیک کنیم با یک هشدار مواجه می شویم (در قسمت Console مرورگر):

Warning: Each child in a list should have a unique "key" prop. See https://fb.me/react-warning-keys for more information

می بینید که باز هم به همان Key برخورد کرده ایم بنابراین به Person.js برگشته و به اعضای آرایه یک key اضافه می کنیم:

    render() {
        console.log('[Person.js rendering...');
        return [
            <p key="i1" onClick={this.props.click}>I'm {this.props.name} and I am {this.props.age} years old!</p>,
            <p key="i2">{this.props.children}</p>,
            <input key="i3" type="text" onChange={this.props.changed} value={this.props.name} />
        ]
    }

ما در اینجا پایگاه داده نداریم تا با استفاده از آن مقدار خاصی را به key ها بدهیم بنابراین فعلا مقادیر i1 تا i3 را به صورت دستی وارد کرده ایم تا از بروز خطا جلوگیری کنیم. حالا اگر به مرورگر بروید همه چیز مانند قبل کار می کند. البته استایل های خود را از دست داده ایم چرا که div ریشه ای موجود در return کار استایل دهی را نیز بر عهده داشت و حالا که آن را حذف کرده ایم، استایل های ما نیز از بین رفته اند.

با اینکه این روش کاملا جواب می دهد اما کمی شلوغ است؛ باید پرانتزها را با براکت ها عوض کنیم، بین هر دو تگ از ویرگول استفاده کنیم، برای هر عنصر یک key تعیین کنیم و الی آخر... روش بهتری نیز برای این کار وجود دارد.

ابتدا یک پوشه به نام hoc در پوشه اصلی پروژه ایجاد کنید و سپس فایل جدید ایجاد کرده و نام آن را Auxiliary.js قرار دهید. hoc مخفف higher order component است و به زبان ساده این نوع از کامپوننت ها، کامپوننت های دیگر را در برمی گیرند. حالا درون آن می نویسیم:

import React from 'react';

const aux = props => props.children;

export default aux;

واضح است که اولین کار import کردن کتابخانه react می باشد. سپس یک تابع به نام aux داریم که props را دریافت می کند و تنها children را برمی گرداند. children یک خصوصیت ویژه در react است و هر چیزی که درون تگ های آغازین و پایانی این کامپوننت قرار بگیرد را به صورت خروجی برمی گرداند. در آخر نیز این کامپوننت را export کرده ایم تا بتوانیم از آن در Person.js استفاده کنیم.

درون فایل Person.js می گوییم:

import Aux from '../../../hoc/Auxiliary';

سپس در قسمت JSX آن از Aux استفاده می کنیم:

    render() {
        console.log('[Person.js rendering...');
        return (
            <Aux>
            <p key="i1" onClick={this.props.click}>I'm {this.props.name} and I am {this.props.age} years old!</p>
            <p key="i2">{this.props.children}</p>
            <input key="i3" type="text" onChange={this.props.changed} value={this.props.name} />
            </Aux>
        );
    }

همانطور که می بینید از Aux به جای عنصر ریشه ای استفاده کرده ایم و برنامه ما نیز به خوبی کار می کند. در واقع خصوصیت children که در فایل Auxiliary.js از آن استفاده کردیم به همین قسمت اشاره می کند؛ یعنی تمام چیز هایی که بین تگ آغازین و پایانی <Aux> قرار بگیرند و آن ها را برمی گرداند.

نکته: از آنجایی که درون فایل Auxiliary.js از هیچ کد JSX ای استفاده نمی کنیم، می توانیم کتابخانه react را اصلا import نکنیم:

const aux = props => props.children;

export default aux;

اگر دوست دارید علت این مسئله (داشتن یک عنصر ریشه ای) را بدانید به شما پیشنهاد می دهم که این مقاله بسیار کوتاه را مطالعه کنید.

نکته: از نسخه 16.2 به بعد react عنصری به نام Fragment را ارائه داده است که همان معادل Aux خودمان است. بنابراین بدون ایجاد کردن Aux می توانید کدهایتان را اینطور بنویسید:

    render() {
        console.log('[Person.js rendering...');
        return (
            <React.Fragment>
                <p key="i1" onClick={this.props.click}>I'm {this.props.name} and I am {this.props.age} years old!</p>
                <p key="i2">{this.props.children}</p>
                <input key="i3" type="text" onChange={this.props.changed} value={this.props.name} />
            </React.Fragment>
        );
    }

البته اگر در هنگام import کردن React این کار را انجام دهید:

import React, { Component, Fragment } from 'react';

می توانید کدها را به این شکل هم بنویسید:

    render() {
        console.log('[Person.js rendering...');
        return (
            <Fragment>
                <p key="i1" onClick={this.props.click}>I'm {this.props.name} and I am {this.props.age} years old!</p>
                <p key="i2">{this.props.children}</p>
                <input key="i3" type="text" onChange={this.props.changed} value={this.props.name} />
            </Fragment>
        );
    }

Fragment همان کاری را می کند که Aux می کند: عناصری را بدون عنصر ریشه ای در کنار هم قرار می دهد. توجه داشته باشید که Aux یا Fragment هیچ کدام هیچ عنصری را در HTML ایجاد نمیکنند. البته ما در طول دوره از Aux استفاده می کنیم.

امیدوارم با این مسئله به طور کامل آشنا شده باشید.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری دوره جامع آموزش ری اکت توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.