كيفية إنشاء تطبيق Full-Stack React ونشره

مرحبا يا هبر! أنا أمثل انتباهكم ترجمة هذه المادة «كيفية بناء ونشر كامل -Stack تتفاعل داخل التطبيق» الكاتب فرانك Zickert.

مكونات البنية التحتية يسمح لك لخلق بسهولة، تشغيل، ونشر كامل تتفاعل التطبيق. باستخدام مكونات React ، يمكنك التركيز على كتابة منطق العمل في التطبيق الخاص بك. لا داعي للقلق بشأن التكوين الخاص به.

تريد أن تصبح مطور مكدس كامل؟ يكمل تطبيق المكدس الكامل واجهة الويب التفاعلية React بخادم وقاعدة بيانات. ولكن مثل هذا التطبيق يتطلب إعدادات أكثر بكثير من تطبيق بسيط من صفحة واحدة.

نحن نستخدم مكونات البنية التحتية . تسمح لنا مكونات React هذه بتعريف بنية البنية التحتية الخاصة بنا كجزء من تطبيق React الخاص بنا. لم نعد بحاجة إلى أي إعدادات أخرى ، مثل Webpack أو Babel أو Serverless.

بداية


يمكنك تكوين المشروع الخاص بك في ثلاث طرق:


بمجرد تثبيت التبعيات (تشغيل npm install ) ، يمكنك إنشاء المشروع باستخدام أمر واحد: npm run build .

ويضيف النصي بناء ثلاثة نصوص في package.json:

  • npm run{your-project-name} محليًا (وضع npm run{your-project-name} ، بدون جزء خادم وقاعدة بيانات)
  • npm run start-{your-env-name} يبدأ تشغيل مكدس البرنامج بالكامل محليًا. ملاحظة: لتشغيل قاعدة البيانات في وضع غير متصل بالشبكة يتطلب جافا 8 JDK. إليك كيفية تثبيت JDK .
  • npm run deploy-{your-env-name} اسم} تنشر التطبيق الخاص بك على AWS.

المذكرة. لنشر تطبيقك على AWS ، فأنت بحاجة إلى مستخدم تقني لـ IAM له هذه الحقوق. ضع بيانات اعتماد المستخدم في ملف .env الخاص بك كما يلي:

 AWS_ACCESS_KEY_ID = *** AWS_SECRET_ACCESS_KEY = *** 

تحديد بنية التطبيق الخاص بك


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

تقوم المكونات الفرعية (المكونات الفرعية) بتحسين (تمديد) سلوك التطبيق وإضافة وظائف.

في المثال التالي، المكون <ServiceOrientedApp /> - انها عنصر المستوى الأعلى لدينا. نحن تصديره كملف الافتراضي في نقطة دخول ملفنا ( src / index.tsx) .

 export default ( <ServiceOrientedApp stackName = "soa-dl" buildPath = 'build' region='eu-west-1'> <Environment name="dev" /> <Route path='/' name='My Service-Oriented React App' render={()=><DataForm />} /> <DataLayer id="datalayer"> <UserEntry /> <GetUserService /> <AddUserService /> </DataLayer> </ServiceOrientedApp> ); > export default ( <ServiceOrientedApp stackName = "soa-dl" buildPath = 'build' region='eu-west-1'> <Environment name="dev" /> <Route path='/' name='My Service-Oriented React App' render={()=><DataForm />} /> <DataLayer id="datalayer"> <UserEntry /> <GetUserService /> <AddUserService /> </DataLayer> </ServiceOrientedApp> ); 

<ServiceOrientedApp /> - هو تطبيق على شبكة الإنترنت التفاعلية. يمكنك تنقيح (تمديد) وظيفة التطبيق باستخدام التي تقدمها مكونات الطفل. وهو يدعم <DataLayer /> <Environment /> و <Route /> و <Service /> و <DataLayer /> .

<Envrionment /> يحدد وقت تشغيل التطبيق الخاص بك. على سبيل المثال ، يمكن أن يكون لديك نسخة مطورة ومحفزة. يمكنك تشغيل ونشر كل على حدة.

