إعداد TypeScript لمشاريع التفاعل الحديثة باستخدام Webpack

نشرت: 2022-03-10
ملخص سريع ↬ يقدم هذا المقال Typescript ، وهو نص مرتفع لجافا سكريبت يقدم ميزة النوع الثابت لاكتشاف الأخطاء الشائعة كرموز للمطورين ، مما يعزز الأداء ، وبالتالي ينتج عنه تطبيقات مؤسسية قوية. ستتعلم أيضًا كيفية إعداد TypeScript بكفاءة في مشروع React أثناء قيامنا ببناء تطبيق Money Heist Episode Picker ، واستكشاف TypeScript و React hooks مثل useReducer و useContext و Reach Router.

في عصر تطوير البرامج هذا ، يمكن استخدام JavaScript لتطوير أي نوع من التطبيقات تقريبًا. ومع ذلك ، فإن حقيقة أن JavaScript مكتوبة ديناميكيًا قد تكون مصدر قلق لمعظم شركات المؤسسات الكبيرة ، بسبب ميزة التحقق من النوع غير المحكم.

لحسن الحظ ، لا يتعين علينا الانتظار حتى تقدم Ecma Technical Committee 39 نظام كتابة ثابت في JavaScript. يمكننا استخدام TypeScript بدلاً من ذلك.

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

في هذا البرنامج التعليمي ، سوف نتعلم ماهية TypeScript وكيفية التعامل معها في مشروع React. في النهاية ، سنكون قد أنشأنا مشروعًا يتكون من تطبيق منتقي الحلقات للبرنامج التلفزيوني Money Heist ، باستخدام TypeScript وخطافات React-like الحالية ( useState ، useEffect ، useReducer ، useContext ). بهذه المعرفة ، يمكنك الاستمرار في تجربة TypeScript في مشاريعك الخاصة.

هذه المقالة ليست مقدمة إلى TypeScript. ومن ثم ، فإننا لن نمر في بناء الجملة الأساسي لـ TypeScript و JavaScript. ومع ذلك ، ليس عليك أن تكون خبيرًا في أي من هذه اللغات لمتابعة ذلك ، لأننا سنحاول اتباع مبدأ KISS (اجعله بسيطًا ، غبيًا).

المزيد بعد القفز! أكمل القراءة أدناه ↓

ما هو TypeScript؟

في عام 2019 ، صنفت TypeScript في المرتبة السابعة بين اللغات الأكثر استخدامًا والخامس الأسرع نموًا على GitHub. ولكن ما هو TypeScript بالضبط؟

وفقًا للوثائق الرسمية ، تعد TypeScript مجموعة شاملة مكتوبة من JavaScript يتم تجميعها إلى JavaScript عادي. تم تطويره وصيانته بواسطة Microsoft ومجتمع المصادر المفتوحة.

تعني كلمة "Superset" في هذا السياق أن اللغة تحتوي على جميع ميزات ووظائف JavaScript ثم بعضها. TypeScript هي لغة برمجة نصية مكتوبة.

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

لم يتم إنشاء TypeScript لتغيير JavaScript. بدلاً من ذلك ، يتوسع في JavaScript بميزات جديدة قيمة. سيتم أيضًا تشغيل أي برنامج مكتوب بلغة JavaScript عادية كما هو متوقع في TypeScript ، بما في ذلك تطبيقات الأجهزة المحمولة عبر الأنظمة الأساسية والنهايات الخلفية في Node.js.

هذا يعني أنه يمكنك أيضًا كتابة تطبيقات React في TypeScript ، كما سنفعل في هذا البرنامج التعليمي.

لماذا تيب سكريبت؟

ربما ، أنت غير مقتنع باحتضان جودة TypeScript. دعنا نفكر في بعض مزاياها.

عدد أقل من البق

لا يمكننا القضاء على جميع الأخطاء في التعليمات البرمجية الخاصة بنا ، ولكن يمكننا تقليلها. يتحقق TypeScript من الأنواع في وقت الترجمة ويرمي أخطاء إذا تغير نوع المتغير.

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

إعادة الهيكلة أسهل

ربما ترغب غالبًا في إعادة تشكيل الكثير من الأشياء ، ولكن نظرًا لأنها تلامس الكثير من التعليمات البرمجية الأخرى والعديد من الملفات الأخرى ، فأنت حذر من تعديلها.

في TypeScript ، يمكن إعادة بناء مثل هذه الأشياء بنقرة واحدة فقط على أمر "إعادة تسمية الرمز" في بيئة التطوير المتكاملة (IDE).

إعادة تسمية التطبيق إلى expApp (معاينة كبيرة)

في لغة مكتوبة ديناميكيًا مثل JavaScript ، فإن الطريقة الوحيدة لإعادة تشكيل ملفات متعددة في نفس الوقت هي باستخدام وظيفة "البحث والاستبدال" التقليدية باستخدام التعبيرات العادية (RegExp).

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

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

تتميز TypeScript بمزايا أكثر مما قمنا بتغطيته هنا.

عيوب TypeScript

من المؤكد أن TypeScript لا يخلو من عيوبه ، حتى مع الأخذ في الاعتبار الميزات الواعدة الموضحة أعلاه.

شعور زائف بالأمن

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

لذلك ، ستعتمد قوة برنامجك على استخدامك لـ TypeScript ، لأن الأنواع يكتبها المطور ولا يتم التحقق منها في وقت التشغيل.

إذا كنت تبحث عن TypeScript لتقليل الأخطاء ، فالرجاء التفكير في التطوير القائم على الاختبار بدلاً من ذلك.

نظام الكتابة المعقد

نظام الكتابة ، رغم أنه أداة رائعة من نواحٍ عديدة ، يمكن أن يكون أحيانًا معقدًا بعض الشيء. ينبع هذا الجانب السلبي من كونه قابلاً للتشغيل البيني تمامًا مع JavaScript ، مما يترك مجالًا أكبر للتعقيد.

ومع ذلك ، لا يزال TypeScript هو JavaScript ، لذا من المهم فهم JavaScript.

متى تستخدم TypeScript؟

أنصحك باستخدام TypeScript في الحالات التالية:

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

