00:00:00React Server Components. Entweder man liebt oder hasst sie. Zurzeit scheint man sie eher zu hassen, aber das
00:00:04könnte sich bald ändern, denn TanStack mischt jetzt mit. Richtig, wir haben jetzt TanStack
00:00:08Server Components, und sie verfolgen einen ganz anderen Ansatz als Next.js. Schauen wir es uns an.
00:00:13Ich beginne mit einem Absatz aus ihrem Ankündigungsbeitrag, der meiner Meinung nach
00:00:21viele Leute beruhigen wird. Er lautet: "Die meisten Leute denken bei React Server Components jetzt in einem server-first
00:00:26Ansatz. Der Server besitzt den Baum, "useClient" markiert die interaktiven Teile, und die Konventionen des Frameworks
00:00:31entscheiden, wie das Ganze zusammenpasst. Das macht React Server Components von einem nützlichen
00:00:35Grundbaustein zu einer Sache, um die sich die ganze App drehen muss. Wir glauben nicht, dass man sich auf
00:00:40dieses komplette Modell einlassen muss, nur um den Nutzen von React Server Components zu erhalten."
00:00:45Im Grunde sagen sie, dass sie nicht den Weg von Next.js gehen wollen, bei dem es
00:00:48standardmäßig Server Components gibt und man dann das "useClient"-Direktive benötigt, wo man
00:00:52Interaktivität haben möchte. Stattdessen möchte TanStack es so sehen: Was wäre, wenn man React Server Components
00:00:57so granular wie JSON auf dem Client abrufen könnte? Mit diesem Ziel vor Augen schauen wir uns an,
00:01:01wie sie Server Components tatsächlich implementiert haben, denn Spoiler-Alarm: Ich mag die Art, wie sie
00:01:06es gemacht haben, sehr. Was ich hier habe, ist eine normale TanStack-Start-Anwendung, also ist im Moment alles
00:01:10ein Client-Component, und ich habe nur einige kleine Installationsschritte durchgeführt, die man braucht, um
00:01:15Server Components zum Laufen zu bringen, was im Grunde nur bedeutet, einige Pakete zu installieren und die
00:01:18vconfig zu ändern. So sieht die Seite aktuell aus: Wir haben hier unsere Greeting-Component, die
00:01:22derzeit eine Client-Component sein sollte, und im Code ist sie buchstäblich nur eine einzige React-Component.
00:01:27Dann haben wir hier unten eine normale TanStack-Route und verwenden dort die Greeting-Component. Sagen wir nun,
00:01:32in unserer Greeting-Component wollten Sie etwas Logik auf dem Server ausführen. In meinem Fall möchte ich den
00:01:36Betriebssystem-Hostname und dann auch einige Umgebungsvariablen abrufen, die nur für den Server verfügbar sind,
00:01:40nur um Ihnen zu zeigen, dass dies tatsächlich dort läuft. Im Moment, wenn ich versuche, os.hostname zu verwenden,
00:01:45funktioniert es nicht, da dies eine Node-Funktion ist und im Browser nicht verfügbar ist.
00:01:49Was wir also tun müssen, ist, unsere Greeting-Component zu nehmen und diese auf dem Server zu rendern, und der
00:01:53erste Schritt dazu in TanStack Start ist eine einfache Server-Funktion. Wie Sie sehen können, habe ich
00:01:58hier eine namens "getGreeting", und alles, was wir darin tun, ist, die neue "renderServerComponent"-Funktion zu nutzen,
00:02:01unsere Komponente darin zu platzieren und die renderbare Server-Component zurückzugeben,
00:02:06die wir zurückbekommen. Sie können sich das so einfach vorstellen, als würden wir einen GET-Request für unsere Komponente erstellen.
00:02:10Als nächstes müssen wir die Komponente einfach von der Server-Funktion abrufen, die wir erstellt haben,
00:02:14und wir können das innerhalb eines Loaders auf einer Route hier tun, also erwarten wir einfach "getGreeting" und dann
00:02:18geben wir das ebenfalls zurück, und es ist immer noch eine renderbare Server-Component. Dann können wir das innerhalb
00:02:23unserer Route hier mit "useLoaderData" verwenden und die Komponente hier unten wie folgt nutzen. Damit
00:02:27haben wir jetzt unsere erste TanStack Server Component. Sie können sehen, dass "os.hostname" jetzt funktioniert, und es zieht auch
00:02:32Umgebungsvariablen, die nur auf dem Server verfügbar sind. Das, was ich
00:02:36absolut an dieser Implementierung liebe, ist, wenn Sie bemerken, dass das einzig Neue hier die "renderServerComponent"-
00:02:41Funktion ist. Der Rest davon war einfach normales TanStack Start. Sie könnten dies
00:02:46mit einfach zurückgegebenen JSON-Daten ersetzen und sie auf genau dieselbe Weise abrufen. Ich denke auch,
00:02:51dass diese Implementierung einfach sehr explizit ist, wo Ihr Code tatsächlich läuft. Sie machen das alles
00:02:55innerhalb einer Server-Funktion, also ist ziemlich klar, dass dies auf dem Server ausgeführt wird, und auch
00:02:59innerhalb der "renderServerComponent". Tatsächlich denke ich, dass ich meinen Demo-Code hier verbessern kann, indem ich einfach
00:03:03die Funktionen, die wir hier oben ausführen, die ich auf dem Server ausführen möchte, innerhalb der
00:03:07Server-Funktion platziere und dann einfach die Werte als Props an meine Greeting-Component übergebe, sodass
00:03:12jetzt meine Greeting-Component im Wesentlichen nur eine einfache Komponente ist, die tatsächlich auf dem
00:03:16Client oder dem Server verwendet werden kann. Ich führe die gesamte Logik, die ich auf dem Server ausführen möchte, innerhalb meiner
00:03:21Server-Funktion hier aus. Wieder ist es ziemlich explizit, dass dies auf dem Server laufen wird. Das
00:03:25fühlt sich buchstäblich wie das genaue Gegenteil der Logik in Next.js an, und ich liebe es absolut.
00:03:30Es bedeutet, dass ich React auf normale Weise denken kann: alles client-first und dann bei Bedarf Server Components
00:03:34hinzufügen. Aber was, wenn ich eine Client-Component innerhalb meiner Server-
00:03:38Component verwenden wollte, also verschachtelt im Baum? Nehmen wir an, ich wollte hier einen Zähler-Button hinzufügen, der jedes Mal, wenn wir
00:03:43darauf klicken, den Zähler erhöht. Nun, wenn ich einfach versuche, dies in meine Server-Component hier mit
00:03:47einem "onClick" und dann auch einem "useState"-Aufruf einzufügen, sehen Sie, dass es komplett kaputt geht. Es heißt, "useState" ist nicht
00:03:52eine Funktion oder ihr Rückgabewert ist nicht iterierbar, und das liegt daran, dass "greeting" als Server-
00:03:56Component verwendet wird. Wir können diese Client-Funktionalität nicht verwenden. Um das zu beheben, haben Sie zwei Optionen, und die zweite
00:04:01Option ist definitiv die beste, aber die erste Option wird denjenigen vertraut vorkommen, die
00:04:05Next.js verwendet haben. Wir können diese Logik einfach in eine eigene Komponente verschieben und dann die
00:04:10"use client"-Direktive verwenden. Das funktioniert in TanStack Start Server Components; wir können einfach die
00:04:14Komponente innerhalb der Server-Component verwenden, und wie Sie sehen, funktioniert alles gut. Der Nachteil
00:04:18dieses Ansatzes ist jedoch, dass wir jetzt eine Server-Component haben, die das Rendern einer
00:04:22Client-Component steuert, und das kann anfangen, ein wenig chaotisch zu werden, d. h., wenn ich herausfinden wollte, wo meine
00:04:28Counter-Component im Baum war, würde ich zu meiner Route hier gehen und sehen, dass wir eine Greeting-Server-
00:04:32Component haben, dann zurück zur Greeting-Server-Component gehen und innerhalb der Server-Component sehen,
00:04:37dass wir eine Client-Component haben, und dieses Chaos kann sich wirklich summieren und die Grenze
00:04:42zwischen Server und Client etwas unklar machen. TanStack würde sich damit jedoch nicht zufriedengeben. Sie
00:04:47fragten sich: Was wäre, wenn der Server überhaupt nicht entscheiden müsste, welcher Teil der UI Client-gesteuert ist?
00:04:51Und das führte sie dazu, etwas völlig Neues zu schaffen: Composite Components. Um eine zu verwenden, mache ich als Erstes,
00:04:56dass ich unsere Client-Component aus unserer Server-Component entferne und sie einfach
00:05:00durch alle "children" unserer Greeting-Component ersetze. Als nächstes müssen wir auch ändern, was wir von
00:05:05unserer Server-Funktion hier zurückgeben. Statt eine renderbare Server-Component zurückzugeben, müssen wir
00:05:09etwas zurückgeben, das "composite source" heißt. Dafür können wir unsere zweite TanStack Server Component-Helper-
00:05:14Funktion verwenden: "createCompositeComponent". Hier bauen wir im Wesentlichen einfach eine Komponente auf,
00:05:18wobei "props" hier als Slots betrachtet werden. Ich verwende nur einen sehr einfachen "children"-Slot, also wird
00:05:22alles, was wir als Kind meiner Composite-Component einfügen, an dieses "props.children" weitergegeben, das
00:05:27ich an die Greeting-Component weitergebe, die wir gerade hatten. Damit müssen wir wieder
00:05:31einfach unsere Composite-Component von unserer Server-Funktion abrufen, und das machen wir auf genau dieselbe Weise im
00:05:36Loader hier. Sie sehen, ich benenne einfach "source" in "greeting" um, dann lade ich es mit "useLoaderData".
00:05:41Der einzige Unterschied hier ist jedoch, dass wir das nicht als Komponente verwenden können. Sie sehen, es wirft hier einen
00:05:45Fehler. Um tatsächlich eine Composite-Component zu rendern, müssen wir den "CompositeComponent"-Helper-
00:05:49Component verwenden, den wir von TanStack Server Components bekommen, und als "source"-Prop übergeben wir
00:05:53die Composite-Component, die wir von unserer Server-Funktion abrufen, die wir zuvor erstellt haben.
00:05:57Damit rendert meine Server-Component jetzt wie zuvor, und ich gebe auch den
00:06:01Counter als Kinder dieser Composite-Component weiter, und das wird an die Greeting-Component weitergegeben, wo wir
00:06:05es hier eingerichtet hatten, also wird es an "props.children" weitergegeben, sodass jetzt alles
00:06:10gut funktioniert. Ich kann auch in meine Counter-Component gehen und die Direktive entfernen, die wir hier hatten, da dies
00:06:14nicht mehr nötig ist, da bekannt ist, dass dies eine Client-Component sein wird, da sie sich innerhalb einer Composite-
00:06:18Component befindet. Dies wird als Slot verwendet. Nun mag es so aussehen, als hätten wir genau dasselbe Ergebnis
00:06:23erzielt, aber mit mehr Arbeit als nur die "use client"-Direktive zu verwenden, aber die Macht kommt wirklich von der
00:06:27Entwicklererfahrung, und es ist ein kleiner Wechsel vom "use client"-Modell. Anstatt dass unser
00:06:32Server entscheidet, wo unsere Client-Components rendern, wie als wir die Counter-Component innerhalb
00:06:36der Server-Component selbst hatten, sagen wir stattdessen mit Composite Components: Hey,
00:06:40hier wird ein Slot sein, wir werden dort eine Client-Component rendern, aber die Server-
00:06:44Component selbst hat keine Ahnung, was das sein wird. Wir fügen das später in unserem Client-Code hinzu,
00:06:48also handhaben wir alle Client-basierten Komponenten innerhalb des Client-Codes selbst. Das ist auch erst der Anfang.
00:06:53Wenn wir uns eine komplexere Seite wie diese Post-Seite ansehen, wo dieser Beitrag serverseitig gerendert wird,
00:06:58gibt es zwei Probleme, die ich lösen möchte. Das erste ist, dass ich einige Aktionen wie
00:07:03das Liken des Beitrags und das Folgen des Autors hinzufügen möchte, aber ich möchte sie über dem Titel hier hinzufügen und ich möchte
00:07:08eine Client-Component verwenden. Im Moment verwende ich einfach dieses "children"-Slot-Muster. Das bedeutet, wenn ich
00:07:12meine Post-Aktionen hier unten hinzufügen würde, würden sie einfach dort landen, wo die Kommentare sind, da wir so
00:07:17die Komponente eingerichtet haben, also möchte ich einen Weg, meiner Server-Component zu sagen, wo spezifische Client-
00:07:22Components platziert werden sollen. Dann haben wir ein zweites Problem: wenn ich einen "Folge dem Autor"-Button habe.
00:07:27Im Moment hat diese Post-Seite eigentlich keine Ahnung, wer der Autor des Beitrags ist. Wir haben all diese Logik
00:07:32in die Server-Component selbst ausgelagert. Wenn ich den Autor in einer Client-Component hier unten abrufen wollte, müsste ich
00:07:37eigentlich das JSON für den Beitrag abrufen und den Autor auf diese Weise erhalten, und das ist nicht wirklich ein tolles
00:07:42Muster, wir würden die Daten doppelt abrufen. Zum Glück hat TanStack zwei andere Slot-Typen,
00:07:46die wir auf einer Composite-Component neben diesem "children"-Slot verwenden können, und der erste
00:07:50sind "render props". Das ist im Wesentlichen jede Prop, die eine Funktion ist, die ein React-Element zurückgibt, also
00:07:56kann dies beliebig genannt werden, es muss nicht "renderActions" heißen, und hier sage ich nur, welche
00:07:59Daten die Server-Component durchreichen soll, und das sind die Post-ID und die Autoren-ID.
00:08:04Jetzt müssen wir in unserer Composite-Component einfach diese Funktion verwenden, die wir als
00:08:08Prop übergeben, wo immer wir die Komponente haben wollen, die schließlich gerendert werden soll.
00:08:12In meinem Fall möchte ich, dass dies unter dem Karten-Header ist, also kann ich "props.renderActions" aufrufen,
00:08:16wir können ein optionales verwenden, also wenn es nicht durchgereicht wird, bricht es nicht, es wird einfach nicht
00:08:20gerendert. Dann können wir auch die Informationen durchreichen, die wir von der Server-Component
00:08:24an unsere Client-Component haben möchten. Danach akzeptiert unsere Composite-Component das "renderActions"-
00:08:28Prop, das wir gerade erstellt haben, und für den Wert übergeben wir einfach eine Funktion, die eine Post-
00:08:32ID und eine Autoren-ID als Argument hat, das der Server ausfüllen wird, dann rendern wir einfach
00:08:36unsere Post-Aktionen-Client-Component und wir können diese Daten als Props durchreichen. Jetzt habe ich also einen Button
00:08:41hier oben, wo ich liken und den Link zum Beitrag kopieren kann, und auch auf "Folge dem Autor" hier klicken kann, wo
00:08:45er den Namen des Autors kennt, obwohl ich ihn auf dieser Seite eigentlich nie abgerufen habe.
00:08:49Ich rufe ihn nur in der Server-Component ab, und die Server-Component reicht diese Daten in die
00:08:53Client-Component für mich durch. Jetzt könnten Sie denken, dass dies die Logik bricht, die wir zuvor hatten, wo wir sagten,
00:08:57wir wollen nicht, dass irgendeine Server-Component für das Rendern von Client-Components verantwortlich ist, aber das tut es nicht, und das liegt daran,
00:09:01dass Slots eigentlich undurchsichtig sind. Die Server-Component hier oben hat keine Ahnung, was sich
00:09:06darin befindet, sie weiß nur, dass hier etwas hingehört und das diese Werte durchreichen muss, die in
00:09:10diesem Fall die Post-ID und die Autoren-ID sind. Diese Funktion läuft nicht auf dem Server, stattdessen sieht der
00:09:15Server einfach, dass er Daten durchreichen muss, und dann unten in unserem Client ist dies der Zeitpunkt,
00:09:19an dem die Funktion tatsächlich ausgeführt wird und die Komponente gerendert wird. Dasselbe gilt auch für
00:09:23unseren dritten Slot-Typ, nämlich "component props". Dieser ist tatsächlich etwas einfacher als
00:09:28die "render props"; alles, was wir tun, ist, anstatt eine Funktion zu haben, die dann unsere Client-Component
00:09:33zurückgibt, übergeben wir einfach die Client-Component als Prop selbst, dann auf unserer Composite-Component
00:09:38Definition hier oben sagen wir, dass wir ein Prop akzeptieren wollen, das eine React-Komponente ist, die
00:09:42die Props "postId" und "authorId" hat, dann können wir dies innerhalb der Komponente selbst verwenden. Sie können sich
00:09:47"component props" wie einen Platzhalter vorstellen; die Server-Component weiß, dass es dort eine Komponente
00:09:51geben wird, die einige Daten benötigt, in unserem Fall die Post-ID und die Autoren-ID, aber es ist ihr eigentlich egal, was das für eine
00:09:56Komponente ist, solange sie diese Props akzeptiert, also habe ich meine "postActions"-Komponente hier unten in
00:10:01eine andere geändert, die ich gemacht habe, namens "fakePostActions", und dann speichern wir das, Sie können sehen, dass das
00:10:05immer noch rendern wird, weil es der Client ist, der für das Rendern dieser Komponente verantwortlich ist;
00:10:10es ist nur der Server, der die Daten bereitstellt. Wenn man sich die Dokumentation ansieht, scheint es
00:10:14keinen wirklichen Unterschied zu geben, welchen Ansatz man wählt, ob man mit "component props" oder "render props" geht;
00:10:18es könnte einfach auf eine Präferenz hinauslaufen. Der einzige Unterschied, den ich sehen kann, ist, dass man vielleicht die
00:10:22Daten, die man vom Server bekommt, ändern möchte, also können wir in diesem Fall mit
00:10:26der "postId" und "authorId" machen, was wir wollen, da es nur eine Funktion ist, und dann können wir das an unsere Komponente
00:10:31weitergeben, während man, wenn man "component props" verwendet, einfach die Komponente selbst durchreicht und der Server
00:10:36handhabt das Durchreichen der Props. Das sind die Grundlagen von TanStack Server Components, aber da gibt es
00:10:40noch so viel mehr zu mögen. Wenn Sie zum Beispiel möchten, dass der Großteil Ihrer Seite serverseitig gerendert wird,
00:10:44vielleicht haben Sie eine Header-Komponente, eine Inhalts-Komponente und eine Footer-Komponente, und Sie möchten, dass sie alle serverseitig
00:10:49gerendert werden, müssen Sie sie nicht alle in eine "renderServerComponent"-Funktion bündeln; Sie können tatsächlich
00:10:53 "Promise.all" verwenden, sie in drei verschiedene Funktionen aufteilen und sie dann einfach als Objekt
00:10:58von einer einzigen Server-Funktion zurückgeben. Aber was, wenn eine dieser Komponenten lange zum Laden braucht? Das würde
00:11:03bedeuten, dass die gesamte Server-Funktion und damit die gesamte Seite warten würde. Nun, machen Sie sich keine Sorgen,
00:11:07was wir tatsächlich tun können, ist, anstatt auf die "renderServerComponent"-Funktion zu warten, können wir tatsächlich
00:11:12das Promise zurückgeben, das sie erstellt, und dann auf dem Client können wir den "use"-Hook und
00:11:16"suspense"-Grenzen nutzen, um Skeletons zu laden, sodass Server Components einfach laden, wenn sie bereit sind.
00:11:21Ich mag den Ansatz, den TanStack hier gewählt hat, wirklich; er fühlt sich nicht aufdringlich an, ich werde nicht gezwungen,
00:11:25ihn zu übernehmen, und ich kann ihn ohne seltsame Umwege übernehmen. Außerdem sind die
00:11:31Server Components selbst, wenn ich sie tatsächlich verwende, eigentlich nur drei neue Funktionen, der Rest ist einfach
00:11:36TanStack Start Server-Funktionen, etwas, das ich bereits verwendet habe, und es ist so einfach wie das Abrufen von
00:11:41Daten. Das bedeutet auch, dass es gut mit Tools wie TanStack Query integriert ist, etwas, das ich
00:11:45definitiv tun werde, und es macht Dinge wie Caching einfacher; wenn Sie wollten, könnten Sie buchstäblich
00:11:49einfach die Antwort des GET-Requests auf Ihrem CDN cachen. Ich werde diese definitiv weiter erforschen,
00:11:54also lassen Sie mich in den Kommentaren unten wissen, was Sie davon halten und ob Sie mehr Videos dazu sehen möchten.
00:11:59Nun, ja, abonnieren Sie und wie immer: Wir sehen uns beim nächsten Mal.