تطوير مكتبة الشركة لمكونات رد الفعل. نهج عبر منصة

تحكي هذه المقالة قصة التنفيذ الناجح لنظام التصميم في شركة واحدة من أكبر بائعي التجزئة DIY. تم وصف مبادئ ومناهج التطوير عبر الأنظمة الأساسية لمكونات واجهة المستخدم باستخدام مكتبات React و React Native ، بالإضافة إلى حل مشكلة إعادة استخدام التعليمات البرمجية بين المشاريع لمنصات مختلفة.

أولاً ، بضع كلمات حول كيف بدأ كل شيء ، ولماذا ظهرت فكرة تنفيذ تصميم النظام. بدأ كل شيء باستخدام تطبيق Android للجوال للبائعين في المتاجر. تم بناء التطبيق على إطار التفاعل الأصلي. تم تمثيل وظيفة البداية من خلال عدد قليل من الوحدات النمطية ، مثل البحث عن المنتجات في الكتالوج وبطاقة المنتج ، ووثيقة المبيعات. بالمناسبة ، الآن هذا تطبيق قوي إلى حد ما وقد حل بالفعل محل وظائف مكاتب المعلومات في المتاجر.

بعد ذلك ، تم إطلاق مشاريع تطبيقات الويب لموظفي قسم اللوجستيات ، بالإضافة إلى العديد من المكوِّنات.

في هذه المرحلة ، ظهر فهم للنهج العامة لتصميم هذه التطبيقات ، بالإضافة إلى وجود قاعدة رمز كبيرة إلى حد ما. وكان من المنطقي تنظيم الآخر لمزيد من إعادة الاستخدام.

لتنظيم UI / UX ، تقرر تطوير نظام تصميم. لن أخوض في تفاصيل حول ما هو عليه. على الإنترنت ، يمكنك العثور على العديد من المقالات حول هذا الموضوع. على سبيل المثال ، في حبري ، يمكن التوصية بقراءة أعمال أندريه سوندييف .

لماذا تصميم النظام وما مميزاته؟ الأول هو تجربة مشتركة والشعور باستخدام المنتجات. يحصل المستخدمون على واجهة مألوفة بغض النظر عن التطبيق: تبدو الأزرار وتعمل بالطريقة التي اعتادوا عليها ، تفتح القائمة في المكان الصحيح ومع الديناميكيات الصحيحة ، تعمل حقول الإدخال بالطريقة المعتادة. الميزة الثانية هي إدخال معايير معينة ومناهج مشتركة من جانب التصميم ومن جانب التطوير. يتم تطوير كل وظيفة جديدة وفقًا للشرائع والأساليب القائمة بالفعل. من الأيام الأولى ، يتلقى الموظفون الجدد خط عمل واضح. التالي هو إعادة استخدام المكونات وتبسيط التطوير. ليست هناك حاجة "لإعادة اختراع العجلة" في كل مرة. يمكنك بناء واجهات من الكتل الجاهزة مع النتيجة النهائية المتوقعة.حسنًا ، الميزة الرئيسية في المقام الأول للعميل هي توفير المال والوقت.

لذلك ماذا فعلنا. في الواقع ، لم نقم بإنشاء مكتبة مكونة فحسب ، بل قمنا بإنشاء إطار عمل شامل متعدد المنصات. يعتمد الإطار على مخطط دفعي. لدينا 5 حزم npm الأساسية. إنه جوهر نشر الويب عبر الأنظمة الأساسية وتطبيقات Android. حزم الوحدات والمرافق والخدمات. ومجموعة من المكونات التي سيتم مناقشتها لاحقًا.
فيما يلي رسم تخطيطي لـ UML لحزمة المكونات.

صورة

وهي تشمل المكونات نفسها ، وبعضها مستقل (عناصر) ، وبعضها مرتبط ببعضها البعض ، وكذلك النواة الداخلية أو "النواة الفرعية".

