


Von Null zum Helden: Meine Reise zum Aufbau einer Immobilienvermietungs-Website und einer mobilen App
Nov 11, 2024 pm 04:09 PMInhalt
- Einführung
- Tech Stack
- Kurzübersicht
- Live-Demo
- API
- Frontend
- Mobile App
- Admin-Dashboard
- Sehenswürdigkeiten
- Ressourcen
Quellcode: https://github.com/aelassas/movinin
Demo: https://movinin.dynv6.net:3004
Einführung
Die Idee entstand aus dem Wunsch heraus, ohne Grenzen zu bauen – eine vollst?ndig anpassbare und funktionsf?hige Website zur Immobilienvermietung und eine mobile App, bei der jeder Aspekt in Ihrer Kontrolle liegt:
- Besitzen Sie die Benutzeroberfl?che/UX: Entwerfen Sie einzigartige Kundenerlebnisse, ohne gegen Vorlagenbeschr?nkungen anzuk?mpfen
- Kontrollieren Sie das Backend: Implementieren Sie benutzerdefinierte Gesch?ftslogik und Datenstrukturen, die perfekt zu den Anforderungen passen
- Master DevOps: Stellen Sie die Anwendung mit bevorzugten Tools und Workflows bereit, skalieren und überwachen Sie sie
- Frei erweitern: Fügen Sie neue Funktionen und Integrationen ohne Plattformbeschr?nkungen oder zus?tzliche Gebühren hinzu
Technische Anforderungen:
-
Zahlungsgateway:
- Implementieren Sie ein sicheres, international unterstütztes Zahlungsgateway
- Stellen Sie die Kompatibilit?t über mehrere L?nder und W?hrungen hinweg sicher
-
DevOps:
- Bereitstellung mit Docker-Containern für Konsistenz und Skalierbarkeit
- Host auf minimaler Infrastruktur (1 GB RAM-Server)
- Halten Sie die monatlichen Hosting-Kosten bei Anbietern wie Hetzner oder DigitalOcean unter 5?$
- Optimieren Sie die Ressourcennutzung für einen effizienten Betrieb
Tech-Stack
Hier ist der Tech-Stack, der es m?glich gemacht hat:
- TypeScript
- Node.js
- MongoDB
- Reagieren
- MUI
- Nativ reagieren
- Ausstellung
- Streifen
- Docker
Eine wichtige Designentscheidung wurde aufgrund seiner zahlreichen Vorteile für die Verwendung von TypeScript getroffen. TypeScript bietet starke Typisierung, Tools und Integration, was zu hochwertigem, skalierbarem, besser lesbarem und wartbarem Code führt, der einfach zu debuggen und zu testen ist.
Ich habe mich für React wegen seiner leistungsstarken Rendering-Funktionen, MongoDB für die flexible Datenmodellierung und Stripe für die sichere Zahlungsabwicklung entschieden.
Wenn Sie sich für diesen Stack entscheiden, erstellen Sie nicht nur eine Website und eine mobile App – Sie investieren in eine Grundlage, die sich mit Ihren Anforderungen weiterentwickeln kann, unterstützt durch robuste Open-Source-Technologien und eine wachsende Entwickler-Community.
React zeichnet sich durch Folgendes als ausgezeichnete Wahl aus:
- Komponentenbasierte Architektur
- Erm?glicht die Aufteilung komplexer Benutzeroberfl?chen in kleinere, wiederverwendbare Teile
- Macht Code wartbarer und einfacher zu testen
- Erm?glicht eine bessere Codeorganisation und Wiederverwendbarkeit
- Virtuelle DOM-Leistung
- Das virtuelle DOM von React aktualisiert effizient nur das Notwendige
- Führt zu schnelleren Seitenladevorg?ngen und einem besseren Benutzererlebnis
- Reduziert unn?tiges erneutes Rendern
- Reichhaltiges ?kosystem
- Umfangreiche Bibliothek vorgefertigter Komponenten
- Umfangreiche Werkzeugausstattung
- Gro?e Community für Unterstützung und Ressourcen
- Starke Entwicklererfahrung
- Hei?es Nachladen für sofortiges Feedback
- Ausgezeichnete Debugging-Tools
- JSX macht das Schreiben von UI-Code intuitiver
- Industrieunterstützung
- Unterstützt von Meta (ehemals Facebook)
- Wird von vielen gro?en Unternehmen verwendet
- Kontinuierliche Weiterentwicklung und Verbesserungen
- Flexibilit?t
- Funktioniert gut für kleine und gro?e Anwendungen
- Kann schrittweise in bestehende Projekte integriert werden
- Unterstützt mehrere Rendering-Strategien (clientseitig, serverseitig, statisch)
Schneller überblick
In diesem Abschnitt sehen Sie die Hauptseiten des Frontends, des Admin-Dashboards und der mobilen App.
Frontend
Vom Frontend aus kann der Kunde nach verfügbaren Immobilien suchen, eine Immobilie ausw?hlen und zur Kasse gehen.
Unten befindet sich die Hauptseite des Frontends, auf der der Kunde einen Ort und eine Uhrzeit eingeben und nach verfügbaren Immobilien suchen kann.
Unten finden Sie das Suchergebnis der Hauptseite, auf der der Kunde eine Immobilie zur Vermietung ausw?hlen kann.
Unten finden Sie die Seite, auf der der Kunde die Details der Immobilie einsehen kann:
Unten finden Sie eine Ansicht der Bilder der Immobilie:
Unten befindet sich die Checkout-Seite, auf der der Kunde Mietoptionen und den Checkout festlegen kann. Ist der Kunde nicht registriert, kann er zur Kasse gehen und sich gleichzeitig registrieren. Er erh?lt eine Best?tigungs- und Aktivierungs-E-Mail, um sein Passwort festzulegen, wenn er noch nicht registriert ist.
Unten finden Sie die Anmeldeseite. In der Produktion sind Authentifizierungscookies httpOnly, signiert, sicher und strikt sameSite. Diese Optionen verhindern XSS-, CSRF- und MITM-Angriffe. Authentifizierungscookies sind auch über eine benutzerdefinierte Middleware vor XST-Angriffen geschützt.
Unten finden Sie die Anmeldeseite.
Unten finden Sie die Seite, auf der der Kunde seine Buchungen einsehen und verwalten kann.
Unten befindet sich die Seite, auf der der Kunde eine Buchung im Detail sehen kann.
Unten finden Sie die Seite, auf der der Kunde seine Benachrichtigungen sehen kann.
Unten finden Sie die Seite, auf der der Kunde seine Einstellungen verwalten kann.
Unten finden Sie die Seite, auf der der Kunde sein Passwort ?ndern kann.
Das ist es. Das sind die Hauptseiten des Frontends.
Admin-Dashboard
Drei Arten von Benutzern:
- Administratoren: Sie haben vollen Zugriff auf das Admin-Dashboard. Sie k?nnen alles.
- Agenturen: Sie haben eingeschr?nkten Zugriff auf das Admin-Dashboard. Sie k?nnen nur ihre Objekte, Buchungen und Kunden verwalten.
- Kunden: Sie haben nur Zugriff auf das Frontend und die mobile App. Sie k?nnen nicht auf das Admin-Dashboard zugreifen.
Die Plattform ist für die Zusammenarbeit mit mehreren Agenturen konzipiert. Jede Agentur kann ihre Unterkünfte, Kunden und Buchungen über das Admin-Dashboard verwalten. Die Plattform kann auch mit nur einer Agentur zusammenarbeiten.
Vom Backend aus k?nnen Administratoren Agenturen, Objekte, Standorte, Kunden und Buchungen erstellen und verwalten.
Wenn neue Agenturen erstellt werden, erhalten sie eine E-Mail mit der Aufforderung, ihr Konto zu erstellen, um auf das Admin-Dashboard zuzugreifen, damit sie ihre Unterkünfte, Kunden und Buchungen verwalten k?nnen.
Unten finden Sie die Anmeldeseite des Admin-Dashboards.
Unten finden Sie die Dashboard-Seite, auf der Administratoren und Agenturen Buchungen sehen und verwalten k?nnen.
Wenn sich der Status einer Buchung ?ndert, erh?lt der entsprechende Kunde eine Benachrichtigung und eine E-Mail.
Unten finden Sie die Seite, auf der Eigenschaften angezeigt und verwaltet werden k?nnen.
Unten finden Sie die Seite, auf der Administratoren und Agenturen neue Immobilien erstellen k?nnen, indem sie Bilder und Immobilieninformationen bereitstellen. Für eine kostenlose Stornierung setzen Sie den Wert auf 0. Andernfalls legen Sie den Preis der Option fest oder lassen Sie das Feld leer, wenn Sie sie nicht einbeziehen m?chten.
Unten finden Sie die Seite, auf der Administratoren und Agenturen Eigenschaften bearbeiten k?nnen.
Unten finden Sie die Seite, auf der Administratoren Kunden verwalten k?nnen.
Unten finden Sie die Seite, auf der Sie Buchungen erstellen k?nnen, wenn die Agentur eine Buchung über das Admin-Dashboard erstellen m?chte. Andernfalls werden Buchungen automatisch erstellt, wenn der Checkout-Vorgang über das Frontend oder die mobile App abgeschlossen wird.
Unten finden Sie die Seite, auf der Sie Buchungen bearbeiten k?nnen.
Unten finden Sie die Seite, auf der Sie Agenturen verwalten k?nnen.
Unten finden Sie die Seite, auf der Sie neue Agenturen erstellen k?nnen.
Unten finden Sie die Seite, auf der Sie Agenturen bearbeiten k?nnen.
Unten finden Sie die Seite, auf der Sie die Immobilien der Agenturen sehen k?nnen.
Unten finden Sie die Seite, auf der Sie die Buchungen der Kunden sehen k?nnen.
Unten finden Sie die Seite, auf der Administratoren und Agenturen ihre Einstellungen verwalten k?nnen.
Es gibt noch andere Seiten, aber dies sind die Hauptseiten des Admin-Dashboards.
Das ist es. Das sind die Hauptseiten des Admin-Dashboards.
Live-Demo
Frontend
- URL: https://movinin.dynv6.net:3004/
- Login: jdoe@movinin.io
- Passwort: M00vinin
Admin-Dashboard
- URL: https://movinin.dynv6.net:3003/
- Login: admin@movinin.io
- Passwort: M00vinin
Mobile App
Sie k?nnen die Android-App auf jedem Android-Ger?t installieren.
Scannen Sie diesen Code mit einem Ger?t
?ffnen Sie die Kamera-App und richten Sie sie auf diesen Code. Tippen Sie dann auf die angezeigte Benachrichtigung.
So installieren Sie die mobile App auf Android
Auf Ger?ten mit Android 8.0 (API-Level 26) und h?her müssen Sie zum Bildschirm ?Unbekannte Apps installieren“ mit den Systemeinstellungen navigieren, um App-Installationen von einem bestimmten Ort aus zu erm?glichen (d. h. dem Webbrowser, von dem Sie die App herunterladen). .
Auf Ger?ten mit Android 7.1.1 (API-Level 25) und niedriger sollten Sie die Systemeinstellung ?Unbekannte Quellen“ aktivieren, die Sie unter Einstellungen > finden. Sicherheit auf Ihrem Ger?t.
Alternativer Weg
Sie k?nnen die Android-App auch installieren, indem Sie die APK direkt herunterladen und auf einem beliebigen Android-Ger?t installieren.
- APK herunterladen
- Login: jdoe@movinin.io
- Passwort: M00vinin
API
Die API stellt alle Funktionen bereit, die für das Admin-Dashboard, das Frontend und die mobile App ben?tigt werden. Die API folgt dem MVC-Entwurfsmuster. Zur Authentifizierung wird JWT verwendet. Es gibt einige Funktionen, die eine Authentifizierung erfordern, wie zum Beispiel Funktionen im Zusammenhang mit der Verwaltung von Unterkünften, Buchungen und Kunden, und andere, die keine Authentifizierung erfordern, wie zum Beispiel das Abrufen von Standorten und verfügbaren Unterkünften für nicht authentifizierte Benutzer:
- ./api/src/models/ Ordner enth?lt MongoDB-Modelle.
- ./api/src/routes/-Ordner enth?lt Express-Routen.
- ./api/src/controllers/ Ordner enth?lt Controller.
- ./api/src/middlewares/ Ordner enth?lt Middlewares.
- ./api/src/config/env.config.ts enth?lt die Konfiguration und TypeScript-Typdefinitionen.
- ./api/src/lang/-Ordner enth?lt die Lokalisierung.
- ./api/src/app.ts ist der Hauptserver, auf den Routen geladen werden.
- ./api/index.ts ist der Haupteinstiegspunkt der API.
index.ts ist der Haupteinstiegspunkt der API:
import 'dotenv/config' import process from 'node:process' import fs from 'node:fs/promises' import http from 'node:http' import https, { ServerOptions } from 'node:https' import app from './app' import * as databaseHelper from './common/databaseHelper' import * as env from './config/env.config' import * as logger from './common/logger' if ( await databaseHelper.connect(env.DB_URI, env.DB_SSL, env.DB_DEBUG) && await databaseHelper.initialize() ) { let server: http.Server | https.Server if (env.HTTPS) { https.globalAgent.maxSockets = Number.POSITIVE_INFINITY const privateKey = await fs.readFile(env.PRIVATE_KEY, 'utf8') const certificate = await fs.readFile(env.CERTIFICATE, 'utf8') const credentials: ServerOptions = { key: privateKey, cert: certificate } server = https.createServer(credentials, app) server.listen(env.PORT, () => { logger.info('HTTPS server is running on Port', env.PORT) }) } else { server = app.listen(env.PORT, () => { logger.info('HTTP server is running on Port', env.PORT) }) } const close = () => { logger.info('Gracefully stopping...') server.close(async () => { logger.info(`HTTP${env.HTTPS ? 'S' : ''} server closed`) await databaseHelper.close(true) logger.info('MongoDB connection closed') process.exit(0) }) } ['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach((signal) => process.on(signal, close)) }
Dies ist eine TypeScript-Datei, die einen Server mit Node.js und Express startet. Es importiert mehrere Module, darunter dotenv, Process, fs, http, https, mongoose und app. Anschlie?end prüft es, ob die HTTPS-Umgebungsvariable auf ?true“ gesetzt ist, und erstellt in diesem Fall mithilfe des https-Moduls und des bereitgestellten privaten Schlüssels und Zertifikats einen HTTPS-Server. Andernfalls wird mithilfe des http-Moduls ein HTTP-Server erstellt. Der Server lauscht auf dem Port, der in der Umgebungsvariablen PORT angegeben ist.
Die Schlie?funktion ist so definiert, dass sie den Server ordnungsgem?? stoppt, wenn ein Beendigungssignal empfangen wird. Es schlie?t den Server und die MongoDB-Verbindung und beendet dann den Prozess mit dem Statuscode 0. Schlie?lich registriert es die Schlie?funktion, die aufgerufen werden soll, wenn der Prozess ein SIGINT-, SIGTERM- oder SIGQUIT-Signal empf?ngt.
app.ts ist der Haupteinstiegspunkt der API:
import express from 'express' import compression from 'compression' import helmet from 'helmet' import nocache from 'nocache' import cookieParser from 'cookie-parser' import i18n from './lang/i18n' import * as env from './config/env.config' import cors from './middlewares/cors' import allowedMethods from './middlewares/allowedMethods' import agencyRoutes from './routes/agencyRoutes' import bookingRoutes from './routes/bookingRoutes' import locationRoutes from './routes/locationRoutes' import notificationRoutes from './routes/notificationRoutes' import propertyRoutes from './routes/propertyRoutes' import userRoutes from './routes/userRoutes' import stripeRoutes from './routes/stripeRoutes' import countryRoutes from './routes/countryRoutes' import * as helper from './common/helper' const app = express() app.use(helmet.contentSecurityPolicy()) app.use(helmet.dnsPrefetchControl()) app.use(helmet.crossOriginEmbedderPolicy()) app.use(helmet.frameguard()) app.use(helmet.hidePoweredBy()) app.use(helmet.hsts()) app.use(helmet.ieNoOpen()) app.use(helmet.noSniff()) app.use(helmet.permittedCrossDomainPolicies()) app.use(helmet.referrerPolicy()) app.use(helmet.xssFilter()) app.use(helmet.originAgentCluster()) app.use(helmet.crossOriginResourcePolicy({ policy: 'cross-origin' })) app.use(helmet.crossOriginOpenerPolicy()) app.use(nocache()) app.use(compression({ threshold: 0 })) app.use(express.urlencoded({ limit: '50mb', extended: true })) app.use(express.json({ limit: '50mb' })) app.use(cors()) app.options('*', cors()) app.use(cookieParser(env.COOKIE_SECRET)) app.use(allowedMethods) app.use('/', agencyRoutes) app.use('/', bookingRoutes) app.use('/', locationRoutes) app.use('/', notificationRoutes) app.use('/', propertyRoutes) app.use('/', userRoutes) app.use('/', stripeRoutes) app.use('/', countryRoutes) i18n.locale = env.DEFAULT_LANGUAGE helper.mkdir(env.CDN_USERS) helper.mkdir(env.CDN_TEMP_USERS) helper.mkdir(env.CDN_PROPERTIES) helper.mkdir(env.CDN_TEMP_PROPERTIES) helper.mkdir(env.CDN_LOCATIONS) helper.mkdir(env.CDN_TEMP_LOCATIONS) export default app
Zuerst rufen wir die MongoDB-Verbindungszeichenfolge ab und stellen dann eine Verbindung mit der MongoDB-Datenbank her. Dann erstellen wir eine Express-App und laden Middleware wie Cors, Kompression, Helm und Nocache. Mithilfe der Helm-Middleware-Bibliothek haben wir verschiedene Sicherheitsma?nahmen eingerichtet. Wir importieren auch verschiedene Routendateien für verschiedene Teile der Anwendung, z. B. ?supplierRoutes“, ?bookingRoutes“, ?locationRoutes“, ?notificationRoutes“, ?propertyRoutes“ und ?userRoutes“. Zum Schluss laden wir Express-Routen und exportieren die App.
Es gibt 8 Routen in der API. Jede Route verfügt über einen eigenen Controller, der dem MVC-Entwurfsmuster und den SOLID-Prinzipien folgt. Nachfolgend sind die Hauptrouten aufgeführt:
- userRoutes: Stellt REST-Funktionen für Benutzer bereit
- agencyRoutes: Bietet REST-Funktionen im Zusammenhang mit Agenturen
- countryRoutes: Bietet REST-Funktionen im Zusammenhang mit L?ndern
- locationRoutes: Bietet REST-Funktionen im Zusammenhang mit Standorten
- propertyRoutes: Stellt REST-Funktionen im Zusammenhang mit Eigenschaften bereit
- bookingRoutes: Bietet REST-Funktionen im Zusammenhang mit Buchungen
- notificationRoutes: Bietet REST-Funktionen im Zusammenhang mit Benachrichtigungen
- stripeRoutes: Bietet REST-Funktionen im Zusammenhang mit dem Stripe-Zahlungsgateway
Wir werden nicht jede Route einzeln erkl?ren. Wir nehmen zum Beispiel propertyRoutes und sehen, wie es erstellt wurde. Sie k?nnen den Quellcode durchsuchen und alle Routen sehen.
Hier ist propertyRoutes.ts:
import 'dotenv/config' import process from 'node:process' import fs from 'node:fs/promises' import http from 'node:http' import https, { ServerOptions } from 'node:https' import app from './app' import * as databaseHelper from './common/databaseHelper' import * as env from './config/env.config' import * as logger from './common/logger' if ( await databaseHelper.connect(env.DB_URI, env.DB_SSL, env.DB_DEBUG) && await databaseHelper.initialize() ) { let server: http.Server | https.Server if (env.HTTPS) { https.globalAgent.maxSockets = Number.POSITIVE_INFINITY const privateKey = await fs.readFile(env.PRIVATE_KEY, 'utf8') const certificate = await fs.readFile(env.CERTIFICATE, 'utf8') const credentials: ServerOptions = { key: privateKey, cert: certificate } server = https.createServer(credentials, app) server.listen(env.PORT, () => { logger.info('HTTPS server is running on Port', env.PORT) }) } else { server = app.listen(env.PORT, () => { logger.info('HTTP server is running on Port', env.PORT) }) } const close = () => { logger.info('Gracefully stopping...') server.close(async () => { logger.info(`HTTP${env.HTTPS ? 'S' : ''} server closed`) await databaseHelper.close(true) logger.info('MongoDB connection closed') process.exit(0) }) } ['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach((signal) => process.on(signal, close)) }
Zuerst erstellen wir einen Express-Router. Anschlie?end erstellen wir die Routen anhand ihres Namens, ihrer Methode, Middleware und Controller.
routeNames enth?lt propertyRoutes-Routennamen:
import express from 'express' import compression from 'compression' import helmet from 'helmet' import nocache from 'nocache' import cookieParser from 'cookie-parser' import i18n from './lang/i18n' import * as env from './config/env.config' import cors from './middlewares/cors' import allowedMethods from './middlewares/allowedMethods' import agencyRoutes from './routes/agencyRoutes' import bookingRoutes from './routes/bookingRoutes' import locationRoutes from './routes/locationRoutes' import notificationRoutes from './routes/notificationRoutes' import propertyRoutes from './routes/propertyRoutes' import userRoutes from './routes/userRoutes' import stripeRoutes from './routes/stripeRoutes' import countryRoutes from './routes/countryRoutes' import * as helper from './common/helper' const app = express() app.use(helmet.contentSecurityPolicy()) app.use(helmet.dnsPrefetchControl()) app.use(helmet.crossOriginEmbedderPolicy()) app.use(helmet.frameguard()) app.use(helmet.hidePoweredBy()) app.use(helmet.hsts()) app.use(helmet.ieNoOpen()) app.use(helmet.noSniff()) app.use(helmet.permittedCrossDomainPolicies()) app.use(helmet.referrerPolicy()) app.use(helmet.xssFilter()) app.use(helmet.originAgentCluster()) app.use(helmet.crossOriginResourcePolicy({ policy: 'cross-origin' })) app.use(helmet.crossOriginOpenerPolicy()) app.use(nocache()) app.use(compression({ threshold: 0 })) app.use(express.urlencoded({ limit: '50mb', extended: true })) app.use(express.json({ limit: '50mb' })) app.use(cors()) app.options('*', cors()) app.use(cookieParser(env.COOKIE_SECRET)) app.use(allowedMethods) app.use('/', agencyRoutes) app.use('/', bookingRoutes) app.use('/', locationRoutes) app.use('/', notificationRoutes) app.use('/', propertyRoutes) app.use('/', userRoutes) app.use('/', stripeRoutes) app.use('/', countryRoutes) i18n.locale = env.DEFAULT_LANGUAGE helper.mkdir(env.CDN_USERS) helper.mkdir(env.CDN_TEMP_USERS) helper.mkdir(env.CDN_PROPERTIES) helper.mkdir(env.CDN_TEMP_PROPERTIES) helper.mkdir(env.CDN_LOCATIONS) helper.mkdir(env.CDN_TEMP_LOCATIONS) export default app
propertyController enth?lt die Hauptgesch?ftslogik in Bezug auf Standorte. Wir werden nicht den gesamten Quellcode des Controllers sehen, da dieser recht umfangreich ist, aber wir nehmen zum Beispiel die Funktion zum Erstellen eines Controllers.
Unten finden Sie das Immobilienmodell:
import express from 'express' import multer from 'multer' import routeNames from '../config/propertyRoutes.config' import authJwt from '../middlewares/authJwt' import * as propertyController from '../controllers/propertyController' const routes = express.Router() routes.route(routeNames.create).post(authJwt.verifyToken, propertyController.create) routes.route(routeNames.update).put(authJwt.verifyToken, propertyController.update) routes.route(routeNames.checkProperty).get(authJwt.verifyToken, propertyController.checkProperty) routes.route(routeNames.delete).delete(authJwt.verifyToken, propertyController.deleteProperty) routes.route(routeNames.uploadImage).post([authJwt.verifyToken, multer({ storage: multer.memoryStorage() }).single('image')], propertyController.uploadImage) routes.route(routeNames.deleteImage).post(authJwt.verifyToken, propertyController.deleteImage) routes.route(routeNames.deleteTempImage).post(authJwt.verifyToken, propertyController.deleteTempImage) routes.route(routeNames.getProperty).get(propertyController.getProperty) routes.route(routeNames.getProperties).post(authJwt.verifyToken, propertyController.getProperties) routes.route(routeNames.getBookingProperties).post(authJwt.verifyToken, propertyController.getBookingProperties) routes.route(routeNames.getFrontendProperties).post(propertyController.getFrontendProperties) export default routes
Unten finden Sie den Immobilientyp:
const routes = { create: '/api/create-property', update: '/api/update-property', delete: '/api/delete-property/:id', uploadImage: '/api/upload-property-image', deleteTempImage: '/api/delete-temp-property-image/:fileName', deleteImage: '/api/delete-property-image/:property/:image', getProperty: '/api/property/:id/:language', getProperties: '/api/properties/:page/:size', getBookingProperties: '/api/booking-properties/:page/:size', getFrontendProperties: '/api/frontend-properties/:page/:size', checkProperty: '/api/check-property/:id', } export default routes
Eine Eigenschaft besteht aus:
- Ein Name
- Typ A (Wohnung, Gewerbe, Bauernhof, Haus, Industrie, Grundstück, Reihenhaus)
- Ein Verweis auf die Agentur, die es erstellt hat
- Eine Beschreibung
- Ein Hauptbild
- Zus?tzliche Bilder
- Anzahl der Schlafzimmer
- Anzahl der Badezimmer
- Anzahl der Küchen
- Anzahl der Parkpl?tze
- Gr??e A
- Mindestalter für die Anmietung
- Ein Ort
- Eine Adresse (optional)
- Ein Preis
- Eine Mietdauer (monatlich, w?chentlich, t?glich, j?hrlich)
- Stornierungspreis (setzen Sie ihn auf 0, um ihn kostenlos einzuschlie?en, lassen Sie das Feld leer, wenn Sie ihn nicht einschlie?en m?chten, oder legen Sie den Preis für die Stornierung fest)
- Eine Flagge, die angibt, ob Haustiere erlaubt sind oder nicht
- Eine Markierung, die angibt, ob die Immobilie m?bliert ist oder nicht
- Eine Markierung, die angibt, ob die Eigenschaft ausgeblendet ist oder nicht
- Eine Flagge, die anzeigt, ob eine Klimaanlage verfügbar ist oder nicht
- Eine Markierung, die angibt, ob die Immobilie zur Vermietung verfügbar ist oder nicht
Unten finden Sie die Funktion zum Erstellen eines Controllers:
import { Schema, model } from 'mongoose' import * as movininTypes from ':movinin-types' import * as env from '../config/env.config' const propertySchema = new Schema<env.Property>( { name: { type: String, required: [true, "can't be blank"], }, type: { type: String, enum: [ movininTypes.PropertyType.House, movininTypes.PropertyType.Apartment, movininTypes.PropertyType.Townhouse, movininTypes.PropertyType.Plot, movininTypes.PropertyType.Farm, movininTypes.PropertyType.Commercial, movininTypes.PropertyType.Industrial, ], required: [true, "can't be blank"], }, agency: { type: Schema.Types.ObjectId, required: [true, "can't be blank"], ref: 'User', index: true, }, description: { type: String, required: [true, "can't be blank"], }, available: { type: Boolean, default: true, }, image: { type: String, }, images: { type: [String], }, bedrooms: { type: Number, required: [true, "can't be blank"], validate: { validator: Number.isInteger, message: '{VALUE} is not an integer value', }, }, bathrooms: { type: Number, required: [true, "can't be blank"], validate: { validator: Number.isInteger, message: '{VALUE} is not an integer value', }, }, kitchens: { type: Number, default: 1, validate: { validator: Number.isInteger, message: '{VALUE} is not an integer value', }, }, parkingSpaces: { type: Number, default: 0, validate: { validator: Number.isInteger, message: '{VALUE} is not an integer value', }, }, size: { type: Number, }, petsAllowed: { type: Boolean, required: [true, "can't be blank"], }, furnished: { type: Boolean, required: [true, "can't be blank"], }, minimumAge: { type: Number, required: [true, "can't be blank"], min: env.MINIMUM_AGE, max: 99, }, location: { type: Schema.Types.ObjectId, ref: 'Location', required: [true, "can't be blank"], }, address: { type: String, }, price: { type: Number, required: [true, "can't be blank"], }, hidden: { type: Boolean, default: false, }, cancellation: { type: Number, default: 0, }, aircon: { type: Boolean, default: false, }, rentalTerm: { type: String, enum: [ movininTypes.RentalTerm.Monthly, movininTypes.RentalTerm.Weekly, movininTypes.RentalTerm.Daily, movininTypes.RentalTerm.Yearly, ], required: [true, "can't be blank"], }, }, { timestamps: true, strict: true, collection: 'Property', }, ) const Property = model<env.Property>('Property', propertySchema) export default Property
Frontend
Das Frontend ist eine Webanwendung, die mit Node.js, React, MUI und TypeScript erstellt wurde. Im Frontend kann der Kunde je nach Abhol- und Rückgabeort und -zeit nach verfügbaren Autos suchen, ein Auto ausw?hlen und zur Kasse gehen:
- ./frontend/src/assets/ Ordner enth?lt CSS und Bilder.
- ./frontend/src/pages/ Ordner enth?lt React-Seiten.
- ./frontend/src/components/ Ordner enth?lt React-Komponenten.
- ./frontend/src/services/ enth?lt API-Client-Dienste.
- ./frontend/src/App.tsx ist die Haupt-React-App, die Routen enth?lt.
- ./frontend/src/index.tsx ist der Haupteinstiegspunkt des Frontends.
TypeScript-Typdefinitionen werden im Paket ./packages/movinin-types definiert.
App.tsx ist die Hauptreaktions-App:
import 'dotenv/config' import process from 'node:process' import fs from 'node:fs/promises' import http from 'node:http' import https, { ServerOptions } from 'node:https' import app from './app' import * as databaseHelper from './common/databaseHelper' import * as env from './config/env.config' import * as logger from './common/logger' if ( await databaseHelper.connect(env.DB_URI, env.DB_SSL, env.DB_DEBUG) && await databaseHelper.initialize() ) { let server: http.Server | https.Server if (env.HTTPS) { https.globalAgent.maxSockets = Number.POSITIVE_INFINITY const privateKey = await fs.readFile(env.PRIVATE_KEY, 'utf8') const certificate = await fs.readFile(env.CERTIFICATE, 'utf8') const credentials: ServerOptions = { key: privateKey, cert: certificate } server = https.createServer(credentials, app) server.listen(env.PORT, () => { logger.info('HTTPS server is running on Port', env.PORT) }) } else { server = app.listen(env.PORT, () => { logger.info('HTTP server is running on Port', env.PORT) }) } const close = () => { logger.info('Gracefully stopping...') server.close(async () => { logger.info(`HTTP${env.HTTPS ? 'S' : ''} server closed`) await databaseHelper.close(true) logger.info('MongoDB connection closed') process.exit(0) }) } ['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach((signal) => process.on(signal, close)) }
Wir verwenden React Lazy Loading, um jede Route zu laden.
Wir werden nicht jede Seite des Frontends behandeln, aber Sie k?nnen den Quellcode durchsuchen und jede einzelne sehen.
Mobile App
Die Plattform bietet eine native mobile App für Android und iOS. Die mobile App wurde mit React Native, Expo und TypeScript erstellt. Wie beim Frontend erm?glicht die mobile App dem Kunden, je nach Abhol- und Rückgabeort und -zeit nach verfügbaren Autos zu suchen, ein Auto auszuw?hlen und zur Kasse zu gehen.
Der Kunde erh?lt Push-Benachrichtigungen, wenn seine Buchung aus dem Backend aktualisiert wird. Push-Benachrichtigungen werden mit Node.js, Expo Server SDK und Firebase erstellt.
- ./mobile/assets/ Ordner enth?lt Bilder.
- ./mobile/screens/-Ordner enth?lt die Hauptbildschirme von React Native.
- ./mobile/components/ Ordner enth?lt React Native-Komponenten.
- ./mobile/services/ enth?lt API-Client-Dienste.
- ./mobile/App.tsx ist die wichtigste React Native App.
TypeScript-Typdefinitionen sind definiert in:
- ./mobile/types/index.d.ts
- ./mobile/types/env.d.ts
- ./mobile/miscellaneous/movininTypes.ts
./mobile/types/ wird wie folgt in ./mobile/tsconfig.json geladen:
import express from 'express' import compression from 'compression' import helmet from 'helmet' import nocache from 'nocache' import cookieParser from 'cookie-parser' import i18n from './lang/i18n' import * as env from './config/env.config' import cors from './middlewares/cors' import allowedMethods from './middlewares/allowedMethods' import agencyRoutes from './routes/agencyRoutes' import bookingRoutes from './routes/bookingRoutes' import locationRoutes from './routes/locationRoutes' import notificationRoutes from './routes/notificationRoutes' import propertyRoutes from './routes/propertyRoutes' import userRoutes from './routes/userRoutes' import stripeRoutes from './routes/stripeRoutes' import countryRoutes from './routes/countryRoutes' import * as helper from './common/helper' const app = express() app.use(helmet.contentSecurityPolicy()) app.use(helmet.dnsPrefetchControl()) app.use(helmet.crossOriginEmbedderPolicy()) app.use(helmet.frameguard()) app.use(helmet.hidePoweredBy()) app.use(helmet.hsts()) app.use(helmet.ieNoOpen()) app.use(helmet.noSniff()) app.use(helmet.permittedCrossDomainPolicies()) app.use(helmet.referrerPolicy()) app.use(helmet.xssFilter()) app.use(helmet.originAgentCluster()) app.use(helmet.crossOriginResourcePolicy({ policy: 'cross-origin' })) app.use(helmet.crossOriginOpenerPolicy()) app.use(nocache()) app.use(compression({ threshold: 0 })) app.use(express.urlencoded({ limit: '50mb', extended: true })) app.use(express.json({ limit: '50mb' })) app.use(cors()) app.options('*', cors()) app.use(cookieParser(env.COOKIE_SECRET)) app.use(allowedMethods) app.use('/', agencyRoutes) app.use('/', bookingRoutes) app.use('/', locationRoutes) app.use('/', notificationRoutes) app.use('/', propertyRoutes) app.use('/', userRoutes) app.use('/', stripeRoutes) app.use('/', countryRoutes) i18n.locale = env.DEFAULT_LANGUAGE helper.mkdir(env.CDN_USERS) helper.mkdir(env.CDN_TEMP_USERS) helper.mkdir(env.CDN_PROPERTIES) helper.mkdir(env.CDN_TEMP_PROPERTIES) helper.mkdir(env.CDN_LOCATIONS) helper.mkdir(env.CDN_TEMP_LOCATIONS) export default app
App.tsx ist der Haupteinstiegspunkt der React Native-App:
'react-native-gesture-handler' importieren importiere React, { useCallback, useEffect, useRef, useState } aus 'react' importiere { RootSiblingParent } aus 'react-native-root-siblings' import { NavigationContainer, NavigationContainerRef } aus '@react-navigation/native' importiere { StatusBar as ExpoStatusBar } aus 'expo-status-bar' importiere { SafeAreaProvider } aus 'react-native-safe-area-context' { Provider } aus 'react-native-paper' importieren * als SplashScreen aus 'expo-splash-screen' importieren * als Benachrichtigungen aus ?expo-notifications“ importieren importiere { StripeProvider } aus '@stripe/stripe-react-native' SchubladeNavigator aus ?./components/DrawerNavigator“ importieren * als Helfer aus './common/helper' importieren * als NotificationService aus './services/NotificationService' importieren * als UserService aus './services/UserService' importieren importiere { GlobalProvider } aus './context/GlobalContext' * als Umgebung aus ?./config/env.config“ importieren Notifications.setNotificationHandler({ handleNotification: async () => ({ ShouldShowAlert: wahr, ShouldPlaySound: wahr, ShouldSetBadge: wahr, }), }) // // Verhindern Sie, dass der native Begrü?ungsbildschirm vor der Deklaration der App-Komponente automatisch ausgeblendet wird // SplashScreen.preventAutoHideAsync() .then((result) => console.log(`SplashScreen.preventAutoHideAsync() erfolgreich: ${result}`)) .catch(console.warn) // Es ist gut, jeden Fehler explizit abzufangen und zu untersuchen const App = () => { const [appIsReady, setAppIsReady] = useState(false) const ResponseListener = useRef<Notifications.Subscription>() const navigationRef = useRef<NavigationContainerRef<StackParams>>(null) useEffect(() => { const register = async () => { const login = Warten auf UserService.loggedIn() if (eingeloggt) { const currentUser = Warten auf UserService.getCurrentUser() if (currentUser?._id) { Warten auf helper.registerPushToken(currentUser._id) } anders { helper.error() } } } // // Push-Benachrichtigungstoken registrieren // registrieren() // // Dieser Listener wird immer dann ausgel?st, wenn ein Benutzer auf eine Benachrichtigung tippt oder mit ihr interagiert (funktioniert, wenn die App im Vordergrund, Hintergrund oder beendet ist). // ResponseListener.current = Notifications.addNotificationResponseReceivedListener(async (response) => { versuchen { if (navigationRef.current) { const { data } = Response.notification.request.content if (data.booking) { if (data.user && data.notification) { Warten Sie auf NotificationService.markAsRead(data.user, [data.notification]) } navigationRef.current.navigate('Booking', { id: data.booking }) } anders { navigationRef.current.navigate('Benachrichtigungen', {}) } } } fangen (irrt) { helper.error(err, false) } }) return () => { Notifications.removeNotificationSubscription(responseListener.current!) } }, []) setTimeout(() => { setAppIsReady(true) }, 500) const onReady = useCallback(async () => { if (appIsReady) { // // Dies weist den Begrü?ungsbildschirm an, sich sofort auszublenden! Wenn wir das nachrufen // `setAppIsReady`, dann sehen wir m?glicherweise einen leeren Bildschirm, w?hrend die App l?uft // seinen Anfangszustand laden und seine ersten Pixel rendern. Also stattdessen, // Wir blenden den Begrü?ungsbildschirm aus, sobald wir wissen, dass die Root-Ansicht dies bereits getan hat // Layout ausgeführt. // Warten Sie auf SplashScreen.hideAsync() } }, [appIsReady]) if (!appIsReady) { null zurückgeben } zurückkehren ( <GlobalProvider> <SafeAreaProvider> <Anbieter> <StripeProvider publizierbarKey={env.STRIPE_PUBLISHABLE_KEY} MerchantIdentifier={env.STRIPE_MERCHANT_IDENTIFIER}> <RootSiblingParent> <NavigationContainer ref={navigationRef} onReady={onReady}> <ExpoStatusBar> <p>Wir werden nicht jeden Bildschirm der mobilen App behandeln, aber Sie k?nnen den Quellcode durchsuchen und jeden einzelnen sehen.</p> <h2> Admin-Dashboard </h2> <p>Das Admin-Dashboard ist eine Webanwendung, die mit Node.js, React, MUI und TypeScript erstellt wurde. Vom Backend aus k?nnen Administratoren Lieferanten, Autos, Standorte, Kunden und Buchungen erstellen und verwalten. Wenn neue Lieferanten über das Backend erstellt werden, erhalten sie eine E-Mail mit der Aufforderung, ein Konto zu erstellen, um auf das Admin-Dashboard zuzugreifen und ihre Fahrzeugflotte und Buchungen zu verwalten.</p>
- ./backend/assets/ Ordner enth?lt CSS und Bilder.
- ./backend/pages/ Ordner enth?lt React-Seiten.
- ./backend/components/ Ordner enth?lt React-Komponenten.
- ./backend/services/ enth?lt API-Client-Dienste.
- ./backend/App.tsx ist die Haupt-React-App, die Routen enth?lt.
- ./backend/index.tsx ist der Haupteinstiegspunkt des Admin-Dashboards.
TypeScript-Typdefinitionen werden im Paket ./packages/movinin-types definiert.
App.tsx des Admin-Dashboards folgt einer ?hnlichen Logik wie App.tsx des Frontends.
Wir werden nicht jede Seite des Admin-Dashboards behandeln, aber Sie k?nnen den Quellcode durchsuchen und jede einzelne sehen.
Sehenswürdigkeiten
Das Erstellen der mobilen App mit React Native und Expo ist sehr einfach. Expo macht die mobile Entwicklung mit React Native sehr einfach.
Die Verwendung derselben Sprache (TypeScript) für Backend-, Frontend- und Mobilentwicklung ist sehr praktisch.
TypeScript ist eine sehr interessante Sprache und hat viele Vorteile. Durch das Hinzufügen statischer Typisierung zu JavaScript k?nnen wir viele Fehler vermeiden und qualitativ hochwertigen, skalierbaren, besser lesbaren und wartbaren Code erstellen, der leicht zu debuggen und zu testen ist.
Das ist es! Ich hoffe, dass Ihnen die Lektüre dieses Artikels gefallen hat.
Ressourcen
- übersicht
- Architektur
- Installation (selbst gehostet)
- Installieren (VPS)
-
Installieren (Docker)
- Docker-Image
- SSL
- Stripe einrichten
- Mobile App erstellen
-
Demo-Datenbank
- Windows, Linux und macOS
- Docker
- Von der Quelle ausführen
-
Führen Sie die mobile App aus
- Voraussetzungen
- Anleitung
- Push-Benachrichtigungen
- W?hrung ?ndern
- Neue Sprache hinzufügen
- Unit-Tests und Abdeckung
- Protokolle
Das obige ist der detaillierte Inhalt vonVon Null zum Helden: Meine Reise zum Aufbau einer Immobilienvermietungs-Website und einer mobilen App. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Hei?e KI -Werkzeuge

Undress AI Tool
Ausziehbilder kostenlos

Undresser.AI Undress
KI-gestützte App zum Erstellen realistischer Aktfotos

AI Clothes Remover
Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

Clothoff.io
KI-Kleiderentferner

Video Face Swap
Tauschen Sie Gesichter in jedem Video mühelos mit unserem v?llig kostenlosen KI-Gesichtstausch-Tool aus!

Hei?er Artikel

Hei?e Werkzeuge

Notepad++7.3.1
Einfach zu bedienender und kostenloser Code-Editor

SublimeText3 chinesische Version
Chinesische Version, sehr einfach zu bedienen

Senden Sie Studio 13.0.1
Leistungsstarke integrierte PHP-Entwicklungsumgebung

Dreamweaver CS6
Visuelle Webentwicklungstools

SublimeText3 Mac-Version
Codebearbeitungssoftware auf Gottesniveau (SublimeText3)

Es gibt drei g?ngige M?glichkeiten, HTTP-Anforderungen in Node.js zu initiieren: Verwenden Sie integrierte Module, Axios und Knotenfetch. 1. Verwenden Sie das integrierte HTTP/HTTPS-Modul ohne Abh?ngigkeiten, das für grundlegende Szenarien geeignet ist, jedoch eine manuelle Verarbeitung von Datengen?hten und Fehlerüberwachung erfordert, z. 2.Axios ist eine auf Versprechen basierende Bibliothek von Drittanbietern. Es verfügt über eine kurze Syntax und leistungsstarke Funktionen, unterstützt Async/Auseait, automatische JSON -Konvertierung, Interceptor usw. Es wird empfohlen, asynchrone Anforderungsvorg?nge zu vereinfachen. 3.Node-Fetch bietet einen Stil ?hnlich dem Browser-Abruf, basierend auf Versprechen und einfacher Syntax

JavaScript -Datentypen sind in primitive Typen und Referenztypen unterteilt. Zu den primitiven Typen geh?ren String, Anzahl, Boolesche, Null, undefiniertes und Symbol. Die Werte sind unver?nderlich und Kopien werden bei der Zuweisung von Werten kopiert, sodass sie sich nicht gegenseitig beeinflussen. Referenztypen wie Objekte, Arrays und Funktionen speichern Speicheradressen, und Variablen, die auf dasselbe Objekt zeigen, wirkt sich gegenseitig aus. Typeof und Instanz k?nnen verwendet werden, um die Typen zu bestimmen, aber auf die historischen Probleme der TypeOfnull zu achten. Das Verst?ndnis dieser beiden Arten von Unterschieden kann dazu beitragen, einen stabileren und zuverl?ssigeren Code zu schreiben.

Hallo, JavaScript -Entwickler! Willkommen in den JavaScript -Nachrichten dieser Woche! Diese Woche konzentrieren wir uns auf: Oracas Markenstreit mit Deno, neue JavaScript -Zeitobjekte werden von Browsern, Google Chrome -Updates und einigen leistungsstarken Entwickler -Tools unterstützt. Fangen wir an! Der Markenstreit von Oracle mit dem Versuch von Deno Oracle, ein "JavaScript" -Marke zu registrieren, hat Kontroversen verursacht. Ryan Dahl, der Sch?pfer von Node.js und Deno, hat eine Petition zur Absage der Marke eingereicht, und er glaubt, dass JavaScript ein offener Standard ist und nicht von Oracle verwendet werden sollte

Versprechen ist der Kernmechanismus für den Umgang mit asynchronen Operationen in JavaScript. Das Verst?ndnis von Kettenanrufen, Fehlerbehebung und Kombination ist der Schlüssel zum Beherrschen ihrer Anwendungen. 1. Der Kettenaufruf gibt ein neues Versprechen durch .then () zurück, um asynchrone Prozessverkampferung zu realisieren. Jeder. Dann () erh?lt das vorherige Ergebnis und kann einen Wert oder ein Versprechen zurückgeben; 2. Die Fehlerbehandlung sollte .Catch () verwenden, um Ausnahmen zu fangen, um stille Ausf?lle zu vermeiden, und den Standardwert im Fang zurückgeben, um den Prozess fortzusetzen. 3. Combinatoren wie Promise.All () (erfolgreich erfolgreich erfolgreich nach allen Erfolg), Versprechen.Race () (Die erste Fertigstellung wird zurückgegeben) und Versprechen.Allsettled () (Warten auf alle Fertigstellungen)

Cacheapi ist ein Tool, das der Browser zur Cache -Netzwerkanfragen bereitstellt, das h?ufig in Verbindung mit dem Servicearbeiter verwendet wird, um die Leistung der Website und die Offline -Erfahrung zu verbessern. 1. Es erm?glicht Entwicklern, Ressourcen wie Skripte, Stilbl?tter, Bilder usw. Zu speichern; 2. Es kann die Cache -Antworten entsprechend den Anfragen übereinstimmen. 3. Es unterstützt das L?schen bestimmter Caches oder das L?schen des gesamten Cache. 4.. Es kann Cache -Priorit?ts- oder Netzwerkpriorit?tsstrategien durch Servicearbeiter implementieren, die sich auf Fetch -Ereignisse anh?ren. 5. Es wird h?ufig für die Offline -Unterstützung verwendet, die wiederholte Zugriffsgeschwindigkeit, die Vorspannungs -Schlüsselressourcen und den Inhalt des Hintergrundaktualisierungss beschleunigen. 6. Wenn Sie es verwenden, müssen Sie auf die Cache -Versionskontrolle, Speicherbeschr?nkungen und den Unterschied zum HTTP -Caching -Mechanismus achten.

Die Ereignisschleife von JavaScript verwaltet asynchrone Vorg?nge, indem sie Call -Stapel, Webapis und Task -Warteschlangen koordinieren. 1. Der Anrufstack führt synchronen Code aus, und wenn er auf asynchrone Aufgaben begegnet, wird er zur Verarbeitung an Webapi übergeben. 2. Nachdem das Webapi die Aufgabe im Hintergrund abgeschlossen hat, wird der Rückruf in die entsprechende Warteschlange (Makroaufgabe oder Micro -Aufgabe) eingebaut. 3. Die Ereignisschleife prüft, ob der Anrufstapel leer ist. Wenn es leer ist, wird der Rückruf aus der Warteschlange herausgenommen und zur Ausführung in den Anrufstapel geschoben. V. 5. Das Verst?ndnis der Ereignisschleife hilft zu vermeiden, den Haupt -Thread zu blockieren und die Codeausführungsreihenfolge zu optimieren.

Ereignisblasen verbreiten sich vom Zielelement nach au?en zum Vorfahrknoten aus, w?hrend Ereignisfassungen sich von der ?u?eren Schicht nach innen zum Zielelement ausbreiten. 1. Ereignisblasen: Nach dem Klicken auf das untergeordnete Element l?st das Ereignis den H?rer des übergeordneten Elements nach oben aus. Nach dem Klicken auf die Schaltfl?che gibt es beispielsweise zuerst die untergeordnete und dann entzündete Eltern aus. 2. Ereigniserfassung: Stellen Sie den dritten Parameter auf True ein, so dass der H?rer in der Erfassungsstufe ausgeführt wird, z. B. das Ausl?sen des Capture -Listeners des übergeordneten Elements, bevor Sie auf die Schaltfl?che klicken. 3. Praktische Verwendungszwecke umfassen ein einheitliches Management von Ereignissen für Kinderelemente, Vorverarbeitung und Leistungsoptimierung von Abfangen. V.

In JavaScript -Arrays gibt es zus?tzlich zu MAP und Filter andere leistungsstarke und selten verwendete Methoden. 1. Reduzierung kann nicht nur summieren, sondern auch z?hlen, gruppen, flach Arrays ab und bauen neue Strukturen auf. 2. FindingIndex werden verwendet, um einzelne Elemente oder Indizes zu finden. 3. Einige und alles werden verwendet, um festzustellen, ob Bedingungen bestehen oder sich alle treffen. 4. SORT kann sortiert werden, wechselt aber das ursprüngliche Array. 5. Achten Sie darauf, das Array zu kopieren, wenn Sie es verwenden, um Nebenwirkungen zu vermeiden. Diese Methoden machen den Code pr?gnanter und effizienter.