في الأقسام القليلة الماضية ، قمنا بموازنة إيجابيات وسلبيات TypeScript. دعنا ننتقل إلى أعمال اليوم: إعداد TypeScript في مشروع React الحديث .

ابدء

هناك عدة طرق لإعداد TypeScript في مشروع React. في هذا البرنامج التعليمي ، سنغطي اثنين فقط.

الطريقة الأولى: إنشاء تطبيق React + TypeScript

منذ حوالي عامين ، أصدر فريق React إنشاء تطبيق React 2.1 ، بدعم TypeScript. لذلك ، قد لا تضطر أبدًا إلى القيام بأي رفع ثقيل لإدخال TypeScript في مشروعك.

إعلان عن TypeScript في إنشاء تطبيق React (معاينة كبيرة)

لبدء مشروع Create React App الجديد ، يمكنك تشغيل هذا ...

 npx create-react-app my-app --folder-name

… أو هذا:

 yarn create react-app my-app --folder-name

لإضافة TypeScript إلى مشروع Create React App ، قم أولاً بتثبيته وأنواع @types الخاصة به:

 npm install --save typescript @types/node @types/react @types/react-dom @types/jest

… أو:

 yarn add typescript @types/node @types/react @types/react-dom @types/jest

بعد ذلك ، أعد تسمية الملفات (على سبيل المثال ، index.js إلى index.tsx ) ، وأعد تشغيل خادم التطوير !

كان ذلك سريعًا ، أليس كذلك؟

الطريقة الثانية: إعداد TypeScript باستخدام حزمة الويب

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

إنشاء مشروع جديد

لنبدأ بإنشاء دليل جديد لمشروعنا:

 mkdir react-webpack cd react-webpack

سنستخدم npm لتهيئة مشروعنا:

 npm init -y

سينشئ الأمر أعلاه ملف package.json مع بعض القيم الافتراضية. دعنا نضيف أيضًا بعض التبعيات لـ webpack و TypeScript وبعض الوحدات النمطية الخاصة بـ React.

تركيب الحزم

أخيرًا ، سنحتاج إلى تثبيت الحزم الضرورية. افتح واجهة سطر الأوامر (CLI) وقم بتشغيل هذا:

 #Installing devDependencies npm install --save-dev @types/react @types/react-dom awesome-typescript-loader css-loader html-webpack-plugin mini-css-extract-plugin source-map-loader typescript webpack webpack-cli webpack-dev-server #installing Dependencies npm install react react-dom

react-webpack أيضًا بعض الملفات والمجلدات المختلفة يدويًا ضمن مجلد حزمة الويب التفاعلية:

  1. أضف webpack.config.js لإضافة التكوينات ذات الصلة بـ webpack.
  2. أضف tsconfig.json لجميع تكوينات TypeScript الخاصة بنا.
  3. إضافة دليل جديد ، src .
  4. قم بإنشاء دليل جديد ، components ، في مجلد src .
  5. أخيرًا ، أضف index.html و App.tsx و index.tsx في مجلد components .

هيكل المشروع

وبالتالي ، سيبدو هيكل المجلد الخاص بنا كما يلي:

 ├── package.json ├── package-lock.json ├── tsconfig.json ├── webpack.config.js ├── .gitignore └── src └──components ├── App.tsx ├── index.tsx ├── index.html

ابدأ في إضافة بعض التعليمات البرمجية

سنبدأ بـ index.html :

 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>React-Webpack Setup</title> </head> <body> <div></div> </body> </html>

سيؤدي ذلك إلى إنشاء HTML ، مع وجود div فارغ بمعرف output .

دعنا نضيف الكود إلى مكون App.tsx :

 import * as React from "react"; export interface HelloWorldProps { userName: string; lang: string; } export const App = (props: HelloWorldProps) => ( <h1> Hi {props.userName} from React! Welcome to {props.lang}! </h1> );

لقد أنشأنا كائن واجهة lang عليه اسم HelloWorldProps ، مع وجود اسم المستخدم و userName من نوع string .

لقد مررنا props إلى مكون App لدينا وقمنا بتصديرها.

الآن ، دعنا نحدِّث الكود في index.tsx :

 import * as React from "react"; import * as ReactDOM from "react-dom"; import { App } from "./App"; ReactDOM.render( <App userName="Beveloper" lang="TypeScript" />, document.getElementById("output") );

لقد قمنا للتو باستيراد مكون App إلى index.tsx . عندما ترى حزمة الويب أي ملف بامتداد .ts أو .tsx ، فسيتم تحويل هذا الملف باستخدام مكتبة awesome-typescript-loader.

تكوين TypeScript

سنضيف بعد ذلك بعض التهيئة إلى tsconfig.json :

 { "compilerOptions": { "jsx": "react", "module": "commonjs", "noImplicitAny": true, "outDir": "./build/", "preserveConstEnums": true, "removeComments": true, "sourceMap": true, "target": "es5" }, "include": [ "src/components/index.tsx" ] }

لنلقِ نظرة أيضًا على الخيارات المختلفة التي أضفناها إلى tsconfig.json :

  • compilerOptions يمثل خيارات المترجم المختلفة.
  • jsx:react يضيف دعمًا لـ JSX في ملفات .tsx .
  • lib يضيف قائمة بملفات المكتبة إلى التجميع (على سبيل المثال ، يتيح لنا استخدام es2015 استخدام بناء جملة ECMAScript 6).
  • module يولد رمز الوحدة النمطية.
  • noImplicitAny أخطاء للإعلانات ذات any نوع ضمني.
  • يمثل outDir دليل الإخراج.
  • sourceMap ملف .map ، والذي يمكن أن يكون مفيدًا جدًا لتصحيح أخطاء التطبيق.
  • target يمثل إصدار ECMAScript الهدف لتحويل الكود الخاص بنا إلى (يمكننا إضافة إصدار بناءً على متطلبات المتصفح الخاصة بنا).
  • include Used لتحديد قائمة الملفات المراد تضمينها.

تكوين حزمة الويب