دعونا نفكر بمزيد من التفصيل في ما هو مدرج في "النواة الفرعية". الأول هو الطبقة المرئية لتصميم النظام. كل شيء هنا يتعلق بلوحة الألوان والطباعة ونظام المسافة البادئة والشبكات وما إلى ذلك. الكتلة التالية هي الخدمات اللازمة لعمل المكونات ، مثل: ComponentsConfig (مكون التكوين) ، StyleSet (سأناقش هذا المفهوم بمزيد من التفاصيل لاحقًا) والجهاز (طريقة للعمل مع واجهة برمجة تطبيقات الجهاز). والكتلة الثالثة هي جميع أنواع المساعدين (المحللون ، مولدات الأنماط ، إلخ).

صورة

عند تطوير المكتبة ، استخدمنا نهجًا ذريًا لتصميم المكونات. بدأ كل شيء بإنشاء مكونات أو عناصر أولية. إنها "جسيمات" أولية مستقلة عن بعضها البعض. الرئيسية هي عرض ، نص ، صورة ، أيقونة. فيما يلي المكونات الأكثر تعقيدًا. يستخدم كل واحد منهم عنصرًا واحدًا أو أكثر لبناء هيكله. على سبيل المثال ، الأزرار ، حقول الإدخال ، التحديدات ، إلخ. المستوى التالي هو الأنماط. وهي مزيج من المكونات لحل أي مشكلة في واجهة المستخدم. على سبيل المثال ، نموذج تفويض ، رأس بمعلمات وإعدادات ، أو بطاقة منتج صممها مصمم يمكن استخدامها في وحدات مختلفة. المستوى الأخير والأصعب والأهم في نفس الوقت هو ما يسمى بالسلوك. هذه وحدات جاهزة للاستخدام ،تنفيذ منطق عمل معين ، وربما بما في ذلك المجموعة اللازمة من الطلبات الخلفية.

صورة

لذا ، دعنا ننتقل إلى تنفيذ مكتبة المكونات. كما ذكرت من قبل ، لدينا نظامان أساسيان مستهدفان - الويب وأندرويد (رد فعل أصلي). إذا كانت هذه العناصر على الويب معروفة جيدًا لجميع مطوري الويب مثل div ، و span ، و img ، و header ، إلخ. وأول شيء اتفقنا عليه هو اسم المكونات. قررنا استخدام نظام على غرار رد الفعل الأصلي ، مثل أولاً ، تم بالفعل تنفيذ بعض المكونات الأساسية في المشاريع ، وثانيًا ، هذه الأسماء هي الأكثر شمولية ومفهومة لكل من مطوري الويب والمطورين الأصليين. على سبيل المثال ، ضع في الاعتبار مكون العرض. تبدو طريقة مكون العرض الشرطي للويب كما يلي:

render() {
	return(
		<div {...props}>
			{children}
		</div>
	)
}

أولئك. تحت غطاء المحرك ، هذا ليس أكثر من div مع الدعائم والأحفاد اللازمة. في الأصل المتفاعل ، تكون البنية متشابهة جدًا ، ويتم استخدام مكون العرض فقط بدلاً من divs:

render() {
	return(
		<View {...props}>
			{children}
		</View>
	)
}

السؤال الذي يطرح نفسه: كيفية دمج هذا في مكون واحد وفي نفس الوقت تقسيم التقديم؟

هذا هو المكان الذي يأتي فيه نمط رد فعل يسمى HOC أو مكون ترتيب أعلى لإنقاذ. إذا حاولت رسم مخطط UML لهذا النمط ، فستحصل على شيء مثل ما يلي:

صورة

وبالتالي ، يتكون كل مكون من ما يسمى بالمندوب الذي يتلقى الدعائم من الخارج وهو مسؤول عن المنطق المشترك لكل من المنصتين ، وجزءين من المنصة يتم فيه تغليف طرق محددة لكل منصة بالفعل وأهم عرض. على سبيل المثال ، ضع في اعتبارك رمز مفوض الزر:

export default function buttonDelegate(ReactComponent: ComponentType<Props>): ComponentType<Props> {
    return class ButtonDelegate extends PureComponent<Props> {
        
        // Button common methods

        render() {
           const { onPress, onPressIn, onPressOut } = this.props;
            const delegate = {
                buttonContent: this.buttonContent,
                buttonSize: this.buttonSize,
                iconSize: this.iconSize,
                onClick: onPress,
                onMouseUp: onPressIn,
                onMouseDown: onPressOut,
                onPress: this.onPress,
                textColor: this.textColor,
            };
            return (<ReactComponent {...this.props} delegate={delegate} />);
        }
    };
}