<Route /> - صفحة من التطبيق الخاص بك. ويعمل <Route /> للرد الموجه. وهنا البرنامج التعليمي على كيفية تشغيل الطرق .

تعرف <Service /> الوظيفة التي تعمل على جانب الخادم. يمكن أن تحتوي على واحد أو عدة مكونات <Middleware /> كأطفال.

<Middleware /> يعمل مثل Express.js الوسيطة.
يضيف <DataLayer /> قاعدة بيانات NoSQL إلى التطبيق الخاص بك. يقبل <الإدخال /> - المكونات كأطفال. <الدخول /> يصف نوع العناصر في قاعدة البيانات الخاصة بك.

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

توفر بنية المكون رؤية واضحة للتطبيق الخاص بك. كلما زاد تطبيقك ، كلما زاد أهميته.

ربما لاحظت أن <Service /> هم أبناء <DataLayer /> . هذا له تفسير بسيط. نريد خدماتنا إلى الوصول إلى قاعدة البيانات. انها حقا بهذه البساطة!

تصميم قواعد البيانات


<DataLayer /> Amazon DynamoDB. هذه قاعدة بيانات ذات قيمة مفتاح (NoSQL). ويوفر أداء عاليا في أي نطاق. ولكن على النقيض من قاعدة بيانات علائقية، فإنه لا يعتمد استعلامات معقدة.

يحتوي مخطط قاعدة البيانات على ثلاثة حقول: primaryKey rangeKey و rangeKey data . هذا مهم لأنك تحتاج إلى معرفة أنه يمكنك فقط العثور على إدخالات بواسطة مفاتيحها. إما بواسطة primaryKey ، أو بواسطة rangeKey ، أو كليهما.

مع هذه المعرفة ، دعونا نلقي نظرة على <Entry /> :

 export const USER_ENTRY_ID = "user_entry"; export default function UserEntry (props) { return <Entry id={ USER_ENTRY_ID } primaryKey="username" rangeKey="userid" data={{ age: GraphQLString, address: GraphQLString }} /> }; 

يصف <Entry /> بنية بياناتنا. نحن نحدد أسماء مفاتيحنا الأساسية و rangeKey. يمكنك استخدام أي اسم بخلاف بعض الكلمات الأساسية DynamoDB التي يمكنك العثور عليها هنا. لكن الأسماء التي نستخدمها لها آثار وظيفية:

  • عندما نضيف العناصر في قاعدة البيانات الخاصة بنا، ونحن بحاجة لتقديم قيم هذه الأسماء الرئيسية.
  • يصف الجمع بين كلا المفتاحين عنصرًا فريدًا في قاعدة البيانات.
  • يجب ألا يكون هناك <إدخال /> آخر بنفس أسماء المفاتيح (قد يكون الاسم واحدًا ، ولكن ليس كلاهما).
  • يمكننا العثور على عناصر في قاعدة البيانات فقط عندما يكون لدينا قيمة اسم مفتاح واحد على الأقل.

في هذا المثال، يعني ذلك أن:

  • يجب أن يكون لكل مستخدم اسم مستخدم ومستخدم.
  • لا يمكن أن يكون هناك مستخدم ثان له نفس اسم المستخدم ونفس معرف المستخدم. من وجهة نظر قاعدة البيانات أنه سيكون من الرائع إذا كان العضو هما الشيء نفسه اسم المستخدم، عندما يكون لديهم حاليا مختلف (أو العكس بالعكس).
  • لا يمكن أن يكون لدينا <Entry /> آخر في قاعدة البيانات الخاصة بنا مع primaryKey = "username" و rangeKey = "userid".
  • يمكننا الاستعلام عن قاعدة البيانات للمستخدمين عندما يكون لدينا اسم مستخدم أو id مستخدم. ولكن لا يمكننا طلب حسب العمر أو العنوان.

إضافة عناصر إلى قاعدة البيانات