دعنا نضيف بعض إعدادات webpack إلى webpack.config.js .

 const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = { entry: "./src/components/index.tsx", target: "web", mode: "development", output: { path: path.resolve(\__dirname, "build"), filename: "bundle.js", }, resolve: { extensions: [".js", ".jsx", ".json", ".ts", ".tsx"], }, module: { rules: [ { test: /\.(ts|tsx)$/, loader: "awesome-typescript-loader", }, { enforce: "pre", test: /\.js$/, loader: "source-map-loader", }, { test: /\.css$/, loader: "css-loader", }, ], }, plugins: [ new HtmlWebpackPlugin({ template: path.resolve(\__dirname, "src", "components", "index.html"), }), new MiniCssExtractPlugin({ filename: "./src/yourfile.css", }), ], };

لنلقِ نظرة على الخيارات المختلفة التي أضفناها إلى webpack.config.js :

  • entry هذا يحدد نقطة الدخول لتطبيقنا. قد يكون ملفًا واحدًا أو مجموعة من الملفات التي نريد تضمينها في بنائنا.
  • output هذا يحتوي على تكوين الإخراج. ينظر التطبيق إلى هذا عند محاولة إخراج كود مجمّع من مشروعنا إلى القرص. يمثل المسار دليل الإخراج للتعليمة البرمجية التي سيتم إخراجها إليه ، ويمثل اسم الملف اسم الملف له. ويسمى بشكل bundle.js .
  • يبحث resolve Webpack في هذه السمة لتحديد ما إذا كان سيتم تجميع الملف أو تخطيه. وبالتالي ، في مشروعنا ، سيأخذ webpack في الاعتبار الملفات ذات الامتدادات .js و .jsx و .json و .ts و .tsx .
  • module يمكننا تمكين حزمة الويب لتحميل ملف معين عند طلب التطبيق ، باستخدام برامج التحميل. يأخذ كائن قواعد يحدد ما يلي:
    • يجب أن يستخدم أي ملف ينتهي بالملحق .tsx أو .ts تحميل awesome-typescript-loader ؛
    • يجب تحميل الملفات التي تنتهي بامتداد .js مع source-map-loader ؛
    • يجب تحميل الملفات التي تنتهي بامتداد .css مع محمل css-loader .
  • plugins Webpack لها قيودها الخاصة ، وهي توفر مكونات إضافية للتغلب عليها وتوسيع إمكانياتها. على سبيل المثال ، يقوم html-webpack-plugin بإنشاء ملف نموذج يتم عرضه على المتصفح من ملف index.html في دليل ./src/component/index.html .

يعرض MiniCssExtractPlugin ملف CSS الأصلي للتطبيق.

إضافة البرامج النصية إلى package.json

يمكننا إضافة نصوص مختلفة لبناء تطبيقات React في ملف package.json الخاص بنا:

 "scripts": { "start": "webpack-dev-server --open", "build": "webpack" },

الآن ، قم بتشغيل npm start في CLI الخاص بك. إذا سارت الأمور على ما يرام ، يجب أن ترى هذا:

إخراج إعداد React-Webpack (معاينة كبيرة)

إذا كانت لديك موهبة في حزمة الويب ، فاستنسخ المستودع لهذا الإعداد ، واستخدمه عبر مشروعاتك.

إنشاء الملفات

قم بإنشاء مجلد src وملف index.tsx . سيكون هذا هو الملف الأساسي الذي يعرض React.

الآن ، إذا قمنا بتشغيل npm start ، فسيتم تشغيل الخادم الخاص بنا وفتح علامة تبويب جديدة. سيؤدي تشغيل npm run build إلى إنشاء حزمة ويب للإنتاج وإنشاء مجلد بناء لنا.

لقد رأينا كيفية إعداد TypeScript من البداية باستخدام تطبيق Create React وطريقة تكوين حزمة الويب.

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

بعد ذلك ، سننظر في كيفية ترحيل مشروع React بسهولة إلى TypeScript.

قم بترحيل تطبيق Create React الحالي إلى TypeScript

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

  1. أضف TypeScript وأنواعها.
  2. أضف tsconfig.json .
  3. تبدأ صغيرة.
  4. إعادة تسمية امتداد الملفات إلى .tsx .

1. أضف TypeScript إلى المشروع

أولاً ، سنحتاج إلى إضافة TypeScript إلى مشروعنا. بافتراض أن مشروع React الخاص بك قد تم تمهيده باستخدام تطبيق Create React ، يمكننا تشغيل ما يلي:

 # Using npm npm install --save typescript @types/node @types/react @types/react-dom @types/jest # Using Yarn yarn add typescript @types/node @types/react @types/react-dom @types/jest

لاحظ أننا لم نقم بتغيير أي شيء إلى TypeScript حتى الآن. إذا قمنا بتشغيل الأمر لبدء المشروع محليًا ( npm start أو yarn start ) ، فلن يتغير شيء. إذا كان الأمر كذلك ، فهذا رائع! نحن جاهزون للخطوة التالية.

2. قم بإضافة ملف tsconfig.json

قبل الاستفادة من TypeScript ، نحتاج إلى تهيئته عبر ملف tsconfig.json . إن أبسط طريقة للبدء هي سقالة واحدة باستخدام هذا الأمر:

 npx tsc --init

هذا يوفر لنا بعض الأساسيات ، مع الكثير من التعليمات البرمجية المعلقة. الآن ، استبدل كل الكود في tsconfig.json بهذا:

 { "compilerOptions": { "jsx": "react", "module": "commonjs", "noImplicitAny": true, "outDir": "./build/", "preserveConstEnums": true, "removeComments": true, "sourceMap": true, "target": "es5" }, "include": [ "./src/**/**/\*" ] }

تكوين TypeScript