يتلقى المندوب كحجة جزء النظام الأساسي للمكون ، وينفذ الأساليب الشائعة لكل من المنصات ويمررها إلى جزء النظام الأساسي. جزء النظام الأساسي للمكون نفسه كما يلي:

class Button extends PureComponent<WebProps, State> {
    
   // Web specific methods

    render() {
        const { delegate: { onPress, buttonContent } } = this.props;
        return (
            <button
                className={this.classes}
                {...buttonProps}
                onClick={onPress}
                style={style}
            >
                {buttonContent(this.spinner, this.iconText)}
            </button>
        );
    }
}

export default buttonDelegate(Button);

فيما يلي طريقة التقديم مع جميع ميزات المنصة الخاصة بها. الوظيفة العامة للمفوض تأتي في شكل كائن من خلال مفوض الدعائم. مثال على جزء النظام الأساسي لزر لتنفيذ تطبيق تفاعلي أصلي:

class Button extends PureComponent<NativeProps, State> {

    // Native specific methods

    render() {
        const { delegate: { onPress, buttonContent } } = this.props;
        return (
            <View styleSet={this.styles} style={style}>
                <TouchableOpacity
                    {...butonProps}
                    onPress={onPress}
                    style={this.touchableStyles}
                    {...touchableProps}    
                >
                    {buttonContent(this.spinner, this.iconText)}
                </TouchableOpacity>
            </View>
        );
    }
}

export default buttonDelegate(Button);

في هذه الحالة ، يكون المنطق متشابهًا ، ولكن يتم استخدام مكونات التفاعل الأصلية. في كلتا القائمتين ، buttonDelegate عبارة عن HOC له منطق مشترك.

مع هذا النهج في تنفيذ المكونات ، ينشأ السؤال عن فصل أجزاء المنصة أثناء تجميع المشروع. من الضروري التأكد من أن حزمة الويب التي نستخدمها في المشاريع للويب تجمع فقط أجزاء من المكونات المخصصة للويب ، في حين أن أداة المترو في رد الفعل الأصلي يجب أن "تربط" أجزاء النظام الأساسي ، مع عدم الانتباه إلى المكون للويب.

لحل هذه المشكلة ، استخدموا ميزة حزمة المترو المضمنة ، والتي تتيح لك تحديد بادئة امتداد ملف النظام الأساسي. في حالتنا ، يبدو metro.config.js كما يلي:

module.exports = {
    resolver: {
        useWatchman: false,
        platforms: ['native'],
    },
};

وبالتالي ، عند إنشاء الحزمة ، يبحث المترو أولاً عن الملفات ذات الامتداد original.js ، ثم إذا لم يكن في الدليل الحالي ، فإنه يربط الملف بالملحق .js. جعلت هذه الوظيفة من الممكن وضع أجزاء النظام الأساسي للمكونات في ملفات منفصلة: يقع جزء الويب في ملف .js ، ويتم وضع جزء التفاعل الأصلي في الملف بالملحق .native.js.

بالمناسبة ، يحتوي webpack على نفس الوظيفة باستخدام NormalModuleReplacementPlugin.

كان الهدف الآخر للنهج عبر الأنظمة الأساسية هو توفير آلية واحدة لمكونات التصميم. في حالة تطبيقات الويب ، اخترنا المعالج الأولي لـ sass ، والذي يتم تجميعه في نهاية المطاف إلى css عادي. أولئك. لمكونات الويب ، استخدمنا مطوري className للتفاعل المألوف.

يتم تصميم مكونات التفاعل الأصلية من خلال الأنماط المضمنة ونمط الدعائم. كان من الضروري الجمع بين هذين النهجين ، مما يجعل من الممكن استخدام فئات الأنماط لتطبيقات Android. لهذا الغرض ، تم تقديم مفهوم styleSet ، وهو ليس أكثر من مجموعة من السلاسل - أسماء الفئات:

styleSet: Array<string>