لقد حددنا اثنين <Service /> - مكون في منطقتنا <ServiceOrientedApp /> . خدمة POST تضيف المستخدم إلى قاعدة البيانات وخدمة GET التي تسترد المستخدم منها.

لنبدأ بـ <AddUserService /> . هنا هو رمز لهذه الخدمة:

 import * as React from 'react'; import { callService, Middleware, mutate, Service, serviceWithDataLayer } from "infrastructure-components"; import { USER_ENTRY_ID, IUserEntry } from './user-entry'; const ADDUSER_SERVICE_ID = "adduser"; export default function AddUserService () { return <Service id={ ADDUSER_SERVICE_ID } path="/adduser" method="POST"> <Middleware callback={serviceWithDataLayer(async function (dataLayer, req, res, next) { const parsedBody: IUserEntry = JSON.parse(req.body); await mutate( dataLayer.client, dataLayer.setEntryMutation(USER_ENTRY_ID, parsedBody) ); res.status(200).set({ "Access-Control-Allow-Origin" : "*", // Required for CORS support to work }).send("ok"); })}/> </Service> }; ./user-entry'؛ import * as React from 'react'; import { callService, Middleware, mutate, Service, serviceWithDataLayer } from "infrastructure-components"; import { USER_ENTRY_ID, IUserEntry } from './user-entry'; const ADDUSER_SERVICE_ID = "adduser"; export default function AddUserService () { return <Service id={ ADDUSER_SERVICE_ID } path="/adduser" method="POST"> <Middleware callback={serviceWithDataLayer(async function (dataLayer, req, res, next) { const parsedBody: IUserEntry = JSON.parse(req.body); await mutate( dataLayer.client, dataLayer.setEntryMutation(USER_ENTRY_ID, parsedBody) ); res.status(200).set({ "Access-Control-Allow-Origin" : "*", // Required for CORS support to work }).send("ok"); })}/> </Service> };import * as React from 'react'; import { callService, Middleware, mutate, Service, serviceWithDataLayer } from "infrastructure-components"; import { USER_ENTRY_ID, IUserEntry } from './user-entry'; const ADDUSER_SERVICE_ID = "adduser"; export default function AddUserService () { return <Service id={ ADDUSER_SERVICE_ID } path="/adduser" method="POST"> <Middleware callback={serviceWithDataLayer(async function (dataLayer, req, res, next) { const parsedBody: IUserEntry = JSON.parse(req.body); await mutate( dataLayer.client, dataLayer.setEntryMutation(USER_ENTRY_ID, parsedBody) ); res.status(200).set({ "Access-Control-Allow-Origin" : "*", // Required for CORS support to work }).send("ok"); })}/> </Service> }; ": "*"، // مطلوب للCORS دعم للعمل import * as React from 'react'; import { callService, Middleware, mutate, Service, serviceWithDataLayer } from "infrastructure-components"; import { USER_ENTRY_ID, IUserEntry } from './user-entry'; const ADDUSER_SERVICE_ID = "adduser"; export default function AddUserService () { return <Service id={ ADDUSER_SERVICE_ID } path="/adduser" method="POST"> <Middleware callback={serviceWithDataLayer(async function (dataLayer, req, res, next) { const parsedBody: IUserEntry = JSON.parse(req.body); await mutate( dataLayer.client, dataLayer.setEntryMutation(USER_ENTRY_ID, parsedBody) ); res.status(200).set({ "Access-Control-Allow-Origin" : "*", // Required for CORS support to work }).send("ok"); })}/> </Service> }; 

يقبل المكون <Service /> - ثلاثة معلمات:

  • يجب أن يكون id ( id ) عبارة عن سلسلة فريدة. نستخدم معرفًا ( id ) عندما نحتاج إلى الاتصال service في المكونات الأخرى.
  • المسار ( path ) (مع / الأولي) يشير إلى URL مسار نسبي خدمتكم
  • يجب أن تكون method واحدة من GET ، POST ، UPDATE ، DELETE . يشير إلى طلب HTTP الذي نستخدمه عند الاتصال بالخدمة.

نضيف <Middleware /> كعنصر الطفل. يأخذ هذا <Middleware /> وظيفة رد الاتصال كمعلمة. يمكن أن نقدم مباشرة الوسيطة Express.js. بما أننا نريد الوصول إلى قاعدة البيانات ، فإننا serviceWithDataLayer الوظيفة في serviceWithDataLayer . ويضيف هذا dataLayer كمعلمة الأول إلى الاستدعاء لدينا.

DataLayer يوفر الوصول إلى قاعدة البيانات. لنرى كيف!

تطبق الوظيفة غير المتزامنة mutate التغييرات على البيانات الموجودة في قاعدة البيانات الخاصة بنا. وهذا يتطلب من العميل والأمر الطفرات كمعلمات.

بيانات العنصر هي كائن Javascript يحتوي على جميع أزواج قيمة المفتاح الضرورية. في خدمتنا ، نحصل على هذا الكائن من نص الطلب. لل User كائن لديه البنية التالية:

 export interface IUserEntry { username: string, userid: string, age: string, address: string } 

هذا الكائن يأخذ أسماء primaryKey و rangeKey كل هذه المفاتيح، حددنا في <Entry /> .

ملاحظة: في هذه اللحظة نوع معتمد فقط هو السلسلة، والذي يتوافق مع تعريف GraphQLString <الدخول />.
ذكرنا أعلاه أن نتخذ البيانات IUserEntry من الجسم. كيف الحال؟

توفر مكونات البنية التحتية callService (serviceId, dataObject) لوظيفة غير متزامنة callService (serviceId, dataObject) . تقبل هذه الوظيفة معرف خدمة وكائن Javascript (للإرسال كنص طلب عند استخدام POST ) ووظيفة success ووظيفة رد خطأ.

يعرض المقتطف التالي كيف نستخدم هذه الوظيفة للاتصال بـ <AddUserService /> . نشير serviceId . userData ، والتي نأخذها كمعلمة لوظيفة لدينا.

 export async function callAddUserService (userData: IUserEntry) { await callService( ADDUSER_SERVICE_ID, userData, (data: any) => { console.log("received data: ", data); }, (error) => { console.log("error: " , error) } ); }; ) export async function callAddUserService (userData: IUserEntry) { await callService( ADDUSER_SERVICE_ID, userData, (data: any) => { console.log("received data: ", data); }, (error) => { console.log("error: " , error) } ); }; 

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

 <button onClick={() => callAddUserService({ username: username, userid: userid, age: age, address: address })}>Save</button> 

نحن فقط نسميها باستخدام الكائن IUserEntry . تستدعي الخدمة الصحيحة (كما هو محدد بواسطة المعرف ( id )). يضع userData في نص الطلب. <AddUserService /> يأخذ البيانات من الجسم ويضعها في قاعدة بيانات.

الحصول على هذه البنود من قاعدة البيانات


يعد استرداد العناصر من قاعدة بيانات سهلاً مثل إضافتها.

 export default function GetUserService () { return <Service id={ GETUSER_SERVICE_ID } path="/getuser" method="GET"> <Middleware callback={serviceWithDataLayer(async function (dataLayer, req, res, next) { const data = await select( dataLayer.client, dataLayer.getEntryQuery(USER_ENTRY_ID, { username: req.query.username, userid: req.query.userid }) ); res.status(200).set({ "Access-Control-Allow-Origin" : "*", // Required for CORS support to work }).send(JSON.stringify(data)); })}/> </Service> } المتغير dataLayer، مسا، الدقة، القادمة) { export default function GetUserService () { return <Service id={ GETUSER_SERVICE_ID } path="/getuser" method="GET"> <Middleware callback={serviceWithDataLayer(async function (dataLayer, req, res, next) { const data = await select( dataLayer.client, dataLayer.getEntryQuery(USER_ENTRY_ID, { username: req.query.username, userid: req.query.userid }) ); res.status(200).set({ "Access-Control-Allow-Origin" : "*", // Required for CORS support to work }).send(JSON.stringify(data)); })}/> </Service> } 

مرة أخرى ، نستخدم <الخدمة /> ، <الوسيطة /> ووظيفة رد الاتصال مع إمكانية الوصول إلى قاعدة البيانات.

بدلاً من دالة mutate ، التي تضيف عنصرًا إلى قاعدة البيانات ، نستخدم وظيفة select . تطلب هذه الوظيفة العميل، ونحن نأخذ من dataLayer . الخيار الثاني - فريق select . كفريق واحد mutation ، يمكننا أن نبني فريقا من select باستخدام dataLayer .

هذه المرة نحن نستخدم getEntryQuery . نحن نوفر المعرف ( id ) <Entry /> الذي نريد استلام عنصره. ونحن نقدم مفاتيح ( primaryKey و rangeKey ) عنصرا محددا في كائن جافا سكريبت. نظرًا لأننا نقدم كلا المفتاحين ، فإننا نعيد عنصرًا واحدًا. إذا كان موجودا.

كما ترون، ونحن نأخذ القيم الأساسية من الاستعلام. ولكن هذه المرة، ونحن نأخذ منهم من request.query ، بدلا من request.body . والسبب هو أن هذه الخدمة تستخدم GET -method. لا يعتمد هذا الأسلوب الجسم في الطلب. ولكنه يوفر جميع البيانات كمعلمة الاستعلام.

وظيفة callService تعالج هذا بالنسبة لنا. كما هو الحال في callAddUserService-function ، نحن نوفر المعرف ( id ) <Service /> الذي نريد الاتصال به. نحن نقدم البيانات اللازمة. هنا فقط المفاتيح. ونحن نقدم وظائف رد الاتصال.

رد اتصال ناجح يوفر استجابة. يحتوي نص الاستجابة بتنسيق json على العنصر الموجود لدينا. يمكننا الوصول إلى هذا العنصر من خلال مفتاح get_user_entry . « Get_ » يعرف استعلام أن نضع في اختيارنا. " User_entry " هو مفتاح <Entry /> .

 export async function callGetUserService (username: string, userid: string, onData: (userData: IUserEntry) => void) { await callService( GETUSER_SERVICE_ID, { username: username, userid: userid }, async function (response: any) { await response.json().then(function(data) { console.log(data[`get_${USER_ENTRY_ID}`]); onData(data[`get_${USER_ENTRY_ID}`]); }); }, (error) => { console.log("error: " , error) } ); } (بيانات) { export async function callGetUserService (username: string, userid: string, onData: (userData: IUserEntry) => void) { await callService( GETUSER_SERVICE_ID, { username: username, userid: userid }, async function (response: any) { await response.json().then(function(data) { console.log(data[`get_${USER_ENTRY_ID}`]); onData(data[`get_${USER_ENTRY_ID}`]); }); }, (error) => { console.log("error: " , error) } ); } 

انظروا إلى التطبيق الخاص بك كامل المكدس في العمل


إذا لم تكن قد بدأت التطبيق الخاص بك، والآن هو الوقت للقيام بذلك: npm run start-{your-env-name} .

يمكنك حتى نشر التطبيق الخاص بك على AWS باستخدام أمر واحد: npm run deploy-{your-env-name} . (تذكر أن تضع أوراق اعتماد AWS في ملف .env).

لا يصف هذا المنشور كيفية إدخال البيانات التي تضعها في قاعدة البيانات وكيفية عرض النتائج. callAddUserService و callGetUserService بتغليف كل ما يتعلق بالخدمات وقاعدة البيانات. أنت فقط وضعت كائن Javascript في هناك واستعادته.

ستجد شفرة المصدر لهذا المثال في مستودع جيثب هذا. وهو يتضمن واجهة مستخدم بسيطة جدا.

Source: https://habr.com/ru/post/ar476696/


All Articles