لنلقِ نظرة أيضًا على الخيارات المختلفة التي أضفناها إلى tsconfig.json :

  • compilerOptions يمثل خيارات المترجم المختلفة.
    • target يترجم أحدث إنشاءات JavaScript وصولاً إلى إصدار أقدم ، مثل ECMAScript 5.
    • lib يضيف قائمة بملفات المكتبة إلى التجميع (على سبيل المثال ، يتيح لنا استخدام es2015 استخدام بناء جملة ECMAScript 6).
    • jsx:react يضيف دعمًا لـ JSX في ملفات .tsx .
    • lib يضيف قائمة بملفات المكتبة إلى التجميع (على سبيل المثال ، يتيح لنا استخدام es2015 استخدام بناء جملة ECMAScript 6).
    • module يولد رمز الوحدة النمطية.
    • noImplicitAny استخدامه لإثارة أخطاء التصريحات any نوع ضمني.
    • يمثل outDir دليل الإخراج.
    • sourceMap بإنشاء ملف .map ، والذي يمكن أن يكون مفيدًا جدًا لتصحيح أخطاء تطبيقنا.
    • include Used لتحديد قائمة الملفات المراد تضمينها.

تختلف خيارات التكوينات حسب طلب المشروع. قد تحتاج إلى التحقق من جدول بيانات خيارات TypeScript لمعرفة ما يناسب مشروعك.

لقد اتخذنا فقط الإجراءات المطلوبة لتجهيز الأمور. خطوتنا التالية هي ترحيل ملف إلى TypeScript.

3. ابدأ بمكون بسيط

استفد من إمكانية اعتماد TypeScript تدريجيًا. انتقل إلى ملف واحد في كل مرة وفقًا لسرعتك الخاصة. افعل ما هو منطقي لك ولفريقك. لا تحاول معالجة كل ذلك مرة واحدة.

لتحويل هذا بشكل صحيح ، علينا القيام بأمرين:

  1. قم بتغيير امتداد الملف إلى .tsx .
  2. أضف التعليق التوضيحي للنوع (والذي قد يتطلب بعض المعرفة في TypeScript).

4. إعادة تسمية ملحقات الملف إلى .tsx

في قاعدة التعليمات البرمجية الكبيرة ، قد يبدو من المتعب إعادة تسمية الملفات بشكل فردي.

إعادة تسمية الملفات المضاعفة على macOS

يمكن أن تكون إعادة تسمية ملفات متعددة مضيعة للوقت. إليك كيفية القيام بذلك على جهاز Mac. انقر بزر الماوس الأيمن (أو Ctrl + النقر أو انقر بإصبعين في وقت واحد على لوحة التتبع إذا كنت تستخدم MacBook) على المجلد الذي يحتوي على الملفات التي تريد إعادة تسميتها. ثم انقر على "كشف في الباحث". في Finder ، حدد جميع الملفات التي تريد إعادة تسميتها. انقر بزر الماوس الأيمن فوق الملفات المحددة ، واختر "إعادة تسمية عناصر X ..." ثم سترى شيئًا مثل هذا:

إعادة تسمية الملفات على جهاز Mac (معاينة كبيرة)

أدخل السلسلة التي تريد البحث عنها ، والسلسلة التي تريد استبدال السلسلة التي تم العثور عليها بها ، واضغط على "إعادة تسمية". منجز.

إعادة تسمية الملفات المضاعفة على Windows

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

لقد غطينا كيفية إعداد TypeScript في تطبيق React. الآن ، لنقم ببناء تطبيق منتقي الحلقات لـ Money Heist باستخدام TypeScript.

لن نغطي الأنواع الأساسية من TypeScript. مطلوب مراجعة الوثائق قبل المتابعة في هذا البرنامج التعليمي.

حان وقت البناء

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

  • سقالة إنشاء تطبيق تفاعل.
  • إحضار الحلقات.
    • قم بإنشاء الأنواع والواجهات المناسبة لحلقاتنا في interface.ts .
    • قم بإعداد متجر لجلب الحلقات في store.tsx .
    • قم بإنشاء الإجراء الخاص بجلب الحلقات قيد التشغيل. action.ts .
    • قم بإنشاء مكون EpisodeList.tsx يحتوي على الحلقات التي تم جلبها.
    • قم باستيراد مكون EpisodesList إلى صفحتنا الرئيسية باستخدام React Lazy and Suspense .
  • أضف الحلقات.
    • قم بإعداد متجر لإضافة الحلقات في store.tsx .
    • قم بإنشاء الإجراء لإضافة حلقات in action.ts .
  • إزالة الحلقات.
    • قم بإعداد متجر لحذف الحلقات في store.tsx .
    • قم بإنشاء الإجراء الخاص بحذف الحلقات أثناء العمل. action.ts .
  • الحلقة المفضلة.
    • استيراد مكون EpisodesList في الحلقة المفضلة.
    • عرض EpisodesList قائمة داخل الحلقة المفضلة.
  • استخدام Reach Router للملاحة.

إعداد React

أسهل طريقة لإعداد React هي استخدام تطبيق Create React. يعد إنشاء تطبيق React طريقة مدعومة رسميًا لإنشاء تطبيقات React أحادية الصفحة. إنه يوفر إعداد بناء حديث بدون تكوين.

سنستخدمه في تمهيد التطبيق الذي سنبنيه. من CLI الخاص بك ، قم بتشغيل الأمر أدناه:

 npx create-react-app react-ts-app && cd react-ts-app

بمجرد نجاح التثبيت ، ابدأ خادم React عن طريق تشغيل npm start .

صفحة بدء التفاعل (معاينة كبيرة)

فهم الواجهات والأنواع في الكتابة المطبوعة

