Webbutveckling är ett av de mest populära användningsfallen för programmering. Python är ett av de mest populära programmeringsspråken i världen. Så varför kan vi inte bygga webbappar i Python?
Att skapa ett användargränssnitt ska vara enkelt, men även du har fantastiska ingenjörer i ditt team, det var ett stort hinder att lära sig ett nytt språk och nya verktyg. Att skapa ett användargränssnitt kan ofta vara svårare än det faktiska arbetet man gör! Vi byggde Reflex, ett Python-webbramverk med öppen källkod för att lösa detta exakta problem.
Under huven kompilerar Reflex-appar till en React frontend-app och en FastAPI- backend-app. Endast användargränssnittet är kompilerat till Javascript; all applogik och tillståndshantering stannar i Python och körs på servern. Reflex använder WebSockets för att skicka händelser från frontend till backend, och för att skicka tillståndsuppdateringar från backend till frontend.
Det fanns redan några sätt att bygga appar i Python, men inget av dem passar våra behov.
Å ena sidan finns det ramverk som Django och Flask som är bra för att bygga webbappar i produktionsklass. Men de hanterar bara backend - du behöver fortfarande använda JavaScript och ett frontend-ramverk, samt att skriva en massa boilerplate-kod för att koppla ihop frontend och backend.
Å andra sidan kan rena Python-bibliotek som Dash och Streamlit vara bra för små projekt, men de är begränsade till ett specifikt användningsfall och har inte funktioner och prestanda för att bygga en fullständig webbapp. När din app växer i funktioner och komplexitet kan du komma på att du når ramverkets gränser, då måste du antingen begränsa din idé för att passa ramverket, eller skrota ditt projekt och bygga om det med ett "riktigt webbramverk".
Vi vill överbrygga detta gap genom att skapa ett ramverk som är enkelt och intuitivt att komma igång med, samtidigt som det förblir flexibelt och kraftfullt för att stödja alla appar.
Låt oss nu dyka in i hur vi byggde Reflex för att uppfylla dessa mål.
Full-stack webbappar består av en frontend och en backend. Frontend är användargränssnittet och serveras som en webbsida som körs i användarens webbläsare. Backend hanterar logik och tillståndshantering (som databaser och API:er) och körs på en server. I traditionell webbutveckling är dessa vanligtvis två separata appar, och skrivs ofta i olika ramverk eller språk. Till exempel kan du kombinera en Flask-backend med en React-gränssnitt. Med det här tillvägagångssättet måste du underhålla två separata appar och sluta skriva en hel del kod för att ansluta frontend och backend.
Vi vill förenkla denna process i Reflex genom att definiera både frontend och backend i en enda kodbas, samtidigt som vi använder Python för allt. Utvecklare bör bara oroa sig för sin app logik och inte om implementeringsdetaljerna på låg nivå.
Vi vill att Reflex-appar ska se ut och kännas som en traditionell webbapp för slutanvändaren, samtidigt som de är enkla att bygga och underhålla för utvecklaren. För att göra detta byggde vi på mogna och populära webbteknologier.
När du reflex run
din app kompilerar Reflex gränssnittet till en Next.js -app på en sida och serverar den på en port (som standard 3000
) som du kan komma åt i din webbläsare.
Frontends uppgift är att återspegla appens tillstånd och skicka händelser till backend när användaren interagerar med användargränssnittet. Ingen egentlig logik körs på frontend.
Reflex-gränssnitt är byggda med komponenter som kan sammanställas för att skapa komplexa användargränssnitt. Istället för att använda ett mallspråk som blandar HTML och Python, använder vi bara Python-funktioner för att definiera användargränssnittet.
Under huven sammanställs komponenter till React-komponenter. Många av våra kärnkomponenter är baserade på Radix , ett populärt React-komponentbibliotek. Vi har även många andra komponenter för grafer, datatabeller och mer. Vi valde React för att det är ett populärt bibliotek med ett enormt ekosystem. Vårt mål är inte att återskapa webbekosystemet, utan att göra det tillgängligt för Python-utvecklare.
Detta låter också våra användare ta med sina egna komponenter om vi inte har en komponent de behöver. Användare kan slå in sina egna React-komponenter och sedan publicera dem för andra att använda. Med tiden kommer vi att bygga ut vårt tredje parts komponentekosystem så att användare enkelt kan hitta och använda komponenter som andra har byggt.
Vi ville se till att Reflex-appar ser bra ut direkt, samtidigt som de fortfarande ger utvecklare full kontroll över utseendet på deras app.
Vi har ett kärntemasystem som låter dig ställa in stilalternativ på hög nivå som mörkt läge och accentfärg i hela din app för att ge den ett enhetligt utseende och känsla.
Utöver detta kan Reflex-komponenter stylas med den fulla kraften i CSS. Vi utnyttjar Emotion- biblioteket för att tillåta "CSS-in-Python"-styling, så att du kan skicka vilken CSS-rekvisita som helst som nyckelordsargument till en komponent. Detta inkluderar responsiv rekvisita genom att skicka en lista med värden.
I Reflex kompilerar endast frontend till Javascript och körs på användarens webbläsare, medan all tillstånd och logik stannar i Python och körs på servern. När du reflex run
startar vi en FastAPI-server (som standard på port 8000
) som fronten ansluter till via en websocket.
Alla tillstånd och logik definieras inom en State
. Staten består av vars och händelsehanterare . Vars är alla värden i din app som kan ändras över tiden. De definieras som klassattribut på din State
-klass och kan vara vilken Python-typ som helst som kan serialiseras till JSON.
Händelsehanterare är metoder i din State
-klass som anropas när användaren interagerar med användargränssnittet. De är det enda sättet som vi kan ändra vars i Reflex, och kan anropas som svar på användaråtgärder, som att klicka på en knapp eller skriva i en textruta.
Eftersom händelsehanterare körs på backend kan du använda vilket Python-bibliotek som helst inom dem.
Normalt när du skriver webbappar måste du skriva mycket kod för att koppla ihop frontend och backend. Med Reflex behöver du inte oroa dig för det – vi sköter kommunikationen mellan frontend och backend åt dig. Utvecklare behöver bara skriva sin händelsehanterarlogik, och när vars uppdateras uppdateras användargränssnittet automatiskt.
Användaren kan interagera med användargränssnittet på många sätt, som att klicka på en knapp, skriva i en textruta eller hålla muspekaren över ett element. I Reflex kallar vi dessa händelseutlösare .
På gränssnittet upprätthåller vi en händelsekö med alla pågående händelser. En händelse består av tre viktiga data:
När en händelse utlöses läggs den till i kön. Vi har en processing
för att säkerställa att endast en händelse bearbetas åt gången. Detta säkerställer att tillståndet alltid är konsekvent och att det inte finns några tävlingsförhållanden med två händelsehanterare som modifierar tillståndet samtidigt. Det finns undantag från detta, till exempel bakgrundshändelser som låter dig köra händelser i bakgrunden utan att blockera användargränssnittet.
När händelsen är redo att bearbetas skickas den till backend via en WebSocket-anslutning.
När händelsen väl har tagits emot bearbetas den på backend. Reflex använder en tillståndshanterare som upprätthåller en mappning mellan klienttokens och deras tillstånd. Som standard är tillståndshanteraren bara en minnesordbok, men den kan utökas till att använda en databas eller cache. I produktionen använder vi Redis som vår statschef.
När vi väl har användarens tillstånd är nästa steg att köra händelsehanteraren med argumenten.
Varje gång en händelsehanterare återkommer (eller ger efter), sparar vi tillståndet i tillståndshanteraren och skickar tillståndsuppdateringarna till frontend för att uppdatera UI. För att bibehålla prestanda när ditt tillstånd växer, håller Reflex internt reda på vars som uppdaterades under händelsehanteraren ( dirty vars ).
När händelsehanteraren är klar med bearbetningen hittar vi alla smutsiga vars och skapar en tillståndsuppdatering att skicka till frontend.
Vi lagrar det nya tillståndet i vår tillståndshanterare och skickar sedan tillståndsuppdateringen till frontend. Gränssnittet uppdaterar sedan användargränssnittet för att återspegla det nya tillståndet.
Jag hoppas att detta ger en bra överblick över hur Reflex fungerar under huven. Vi kommer att ha fler inlägg för att dela hur vi gjorde Reflex skalbar och prestanda genom funktioner som tillståndsskärning och kompilatoroptimeringar.