إعداد TypeScript لمشاريع التفاعل الحديثة باستخدام Webpack
نشرت: 2022-03-10في عصر تطوير البرامج هذا ، يمكن استخدام 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).
في لغة مكتوبة ديناميكيًا مثل 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 في مشروعك.
لبدء مشروع 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
أيضًا بعض الملفات والمجلدات المختلفة يدويًا ضمن مجلد حزمة الويب التفاعلية:
- أضف
webpack.config.js
لإضافة التكوينات ذات الصلة بـ webpack. - أضف
tsconfig.json
لجميع تكوينات TypeScript الخاصة بنا. - إضافة دليل جديد ،
src
. - قم بإنشاء دليل جديد ،
components
، في مجلدsrc
. - أخيرًا ، أضف
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 الخاص بك. إذا سارت الأمور على ما يرام ، يجب أن ترى هذا:
إذا كانت لديك موهبة في حزمة الويب ، فاستنسخ المستودع لهذا الإعداد ، واستخدمه عبر مشروعاتك.
إنشاء الملفات
قم بإنشاء مجلد src
وملف index.tsx
. سيكون هذا هو الملف الأساسي الذي يعرض React.
الآن ، إذا قمنا بتشغيل npm start
، فسيتم تشغيل الخادم الخاص بنا وفتح علامة تبويب جديدة. سيؤدي تشغيل npm run build
إلى إنشاء حزمة ويب للإنتاج وإنشاء مجلد بناء لنا.
لقد رأينا كيفية إعداد TypeScript من البداية باستخدام تطبيق Create React وطريقة تكوين حزمة الويب.
واحدة من أسرع الطرق للحصول على فهم كامل لـ TypeScript هي عن طريق تحويل أحد مشاريع Vanilla React الحالية إلى TypeScript. لسوء الحظ ، فإن الاعتماد المتزايد على TypeScript في مشروع Vanilla React الحالي أمر مرهق لأنه يستلزم الاضطرار إلى إخراج أو إعادة تسمية جميع الملفات ، مما قد يؤدي إلى تعارضات وطلب سحب ضخم إذا كان المشروع ينتمي إلى فريق كبير.
بعد ذلك ، سننظر في كيفية ترحيل مشروع React بسهولة إلى TypeScript.
قم بترحيل تطبيق Create React الحالي إلى TypeScript
لجعل هذه العملية أكثر قابلية للإدارة ، سنقسمها إلى خطوات ، والتي ستمكننا من الترحيل في أجزاء فردية. فيما يلي الخطوات التي سنتخذها لترحيل مشروعنا:
- أضف TypeScript وأنواعها.
- أضف
tsconfig.json
. - تبدأ صغيرة.
- إعادة تسمية امتداد الملفات إلى
.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 تدريجيًا. انتقل إلى ملف واحد في كل مرة وفقًا لسرعتك الخاصة. افعل ما هو منطقي لك ولفريقك. لا تحاول معالجة كل ذلك مرة واحدة.
لتحويل هذا بشكل صحيح ، علينا القيام بأمرين:
- قم بتغيير امتداد الملف إلى
.tsx
. - أضف التعليق التوضيحي للنوع (والذي قد يتطلب بعض المعرفة في TypeScript).
4. إعادة تسمية ملحقات الملف إلى .tsx
في قاعدة التعليمات البرمجية الكبيرة ، قد يبدو من المتعب إعادة تسمية الملفات بشكل فردي.
إعادة تسمية الملفات المضاعفة على macOS
يمكن أن تكون إعادة تسمية ملفات متعددة مضيعة للوقت. إليك كيفية القيام بذلك على جهاز Mac. انقر بزر الماوس الأيمن (أو Ctrl
+ النقر أو انقر بإصبعين في وقت واحد على لوحة التتبع إذا كنت تستخدم MacBook) على المجلد الذي يحتوي على الملفات التي تريد إعادة تسميتها. ثم انقر على "كشف في الباحث". في Finder ، حدد جميع الملفات التي تريد إعادة تسميتها. انقر بزر الماوس الأيمن فوق الملفات المحددة ، واختر "إعادة تسمية عناصر X ..." ثم سترى شيئًا مثل هذا:
أدخل السلسلة التي تريد البحث عنها ، والسلسلة التي تريد استبدال السلسلة التي تم العثور عليها بها ، واضغط على "إعادة تسمية". منجز.
إعادة تسمية الملفات المضاعفة على 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[]
.
بينما Array<JSX.Element>
تساوي JSX.Element[]
، تسمى Array<JSX.Element>
الهوية العامة. وبالتالي ، سيتم استخدام النمط العام غالبًا في هذه المقالة.
- داخل الوظيفة ، نقوم بتدمير
episodes
منprops
، والتي تحتوي علىIEpisode
كنوع.
اقرأ عن الهوية العامة ، ستكون هذه المعرفة مطلوبة أثناء تقدمنا.
- أعدنا عناصر
episodes
وقمنا بتعيينها من خلالها لإرجاع بعض علامات HTML. - يحتوي القسم الأول على
key
، وهوepisode.id
، واسم classclassName
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.
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.
مراجع
- "كيفية ترحيل تطبيق React إلى TypeScript ،" Joe Previte
- "لماذا وكيف تستخدم TypeScript في تطبيق React الخاص بك ؟،" Mahesh Haldar