يتم استخدام الواجهات في TypeScript عندما نحتاج إلى إعطاء أنواع لخصائص الكائنات. ومن ثم ، سنستخدم واجهات لتحديد أنواعنا.

 interface Employee { name: string, role: string salary: number } const bestEmployee: Employee= { name: 'John Doe', role: 'IOS Developer', salary: '$8500' //notice we are using a string }

عند تجميع الكود أعلاه ، سنرى هذا الخطأ: "أنواع salary الممتلكات غير متوافقة. string النوع غير قابلة للتخصيص لكتابة number . "

تحدث مثل هذه الأخطاء في TypeScript عندما يتم تعيين خاصية أو متغير لنوع آخر غير النوع المحدد. على وجه التحديد ، يعني المقتطف أعلاه أنه تم تعيين نوع string بدلاً من نوع number لخاصية salary .

لنقم بإنشاء ملف interface.ts في مجلد src الخاص بنا. انسخ والصق هذا الرمز فيه:

 /** |-------------------------------------------------- | All the interfaces! |-------------------------------------------------- */ export interface IEpisode { airdate: string airstamp: string airtime: string id: number image: { medium: string; original: string } name: string number: number runtime: number season: number summary: string url: string } export interface IState { episodes: Array<IEpisode> favourites: Array<IEpisode> } export interface IAction { type: string payload: Array<IEpisode> | any } export type Dispatch = React.Dispatch<IAction> export type FavAction = ( state: IState, dispatch: Dispatch, episode: IEpisode ) => IAction export interface IEpisodeProps { episodes: Array<IEpisode> store: { state: IState; dispatch: Dispatch } toggleFavAction: FavAction favourites: Array<IEpisode> } export interface IProps { episodes: Array<IEpisode> store: { state: IState; dispatch: Dispatch } toggleFavAction: FavAction favourites: Array<IEpisode> }

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

واجهة الحلقة

تقوم واجهة برمجة التطبيقات الخاصة بنا بإرجاع مجموعة من الخصائص مثل تاريخ البث airstamp airtime airdate image name number runtime season summary id url . ومن ثم ، قمنا بتعريف واجهة IEpisode بتعيين أنواع البيانات المناسبة لخصائص الكائن.

واجهة IState

تحتوي واجهة IState الخاصة بنا على episodes وخصائص favorites ، على التوالي ، Array<IEpisode> .

IAction

خصائص واجهة IAction هي payload type . تحتوي خاصية type على نوع سلسلة ، بينما تحتوي الحمولة على نوع من Array | any Array | any .

لاحظ أن Array | any Array | any وسيلة مصفوفة من واجهة الحلقة أو أي نوع.

يتم تعيين نوع Dispatch على React.Dispatch <IAction> . لاحظ أن React.Dispatch هو النوع القياسي لوظيفة dispatch ، وفقًا @types/react قاعدة الكود المتفاعل ، بينما <IAction> عبارة عن مصفوفة من إجراء الواجهة.

أيضًا ، يحتوي Visual Studio Code على مدقق TypeScript. لذلك ، بمجرد تمييز الكود أو التمرير فوقه ، يكون من الذكاء بما يكفي لاقتراح النوع المناسب.

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

إحضار الحلقات

إنشاء متجر

لجلب حلقاتنا ، نحتاج إلى متجر يحتوي على الحالة الأولية للبيانات ويحدد وظيفة المخفض لدينا.

سنستفيد من خطاف useReducer لإعداد ذلك. قم بإنشاء ملف store.tsx في مجلد src الخاص بك. انسخ والصق الكود التالي فيه.

 import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext (initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} } import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext (initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} } import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext (initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} } import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext (initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} }

فيما يلي الخطوات التي اتخذناها لإنشاء المتجر:

  • عند تحديد متجرنا ، نحتاج إلى الخطاف useReducer و createContext API من React ، ولهذا السبب قمنا باستيراده.
  • استوردنا IState و IAction من ./types/interfaces .
  • أعلنا عن كائن أولي من نوع initialState ، وخصائص الحلقات IState ، وكلاهما مضبوط على مصفوفة فارغة ، على التوالي.
  • بعد ذلك ، أنشأنا متغير Store يحتوي على طريقة createContext الحالة initialState .

نوع أسلوب createContext هو <IState | any> <IState | any> ، مما يعني أنه يمكن أن يكون نوعًا من <IState> أو any آخر. سنرى any نوع يستخدم غالبًا في هذه المقالة.

  • بعد ذلك ، أعلنا عن وظيفة reducer وقمنا بتمرير state action كمعلمات. تحتوي وظيفة reducer على بيان تبديل يتحقق من قيمة action.type . إذا كانت القيمة FETCH_DATA ، فإنها تُرجع كائنًا يحتوي على نسخة من حالتنا (...state) وحالة الحلقة التي تحمل حمولة الإجراء الخاصة بنا.
  • في بيان التبديل ، نعيد حالة التخلف عن default .

لاحظ أن معلمات state action في وظيفة المخفض لها أنواع IState و IAction ، على التوالي. أيضا ، وظيفة reducer لديها نوع من IState .

  • أخيرًا ، أعلنا عن وظيفة StoreProvider . هذا سيمنح جميع المكونات الموجودة في تطبيقنا الوصول إلى المتجر.
  • تأخذ هذه الوظيفة children كدعم ، وداخل وظيفة StorePrivder ، أعلنا عن خطاف useReducer .
  • لقد دمرنا state dispatch .
  • من أجل جعل متجرنا في متناول جميع المكونات ، مررنا قيمة كائن تحتوي على state dispatch .

state التي تحتوي على الحلقات وحالة التفضيلات الخاصة بنا ستتاح للمكونات الأخرى ، بينما dispatch هو وظيفة تغير الحالة.

  • سنقوم بتصدير Store و StoreProvider ، بحيث يمكن استخدامه عبر تطبيقنا.

قم بإنشاء Action.ts

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

 import { Dispatch } from './interface/interfaces' export const fetchDataAction = async (dispatch: Dispatch) => { const URL = 'https://api.tvmaze.com/singlesearch/shows?q=la-casa-de-papel&embed=episodes' const data = await fetch(URL) const dataJSON = await data.json() return dispatch({ type: 'FETCH_DATA', payload: dataJSON.\_embedded.episodes }) }

أولاً ، نحتاج إلى استيراد واجهاتنا حتى يمكن استخدامها في هذا الملف. تم اتخاذ الخطوات التالية لإنشاء الإجراء:

  • تأخذ الدالة fetchDataAction dispatch كمعامل.
  • نظرًا لأن وظيفتنا غير متزامنة ، فسنستخدم غير async await .
  • نقوم بإنشاء متغير ( URL ) يحمل نقطة نهاية API الخاصة بنا.
  • لدينا متغير آخر يسمى data التي تحمل الاستجابة من API.
  • بعد ذلك ، نقوم بتخزين استجابة JSON في dataJSON ، بعد أن نحصل على الاستجابة بتنسيق JSON عن طريق استدعاء data.json() .
  • أخيرًا ، نعيد دالة إرسال لها خاصية من type وسلسلة من FETCH_DATA . كما أن لديها payload() . _embedded.episodes هي مجموعة كائن الحلقات من endpoint لدينا.

