שלום לכם, ברוכים הבאים להדרכה זו. לפני שנתחיל עליכם להכיר מושגים בסיסיים של ReactJS. אם לא, אני ממליץ לך לעבור על תיעוד ReactJS.
נשתמש ברכיבים הבאים ביישום זה:
- ReactJS
- ממשק משתמש מהותי
- Firebase
- ExpressJS
- דוור
איך היישום שלנו ייראה:


אדריכלות יישומים:

הבנת המרכיבים שלנו:
ייתכן שאתה תוהה מדוע אנו משתמשים ב- Firebase ביישום זה. ובכן, זה מספק מאובטח אימות , A הנתונים בזמן אמת , A רכיב ללא שרת, וכן דלי חפצים .
אנו משתמשים כאן באקספרס כדי שלא נצטרך לטפל בחריגי HTTP. אנו הולכים להשתמש בכל חבילות ה- Firebase ברכיב הפונקציות שלנו. הסיבה לכך היא שאנחנו לא רוצים להפוך את יישום הלקוח שלנו לגדול מדי, אשר נוטה להאט את תהליך הטעינה של ממשק המשתמש.
הערה: אני הולך לחלק הדרכה זו לארבעה חלקים נפרדים. בתחילת כל קטע, תמצא התחייבות git שהקוד פותח בחלק זה. כמו כן אם ברצונך לראות את הקוד השלם, הוא זמין במאגר זה.
סעיף 1: פיתוח ממשקי API של Todo
בזהבסעיף , אנו הולכים לפתח את האלמנטים הבאים:
- הגדר את פונקציות Firebase.
- התקן את מסגרת Express ובנה ממשקי API של Todo.
- הגדרת תצורת חנות האש כמסד נתונים.
קוד API Todo מיושם בחלק זה ניתן למצוא בכתובת זו להתחייב.
הגדר פונקציות Firebase:
עבור למסוף Firebase.

בחר באפשרות הוסף פרויקט . לאחר מכן עקוב אחר ה- gif למטה שלב אחר שלב כדי להגדיר את פרויקט firebase.

עבור לכרטיסיית הפונקציות ולחץ על כפתור התחל :

תראה תיבת דו-שיח הכוללת הוראות כיצד להגדיר את פונקציות Firebase . עבור לסביבה המקומית שלך. פתח כלי שורת פקודה. כדי להתקין את כלי ה- Firebase במחשב שלך השתמש בפקודה למטה:
npm install -g firebase-tools
ברגע שזה נעשה ואז השתמש בפקודה firebase init
כדי להגדיר את פונקציות firebase בסביבה המקומית שלך. בחר באפשרויות הבאות בעת אתחול הפונקציה firebase בסביבה המקומית:
- אילו תכונות CLI של Firebase אתה רוצה להגדיר עבור תיקיה זו? לחץ על רווח כדי לבחור בתכונות, ואז על Enter כדי לאשר את בחירותיך => פונקציות: הגדר ופרוס פונקציות ענן
- ראשית, בואו נקשר את מדריך הפרויקט הזה לפרויקט Firebase .... => השתמש בפרויקט קיים
- בחר פרויקט Firebase המוגדר כברירת מחדל עבור ספרייה זו => שם יישום
- באיזו שפה תרצה להשתמש כדי לכתוב פונקציות ענן? => JavaScript
- האם אתה רוצה להשתמש ב- ESLint כדי לתפוס באגים אפשריים ולאכוף סגנון? => נ
- האם ברצונך להתקין תלות עם npm כעת? (Y / n) => Y
לאחר סיום התצורה תקבל את ההודעה הבאה:
✔ Firebase initialization complete!
זה יהיה מבנה הספרייה שלנו לאחר השלמת האתחול:
+-- firebase.json +-- functions | +-- index.js | +-- node_modules | +-- package-lock.json | +-- package.json
כעת פתח את index.js
ספריית הפונקציות תחת והעתק והדבק את הקוד הבא:
const functions = require('firebase-functions'); exports.helloWorld = functions.https.onRequest((request, response) => { response.send("Hello from Firebase!"); });
פרוס את הקוד לפונקציות firebase באמצעות הפקודה הבאה:
firebase deploy
לאחר סיום הפריסה תקבל את שורת היומן הבאה בסוף שורת הפקודה שלך:
> ✔ Deploy complete! > Project Console: //console.firebase.google.com/project/todoapp-/overview
עבור אל מסוף הפרויקט> פונקציות ושם תמצא את כתובת ה- URL של ה- API. כתובת האתר תיראה כך:
//-todoapp-.cloudfunctions.net/helloWorld
העתק כתובת אתר זו והדבק אותה בדפדפן. תקבל את התגובה הבאה:
Hello from Firebase!
זה מאשר כי פונקציית Firebase שלנו הוגדרה כהלכה.
התקן את מסגרת האקספרס:
כעת נתקין את Express
המסגרת בפרויקט שלנו באמצעות הפקודה הבאה:
npm i express
עכשיו בואו ניצור ספריית APIs בתוך ספריית הפונקציות . בתוך ספרייה זו, ניצור קובץ בשם todos.js
. הסר את הכל מה- index.js
ואז העתק והדבק את הקוד הבא:
//index.js const functions = require('firebase-functions'); const app = require('express')(); const { getAllTodos } = require('./APIs/todos') app.get('/todos', getAllTodos); exports.api = functions.https.onRequest(app);
הקצנו את הפונקציה getAllTodos למסלול / todos . כך שכל קריאות ה- API במסלול זה יבוצעו באמצעות פונקציית getAllTodos. עכשיו עבור todos.js
לקובץ תחת ספריית ה- API וכאן נכתוב את הפונקציה getAllTodos.
//todos.js exports.getAllTodos = (request, response) => { todos = [ { 'id': '1', 'title': 'greeting', 'body': 'Hello world from sharvin shah' }, { 'id': '2', 'title': 'greeting2', 'body': 'Hello2 world2 from sharvin shah' } ] return response.json(todos); }
כאן הכרזנו על אובייקט JSON לדוגמה. בהמשך נגזור זאת מהפיירסטור. אבל בינתיים נחזיר את זה. כעת פרוס זאת לפונקציית firebase שלך באמצעות הפקודה firebase deploy
. זה ישאללקבלת אישור למחיקת המודול helloworld - פשוט הזן y .
The following functions are found in your project but do not exist in your local source code: helloWorld Would you like to proceed with deletion? Selecting no will continue the rest of the deployments. (y/N) y
לאחר שהדבר נעשה, עבור אל מסוף הפרויקט> פונקציות ושם תמצא את כתובת ה- URL של ה- API. ממשק ה- API ייראה כך:
//-todoapp-.cloudfunctions.net/api
כעת עבור לדפדפן והעתק והדבק את כתובת האתר והוסף / תוספים בסוף כתובת אתר זו. תקבל את הפלט הבא:
[ { 'id': '1', 'title': 'greeting', 'body': 'Hello world from sharvin shah' }, { 'id': '2', 'title': 'greeting2', 'body': 'Hello2 world2 from sharvin shah' } ]
Firebase Firestore:
אנו נשתמש בחנות האש של Firebase כמאגר נתונים בזמן אמת ליישום שלנו. עכשיו עבור למסוף> מסד נתונים במסוף Firebase. כדי להגדיר את חנות האש, עקוב אחר ה- gif שלמטה:

לאחר סיום התצורה לחץ על כפתור התחל איסוף והגדר את מזהה האוסף כ- Dos . לחץ על הבא ותקבל את הקופץ הבא:

התעלם ממפתח DocumentID. עבור השדה, הקלד והערך , עיין ב- JSON למטה. עדכן את הערך בהתאם:
{ Field: title, Type: String, Value: Hello World }, { Field: body, Type: String, Value: Hello folks I hope you are staying home... }, { Field: createtAt, type: timestamp, value: Add the current date and time here }
לחץ על כפתור השמירה. תראה שהאוסף והמסמך נוצרים. חזור לסביבה המקומית. עלינו להתקין firebase-admin
את חבילת האחסון הדרושה לנו. השתמש בפקודה זו כדי להתקין אותה:
npm i firebase-admin
צור ספרייה בשם util תחת ספריית הפונקציות .עבור לספרייה זו וצור שם קובץ admin.js
. בקובץ זה נייבא את חבילת הניהול של firebase ונאתחל את אובייקט מסד הנתונים של firestore. אנו נייצא זאת כך שמודולים אחרים יוכלו להשתמש בו.
//admin.js const admin = require('firebase-admin'); admin.initializeApp(); const db = admin.firestore(); module.exports = { admin, db };
עכשיו בואו נכתוב API כדי להביא נתונים אלה. עבור אל todos.js
תחת ספריית פונקציות> APIs . הסר את הקוד הישן והעתק והדבק את הקוד למטה:
//todos.js const { db } = require('../util/admin'); exports.getAllTodos = (request, response) => { db .collection('todos') .orderBy('createdAt', 'desc') .get() .then((data) => { let todos = []; data.forEach((doc) => { todos.push({ todoId: doc.id, title: doc.data().title, body: doc.data().body, createdAt: doc.data().createdAt, }); }); return response.json(todos); }) .catch((err) => { console.error(err); return response.status(500).json({ error: err.code}); }); };
כאן אנו מביאים את כל התוספים ממסד הנתונים ומעבירים אותם ללקוח ברשימה.
ניתן גם להריץ את היישום באופן מקומי באמצעות firebase serve
פקודה במקום לפרוס אותו בכל פעם. כאשר אתה מפעיל פקודה זו אתה עלול לקבל שגיאה בנוגע לאישורים. כדי לתקן את זה, בצע את השלבים המוזכרים להלן:
- עבור אל הגדרות הפרויקט (סמל ההגדרות בפינה השמאלית העליונה)
- עבור לכרטיסייה חשבונות שירות
- שם למטה תהיה האפשרות ליצור מפתח חדש . לחץ על אפשרות זו והיא תוריד קובץ עם סיומת JSON.
- עלינו לייצא את האישורים הללו למושב שורת הפקודה שלנו. השתמש בפקודה למטה כדי לעשות זאת:
export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/[FILE_NAME].json"
לאחר מכן הפעל את פקודת ההגשה של firebase. אם אתה עדיין מקבל את השגיאה אז השתמש בפקודה הבאה: firebase login --reauth
. זה יפתח את דף הכניסה של Google בדפדפן. לאחר ביצוע הכניסה אז זה יעבוד ללא שום שגיאה.
תמצא כתובת URL ביומני כלי שורת הפקודה שלך כאשר אתה מפעיל פקודת הגשה של firebase. פתח את כתובת האתר הזו בדפדפן והוסף /todos
אותה אחריה.
✔ functions[api]: http function initialized (//localhost:5000/todoapp-//api).
תקבל את הפלט JSON הבא בדפדפן שלך:
[ { "todoId":"W67t1kSMO0lqvjCIGiuI", "title":"Hello World", "body":"Hello folks I hope you are staying home...", "createdAt":{"_seconds":1585420200,"_nanoseconds":0 } } ]
כתיבת ממשקי API אחרים:
הגיע הזמן לכתוב את כל יתר ממשקי ה- API של todo שאנחנו נדרשים ליישום שלנו.
- צור פריט Todo: עבור אל
index.js
מתחת לספריית הפונקציות. ייבא את שיטת postOneTodo תחת getAllTodos הקיים. כמו כן, הקצה את מסלול ה- POST לשיטה זו.
//index.js const { .., postOneTodo } = require('./APIs/todos') app.post('/todo', postOneTodo);
עבור אל todos.js
תוך ספריית הפונקציות והוסף שיטה חדשה בשיטה postOneTodo
הקיימת getAllTodos
.
//todos.js exports.postOneTodo = (request, response) => { if (request.body.body.trim() === '') { return response.status(400).json({ body: 'Must not be empty' }); } if(request.body.title.trim() === '') { return response.status(400).json({ title: 'Must not be empty' }); } const newTodoItem = { title: request.body.title, body: request.body.body, createdAt: new Date().toISOString() } db .collection('todos') .add(newTodoItem) .then((doc)=>{ const responseTodoItem = newTodoItem; responseTodoItem.id = doc.id; return response.json(responseTodoItem); }) .catch((err) => { response.status(500).json({ error: 'Something went wrong' }); console.error(err); }); };
בשיטה זו אנו מוסיפים טודו חדש למסד הנתונים שלנו. אם אלמנטים של גופנו ריקים אז נחזיר תגובה של 400 או אחרת נוסיף את הנתונים.
הפעל את פקודת ההגשה של firebase ופתח את יישום הדוור. צור בקשה חדשה ובחר את סוג השיטה כ- POST . הוסף את כתובת ה- URL וגוף מסוג JSON.
URL: //localhost:5000/todoapp-//api/todo METHOD: POST Body: { "title":"Hello World", "body": "We are writing this awesome API" }
לחץ על כפתור השליחה ותקבל את התגובה הבאה:
{ "title": "Hello World", "body": "We are writing this awesome API", "createdAt": "2020-03-29T12:30:48.809Z", "id": "nh41IgARCj8LPWBYzjU0" }
2. מחק את פריט Todo: עבור אל index.js
מתחת לספריית הפונקציות. ייבא את שיטת deleteTodo תחת postOneTodo הקיים. כמו כן, הקצה את המסלול מחק לשיטה זו.
//index.js const { .., deleteTodo } = require('./APIs/todos') app.delete('/todo/:todoId', deleteTodo);
עבור אל todos.js
הוסף שיטה חדשה בשיטה deleteTodo
הקיימת postOneTodo
.
//todos.js exports.deleteTodo = (request, response) => { const document = db.doc(`/todos/${request.params.todoId}`); document .get() .then((doc) => { if (!doc.exists) { return response.status(404).json({ error: 'Todo not found' }) } return document.delete(); }) .then(() => { response.json({ message: 'Delete successfull' }); }) .catch((err) => { console.error(err); return response.status(500).json({ error: err.code }); }); };
בשיטה זו אנו מוחקים Todo ממאגר המידע שלנו. הפעל את פקודת ההגשה של firebase ולך לדוור. צור בקשה חדשה, בחר את סוג השיטה כ- DELETE והוסף את ה- URL.
URL: //localhost:5000/todoapp-//api/todo/ METHOD: DELETE
לחץ על כפתור השליחה ותקבל את התגובה הבאה:
{ "message": "Delete successfull" }
3. ערוך את פריט Todo: עבור אל index.js
מתחת לספריית הפונקציות. ייבא את שיטת editTodo תחת deleteTodo הקיים. כמו כן, הקצה את מסלול ה- PUT לשיטה זו.
//index.js const { .., editTodo } = require('./APIs/todos') app.put('/todo/:todoId', editTodo);
עבור אל todos.js
הוסף שיטה חדשה בשיטה editTodo
הקיימת deleteTodo
.
//todos.js exports.editTodo = ( request, response ) => { if(request.body.todoId || request.body.createdAt){ response.status(403).json({message: 'Not allowed to edit'}); } let document = db.collection('todos').doc(`${request.params.todoId}`); document.update(request.body) .then(()=> { response.json({message: 'Updated successfully'}); }) .catch((err) => { console.error(err); return response.status(500).json({ error: err.code }); }); };
בשיטה זו אנו עורכים Todo ממסד הנתונים שלנו. זכור שכאן איננו מאפשרים למשתמש לערוך את השדות todoId או createdAt. הפעל את פקודת ההגשה של firebase ולך לדוור. צור בקשה חדשה, בחר את סוג השיטה כ- PUT והוסף את כתובת ה- URL.
URL: //localhost:5000/todoapp-//api/todo/ METHOD: PUT
לחץ על כפתור השליחה ותקבל את התגובה הבאה:
{ "message": "Updated successfully" }
מבנה המדריך עד כה:
+-- firebase.json +-- functions | +-- API | +-- +-- todos.js | +-- util | +-- +-- admin.js | +-- index.js | +-- node_modules | +-- package-lock.json | +-- package.json | +-- .gitignore
בכך השלמנו את החלק הראשון של היישום. אתה יכול להמשיך לשתות קפה, לקחת הפסקה, ואחרי זה נעבוד על פיתוח ממשקי ה- API של המשתמשים.
סעיף 2: פיתוח ממשקי API של משתמשים
בזהבסעיף , אנו הולכים לפתח רכיבים אלה:
- ממשק API לאימות משתמשים (כניסה והרשמה).
- ממשק API של פרטי משתמש של GET ועדכן.
- עדכן את ממשק ה- API של תמונת פרופיל המשתמש.
- אבטחת ה- API הקיים של Todo.
קוד ה- API של המשתמש המיושם בסעיף זה נמצא בהתחייבות זו.
אז בואו נתחיל בבניית ממשק ה- API לאימות משתמשים. עבור למסוף Firebase> אימות.

לחץ על כפתור הגדר שיטת כניסה . אנו נשתמש בדוא"ל ובסיסמה לצורך אימות משתמשים. אפשר את האפשרות דוא"ל / סיסמה .

כרגע ניצור את המשתמש שלנו באופן ידני. ראשית, נבנה את ה- API של הכניסה. לאחר מכן נבנה את ה- API של ההרשמה.
עבור לכרטיסיית משתמשים תחת אימות, מלא את פרטי המשתמש ולחץ על כפתור הוסף משתמש .

1. ממשק API לכניסה למשתמש:
ראשית, עלינו להתקין את firebase
החבילה, המורכבת מספריית האימות של Firebase, באמצעות הפקודה הבאה:
npm i firebase
לאחר ההתקנה עבור לספריית פונקציות> APIs . כאן ניצור users.js
קובץ. עכשיו בפנים index.js
אנו מייבאים שיטת loginUser ומקצים לה את מסלול ה- POST.
//index.js const { loginUser } = require('./APIs/users') // Users app.post('/login', loginUser);
עבור אל הגדרות הפרויקט> כללי ושם תמצא את הכרטיס הבא:

בחר בסמל האינטרנט ואז עקוב אחר ה- gif למטה:

בחר באפשרות המשך למסוף . לאחר שתסתיים תראה JSON עם תצורת firebase. עבור לספריית הפונקציות> util וצור config.js
קובץ. העתק והדבק את הקוד הבא בקובץ זה:
// config.js module.exports = { apiKey: "............", authDomain: "........", databaseURL: "........", projectId: ".......", storageBucket: ".......", messagingSenderId: "........", appId: "..........", measurementId: "......." };
החלף ............
בערכים שתקבל תחת קונסולת Firebase> הגדרות פרויקט> כללי> היישומים שלך> קטע קוד Firebase SD> תצורה .
העתק והדבק את הקוד הבא users.js
בקובץ:
// users.js const { admin, db } = require('../util/admin'); const config = require('../util/config'); const firebase = require('firebase'); firebase.initializeApp(config); const { validateLoginData, validateSignUpData } = require('../util/validators'); // Login exports.loginUser = (request, response) => { const user = { email: request.body.email, password: request.body.password } const { valid, errors } = validateLoginData(user); if (!valid) return response.status(400).json(errors); firebase .auth() .signInWithEmailAndPassword(user.email, user.password) .then((data) => { return data.user.getIdToken(); }) .then((token) => { return response.json({ token }); }) .catch((error) => { console.error(error); return response.status(403).json({ general: 'wrong credentials, please try again'}); }) };
כאן אנו משתמשים במודול signInWithEmailAndPassword של firebase כדי לבדוק אם האישורים שהוגשו על ידי המשתמש צודקים. אם הם צודקים, אנו שולחים לאסימון של אותו משתמש או אחרת מעמד 403 עם הודעת "אישורים שגויים".
עכשיו בואו ניצור validators.js
תחת הפונקציה> ספריית util . העתק והדבק את הקוד הבא בקובץ זה:
// validators.js const isEmpty = (string) => { if (string.trim() === '') return true; else return false; }; exports.validateLoginData = (data) => { let errors = {}; if (isEmpty(data.email)) errors.email = 'Must not be empty'; if (isEmpty(data.password)) errors.password = 'Must not be empty'; return { errors, valid: Object.keys(errors).length === 0 ? true : false }; };
בכך הושלם LoginAPI שלנו . הפעל את firebase serve
הפקודה ולך לדוור. צור בקשה חדשה, בחר את סוג השיטה כ- POST והוסף את כתובת האתר והגוף.
URL: //localhost:5000/todoapp-//api/login METHOD: POST Body: { "email":"Add email that is assigned for user in console", "password": "Add password that is assigned for user in console" }
לחץ על כפתור שלח בקשה בדוור ותקבל את הפלט הבא:
{ "token": ".........." }
נשתמש באסימון זה בחלק הקרוב כדי לקבל את פרטי המשתמש . זכור שתוקף של אסימון זה יפוג בעוד 60 דקות . כדי ליצור אסימון חדש השתמש שוב ב- API זה.
2. ממשק API להרשמת משתמשים:
מנגנון האימות המוגדר כברירת מחדל של Firebase מאפשר לך רק לאחסן מידע כמו דוא"ל, סיסמה וכו '. אך אנו זקוקים למידע נוסף כדי לזהות אם משתמש זה הוא הבעלים של אותה מטלה כדי שיוכל לבצע פעולות קריאה, עדכון ומחיקה.
כדי להשיג מטרה זו אנו הולכים ליצור אוסף חדש בשם משתמשים . תחת אוסף זה, נשמור את נתוני המשתמש אשר ימופו לטו על סמך שם המשתמש. כל שם משתמש יהיה ייחודי לכל המשתמשים בפלטפורמה.
עבור אל index.js
. אנו מייבאים שיטת signUpUser ומקצים לה את נתיב ה- POST.
//index.js const { .., signUpUser } = require('./APIs/users') app.post('/signup', signUpUser);
כעת עבור אל validators.js
הוסף את הקוד הבא מתחת validateLoginData
לשיטה.
// validators.js const isEmail = (email) => { const emailRegEx = /^(([^()\[\]\\.,;:\[email protected]"]+(\.[^()\[\]\\.,;:\[email protected]"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; if (email.match(emailRegEx)) return true; else return false; }; exports.validateSignUpData = (data) => { let errors = {}; if (isEmpty(data.email)) { errors.email = 'Must not be empty'; } else if (!isEmail(data.email)) { errors.email = 'Must be valid email address'; } if (isEmpty(data.firstName)) errors.firstName = 'Must not be empty'; if (isEmpty(data.lastName)) errors.lastName = 'Must not be empty'; if (isEmpty(data.phoneNumber)) errors.phoneNumber = 'Must not be empty'; if (isEmpty(data.country)) errors.country = 'Must not be empty'; if (isEmpty(data.password)) errors.password = 'Must not be empty'; if (data.password !== data.confirmPassword) errors.confirmPassword = 'Passowrds must be the same'; if (isEmpty(data.username)) errors.username = 'Must not be empty'; return { errors, valid: Object.keys(errors).length === 0 ? true : false }; };
עכשיו עבור אל users.js
והוסף את הקוד הבא מתחת loginUser
למודול.
// users.js exports.signUpUser = (request, response) => { const newUser = { firstName: request.body.firstName, lastName: request.body.lastName, email: request.body.email, phoneNumber: request.body.phoneNumber, country: request.body.country, password: request.body.password, confirmPassword: request.body.confirmPassword, username: request.body.username }; const { valid, errors } = validateSignUpData(newUser); if (!valid) return response.status(400).json(errors); let token, userId; db .doc(`/users/${newUser.username}`) .get() .then((doc) => { if (doc.exists) { return response.status(400).json({ username: 'this username is already taken' }); } else { return firebase .auth() .createUserWithEmailAndPassword( newUser.email, newUser.password ); } }) .then((data) => { userId = data.user.uid; return data.user.getIdToken(); }) .then((idtoken) => { token = idtoken; const userCredentials = { firstName: newUser.firstName, lastName: newUser.lastName, username: newUser.username, phoneNumber: newUser.phoneNumber, country: newUser.country, email: newUser.email, createdAt: new Date().toISOString(), userId }; return db .doc(`/users/${newUser.username}`) .set(userCredentials); }) .then(()=>{ return response.status(201).json({ token }); }) .catch((err) => { console.error(err); if (err.code === 'auth/email-already-in-use') { return response.status(400).json({ email: 'Email already in use' }); } else { return response.status(500).json({ general: 'Something went wrong, please try again' }); } }); }
אנו מאמתים את נתוני המשתמשים שלנו, ולאחר מכן אנו שולחים דוא"ל וסיסמה למודול ה- firebase createUserWithEmailAndPassword כדי ליצור את המשתמש. לאחר יצירת המשתמש בהצלחה אנו שומרים את אישורי המשתמש במסד הנתונים.
בעזרת זה הושלם ה- API של SignUp שלנו . הפעל את firebase serve
הפקודה ולך לדוור. צור בקשה חדשה, בחר את סוג השיטה כ- POST . הוסף את כתובת האתר והגוף.
URL: //localhost:5000/todoapp-//api/signup METHOD: POST Body: { "firstName": "Add a firstName here", "lastName": "Add a lastName here", "email":"Add a email here", "phoneNumber": "Add a phone number here", "country": "Add a country here", "password": "Add a password here", "confirmPassword": "Add same password here", "username": "Add unique username here" }
לחץ על כפתור שלח בקשה בדוור ותקבל את הפלט הבא:
{ "token": ".........." }
עכשיו עבור למסוף Firebase> מסד נתונים ושם תראה את הפלט הבא:

כפי שאתה יכול לראות אוסף המשתמשים שלנו נוצר בהצלחה עם מסמך אחד בתוכו.
3. העלה תמונת פרופיל משתמש:
המשתמשים שלנו יוכלו להעלות את תמונת הפרופיל שלהם. כדי להשיג זאת נשתמש באחסון דלי. עבור למסוף Firebase> אחסון ולחץ על כפתור התחל . עקוב אחר ה- GIF למטה לתצורה:

כעת עבור לכרטיסייה כללים תחת אחסון ועדכן את ההרשאה לגישה לדלי בהתאם לתמונה למטה:

כדי להעלות את תמונת הפרופיל נשתמש בחבילה ששמה busboy
. כדי להתקין חבילה זו, השתמש בפקודה הבאה:
npm i busboy
לך אל index.js
. ייבא את שיטת uploadProfilePhoto מתחת לשיטת signUpUser הקיימת. הקצה גם את מסלול ה- POST לשיטה זו.
//index.js const auth = require('./util/auth'); const { .., uploadProfilePhoto } = require('./APIs/users') app.post('/user/image', auth, uploadProfilePhoto);
כאן הוספנו שכבת אימות כך שרק משתמש המשויך לאותו חשבון יכול להעלות את התמונה. עכשיו ליצור קובץ בשם auth.js
ב פונקציות> utils בספרייה. העתק והדבק את הקוד הבא בקובץ זה:
// auth.js const { admin, db } = require('./admin'); module.exports = (request, response, next) => { let idToken; if (request.headers.authorization && request.headers.authorization.startsWith('Bearer ')) { idToken = request.headers.authorization.split('Bearer ')[1]; } else { console.error('No token found'); return response.status(403).json({ error: 'Unauthorized' }); } admin .auth() .verifyIdToken(idToken) .then((decodedToken) => { request.user = decodedToken; return db.collection('users').where('userId', '==', request.user.uid).limit(1).get(); }) .then((data) => { request.user.username = data.docs[0].data().username; request.user.imageUrl = data.docs[0].data().imageUrl; return next(); }) .catch((err) => { console.error('Error while verifying token', err); return response.status(403).json(err); }); };
כאן אנו משתמשים במודול ה- Firebase VerifiedIdToken כדי לאמת את האסימון. לאחר מכן אנו מפענחים את פרטי המשתמש ומעבירים אותם לבקשה הקיימת.
עבור אל users.js
הוסף את הקוד הבא מתחת signup
לשיטה:
// users.js deleteImage = (imageName) => { const bucket = admin.storage().bucket(); const path = `${imageName}` return bucket.file(path).delete() .then(() => { return }) .catch((error) => { return }) } // Upload profile picture exports.uploadProfilePhoto = (request, response) => { const BusBoy = require('busboy'); const path = require('path'); const os = require('os'); const fs = require('fs'); const busboy = new BusBoy({ headers: request.headers }); let imageFileName; let imageToBeUploaded = {}; busboy.on('file', (fieldname, file, filename, encoding, mimetype) => { if (mimetype !== 'image/png' && mimetype !== 'image/jpeg') { return response.status(400).json({ error: 'Wrong file type submited' }); } const imageExtension = filename.split('.')[filename.split('.').length - 1]; imageFileName = `${request.user.username}.${imageExtension}`; const filePath = path.join(os.tmpdir(), imageFileName); imageToBeUploaded = { filePath, mimetype }; file.pipe(fs.createWriteStream(filePath)); }); deleteImage(imageFileName); busboy.on('finish', () => { admin .storage() .bucket() .upload(imageToBeUploaded.filePath, { resumable: false, metadata: { metadata: { contentType: imageToBeUploaded.mimetype } } }) .then(() => { const imageUrl = `//firebasestorage.googleapis.com/v0/b/${config.storageBucket}/o/${imageFileName}?alt=media`; return db.doc(`/users/${request.user.username}`).update({ imageUrl }); }) .then(() => { return response.json({ message: 'Image uploaded successfully' }); }) .catch((error) => { console.error(error); return response.status(500).json({ error: error.code }); }); }); busboy.end(request.rawBody); };
בעזרת זה הושלם ה- API שלנו להעלאת תמונת פרופיל . הפעל את firebase serve
הפקודה ולך לדוור. צור בקשה חדשה, בחר את סוג השיטה כ- POST , הוסף את כתובת ה- URL ובקטע גוף בחר סוג כנתוני טופס.
הבקשה מוגנת ולכן יהיה עליך לשלוח גם את אסימון הנושא . כדי לשלוח אסימון הנושא, היכנס שוב אם תוקף האסימון פג. לאחר מכן באפליקציית הדוור> הכרטיסייה הרשאה> סוג> אסימון נושא ובקטע האסימונים הדבק את האסימון.
URL: //localhost:5000/todoapp-//api/user/image METHOD: GET Body: { REFER THE IMAGE down below }

לחץ על כפתור שלח בקשה בדוור ותקבל את הפלט הבא:
{ "message": "Image uploaded successfully" }
4. קבל פרטי משתמש:
כאן אנו מביאים את הנתונים של המשתמש שלנו ממסד הנתונים. עבור אל index.js
וייבא את שיטת getUserDetail והקצה לה מסלול GET.
// index.js const { .., getUserDetail } = require('./APIs/users') app.get('/user', auth, getUserDetail);
עכשיו עבור אל users.js
והוסף את הקוד הבא אחרי uploadProfilePhoto
המודול:
// users.js exports.getUserDetail = (request, response) => { let userData = {}; db .doc(`/users/${request.user.username}`) .get() .then((doc) => { if (doc.exists) { userData.userCredentials = doc.data(); return response.json(userData); } }) .catch((error) => { console.error(error); return response.status(500).json({ error: error.code }); }); }
אנו משתמשים במסמך firebase doc (). Get () כדי להפיק את פרטי המשתמש. בכך הושלם ה- API של פרטי המשתמש שלנו ב- GET . הפעל את firebase serve
הפקודה ולך לדוור. צור בקשה חדשה, בחר את סוג השיטה: GET , והוסף את כתובת האתר והגוף.
הבקשה מוגנת ולכן יהיה עליך לשלוח גם את אסימון הנושא . כדי לשלוח אסימון הנושא, היכנס שוב אם תוקף האסימון פג.
URL: //localhost:5000/todoapp-//api/user METHOD: GET
לחץ על כפתור שלח בקשה בדוור ותקבל את הפלט הבא:
{ "userCredentials": { "phoneNumber": "........", "email": "........", "country": "........", "userId": "........", "username": "........", "createdAt": "........", "lastName": "........", "firstName": "........" } }
5. עדכן את פרטי המשתמש:
עכשיו בואו נוסיף את הפונקציונליות לעדכון פרטי המשתמש. עבור אל index.js
והעתק והדבק את הקוד הבא:
// index.js const { .., updateUserDetails } = require('./APIs/users') app.post('/user', auth, updateUserDetails);
עכשיו עבור אל users.js
הוסף את updateUserDetails
המודול מתחת לקיים getUserDetails
:
// users.js exports.updateUserDetails = (request, response) => { let document = db.collection('users').doc(`${request.user.username}`); document.update(request.body) .then(()=> { response.json({message: 'Updated successfully'}); }) .catch((error) => { console.error(error); return response.status(500).json({ message: "Cannot Update the value" }); }); }
כאן אנו משתמשים בשיטת עדכון firebase . בעזרת זה הושלם ה- API שלנו לעדכון פרטי המשתמש . בצע את אותו ההליך לבקשה כמו ב- API של קבל פרטי משתמש לעיל בשינוי אחד. הוסף גוף לבקשה כאן ושיטה כ- POST.
URL: //localhost:5000/todoapp-//api/user METHOD: POST Body : { // You can edit First Name, last Name and country // We will disable other Form Tags from our UI }
לחץ על כפתור שלח בקשה בדוור ותקבל את הפלט הבא:
{ "message": "Updated successfully" }
6. אבטחת ממשקי API של Todo:
כדי לאבטח את ה- API של Todo כך שרק המשתמש הנבחר יוכל לגשת אליו, נערוך כמה שינויים בקוד הקיים שלנו. ראשית, נעדכן את הדברים שלנו index.js
כדלקמן:
// index.js // Todos app.get('/todos', auth, getAllTodos); app.get('/todo/:todoId', auth, getOneTodo); app.post('/todo',auth, postOneTodo); app.delete('/todo/:todoId',auth, deleteTodo); app.put('/todo/:todoId',auth, editTodo);
עדכנו את כל מסלולי Todo על ידי הוספה auth
כך שכל שיחות ה- API ידרוש אסימון וניתן לגשת רק על ידי המשתמש הספציפי.
לאחר מכן עבור אל todos.js
תחת ספריית פונקציות> APIs .
- צור API של Todo: פתח את
todos.js
ומתחת שיטת postOneTodo והוסף את מפתח שם המשתמש באופן הבא:
const newTodoItem = { .., username: request.user.username, .. }
2. קבל את כל API של Todos: פתח את todos.js
ומתחת שיטת getAllTodos והוסף את סעיף איפה כדלקמן:
db .collection('todos') .where('username', '==', request.user.username) .orderBy('createdAt', 'desc')
הפעל את שירות ה- Firebase ובדוק את ה- GET API שלנו. אל תשכח לשלוח אסימון הנושא. כאן תקבל שגיאת תגובה כדלקמן:
{ "error": 9 }
עבור לשורת הפקודה ותראה את השורות הבאות נרשמות:
i functions: Beginning execution of "api"> Error: 9 FAILED_PRECONDITION: The query requires an index. You can create it here: > at callErrorFromStatus
פתח את זה בדפדפן ולחץ על צור אינדקס.

לאחר בניית האינדקס שלח את הבקשה שוב ותקבל את הפלט הבא:
[ { "todoId": "......", "title": "......", "username": "......", "body": "......", "createdAt": "2020-03-30T13:01:58.478Z" } ]
3. מחק את API של Todo: פתח את todos.js
ומתחת השיטה deleteTodo והוסף את התנאי הבא. הוסף תנאי זה בתוך document.get (). ואז שאילתת () מתחת לתנאי! Doc.exists .
.. if(doc.data().username !== request.user.username){ return response.status(403).json({error:"UnAuthorized"}) }
מבנה הספריות עד כה:
+-- firebase.json +-- functions | +-- API | +-- +-- todos.js | +-- +-- users.js | +-- util | +-- +-- admin.js | +-- +-- auth.js | +-- +-- validators.js | +-- index.js | +-- node_modules | +-- package-lock.json | +-- package.json | +-- .gitignore
בעזרת זאת השלמנו את ה- backend של ה- API שלנו. קחו הפסקה, שתו קפה, ואחרי זה נתחיל לבנות את הקצה הקדמי של היישום שלנו
סעיף 3: לוח מחוונים למשתמשים
בזהבסעיף , אנו הולכים לפתח רכיבים אלה:
- הגדר את תצורת ממשק המשתמש ReactJS וחומר.
- בניית כניסה וטופס SignUp.
- מדור חשבון בניין.
את קוד לוח המחוונים של המשתמשים המיושם בסעיף זה ניתן למצוא בהתחייבות זו.
1. הגדר את ממשק המשתמש ReactJS וחומר:
נשתמש בתבנית יצירת-תגובה-אפליקציה. זה נותן לנו מבנה בסיסי לפיתוח היישום. כדי להתקין אותו, השתמש בפקודה הבאה:
npm install -g create-react-app
עבור לתיקיית הבסיס של הפרויקט שבו נמצאת ספריית הפונקציות. אתחל את יישום החזית שלנו באמצעות הפקודה הבאה:
create-react-app view
זכור להשתמש בגרסה v16.13.1 שלספריית ReactJS .
לאחר השלמת ההתקנה תראה את הדברים הבאים ביומני שורת הפקודה שלך:
cd view npm start Happy hacking!
בעזרת זה הגדרנו את יישום ה- React שלנו. תקבל את מבנה הספריות הבא:
+-- firebase.json +-- functions { This Directory consists our API logic } +-- view { This Directory consists our FrontEnd Compoenents } +-- .firebaserc +-- .gitignore
כעת הפעל את היישום באמצעות הפקודה npm start
. עבור לדפדפן //localhost:3000/
מופעל ותראה את הפלט הבא:

כעת נסיר את כל הרכיבים המיותרים. עבור לספריית התצוגה ואז הסר את כל הקבציםאשר [הסר] לפניהם. לשם כך, עיין במבנה עץ הספריות שלמטה.
+-- README.md [ Remove ] +-- package-lock.json +-- package.json +-- node_modules +-- .gitignore +-- public | +-- favicon.ico [ Remove ] | +-- index.html | +-- logo192.png [ Remove ] | +-- logo512.png [ Remove ] | +-- manifest.json | +-- robots.txt +-- src | +-- App.css | +-- App.test.js | +-- index.js | +-- serviceWorker.js | +-- App.js | +-- index.css [ Remove ] | +-- logo.svg [ Remove ] | +-- setupTests.js
עבור אל index.html
מתחת לספריה הציבורית והסר את השורות הבאות:
עכשיו עבור אל App.js
מתחת לספריית src והחלף את הקוד הישן בקוד הבא:
import React from 'react'; function App() { return ( ); } export default App;
עבור אל index.js
והסר את הייבוא הבא:
import './index.css'
לא מחקתי את התכונה App.css
וגם לא משתמש בה ביישום זה. אך אם ברצונך למחוק או להשתמש בו אתה רשאי לעשות זאת.
עבור לדפדפן //localhost:3000/
מופעל ותקבל פלט מסך ריק.
להתקנת ממשק משתמש של חומר עבור לספריית התצוגה והעתק והדבק פקודה זו במסוף:
npm install @material-ui/core
זכור להשתמש בגרסה v4.9.8 של ספריית ממשק המשתמש.
2. טופס כניסה:
כדי לפתח את טופס הכניסה עבור אל App.js
. בראש App.js
הוסף את היבוא הבא:
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import login from './pages/login';
אנו משתמשים ב- Switch and Route כדי להקצות מסלולים עבור TodoApp שלנו. כרגע נוסיף רק את מסלול / הכניסה ונקצה לו רכיב כניסה.
// App.js
צור דפי ספרייה תחת הקיים נוף בספרייה קובץ בשם login.js
תחת עמודים במדריך.
נייבא רכיבי ממשק משתמש חומרים ואת חבילת Axios ב login.js
:
// login.js // Material UI components import React, { Component } from 'react'; import Avatar from '@material-ui/core/Avatar'; import Button from '@material-ui/core/Button'; import CssBaseline from '@material-ui/core/CssBaseline'; import TextField from '@material-ui/core/TextField'; import Link from '@material-ui/core/Link'; import Grid from '@material-ui/core/Grid'; import LockOutlinedIcon from '@material-ui/icons/LockOutlined'; import Typography from '@material-ui/core/Typography'; import withStyles from '@material-ui/core/styles/withStyles'; import Container from '@material-ui/core/Container'; import CircularProgress from '@material-ui/core/CircularProgress'; import axios from 'axios';
נוסיף את הסגנונות הבאים לדף הכניסה שלנו:
// login.js const styles = (theme) => ({ paper: { marginTop: theme.spacing(8), display: 'flex', flexDirection: 'column', alignItems: 'center' }, avatar: { margin: theme.spacing(1), backgroundColor: theme.palette.secondary.main }, form: { width: '100%', marginTop: theme.spacing(1) }, submit: { margin: theme.spacing(3, 0, 2) }, customError: { color: 'red', fontSize: '0.8rem', marginTop: 10 }, progess: { position: 'absolute' } });
ניצור מחלקה בשם כניסה הכוללת טופס ונשלח מטפל בתוכה.
// login.js class login extends Component { constructor(props) { super(props); this.state = { email: '', password: '', errors: [], loading: false }; } componentWillReceiveProps(nextProps) { if (nextProps.UI.errors) { this.setState({ errors: nextProps.UI.errors }); } } handleChange = (event) => { this.setState({ [event.target.name]: event.target.value }); }; handleSubmit = (event) => { event.preventDefault(); this.setState({ loading: true }); const userData = { email: this.state.email, password: this.state.password }; axios .post('/login', userData) .then((response) => { localStorage.setItem('AuthToken', `Bearer ${response.data.token}`); this.setState({ loading: false, }); this.props.history.push('/'); }) .catch((error) => { this.setState({ errors: error.response.data, loading: false }); }); }; render() { const { classes } = this.props; const { errors, loading } = this.state; return ( Login Sign In {loading && } {"Don't have an account? Sign Up"} {errors.general && ( {errors.general} )} ); } }
בסוף קובץ זה הוסף את הייצוא הבא:
export default withStyles(styles)(login);
הוסף את כתובת האתר של פונקציות firebase שלנו כדי להציג> package.json כדלקמן:
זכור: הוסף מפתח בשם proxy מתחת לאובייקט JSON הקיים ברשימת הדפדפנים"proxy": "//-todoapp-.cloudfunctions.net/api"
התקן את Axios ואת סמל חומר החבילה באמצעות הפקודות הבאות:
// Axios command: npm i axios // Material Icons: npm install @material-ui/icons
הוספנו מסלול התחברות ב App.js
. ב- login.js
יצרנו רכיב מחלקה המטפל במצב, שולח את בקשת ההודעה ל- API הכניסה באמצעות חבילת Axios. אם הבקשה הצליחה אז אנו מאחסנים את האסימון. אם אנו מקבלים שגיאות בתגובה אנו פשוט מעבירים אותן בממשק המשתמש.
עבור לדפדפן בכתובת //localhost:3000/login
UI ותופיע בממשק המשתמש הבא.

נסה למלא אישורים שגויים או לשלוח בקשה ריקה ותקבל את השגיאות. שלח בקשה תקפה. עבור למסוף המפתחים> יישום . תראה כי אסימון המשתמשים מאוחסן באחסון המקומי. ברגע שהכניסה תצליח ננותב חזרה לדף הבית.

3. טופס הרשמה:
לפיתוח טופס ההרשמה עבור אל App.js
עדכן את Route
הרכיב הקיים בשורה למטה:
// App.js
אל תשכח לייבא:
// App.js import signup from './pages/signup';
צור קובץ בשם signup.js
תחת ספריית הדפים .
בתוך ה- signup.js נייבא את חבילת ממשק המשתמש ו- Axios:
// signup.js import React, { Component } from 'react'; import Avatar from '@material-ui/core/Avatar'; import Button from '@material-ui/core/Button'; import CssBaseline from '@material-ui/core/CssBaseline'; import TextField from '@material-ui/core/TextField'; import Link from '@material-ui/core/Link'; import Grid from '@material-ui/core/Grid'; import LockOutlinedIcon from '@material-ui/icons/LockOutlined'; import Typography from '@material-ui/core/Typography'; import Container from '@material-ui/core/Container'; import withStyles from '@material-ui/core/styles/withStyles'; import CircularProgress from '@material-ui/core/CircularProgress'; import axios from 'axios';
נוסיף את הסגנונות הבאים לדף ההרשמה שלנו:
// signup.js const styles = (theme) => ({ paper: { marginTop: theme.spacing(8), display: 'flex', flexDirection: 'column', alignItems: 'center' }, avatar: { margin: theme.spacing(1), backgroundColor: theme.palette.secondary.main }, form: { width: '100%', // Fix IE 11 issue. marginTop: theme.spacing(3) }, submit: { margin: theme.spacing(3, 0, 2) }, progess: { position: 'absolute' } });
ניצור מחלקה בשם הרשמה ובה טופס ונשלח מטפל בתוכה.
// signup.js class signup extends Component { constructor(props) { super(props); this.state = { firstName: '', lastName: '', phoneNumber: '', country: '', username: '', email: '', password: '', confirmPassword: '', errors: [], loading: false }; } componentWillReceiveProps(nextProps) { if (nextProps.UI.errors) { this.setState({ errors: nextProps.UI.errors }); } } handleChange = (event) => { this.setState({ [event.target.name]: event.target.value }); }; handleSubmit = (event) => { event.preventDefault(); this.setState({ loading: true }); const newUserData = { firstName: this.state.firstName, lastName: this.state.lastName, phoneNumber: this.state.phoneNumber, country: this.state.country, username: this.state.username, email: this.state.email, password: this.state.password, confirmPassword: this.state.confirmPassword }; axios .post('/signup', newUserData) .then((response) => { localStorage.setItem('AuthToken', `${response.data.token}`); this.setState({ loading: false, }); this.props.history.push('/'); }) .catch((error) => { this.setState({ errors: error.response.data, loading: false }); }); }; render() { const { classes } = this.props; const { errors, loading } = this.state; return ( Sign up Sign Up {loading && } Already have an account? Sign in ); } }
בסוף קובץ זה הוסף את הייצוא הבא:
export default withStyles(styles)(signup);
ההיגיון של רכיב ההרשמה זהה לרכיב הכניסה. עבור לדפדפן בכתובת //localhost:3000/signup
UI ותראה את ממשק המשתמש הבא לרישום. לאחר ההרשמה בהצלחה ננותב חזרה לדף הבית.

נסה למלא אישורים שגויים או לשלוח בקשה ריקה ותקבל את השגיאות. שלח בקשה תקפה. עבור למסוף המפתחים> יישום . תראה כי אסימון המשתמשים מאוחסן באחסון המקומי.

4. מדור חשבון:
כדי לבנות את דף החשבון נצטרך ליצור תחילה את דף הבית שלנו ממנו נטען את קטע החשבון . עבור אל App.js
ועדכן את המסלול הבא:
// App.js
אל תשכח את הייבוא:
// App.js import home from './pages/home';
צור קובץ חדש בשם home.js
. קובץ זה יהיה אינדקס היישום שלנו. החלקים חשבון וטודו נטענים שניהם בעמוד זה על סמך לחיצה על הכפתור.
ייבא את חבילות ממשק המשתמש החומרי, את חבילת Axios, את החשבון המותאם אישית שלנו, רכיבי המטלות ותוכנת הביניים האמתית.
// home.js import React, { Component } from 'react'; import axios from 'axios'; import Account from '../components/account'; import Todo from '../components/todo'; import Drawer from '@material-ui/core/Drawer'; import AppBar from '@material-ui/core/AppBar'; import CssBaseline from '@material-ui/core/CssBaseline'; import Toolbar from '@material-ui/core/Toolbar'; import List from '@material-ui/core/List'; import Typography from '@material-ui/core/Typography'; import Divider from '@material-ui/core/Divider'; import ListItem from '@material-ui/core/ListItem'; import ListItemIcon from '@material-ui/core/ListItemIcon'; import ListItemText from '@material-ui/core/ListItemText'; import withStyles from '@material-ui/core/styles/withStyles'; import AccountBoxIcon from '@material-ui/icons/AccountBox'; import NotesIcon from '@material-ui/icons/Notes'; import Avatar from '@material-ui/core/avatar'; import ExitToAppIcon from '@material-ui/icons/ExitToApp'; import CircularProgress from '@material-ui/core/CircularProgress'; import { authMiddleWare } from '../util/auth'
נגדיר את רוחב המגירה שלנו באופן הבא:
const drawerWidth = 240;
נוסיף את הסגנון הבא לדף הבית שלנו:
const styles = (theme) => ({ root: { display: 'flex' }, appBar: { zIndex: theme.zIndex.drawer + 1 }, drawer: { width: drawerWidth, flexShrink: 0 }, drawerPaper: { width: drawerWidth }, content: { flexGrow: 1, padding: theme.spacing(3) }, avatar: { height: 110, width: 100, flexShrink: 0, flexGrow: 0, marginTop: 20 }, uiProgess: { position: 'fixed', zIndex: '1000', height: '31px', width: '31px', left: '50%', top: '35%' }, toolbar: theme.mixins.toolbar });
ניצור כיתה בשם בית. בכיתה זו תהיה שיחת API לקבלת תמונת הפרופיל של המשתמש, שם פרטי ושם משפחה. כמו כן יהיה הגיון לבחור איזה רכיב להציג, טודו או חשבון:
class home extends Component { state = { render: false }; loadAccountPage = (event) => { this.setState({ render: true }); }; loadTodoPage = (event) => { this.setState({ render: false }); }; logoutHandler = (event) => { localStorage.removeItem('AuthToken'); this.props.history.push('/login'); }; constructor(props) { super(props); this.state = { firstName: '', lastName: '', profilePicture: '', uiLoading: true, imageLoading: false }; } componentWillMount = () => { authMiddleWare(this.props.history); const authToken = localStorage.getItem('AuthToken'); axios.defaults.headers.common = { Authorization: `${authToken}` }; axios .get('/user') .then((response) => { console.log(response.data); this.setState({ firstName: response.data.userCredentials.firstName, lastName: response.data.userCredentials.lastName, email: response.data.userCredentials.email, phoneNumber: response.data.userCredentials.phoneNumber, country: response.data.userCredentials.country, username: response.data.userCredentials.username, uiLoading: false, profilePicture: response.data.userCredentials.imageUrl }); }) .catch((error) => { if(error.response.status === 403) { this.props.history.push('/login') } console.log(error); this.setState({ errorMsg: 'Error in retrieving the data' }); }); }; render() { const { classes } = this.props; if (this.state.uiLoading === true) { return ( {this.state.uiLoading && } ); } else { return ( TodoApp {' '} {this.state.firstName} {this.state.lastName}
{' '} {' '} {' '} {' '} {' '} {' '} {this.state.render ? : } ); } } }
כאן בקוד תראה authMiddleWare(this.props.history);
שמשתמשים בו. תוכנת הביניים הזו בודקת אם ה- authToken הוא ריק. אם כן אז זה ידחוף את המשתמש חזרה אל login.js
. זה מתווסף כך שהמשתמש שלנו לא יכול לגשת /
למסלול ללא הרשמה או כניסה. בסוף קובץ זה הוסף את הייצוא הבא:
export default withStyles(styles)(home);
עכשיו אתה תוהה ממה קוד זה home.js
עושה?
{this.state.render ? : }
זה בודק את מצב העיבוד שאנו מגדירים בלחיצת כפתור. בואו ניצור את ספריית הרכיבים, ומתחת לספרייה זו ניצור שני קבצים: account.js
ו- todo.js
.
בואו ליצור ספריה בשם util וקובץ בשם auth.js
תחת אותה ספריה. העתק והדבק את הקוד הבא תחת auth.js
:
export const authMiddleWare = (history) => { const authToken = localStorage.getItem('AuthToken'); if(authToken === null){ history.push('/login') } }
בינתיים בתוך todo.js
רק נכתוב כיתה שמעבירה את הטקסט שלום אני עושה . אנו נעבוד על התיקים שלנו בחלק הבא:
import React, { Component } from 'react' import withStyles from '@material-ui/core/styles/withStyles'; import Typography from '@material-ui/core/Typography'; const styles = ((theme) => ({ content: { flexGrow: 1, padding: theme.spacing(3), }, toolbar: theme.mixins.toolbar, }) ); class todo extends Component { render() { const { classes } = this.props; return ( Hello I am todo ) } } export default (withStyles(styles)(todo));
עכשיו הגיע הזמן לחלק החשבון. ייבא את ממשק המשתמש החומר, clsx, axios וכלי השירות authmiddleWare לכלי שלנו account.js
.
// account.js import React, { Component } from 'react'; import withStyles from '@material-ui/core/styles/withStyles'; import Typography from '@material-ui/core/Typography'; import CircularProgress from '@material-ui/core/CircularProgress'; import CloudUploadIcon from '@material-ui/icons/CloudUpload'; import { Card, CardActions, CardContent, Divider, Button, Grid, TextField } from '@material-ui/core'; import clsx from 'clsx'; import axios from 'axios'; import { authMiddleWare } from '../util/auth';
נוסיף את העיצוב הבא לדף החשבון שלנו:
// account.js const styles = (theme) => ({ content: { flexGrow: 1, padding: theme.spacing(3) }, toolbar: theme.mixins.toolbar, root: {}, details: { display: 'flex' }, avatar: { height: 110, width: 100, flexShrink: 0, flexGrow: 0 }, locationText: { paddingLeft: '15px' }, buttonProperty: { position: 'absolute', top: '50%' }, uiProgess: { position: 'fixed', zIndex: '1000', height: '31px', width: '31px', left: '50%', top: '35%' }, progess: { position: 'absolute' }, uploadButton: { marginLeft: '8px', margin: theme.spacing(1) }, customError: { color: 'red', fontSize: '0.8rem', marginTop: 10 }, submitButton: { marginTop: '10px' } });
ניצור רכיב כיתה בשם חשבון. לפי שעה פשוט העתק והדבק את הקוד הבא:
// account.js class account extends Component { constructor(props) { super(props); this.state = { firstName: '', lastName: '', email: '', phoneNumber: '', username: '', country: '', profilePicture: '', uiLoading: true, buttonLoading: false, imageError: '' }; } componentWillMount = () => { authMiddleWare(this.props.history); const authToken = localStorage.getItem('AuthToken'); axios.defaults.headers.common = { Authorization: `${authToken}` }; axios .get('/user') .then((response) => { console.log(response.data); this.setState({ firstName: response.data.userCredentials.firstName, lastName: response.data.userCredentials.lastName, email: response.data.userCredentials.email, phoneNumber: response.data.userCredentials.phoneNumber, country: response.data.userCredentials.country, username: response.data.userCredentials.username, uiLoading: false }); }) .catch((error) => { if (error.response.status === 403) { this.props.history.push('/login'); } console.log(error); this.setState({ errorMsg: 'Error in retrieving the data' }); }); }; handleChange = (event) => { this.setState({ [event.target.name]: event.target.value }); }; handleImageChange = (event) => { this.setState({ image: event.target.files[0] }); }; profilePictureHandler = (event) => { event.preventDefault(); this.setState({ uiLoading: true }); authMiddleWare(this.props.history); const authToken = localStorage.getItem('AuthToken'); let form_data = new FormData(); form_data.append('image', this.state.image); form_data.append('content', this.state.content); axios.defaults.headers.common = { Authorization: `${authToken}` }; axios .post('/user/image', form_data, { headers: { 'content-type': 'multipart/form-data' } }) .then(() => { window.location.reload(); }) .catch((error) => { if (error.response.status === 403) { this.props.history.push('/login'); } console.log(error); this.setState({ uiLoading: false, imageError: 'Error in posting the data' }); }); }; updateFormValues = (event) => { event.preventDefault(); this.setState({ buttonLoading: true }); authMiddleWare(this.props.history); const authToken = localStorage.getItem('AuthToken'); axios.defaults.headers.common = { Authorization: `${authToken}` }; const formRequest = { firstName: this.state.firstName, lastName: this.state.lastName, country: this.state.country }; axios .post('/user', formRequest) .then(() => { this.setState({ buttonLoading: false }); }) .catch((error) => { if (error.response.status === 403) { this.props.history.push('/login'); } console.log(error); this.setState({ buttonLoading: false }); }); }; render() { const { classes, ...rest } = this.props; if (this.state.uiLoading === true) { return ( {this.state.uiLoading && } ); } else { return ( {this.state.firstName} {this.state.lastName}
בסוף קובץ זה הוסף את הייצוא הבא:
export default withStyles(styles)(account);
בשנת account.js
ישנם הרבה רכיבים המשמשים. ראשית בואו נראה איך היישום שלנו נראה. לאחר מכן אסביר את כל הרכיבים המשמשים ומדוע משתמשים בהם.
עבור לדפדפן, ואם פג תוקפו של האסימון שלך הוא יפנה אותך login
לדף. הוסף את פרטיך והיכנס שוב. לאחר שתעשה זאת, עבור לכרטיסייה חשבון ותמצא את ממשק המשתמש הבא:

ישנם 3 מטפלים בסעיף החשבון:
- componentWillMount : זוהי שיטת מחזור החיים המובנה של React. אנו משתמשים בהם כדי לטעון את הנתונים לפני מחזור החיים לעיבוד ולעדכן את ערכי המצב שלנו.
- ProfilePictureUpdate: זהו המטפל המותאם אישית שלנו בו אנו משתמשים כך שכאשר המשתמש שלנו ילחץ על כפתור העלאת תמונה הוא ישלח את הנתונים לשרת וטען מחדש את הדף כדי להציג את תמונת הפרופיל החדשה של המשתמש.
- updateFormValues: זהו גם המטפל המותאם אישית שלנו לעדכון פרטי המשתמש. כאן המשתמש יכול לעדכן את שמם הפרטי, שם המשפחה והמדינה שלהם. איננו מאפשרים עדכוני דוא"ל ושם משתמש מכיוון שלוגיקת ה- backend שלנו תלויה במפתחות אלה.
מלבד 3 המטפלים הללו זהו דף טופס עם עיצוב עליו. הנה מבנה הספריות עד לנקודה זו בתוך תיקיית התצוגה:
+-- public +-- src | +-- components | +-- +-- todo.js | +-- +-- account.js | +-- pages | +-- +-- home.js | +-- +-- login.js | +-- +-- signup.js | +-- util | +-- +-- auth.js | +-- README.md | +-- package-lock.json | +-- package.json | +-- .gitignore
בכך השלמנו את לוח המחוונים שלנו לחשבונות. עכשיו לכו לשתות קפה, לקחת הפסקה ובקטע הבא נבנה את לוח המחוונים של טודו.
סעיף 4: לוח המחוונים של טודו
בזהבסעיף , אנו הולכים לפתח את ממשק המשתמש עבור תכונות אלה של לוח המחוונים של Todos:
- הוסף טודו:
- קבל את כל התפקידים:
- מחק טודו
- ערוך מטלה
- קבל טוטו
- החלת נושא
את קוד לוח המחוונים של טודו המיושם בסעיף זה ניתן למצוא בהתחייבות זו.
עבור אל todos.js
מתחת לספריית הרכיבים . הוסף את הייבוא הבא לייבוא הקיים:
import Button from '@material-ui/core/Button'; import Dialog from '@material-ui/core/Dialog'; import AddCircleIcon from '@material-ui/icons/AddCircle'; import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; import IconButton from '@material-ui/core/IconButton'; import CloseIcon from '@material-ui/icons/Close'; import Slide from '@material-ui/core/Slide'; import TextField from '@material-ui/core/TextField'; import Grid from '@material-ui/core/Grid'; import Card from '@material-ui/core/Card'; import CardActions from '@material-ui/core/CardActions'; import CircularProgress from '@material-ui/core/CircularProgress'; import CardContent from '@material-ui/core/CardContent'; import MuiDialogTitle from '@material-ui/core/DialogTitle'; import MuiDialogContent from '@material-ui/core/DialogContent'; import axios from 'axios'; import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime'; import { authMiddleWare } from '../util/auth';
עלינו להוסיף את רכיבי ה- CSS הבאים ברכיבי הסגנון הקיימים:
const styles = (theme) => ({ .., // Existing CSS elements title: { marginLeft: theme.spacing(2), flex: 1 }, submitButton: { display: 'block', color: 'white', textAlign: 'center', position: 'absolute', top: 14, right: 10 }, floatingButton: { position: 'fixed', bottom: 0, right: 0 }, form: { width: '98%', marginLeft: 13, marginTop: theme.spacing(3) }, toolbar: theme.mixins.toolbar, root: { minWidth: 470 }, bullet: { display: 'inline-block', margin: '0 2px', transform: 'scale(0.8)' }, pos: { marginBottom: 12 }, uiProgess: { position: 'fixed', zIndex: '1000', height: '31px', width: '31px', left: '50%', top: '35%' }, dialogeStyle: { maxWidth: '50%' }, viewRoot: { margin: 0, padding: theme.spacing(2) }, closeButton: { position: 'absolute', right: theme.spacing(1), top: theme.spacing(1), color: theme.palette.grey[500] } });
נוסיף את המעבר לתיבת הדו-שיח הקופץ:
const Transition = React.forwardRef(function Transition(props, ref) { return ; });
הסר את מחלקת המטלות הקיימת והעתק והדבק את המחלקה הבאה:
class todo extends Component { constructor(props) { super(props); this.state = { todos: '', title: '', body: '', todoId: '', errors: [], open: false, uiLoading: true, buttonType: '', viewOpen: false }; this.deleteTodoHandler = this.deleteTodoHandler.bind(this); this.handleEditClickOpen = this.handleEditClickOpen.bind(this); this.handleViewOpen = this.handleViewOpen.bind(this); } handleChange = (event) => { this.setState({ [event.target.name]: event.target.value }); }; componentWillMount = () => { authMiddleWare(this.props.history); const authToken = localStorage.getItem('AuthToken'); axios.defaults.headers.common = { Authorization: `${authToken}` }; axios .get('/todos') .then((response) => { this.setState({ todos: response.data, uiLoading: false }); }) .catch((err) => { console.log(err); }); }; deleteTodoHandler(data) { authMiddleWare(this.props.history); const authToken = localStorage.getItem('AuthToken'); axios.defaults.headers.common = { Authorization: `${authToken}` }; let todoId = data.todo.todoId; axios .delete(`todo/${todoId}`) .then(() => { window.location.reload(); }) .catch((err) => { console.log(err); }); } handleEditClickOpen(data) { this.setState({ title: data.todo.title, body: data.todo.body, todoId: data.todo.todoId, buttonType: 'Edit', open: true }); } handleViewOpen(data) { this.setState({ title: data.todo.title, body: data.todo.body, viewOpen: true }); } render() { const DialogTitle = withStyles(styles)((props) => { const { children, classes, onClose, ...other } = props; return ( {children} {onClose ? ( ) : null} ); }); const DialogContent = withStyles((theme) => ({ viewRoot: { padding: theme.spacing(2) } }))(MuiDialogContent); dayjs.extend(relativeTime); const { classes } = this.props; const { open, errors, viewOpen } = this.state; const handleClickOpen = () => { this.setState({ todoId: '', title: '', body: '', buttonType: '', open: true }); }; const handleSubmit = (event) => { authMiddleWare(this.props.history); event.preventDefault(); const userTodo = { title: this.state.title, body: this.state.body }; let options = {}; if (this.state.buttonType === 'Edit') { options = { url: `/todo/${this.state.todoId}`, method: 'put', data: userTodo }; } else { options = { url: '/todo', method: 'post', data: userTodo }; } const authToken = localStorage.getItem('AuthToken'); axios.defaults.headers.common = { Authorization: `${authToken}` }; axios(options) .then(() => { this.setState({ open: false }); window.location.reload(); }) .catch((error) => { this.setState({ open: true, errors: error.response.data }); console.log(error); }); }; const handleViewClose = () => { this.setState({ viewOpen: false }); }; const handleClose = (event) => { this.setState({ open: false }); }; if (this.state.uiLoading === true) { return ( {this.state.uiLoading && } ); } else { return ( {this.state.buttonType === 'Edit' ? 'Edit Todo' : 'Create a new Todo'} {this.state.buttonType === 'Edit' ? 'Save' : 'Submit'} {this.state.todos.map((todo) => ( {todo.title} {dayjs(todo.createdAt).fromNow()} {`${todo.body.substring(0, 65)}`} this.handleViewOpen({ todo })}> {' '} View{' '} this.handleEditClickOpen({ todo })}> Edit this.deleteTodoHandler({ todo })}> Delete ))} {this.state.title} ); } } }
בסוף קובץ זה הוסף את הייצוא הבא:
export default withStyles(styles)(todo);
ראשית נבין כיצד ממשק המשתמש שלנו פועל ולאחר מכן נבין את הקוד. עבור לדפדפן ותקבל את ממשק המשתמש הבא:

לחץ על כפתור הוסף בפינה השמאלית התחתונה ותקבל את המסך הבא:

הוסף את הכותרת ופרטי Todo ולחץ על כפתור ההגשה. תקבל את המסך הבא:

לאחר לחיצה על לחצן התצוגה ותוכלו לראות את הפרטים המלאים של ה- Todo:

לחץ על כפתור העריכה ותוכל לערוך את המטלה:

לחץ על כפתור המחיקה ותוכל למחוק את Todo. כעת, כשאנו מודעים לאופן שבו Dashboard עובד, נבין את הרכיבים המשמשים בו.
1. הוסף את Todo: לצורך הטמעת ה- add todo נשתמש ברכיב Dialogue של ממשק המשתמש. רכיב זה מיישם פונקציונליות וו. אנו משתמשים בשיעורים ולכן נסיר פונקציונליות זו.
// This sets the state to open and buttonType flag to add: const handleClickOpen = () => { this.setState({ todoId: '', title: '', body: '', buttonType: '', open: true }); }; // This sets the state to close: const handleClose = (event) => { this.setState({ open: false }); };
מלבד זאת נשנה גם את המיקום של כפתור הוסף טודו.
// Position our button floatingButton: { position: 'fixed', bottom: 0, right: 0 },
כעת נחליף את תג הרשימה בטופס בתוך דיאלוג זה. זה יעזור לנו להוסיף את הטודו החדש.
// Show Edit or Save depending on buttonType state {this.state.buttonType === 'Edit' ? 'Save' : 'Submit'} // Our Form to add a todo // TextField here // TextField here
הידית שלחמורכב מהגיון לקרוא את buttonType
המדינה. אם המדינה היא מחרוזת ריקה, (“”)
היא תפורסם בממשק ה- API של הוסף טודו. אם המדינה היא Edit
אז בתרחיש זה היא תעדכן את עריכת טודו.
2. השג את Todos: כדי להציג את ה- todos שנשתמש בהם Grid container
ובתוכו, אנו מניחים את ה- Grid item
. בתוך זה, נשתמש Card
ברכיב להצגת הנתונים.
{this.state.todos.map((todo) => ( // Here will show Todo with view, edit and delete button ))}
אנו משתמשים במפה כדי להציג את פריט המטלות כאשר ה- API שולח אותם לרשימה. נשתמש במחזור החיים של componentWillMount בכדי לקבל ולהגדיר את המצב לפני ביצוע העיבוד. ישנם 3 כפתורים ( הצגה, עריכה ומחיקה ) ולכן נצטרך 3 מטפלים כדי לטפל בפעולה כשלוחצים על הכפתור. נלמד על כפתורים אלה בסעיפי המשנה שלהם.
3. עריכת טודו: לצורך פעולת עריכה, אנו משתמשים שוב בקוד הקופץ של דו-שיח המשמש בתוספת להוסיף. כדי להבדיל בין לחיצות הכפתור אנו משתמשים buttonType
במצב. עבור הוסף טודו buttonType
המדינה היא (“”)
שעבור עריכת טודו היא Edit
.
handleEditClickOpen(data) { this.setState({ .., buttonType: 'Edit', .. }); }
בשנות ה handleSubmit
שיטה אנו קוראים את buttonType
המדינה ולאחר מכן לשלוח את הבקשה בהתאם.
4. מחק את Todo: כאשר לוחצים על כפתור זה אנו שולחים את אובייקט ה- todo ל- deleteTodoHandler שלנו ואז הוא שולח את הבקשה הלאה אל ה- backend.
this.deleteTodoHandler({ todo })}>Delete
5. צפו בטודו: כאשר מציגים את הנתונים קיצצנו אותם כך שהמשתמש יקבל הצצה על מה המטלה. אך אם משתמש רוצה לדעת יותר על כך, עליו ללחוץ על כפתור התצוגה.
לשם כך נשתמש בדיאלוג המותאם אישית. בתוך זה, אנו משתמשים ב- DialogTitle ו- DialogContent. הוא מציג את הכותרת והתוכן שלנו. ב- DialougeContent נשתמש בטופס להצגת התוכן שהמשתמש פרסם. (זה פיתרון אחד שמצאתי שיש רבים ואתה חופשי לנסות אחר.)
// This is used to remove the underline of the Form InputProps={{ disableUnderline: true }} // This is used so that user cannot edit the data readonly
6. יישום נושא: זהו השלב האחרון ביישום שלנו. אנו ניישם נושא על היישום שלנו. לשם כך אנו משתמשים createMuiTheme
ו ThemeProvider
מ UI חומר. העתק והדבק את הקוד הבא ב App.js
:
import { ThemeProvider as MuiThemeProvider } from '@material-ui/core/styles'; import createMuiTheme from '@material-ui/core/styles/createMuiTheme'; const theme = createMuiTheme({ palette: { primary: { light: '#33c9dc', main: '#FF5722', dark: '#d50000', contrastText: '#fff' } } }); function App() { return ( // Router and switch will be here. ); }
התגעגענו להחלת נושא על הכפתור שלנו todo.js
ב CardActions
. הוסף את תג הצבע עבור לחצן התצוגה, העריכה והמחק.
היכנס לדפדפן ותגלה שהכל זהה חוץ מהאפליקציה בצבע אחר.

וגמרנו! בנינו TodoApp באמצעות ReactJS ו- Firebase. אם בנית את זה עד לנקודה זו אז כל הכבוד לך מאוד על ההישג הזה.
אתם מוזמנים להתחבר אלי בטוויטר ובגית'וב.