في الوقت نفسه ، تم تنفيذ خدمة StyleSet التي تحمل الاسم نفسه لخدمة التفاعل الأصلي ، مما يسمح بتسجيل أسماء الفئات:

export default StyleSet.define({
    'lmui-Button': {
        borderRadius: 6,
    },
    'lmui-Button-buttonSize-md': {
        paddingTop: 4,
        paddingBottom: 4,
        paddingLeft: 12,
        paddingRight: 12,
    },
    'lmui-Button-buttonSize-lg': {
        paddingTop: 8,
        paddingBottom: 8,
        paddingLeft: 16,
        paddingRight: 16,
    },
})

بالنسبة لمكونات الويب ، يعد styleSet صفيفًا من أسماء فئات css التي يتم "لصقها" باستخدام مكتبة أسماء الفئات .

نظرًا لأن المشروع متعدد المنصات ، فمن الواضح أنه مع نمو قاعدة التعليمات البرمجية ، يزداد أيضًا عدد التبعيات الخارجية. علاوة على ذلك ، تختلف التبعيات لكل منصة. على سبيل المثال ، بالنسبة لمكونات الويب ، هناك حاجة إلى مكتبات مثل محمل الأنماط ، رد الفعل دوم ، أسماء الفئات ، حزمة الويب ، وما إلى ذلك. بالنسبة للمكونات الأصلية المتفاعلة ، يتم استخدام عدد كبير من المكتبات "الأصلية" ، على سبيل المثال ، التفاعل الأصلي نفسه. إذا كان للمشروع الذي من المفترض أن يستخدم فيه مكتبة المكونات نظامًا أساسيًا مستهدفًا واحدًا فقط ، فإن تثبيت كل التبعيات على نظام أساسي آخر أمر غير منطقي. لحل هذه المشكلة ، استخدمنا خطاف التثبيت اللاحق لـ npm نفسه ، حيث تم تثبيت برنامج نصي لتثبيت التبعيات للنظام الأساسي المحدد. تم تسجيل الاعتماديات نفسها في القسم المقابل من الحزمة. json من الحزمة ،ويجب تحديد المنصة المستهدفة في حزمة المشروع. json كمصفوفة.
ومع ذلك ، كشف هذا النهج عن عيب ، والذي تحول لاحقًا إلى عدة مشاكل أثناء التجميع في نظام CI. كان جذر المشكلة هو أنه مع package-lock.json ، لم يقم البرنامج النصي المحدد في postinstall بتثبيت جميع التبعيات المسجلة.

كان علي البحث عن حل آخر لهذه المشكلة. كان الحل بسيطا. تم تطبيق مخطط مكون من حزمتين تم فيه وضع جميع تبعيات النظام الأساسي في قسم التبعيات في حزمة النظام الأساسي المقابلة. على سبيل المثال ، في حالة الويب ، تسمى الحزمة مكونات ويب ، حيث يوجد ملف package.json واحد. يحتوي على جميع التبعيات لمنصة الويب ، بالإضافة إلى الحزمة الرئيسية مع مكونات المكونات. سمح لنا هذا النهج بالمحافظة على فصل التبعيات والحفاظ على وظيفة package-lock.json.

في الختام ، سأعطي مثالاً على كود JSX باستخدام مكتبة المكونات الخاصة بنا:

<View row>
   <View
      col-xs={12}
      col-md={8}
      col-lg={4}
      col-xl={4}
      middle-xs
      col-md-offset-3
   />
     <Text size=”fs1”>Sample text</Text>
   </View>
</View>

يعد مقتطف الشفرة هذا عبر الأنظمة الأساسية ويعمل بنفس الطريقة في تطبيق رد فعل للويب وفي تطبيق Android على رد فعل أصلي. إذا لزم الأمر ، يمكن "إنهاء" نفس الرمز في نظام iOS.

وهكذا ، تم حل المهمة الرئيسية التي واجهتنا - أقصى إعادة استخدام لكل من مناهج التصميم وقاعدة الكود بين المشاريع المختلفة.
يرجى الإشارة في التعليقات إلى الأسئلة حول هذا الموضوع التي كانت مثيرة للاهتمام لتعلمها في المقالة التالية.

All Articles