لاحظ أن الدالة fetchDataAction تجلب نقطة النهاية الخاصة بنا ، وتحولها إلى كائنات JSON ، وتُعيد وظيفة الإرسال ، التي تُحدِّث الحالة المُعلنة سابقًا في المتجر.

يتم تعيين نوع الإرسال المُصدَّر على React.Dispatch . لاحظ أن React.Dispatch هو النوع القياسي لوظيفة الإرسال وفقًا لقاعدة التعليمات البرمجية @types/react التفاعلية ، بينما <IAction> مصفوفة من إجراء الواجهة.

مكون قائمة الحلقات

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

في مجلد components ، أنشئ ملف EpisodesList.tsx وانسخ الكود التالي والصقه فيه:

 import React from 'react' import { IEpisode, IProps } from '../types/interfaces' const EpisodesList = (props: IProps): Array<JSX.Element> => { const { episodes } = props return episodes.map((episode: IEpisode) => { return ( <section key={episode.id} className='episode-box'> <img src={!!episode.image ? episode.image.medium : ''} alt={`Money Heist ${episode.name}`} /> <div>{episode.name}</div> <section style={{ display: 'flex', justifyContent: 'space-between' }}> <div> Season: {episode.season} Number: {episode.number} </div> <button type='button' > Fav </button> </section> </section> ) }) } export default EpisodesList
  • نقوم باستيراد IEpisode و IProps من interfaces.tsx .tsx.
  • بعد ذلك ، نقوم بإنشاء وظيفة EpisodesList التي تأخذ الخاصيات. ستحتوي الدعائم على نوع من IProps ، بينما تحتوي الوظيفة على نوع من Array<JSX.Element> .

يقترح Visual Studio Code أن يتم كتابة نوع وظيفتنا كـ JSX.Element[] .

يقترح Visual Studio Code نوعًا (معاينة كبيرة)

بينما Array<JSX.Element> تساوي JSX.Element[] ، تسمى Array<JSX.Element> الهوية العامة. وبالتالي ، سيتم استخدام النمط العام غالبًا في هذه المقالة.

  • داخل الوظيفة ، نقوم بتدمير episodes من props ، والتي تحتوي على IEpisode كنوع.

اقرأ عن الهوية العامة ، ستكون هذه المعرفة مطلوبة أثناء تقدمنا.

  • أعدنا عناصر episodes وقمنا بتعيينها من خلالها لإرجاع بعض علامات HTML.
  • يحتوي القسم الأول على key ، وهو episode.id ، واسم class className episode-box ، والذي سيتم إنشاؤه لاحقًا. نحن نعلم أن حلقاتنا بها صور ؛ ومن ثم ، فإن علامة الصورة.
  • تحتوي الصورة على عامل تشغيل ثلاثي يتحقق مما إذا كانت هناك episode.image.medium episode.image وإلا فإننا نعرض سلسلة فارغة إذا لم يتم العثور على صورة. أيضًا ، قمنا بتضمين episode.name في div.

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

مكون الصفحة الرئيسية

نريد من الصفحة الرئيسية تشغيل استدعاء API وعرض الحلقات باستخدام مكون EpisodesList الذي أنشأناه. داخل مجلد components ، أنشئ مكون HomePage ، وانسخ الكود التالي والصقه فيه:

 import React, { useContext, useEffect, lazy, Suspense } from 'react' import App from '../App' import { Store } from '../Store' import { IEpisodeProps } from '../types/interfaces' import { fetchDataAction } from '../Actions' const EpisodesList = lazy<any>(() => import('./EpisodesList')) const HomePage = (): JSX.Element => { const { state, dispatch } = useContext(Store) useEffect(() => { state.episodes.length === 0 && fetchDataAction(dispatch) }) const props: IEpisodeProps = { episodes: state.episodes, store: { state, dispatch } } return ( <App> <Suspense fallback={<div>loading...</div>}> <section className='episode-layout'> <EpisodesList {...props} /> </section> </Suspense> </App> ) } export default HomePage
  • نستورد useContext و useEffect و lazy و Suspense من React. مكون التطبيق الذي تم استيراده هو الأساس الذي يجب أن تتلقى عليه جميع المكونات الأخرى قيمة المتجر بناءً عليه.
  • نقوم أيضًا باستيراد Store و IEpisodeProps و FetchDataAction من ملفاتهم الخاصة.
  • نستورد مكون EpisodesList باستخدام ميزة React.lazy المتوفرة في React 16.6.

يدعم التحميل البطيء في React اصطلاح تقسيم الكود. وبالتالي ، يتم تحميل مكون EpisodesList الخاص بنا ديناميكيًا ، بدلاً من تحميله مرة واحدة ، وبالتالي تحسين أداء تطبيقنا.

  • ندمر state dispatch كدعائم من Store .
  • يتحقق العطف (&&) في الخطاف useEffect إذا كانت حالة الحلقات empty (أو تساوي 0). عدا ذلك ، نعيد الدالة fetchDataAction .
  • أخيرًا ، نعيد مكون App . بداخله ، نستخدم غلاف Suspense ، fallback إلى div بنص loading . سيتم عرض هذا للمستخدم أثناء انتظار استجابة API.
  • سيتم تحميل مكون EpisodesList عند توفر البيانات ، والبيانات التي ستحتوي على episodes هي ما ننشره فيه.

قم بإعداد Index.txs

يجب أن يكون مكون Homepage تابعًا لـ StoreProvider . سيتعين علينا القيام بذلك في ملف index . أعد تسمية index.js إلى index.tsx والصق الكود التالي:

 import React from 'react' import ReactDOM from 'react-dom' import './index.css' import { StoreProvider } from './Store' import HomePage from './components/HomePage' ReactDOM.render( <StoreProvider> <HomePage /> </StoreProvider>, document.getElementById('root') )

نقوم باستيراد StoreProvider و HomePage و index.css من ملفاتهم الخاصة. We wrap the HomePage component in our StoreProvider . This makes it possible for the Homepage component to access the store, as we saw in the previous section.

