يبدو لي أن الوقت قد حان لتقاسم طريقة كتابة تطبيق ReactJS ،
ولا أدعي أنه فريد من نوعه.يمكن تخطي الفقرة الأولى . لقد انخرطت في تطوير الويب لفترة طويلة ، لكنني ظللت على مدار الأربع سنوات الماضية على ReactJS وكل شيء يناسبني ، لقد تراجعت في حياتي ، لكن منذ حوالي عامين قابلت MobX ، منذ شهرين فقط حاولت العودة إلى redux ، لكنني لم استطعت ، كان هناك شعور بأنني كنت أقوم بشيء لا لزوم له ، ربما كان هناك شيء غير صحيح على الإطلاق ، لقد تمت ترجمة الكثير من وحدات البايت على الخوادم بالفعل حول هذا الموضوع ، ولم يكن المقال متعلقًا ببرودة أحدهم قبل الآخر ، فهذه مجرد محاولة لمشاركة أفضل ممارساتي ، ربما شخص ما حقًا هذا النهج سوف يذهب ، وحتى هذه النقطة.
المهام التي سنحلها:- اتصال دي للمكونات
- تقديم الخادم مع تحميل البيانات غير المتزامنة
يمكن الاطلاع على هيكل المشروع على
جيثب . لذلك ، سوف أتخطى كيفية كتابة طلب بدائي وسوف تبرز المقالة فقط
نقدم مفاهيم مثل: نموذج البيانات ، الخدمة ، الجانب.
دعونا الحصول على نموذج بسيط
TodoModel.tsimport { observable, action } from 'mobx'; export class TodoModel { @observable public id: number; @observable public text: string = ''; @observable public isCompleted: boolean = false; @action public set = (key: 'text' | 'isCompleted', value: any): void => { this[key] = value; }; }
ما تراه هو الإجراء المحدد ، في النموذج هو أكثر استثناءًا من لهجة جيدة ، وعادة ما يوجد في المشروع نموذج أساسي يحتوي على مساعدين بدائيين وهو فقط موروث منه ، في النماذج يجب ألا تكون هناك أي إجراءات من أجل الخير.
الآن نحن بحاجة إلى معرفة كيفية العمل مع هذا النموذج ، وبدء خدمة:
TodoService.ts import { Service, Inject } from 'typedi'; import { plainToClass, classToClass } from 'class-transformer'; import { DataStorage } from '../storage/DataStorage'; import { action } from 'mobx'; import { TodoModel } from '../models/TodoModel'; const responseMock = { items: [ { id: 1, isCompleted: false, text: 'Item 1' }, { id: 2, isCompleted: true, text: 'Item 2' } ] }; @Service('TodoService') export class TodoService { @Inject('DataStorage') public dataStorage: DataStorage; @action public load = async () => { await new Promise(resolve => setTimeout(resolve, 300)); this.dataStorage.todos = plainToClass(TodoModel, responseMock.items); }; @action public save(todo: TodoModel): void { if (todo.id) { const idx = this.dataStorage.todos.findIndex(item => todo.id === item.id); this.dataStorage.todos[idx] = classToClass(todo); } else { const todos = this.dataStorage.todos.slice(); todo.id = Math.floor(Math.random() * Math.floor(100000)); todos.push(todo); this.dataStorage.todos = todos; } this.clearTodo(); } @action public edit(todo: TodoModel): void { this.dataStorage.todo = classToClass(todo); } @action public clearTodo(): void { this.dataStorage.todo = new TodoModel(); } }
خدمتنا لديها رابط ل
DataStorage.ts import { Service } from 'typedi'; import { observable } from 'mobx'; import { TodoModel } from '../models/TodoModel'; @Service('DataStorage') export class DataStorage { @observable public todos: TodoModel[] = []; @observable public todo: TodoModel = new TodoModel(); }
في هذا المتجر سنقوم بتخزين حالة تطبيقنا ، يمكن أن يكون هناك العديد من هذه المتاجر ، ولكن كما أظهرت الممارسة ، ليس من المنطقي اقتحام العديد من المتاجر الصغيرة. في المتجر ، وكذلك في النماذج ، يجب ألا يكون هناك أي إجراء.
لدينا كل شيء جاهز تقريبًا ، يبقى توصيل كل شيء بتطبيقنا ، لذلك سنشدد الحاقن قليلاً من mobx-react:
دي import { inject } from 'mobx-react'; export function DI(...classNames: string[]) { return (target: any) => { return inject((props: any) => { const data: any = {}; classNames.forEach(className => { const name = className.charAt(0).toLowerCase() + className.slice(1); data[name] = props.container.get(className); }); data.container = props.container; return data; })(target); }; }
والحصول على حاوية لدينا DI
browser.tsx import 'reflect-metadata'; import * as React from 'react'; import { hydrate } from 'react-dom'; import { renderRoutes } from 'react-router-config'; import { Provider } from 'mobx-react'; import { BrowserRouter } from 'react-router-dom'; import { Container } from 'typedi'; import '../application'; import { routes } from '../application/route'; hydrate( <Provider container={Container}> <BrowserRouter>{renderRoutes(routes)}</BrowserRouter> </Provider>, document.getElementById('root') );
لدينا دائمًا حاوية واحدة للمتصفح ، ولكن بالنسبة لخادم العرض الذي تريده ، من الأفضل تنظيم الحاوية الخاصة بك لكل طلب:
server.tsx import * as express from 'express'; import * as React from 'react'; import { Container } from 'typedi'; import '../application';
يعد تقديم الخادم أمرًا حساسًا في الواقع ، فمن ناحية أريد أن أترك كل شيء من خلاله ،
لكن لديه مهمة عمل واحدة فقط ، لإعطاء المحتوى للروبوتات ، لذلك من الأفضل التحقق من شيء مثل "هل قام المستخدم بتسجيل الدخول مرة واحدة على الأقل في الموقع" ، وتخطي تقديم الخادم مع إنشاء حاويات على الخادم.
حسنا ، الآن إلى مكوناتنا:
MainRoute.tsx import * as React from 'react'; import { TodoService } from '../service/TodoService'; import { observer } from 'mobx-react'; import { DI } from '../annotation/DI'; import { DataStorage } from '../storage/DataStorage'; import { Todo } from '../component/todo'; import { Form } from '../component/form/Form'; import { ContainerInstance } from 'typedi'; interface IProps { todoService?: TodoService; dataStorage?: DataStorage; } @DI('TodoService', 'DataStorage') @observer export class MainRoute extends React.Component<IProps> { public static async loadData(container: ContainerInstance) { const todoService: TodoService = container.get('TodoService'); await todoService.load(); } public componentDidMount() { this.props.todoService.load(); } public render() { return ( <div> <Form /> <ul> {this.props.dataStorage.items.map(item => ( <li key={item.id} ><Todo model={item} /></li> ))} </ul> </div> ); } }
كل شيء يتحول إلى منطقية وجميلة للغاية هنا ، وجهة نظرنا "تجسيد" للرسم تأخذ البيانات من متجرنا ، كما تقول السنانير المكونة في أي وقت يجب تحميل البيانات فيه.
Todo.tsx import * as React from 'react'; import { TodoModel } from '../../models/TodoModel'; import { TodoService } from '../../service/TodoService'; import { DI } from '../../annotation/DI'; import { observer } from 'mobx-react'; interface IProps { model: TodoModel; todoService?: TodoService; } @DI('TodoService') @observer export class Todo extends React.Component<IProps> { public render() { const { model, todoService } = this.props; return ( <> <input type='checkbox' checked={model.isCompleted} onChange={e => model.set('isCompleted', e.target.checked)} /> <h4>{model.text}</h4> <button type='button' onClick={() => todoService.edit(model)}>Edit</button> </> ); } }
Form.tsx import * as React from 'react'; import { observer } from 'mobx-react'; import { DI } from '../../annotation/DI'; import { TodoService } from '../../service'; import { DataStorage } from '../../storage'; import { TextField } from '../text-field'; interface IProps { todoService?: TodoService; dataStorage?: DataStorage; } @DI('TodoService', 'DataStorage') @observer export class Form extends React.Component<IProps> { public handleSave = (e: any) => { e.preventDefault(); this.props.todoService.save(this.props.dataStorage.todo); }; public handleClear = () => { this.props.todoService.clearTodo(); }; public render() { const { dataStorage } = this.props; return ( <form onSubmit={this.handleSave}> <TextField name='text' model={dataStorage.todo} /> <button>{dataStorage.todo.id ? 'Save' : 'Create'}</button> <button type='button' onClick={this.handleClear}> Clear </button> </form> ); } }
في رأيي ، العمل مع النماذج أكثر ملاءمة من خلال النماذج / dtos ، يمكنك استخدام النماذج الأصلية المعتادة ، وتحديث نموذج البيانات وسيتم تحديث كل شخص يستمع إليه على الفور.
شيء من هذا القبيل أستخدم هذه المجموعة من المكتبات: رد الفعل ، محول الصف ، mobx ، typedi
نحن نستخدم الآن هذا النهج في prod ، فهذه مشاريع كبيرة جدًا ذات مكونات وخدمات مشتركة.
إذا كانت هذه الطريقة مثيرة للاهتمام ، فسأخبرك كيف نقوم ، في نفس السياق ، بالتحقق من صحة النماذج قبل إرسالها إلى الخادم ، وكيف نعالج أخطاء الخادم وكيف نقوم بمزامنة حالتنا بين علامات تبويب المتصفح.
في الواقع ، كل شيء مكافأة للغاية: "مصادقة الصف" ، "localStorage + window.addEventListener ('التخزين')"
شكرا لقراءتك :-)
مثال