React Subcomponents – وبلاگ ماکسیم هکل

این اولین مقاله از یک سری 3 قسمتی در مورد اجزای فرعی React است. قسمت 2 و قسمت 3 موجود است اینجا و اینجا.
هر پروژه React که من روی آن کار کردهام، چه شخصی و چه مربوط به کار، در مقطعی به اندازهای بزرگ شده است که درک پایگاه کد آنها دشوار شده است. هر تغییر کوچکی نیاز به تفکر بیشتری دارد، اما منجر به تناقضات و هک های زیادی می شود. در میان بسیاری از مشکلاتی که من با چنین پایگاههای کدی داشتم، عدم استفاده مجدد از برخی نماها اصلیترین مورد بود: این امر منجر به کپی/پیست کردن کد زیادی از مؤلفهها/نماهای پیچیده شد تا اطمینان حاصل شود که یکسان به نظر میرسند، و کد تکراری حاصل، تعمیر و نگهداری یا آزمایش را آسانتر نمیکرد.
با استفاده از a جزء فرعی قالب می تواند به رفع همه این مشکلات کمک کند.
جزء فرعی دقیقا چیست؟
برای این مقاله، به نمای زیر به عنوان مثال اصلی نگاه خواهیم کرد: نمای ساده مقاله برای نمایش عنوان، زیرنویس، محتوا، ابرداده و نظرات یک شی مقاله. همه ما با نماهایی از این دست مواجه شده ایم و به دلایلی که در مقدمه ذکر شد می توانند واقعا مشکل ساز باشند.
نمونه ای از مولفه مشاهده مقاله
1
class MyArticleView extends React.Component {5
div className={css.mainContainer}>6
div className={css.wrapper}>7
div className={css.titleContainer}>8
div className={css.title}>9
span>{this.renderTitle()}/span>11
div className={css.subtitle}>12
div className={css.subtitleBox}> {this.renderSubtitle()}/div>15
ul className={css.articlemetadata}>16
li className={css.item}>{this.renderAuthor()}/li>17
li className={css.item}>{this.renderDate()}/li>20
div className={css.contentArticle}>21
div className={css.contentTextStyle}>{this.renderMainContent()}/div>22
span className={css.inlineComments}>{this.renderComments()}/span>
با استفاده از مولفههای فرعی، میتوانیم همان نمای دقیق را ارائه دهیم، اما با کد بسیار خواناتر و یک جزء قابل استفاده مجدد. در اینجا نتیجه ممکن است به نظر برسد:
مولفه نمای مقاله با “جزئیات فرعی” پیاده سازی شده است
1
class MyArticleView extends React.Component {6
Article.Title>{this.renderTitle()}/Article.Title>7
Article.Subtitle>{this.renderSubtitle()}/Article.Subtitle>12
Article.Content>{this.renderContent()}/Article.Content>13
Article.Comments>{this.renderComments}/Article.Comments>
در این زمینه، اجزای فرعی به این صورت تعریف می شوند مؤلفه هایی که تعریف خاص خود را در مؤلفه والد دیگر اعلام کرده اند و فقط می توانند در زمینه آن مؤلفه اصلی استفاده شوند. در مثال بالا، جزء عنوان، برای مثال، فقط در محدوده مولفه Article وجود دارد. به تنهایی قابل ارائه نیست.
من خودم از نامش مطمئن نیستم، اما این بهترین اصطلاحی است که برای توصیف این الگو پیدا کردم که یاد گرفتم در پروژه هایم از آن قدردانی کنم.
اجزای فرعی را می توان در چندین کتابخانه مانند نمودارهای مجدد یا رابط کاربری معنایی. مورد دوم به اجزای فرعی مانند ماژول ها، مجموعه ها و نماها در کتابخانه خود اشاره دارد و شما را قادر می سازد تا نماها را به همان روشی که در بالا ارائه شده است، ارائه دهید.
این نوع مدل واقعا مفید است:
-
برای ثابت نگه داشتن دیدگاه ها: در واقع می توانید هر نوع داده ای را با استفاده از کامپوننت Article بالا نمایش دهید. نکته مهم در اینجا این است که صرف نظر از هدف آن، در سراسر برنامه یکسان به نظر می رسد.
-
تا کد خود را محکم و تمیز نگه دارید: عنوان، نظرات، زیرنویس، فراداده فقط در مقاله معنا پیدا میکنند و فقط در مقاله قابل استفاده خواهند بود (یعنی زمانی که معنی داشته باشند زیرا این مؤلفهها فقط در زمینه یک «مقاله» استفاده میشوند).
-
تا نماهایی به راحتی قابل آزمایش داشته باشید: برای آزمایش چنین مولفه هایی، تست جست و اسنپ شات متحدان ما هستند. این به ما امکان میدهد در هنگام استفاده از Article به سرعت هر ترکیبی از اجزای فرعی را آزمایش کنیم. در ادامه خواهیم دید که چگونه از Jest برای آزمایش چنین مدلی استفاده کنیم.
نحوه ساخت مولفه های فرعی
در این قسمت کامپوننت Article را مرحله به مرحله می سازیم و ابتدا سعی می کنیم آن را پیاده سازی کنیم Title جزء فرعی
اولین چیزی که برای ساختن مولفه های فرعی در یک کامپوننت نیاز داریم این است برنامه برای یافتن کودکان بر اساس “نوع” یا “نام” بنابراین React می داند که چگونه زیر مولفه عنوان خود را رندر کند. ما دو پارامتر را به این برنامه ارسال می کنیم:
-
کودکان: لیست فرزندان
Article -
جزء: مؤلفه ای که می خواهیم در لیست کودکان پیدا کنیم، در مثال ما خواهد بود
Title.
در اینجا ابزار findByType به نظر می رسد:
1
import React from 'react';2
const findByType = (children, component) => {5
const type = [component.displayName] || [component.name];7
React.Children.forEach(children, (child) => {9
child && child.type && (child.type.displayName || child.type.name);10
if (type.includes(childType)) {17
export default findByType;
حالا که مال خودمونه findByType با این کار، می توانیم شروع به نوشتن مطالب خود کنیم Article جزء و Title جزء فرعی:
یک جزء مقاله با یک جزء فرعی عنوان
1
import React, { Component } from 'react';2
import findByType from './findByType';3
import css from './somestyle.css';5
const Title = () => null;6
class Article extends Component {9
const { children } = this.props;11
const title = findByType(children, Title);17
return div className={css.title}>{title.props.children}/div>;21
div className={css.mainContainer}>22
div className={css.wrapper}>23
div className={css.titleContainer}>{this.renderTitle()}/div>31
export default Article;
ما اکنون توانایی استفاده را داریم Article جزء و آن Title جزء فرعی به این صورت:
با استفاده از زیر جزء Title
2
Article.Title>My Article Title/Article.Title>
برای گسترش مجموعه زیر مولفه های خود، کافی است هر کدام را نمونه سازی کنیم، تابع رندر مناسب را بنویسیم و آن را در تابع رندر اصلی فراخوانی کنیم.
در زیر کامپوننت کاملاً پیادهسازی شده را با تمام اجزای فرعی آن خواهید دید:
اجرای کامل مولفه عضو با تمام اجزای فرعی آن
2
import React, { Component } from 'react';3
import type { Node } from 'react';4
import findByType from './findByType';5
import css from './styles.css';7
const Title = () => null;8
const Subtitle = () => null;9
const Metadata = () => null;10
const Content = () => null;11
const Comments = () => null;18
class Article extends ComponentProps> {19
static Title: Function;20
static Subtitle: Function;21
static Metadata: Function;22
static Content: Function;23
static Comments: Function;26
const { children } = this.props;27
const title = findByType(children, Title);31
return div className={css.title}>{title.props.children}/div>;35
const { children } = this.props;36
const subtitle = findByType(children, Subtitle);41
div className={css.subtitle}>42
div className={css.subtitleBox}>{subtitle}/div>48
const { children } = this.props;49
const metadata = findByType(children, Metadata);56
ul className={css.articlemetadata}>57
{metadata.props.children.map((child) => {58
return li className={css.item}>{child}/li>;64
renderContentAndComment() {65
const { children } = this.props;66
const content = findByType(children, Content);67
const comments = findByType(children, Comment);74
div className={css.contentArticle}>75
div className={css.contentTextStyle}>{content.props.children}/div>76
span className={css.inlineComments}>77
{comments && comments.props.children}84
const { children, className, ...rest } = this.props;87
div className={css.mainContainer}>88
div className={css.wrapper}>89
div className={css.titleContainer}>91
{this.renderSubtitle()}93
{this.renderMetadata()}94
{this.renderContentAndComment()}101
Article.Title = Title;102
Article.Subtitle = Subtitle;103
Article.Metadata = Metadata;104
Article.Content = Content;105
Article.Comments = Comments;107
export default Article;
توجه: renderMetadata تابع در این مثال واقعا جالب است، نشان می دهد که چگونه می توان از یک تابع رندر برای دو زیرمجموعه مختلف استفاده کرد.
استفاده از تست Jest و Snapshot برای تست اجزای فرعی
آزمایش عکسهای فوری از اجزای فرعی ما احتمالاً سریعترین و مطمئنترین راه برای اطمینان از اینکه هر ترکیبی از اجزای فرعی در مؤلفه Article به درستی ارائه میشود است. برای انجام این کار از Jest و Enzyme استفاده خواهیم کرد. در اینجا نحوه نوشتن تست هایی برای مثال ما آمده است:
نمونه ای از اجزای فرعی برای آزمایش فوری
1
import React from 'react';2
import { mount } from 'enzyme';3
import Article from '../';6
const Content = () => div>[Mock] Content/div>;7
const Subtitle = () => div>[Mock] Subtitle/div>;8
const Comments = () => div>[Mock] Comments/div>;9
const Metadata = () => div>[Mock] Metadata/div>;10
const Title = () => div>[Mock] Title/div>;11
const Subtitles = () => div>[Mock] Subtitles/div>;13
it('Renders with all the sub-components', () => {15
const component = mount(35
expect(component).toMatchSnapshot();38
it('Renders with only the Content and Comments', () => {40
const component = mount(50
expect(component).toMatchSnapshot();53
it('Renders with a Title and without a subtitle', () => {54
const component = mount(70
expect(component).toMatchSnapshot();
هنگام نوشتن این مقاله، متوجه شدم که اجزای فرعی پس از قرار گرفتن در Babel 6.26.0 و Webpack 3.10 در IE 11 و Edge رندر نمی شوند. شاید نسخه های دیگر را تحت تاثیر قرار دهد، من هنوز بررسی نکرده ام، اما تنها چیزی که می دانم این است که فقط روی برنامه باندل تاثیر می گذارد، زمانی که پروژه با Webpack Dev Server اجرا می شود، خوب کار می کند.
چه اتفاقی افتاد مقصر اینجا هنگام اشکال زدایی پیدا شد findByType استفاده کنید. child.type.displayName || child.type.name داشت برمی گشت undefined در IE و Edge به دلایل زیر:_type_ در اینجا اشاره ای به سازنده کامپوننت است. بنابراین اگر انجام دهید _child.type.name_، به ویژگی نام سازنده ارجاع می دهد – در IE پشتیبانی نمی شود.“
مرجع: https://github.com/facebook/react/issues/9803
به عنوان یک راه حل، من یک متغیر ثابت به نام displayName را به هر یک از اجزای فرعی خود اضافه کردم تا مطمئن شوم که نام دارند. در مثال ما به این صورت است:
اجزای فرعی با “displayName” اعلام شده است
3
const Title = () => null;4
Title.displayName = 'Title';6
const Subtitle = () => null;7
Subtitle.displayName = 'Subtitle';9
const Metadata = () => null;10
Metadata.displayName = 'Metadata';12
const Content = () => null;13
Content.displayName = 'Content';15
const Comments = () => null;16
Comments.displayName = 'Comments';