لقد قطعنا شوطا طويلا. Let's check what the app looks like, without any CSS.

App without CSS (Large preview)

Create Index.css

Delete the code in the index.css file and replace it with this:

 html { font-size: 14px; } body { margin: 0; padding: 0; font-size: 10px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .episode-layout { display: flex; flex-wrap: wrap; min-width: 100vh; } .episode-box { padding: .5rem; } .header { display: flex; justify-content: space-between; background: white; border-bottom: 1px solid black; padding: .5rem; position: sticky; top: 0; }

Our app now has a look and feel. Here's how it looks with CSS.

(معاينة كبيرة)

Now we see that our episodes can finally be fetched and displayed, because we've adopted TypeScript all the way. Great, isn't it?

Add Favorite Episodes Feature

Let's add functionality that adds favorite episodes and that links it to a separate page. Let's go back to our Store component and add a few lines of code:

Note that the highlighted code is newly added:

 import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext<IState | any>(initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload }
 case 'ADD_FAV': return { ...state, favourites: [...state.favourites, action.payload] }
 default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return <Store.Provider value={{ state, dispatch }}>{children}</Store.Provider> }

To implement the “Add favorite” feature to our app, the ADD_FAV case is added. It returns an object that holds a copy of our previous state, as well as an array with a copy of the favorite state , with the payload .

We need an action that will be called each time a user clicks on the FAV button. Let's add the highlighted code to index.tx :

 import { IAction, IEpisode, Dispatch } from './types/interfaces'
export const fetchDataAction = async (dispatch: Dispatch) => { const URL = 'https://api.tvmaze.com/singlesearch/shows?q=la-casa-de-papel&embed=episodes' const data = await fetch(URL) const dataJSON = await data.json() return dispatch({ type: 'FETCH_DATA', payload: dataJSON._embedded.episodes }) }
export const toggleFavAction = (dispatch: any, episode: IEpisode | any): IAction => { let dispatchObj = { type: 'ADD_FAV', payload: episode } return dispatch(dispatchObj) }
export const toggleFavAction = (dispatch: any, episode: IEpisode | any): IAction => { let dispatchObj = { type: 'ADD_FAV', payload: episode } return dispatch(dispatchObj) }

We create a toggleFavAction function that takes dispatch and episodes as parameters, and any and IEpisode|any as their respective types, with IAction as our function type. We have an object whose type is ADD_FAV and that has episode as its payload. Lastly, we just return and dispatch the object.

سنضيف المزيد من المقتطفات إلى EpisodeList.tsx . انسخ والصق الرمز المميز:

 import React from 'react' import { IEpisode, IProps } from '../types/interfaces' const EpisodesList = (props: IProps): Array<JSX.Element> => {
 const { episodes, toggleFavAction, favourites, store } = props const { state, dispatch } = store

 return episodes.map((episode: IEpisode) => { return ( <section key={episode.id} className='episode-box'> <img src={!!episode.image ? episode.image.medium : ''} alt={`Money Heist - ${episode.name}`} /> <div>{episode.name}</div> <section style={{ display: 'flex', justifyContent: 'space-between' }}> <div> Seasion: {episode.season} Number: {episode.number} </div> <button type='button'
 onClick={() => toggleFavAction(state, dispatch, episode)} > {favourites.find((fav: IEpisode) => fav.id === episode.id) ? 'Unfav' : 'Fav'}
 </button> </section> </section> ) }) } export default EpisodesList

نقوم بتضمين togglefavaction favorites store كدعامات ، ونقوم بإتلاف state ، dispatch من المتجر. من أجل تحديد الحلقة المفضلة لدينا ، نقوم بتضمين طريقة toggleFavAction في حدث onClick ، ​​وتمرير دعائم state dispatch episode كوسيطات للوظيفة.

أخيرًا ، نقوم بإجراء حلقة عبر الحالة favorite للتحقق مما إذا كان fav.id (المعرف المفضل) يطابق episode.id . إذا كان الأمر كذلك ، فسنقوم بالتبديل بين Unfav و Fav . يساعد هذا المستخدم في معرفة ما إذا كان قد فضل تلك الحلقة أم لا.

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

إذا كنت قد وصلت إلى هذا الحد ، فامنح نفسك ظهرك.

مكون Favpage

في مجلد components ، قم بإنشاء ملف FavPage.tsx . انسخ والصق الكود التالي إليه:

 import React, { lazy, Suspense } from 'react' import App from '../App' import { Store } from '../Store' import { IEpisodeProps } from '../types/interfaces' import { toggleFavAction } from '../Actions' const EpisodesList = lazy<any>(() => import('./EpisodesList')) export default function FavPage(): JSX.Element { const { state, dispatch } = React.useContext(Store) const props: IEpisodeProps = { episodes: state.favourites, store: { state, dispatch }, toggleFavAction, favourites: state.favourites } return ( <App> <Suspense fallback={<div>loading...</div>}> <div className='episode-layout'> <EpisodesList {...props} /> </div> </Suspense> </App> ) }

لإنشاء المنطق وراء اختيار الحلقات المفضلة ، قمنا بكتابة رمز صغير. نحن نستورد lazy وتشويق من Suspense . نقوم أيضًا باستيراد Store و IEpisodeProps و toggleFavAction من ملفاتهم الخاصة.

نقوم باستيراد مكون EpisodesList الخاص بنا باستخدام ميزة React.lazy . أخيرًا ، نعيد مكون App . بداخله ، نستخدم غلاف Suspense ، وقمنا بتعيين احتياطي إلى div بنص التحميل.

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

دعنا نضيف بعض المقتطفات إلى ملف HomePage.tsx .

قم بتضمين toggleFavAction من ../Actions . قم أيضًا بتضمين طريقة toggleFavAction .

 import React, { useContext, useEffect, lazy, Suspense } from 'react' import App from '../App' import { Store } from '../Store' import { IEpisodeProps } from '../types/interfaces'
import { fetchDataAction, toggleFavAction } from '../Actions'
const EpisodesList = lazy<any>(() => import('./EpisodesList')) const HomePage = (): JSX.Element => { const { state, dispatch } = useContext(Store) useEffect(() => { state.episodes.length === 0 && fetchDataAction(dispatch) }) const props: IEpisodeProps = { episodes: state.episodes, store: { state, dispatch },
 toggleFavAction, favourites: state.favourites
 } return ( <App> <Suspense fallback={<div>loading...</div>}> <section className='episode-layout'> <EpisodesList {...props} /> </section> </Suspense> </App> ) } export default HomePage

يجب ربط FavPage الخاصة بنا ، لذلك نحتاج إلى ارتباط في العنوان الخاص بنا في App.tsx . لتحقيق ذلك ، نستخدم Reach Router ، مكتبة مشابهة لـ React Router. يشرح William Le الاختلافات بين Reach Router و React Router.

في CLI الخاص بك ، قم بتشغيل npm install @reach/router @types/reach__router . نقوم بتثبيت كل من مكتبة Reach Router وأنواع أجهزة reach-router .

عند التثبيت الناجح ، قم باستيراد Link من @reach/router .

 import React, { useContext, Fragment } from 'react' import { Store } from './tsx'
import { Link } from '@reach/router'
 const App = ({ children }: { children: JSX.Element }): JSX.Element => {
 const { state } = useContext(Store)
return ( <Fragment> <header className='header'> <div> <h1>Money Heist</h1> <p>Pick your favourite episode</p> </div>
 <div> <Link to='/'>Home</Link> <Link to='/faves'>Favourite(s): {state.favourites.length}</Link> </div>
 </header> {children} </Fragment> ) } export default App

ندمر المتجر من useContext . أخيرًا ، سيكون Link ومسار إلى / ، في حين أن المفضل لدينا لديه مسار /faves مفضل.

يتحقق {state.favourites.length} من عدد الحلقات في حالات المفضلة ويعرضها.

أخيرًا ، في ملف index.tsx بنا ، نستورد مكونات FavPage و HomePage ، على التوالي ، ونلفهما في Router .

انسخ الرمز المميز إلى الكود الحالي:

 import React from 'react' import ReactDOM from 'react-dom' import './index.css' import { StoreProvider } from './Store'
import { Router, RouteComponentProps } from '@reach/router' import HomePage from './components/HomePage' import FavPage from './components/FavPage' const RouterPage = ( props: { pageComponent: JSX.Element } & RouteComponentProps ) => props.pageComponent
ReactDOM.render( <StoreProvider>
 <Router> <RouterPage pageComponent={<HomePage />} path='/' /> <RouterPage pageComponent={<FavPage />} path='/faves' /> </Router>
 </StoreProvider>, document.getElementById('root') )

الآن ، دعنا نرى كيف يعمل ADD_FAV .

يعمل رمز "إضافة مفضل" (معاينة كبيرة)

إزالة وظيفة المفضلة

أخيرًا ، سنضيف "ميزة إزالة الحلقة" ، بحيث عند النقر فوق الزر ، نقوم بالتبديل بين إضافة أو إزالة حلقة مفضلة. سنعرض عدد الحلقات التي تمت إضافتها أو إزالتها في العنوان.

متجر

لإنشاء وظيفة "إزالة الحلقة المفضلة" ، سنضيف حالة أخرى في متجرنا. لذلك ، انتقل إلى Store.tsx وأضف الرمز المميز:

 import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext<IState | any>(initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload } case 'ADD_FAV': return { ...state, favourites: [...state.favourites, action.payload] }
 case 'REMOVE_FAV': return { ...state, favourites: action.payload }
 default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} }

نضيف حالة أخرى باسم REMOVE_FAV كائنًا يحتوي على نسخة الحالة initialState . أيضًا ، تحتوي حالة favorites على حمولة الإجراء.

عمل

انسخ الكود المميز التالي والصقه في action.ts :

 import { IAction, IEpisode, IState, Dispatch } from './types/interfaces'
export const fetchDataAction = async (dispatch: Dispatch) => { const URL = 'https://api.tvmaze.com/singlesearch/shows?q=la-casa-de-papel&embed=episodes' const data = await fetch(URL) const dataJSON = await data.json() return dispatch({ type: 'FETCH_DATA', payload: dataJSON.\_embedded.episodes }) } //Add IState withits type
export const toggleFavAction = (state: IState, dispatch: any, episode: IEpisode | any): IAction => { const episodeInFav = state.favourites.includes(episode)
 let dispatchObj = { type: 'ADD_FAV', payload: episode }
 if (episodeInFav) { const favWithoutEpisode = state.favourites.filter( (fav: IEpisode) => fav.id !== episode.id ) dispatchObj = { type: 'REMOVE_FAV', payload: favWithoutEpisode }
 } return dispatch(dispatchObj) }

نستورد واجهة IState من ./types/interfaces ، لأننا سنحتاج إلى تمريرها كنوع إلى خاصيات state في دالة toggleFavAction .

يتم إنشاء متغير episodeInFav للتحقق مما إذا كانت هناك حلقة موجودة في حالة favorites .

نقوم بالتصفية من خلال حالة المفضلة للتحقق مما إذا كان المعرف المفضل لا يساوي معرف الحلقة. وبالتالي ، يتم إعادة تعيين dispatchObj نوعًا من REMOVE_FAV وحمولة من favWithoutEpisode .

دعونا نعاين نتيجة تطبيقنا.

خاتمة

في هذه المقالة ، رأينا كيفية إعداد TypeScript في مشروع React ، وكيفية ترحيل مشروع من Vanilla React إلى TypeScript.

لقد أنشأنا أيضًا تطبيقًا باستخدام TypeScript و React لمعرفة كيفية استخدام TypeScript في مشاريع React. أنا على ثقة من أنك تمكنت من تعلم بعض الأشياء.

يرجى مشاركة ملاحظاتك وخبراتك مع TypeScript في قسم التعليقات أدناه. أحب أن أرى ما توصلت إليه!

المستودع الداعم لهذه المقالة متاح على GitHub.

مراجع

  1. "كيفية ترحيل تطبيق React إلى TypeScript ،" Joe Previte
  2. "لماذا وكيف تستخدم TypeScript في تطبيق React الخاص بك ؟،" Mahesh Haldar