Ghid de autostop pentru dezvoltarea aplicatiilor GraphQL cu sinele pe partea din spate si React / Apollo in partea din fata. A doua parte a acestui tutorial va acoperi mutatii (modul de actualizare a datelor) si subiecte avansate despre memorizarea in cache a clientului.

In prima parte a ghidului am aflat despre ce este GraphQL si am creat o prima versiune a aplicatiei Martian Library. Daca nu ati citit inca articolul, este momentul perfect sa aruncati o privire.

Consultati partea 1 si partea 3

Am configurat deja graficql-ruby gem si framework Apollo pentru a ne asigura ca joaca bine impreuna si a testat batalia aceasta configuratie adaugand chiar primul nod de interogare la schema noastra. Acum a venit timpul sa mergi mai departe si sa adaugi mai multe!

Introducerea mutatiilor

Dupa cum stim, exista trei operatii de baza in GraphQL – interogari, mutatii si abonamente. In acest articol, vom introduce mutatii – un mecanism de modificare a datelor de la GraphQL.

Din perspectiva clientului, mutatiile arata ca intrebari, cu o diferenta subtila – incep cu nodul „mutatie”:

mutatie SignInUser ($ email: String) {signIn (e-mail: $ email) {id}}

Principala diferenta este insa semantica: in primul rand, mutatiile sunt responsabile de schimbarea (sau mutarea ) datelor. De asemenea, exista o diferenta in modul in care le gestioneaza motorul de executie: conform specificatiilor, un server GraphQL trebuie sa garanteze ca mutatiile sunt executate consecutiv, in timp ce interogarile pot fi executate in paralel.

In exemplul de mutatie de mai sus, rugam un server sa autentifice utilizatorul prin adresa de e-mail in acest fel:

  • Incepem cu definirea unei mutatii cu un nume de operatie SignInUser si o variabila numita $ email (toate variabilele din GraphQL incep cu $).
  • Avem o lista de mutatii pe care vrem sa le executam in interiorul bretelelor cretate (aceasta lista se numeste  set de selectie ) – in cazul nostru avem doar un camp numit signIn.
  • La fel ca in cazul interogarilor, putem avea seturi de selectie cuibarite in interiorul celui radacina (adica, selectati campuri specifice din valoarea returnarii mutatiei)

Sintaxa mutatiei

Asta este tot ce trebuie sa stim despre mutatiile din partea teoriei. Restul articolului este dedicat practicii: vom adauga mutatii pentru autentificarea utilizatorilor si le vom permite sa adauge noi elemente in biblioteca martiana.

Menaj

Haideti sa aruncam o privire rapida la ce se afla pe placa noastra dupa ce am terminat partea anterioara a tutorialului. Puteti gasi codul sursa aici – nu uitati sa rulati pachetul de instalare &&arn fire inainte de prima executie. Master reprezinta o stare actuala a proiectului.

Folosim biblioteca grafql-tag pentru a executa interogari si le pastram in apropierea componentei in acelasi fisier:

// app / javascript / components / Library / index.js import Reacteaza de la “reactioneaza”; import {Query} din „react-apollo”; import gql din „graficql-tag”; const LibraryQuery = gql` {elemente {id title user {email}}}}; export default () => (<Query query = {LibraryQuery}> {({data, loading}) => (<div> {loading? “loading …”: data.items.map (({title, id , user}) => (<div key = {id}> <b> {title} </b> {user? `adaugat de $ {user.email}`: null} </div>))} </ div>)} </Query>);

Pastrarea GQL in fisiere separate ne ajuta sa generam tipuri statice pentru proprietatile componentelor atunci cand folosim Flow / TypeScript. Cititi mai multe despre codegen aici.

In mod alternativ, puteti pastra operatiunile in fisiere separate cu extensii .graphql (sau .gql) stocate in acelasi folder cu definitia componentei. Aceasta abordare este utila in special atunci cand lucrati cu aplicatii la scara medie-mare si ofera o structura mai clara a proiectului. Il vom folosi pentru toate noile operatiuni din acest tutorial.

Pentru a face ca Webpack „sa inteleaga” fisierele .gql, trebuie sa configuram un incarcator specific in /config/webpack/environment.js:

// config / webpack / environment.js const {environment} = require (“@ rails / webpacker”); environment.loaders.append (“graphql”, {test: /\.(graphql|gql)$/, exclude: / node_modules /, loader: “graphql-tag / loader”}); module.exports = mediu;

Nu uitati sa reporniti serverul dupa aplicarea acestor modificari.

Acum suntem gata sa implementam logica de autentificare.

Implementarea autentificarii

Specificatia GraphQL nu va spune cum sa implementati logica de autentificare si nici nu necesita sa aveti una – depinde de dezvoltator. Cu toate acestea, cu greu nu va puteti imagina o aplicatie din viata reala si biblioteca noastra martiana nu face exceptie – avem nevoie de o modalitate de a urmari  proprietarii tuturor articolelor adaugate in colectie.

Vom mentine lucrurile simple si autentificam utilizatorii nostri folosind adrese de e-mail, fara parole, SMS, orice alte confirmari.

Iata prezentarea de ansamblu a mecanismului nostru de autentificare:

  • utilizatorul efectueaza cererea de autentificare furnizand adresa de e-mail
  • serverul verifica daca utilizatorul exista si raspunde cu un  jeton de autentificare
  • utilizatorul trimite acest simbol cu ​​fiecare cerere ulterioara (de exemplu, printr-un antet HTTP) pentru a-si demonstra identitatea

Aceasta este o implementare foarte simplificata doar de dragul acestui tutorial. Nu o repetati in productie! Utilizati o biblioteca de autentificare testata in timp si, de exemplu, JWT pentru codificarea jetonului.

Vom folosi o mutatie GraphQL, signIn, pentru a efectua autentificarea, o adresa de e-mail codificata base64 ca jeton de autentificare si un antet „Autorizare” pentru a trece jetonul. Retineti ca nu este necesar sa autentificati utilizatorii folosind API-ul GraphQL: ar putea fi facut „in afara”, de exemplu, prin intermediul vechiului REST. Acest lucru este util mai ales atunci cand permiteti utilizatorilor autentificati sa acceseze API-ul GraphQL.

De asemenea, dorim sa indicam in UI aplicatia daca utilizatorul nostru este autentificat sau nu. Pentru asta, vom adauga un panou care arata numele utilizatorului daca este conectat si butonul „Conectare” in caz contrar:

Componenta UserInfo

Schema de autentificare

Sa adaugam mai intai o API pentru a obtine informatiile curente ale utilizatorului.

Vrem sa-l pastram simplu: adaugati un camp me la radacina de interogare returnand utilizatorul curent din contextul de executie:

# app / graphql / types / query_type.rb module Tipuri de clase QueryType <Tipuri :: BaseObject # … camp: eu, Tipuri :: UserType, null: true def me context [: current_user] end end end end

Cum sa obtineti acest lucru: current_user? Sa adaugam o metoda ApplicationController # current_user care implementeaza logica de autentificare descrisa mai sus:

# app / controllers / application_controller.rb class ApplicationController <ActionController :: Base private def current_user token = request.headers [“Autorizare”]. to_s email = Base64.decode64 (token) User.find_by (email: email) end end

In cele din urma, sa actualizam metoda de executie GraphqlController # pentru a trece curent_user intr-un context:

# app / controllers / graphql_controller.rb clasa GraphqlController <ApplicationController def execute result = MartianLibrarySchema.execute (paramele [: interogare], variabilele: asigura_hash (paramele [: variabile]), # Numai aceasta linie are contextul corelat: {current_user: current_user} , nume_operatie: parame [: operatiuneNume]) reda json: rezultatul terminat # … sfarsit

Grozav! Acum aplicatia noastra client poate obtine informatii despre utilizatorul curent. Din pacate, va intoarce intotdeauna nul – nu am adaugat o modalitate de a spune cine foloseste in prezent aplicatia. Hai sa o rezolvam!

Atentie! graphql-ruby <1.9.16 nu include clasa de baza pentru mutatii, deci ar putea fi necesar sa o creati manual

Haideti sa deschidem o clasa Mutations :: BaseMutation si lipiti urmatorul cod in (generatorul implicit mosteneste de la mai complexa GraphQL :: Schema :: RelayClassicMutation class):

# app / graphql / mutations / base_mutation.rb module Clasa de mutatii BaseMutation <GraphQL :: Schema :: End mutation end

Vom folosi aceasta clasa ca o superclasa pentru SignInMutation:

# app / graphql / mutations / sign_in_mutation.rb module Clasa de mutatii SignInMutation <Mutatii :: Argument BaseMutation: email, String, obligatoriu: true field: token, String, null: true field: user, Types :: UserType, null: true def rezolva (e-mail 🙂 user = User.find_by! (email: email) return {} decat daca user token = Base64.encode64 (user.email) {token: token, user: user} end end end end

Dupa cum vedeti, specificam ca mutatia poate returna un jeton impreuna cu un utilizator curent, iar singurul argument acceptat este e-mailul. In cadrul metodei #resolve, cautam utilizatorul si, daca am gasit-o, raspundem cu un e-mail codat bazat pe 64 ca un jeton, altfel returnam nul.

Din prima privire, clasa de mutatie arata ca un regulator Rails regulat, dar are un avantaj semnificativ: este tipizat puternic si valideaza datele de intrare in conformitate cu schema sa pentru noi.

In cele din urma, trebuie sa expunem prima noastra mutatie in MutationType:

# app / graphql / types / mutation_type.rb module Clase de tipuri MutationType <Tipuri :: BaseObject camp: sign_in, mutatie: Mutations :: SignInMutation end end

Pentru a rezuma, pentru a adauga o noua mutatie, trebuie sa parcurgeti urmatorii pasi:

  1. Adaugati o clasa care implementeaza logica mutatiei, care include:
  2. definitia tipului de intrare (argumente);
  3. definitia tipului de retur;
  4. metoda #resolve.
  5. Adaugati o noua intrare la MutationType.

Retineti ca nu am mentionat deloc specificatiile: nu ezitati sa le adaugati singuri folosind aceeasi tehnica pe care am folosit-o pentru scrierea specificatiilor de interogare. Sau verificati cele pe care le-am scris in exemplul repo!

Adaugarea panoului de informatii pentru utilizatori

Sa uitam de Ruby cateva minute si sa ne indreptam atentia catre aplicatia front-end.

Deoarece baza noastra de coduri este in crestere, trebuie sa ne gandim la o mai buna organizare a codurilor. Va propunem urmatoarea structura pentru componentele UI:

  • fiecare componenta este stocata intr-un folder separat (de exemplu, aplicatia / javascript / components / MyComponent)
  • index.js contine implementarea
  • interogarile sunt definite in operatii.graphql
  • stilurile sunt pastrate in styles.module.css (dupa cum sugereaza numele fisierului ca folosim module css pentru a nu mai face griji cu privire la confruntarile de stil)

Pentru a evita sarcina de a crea manual toate aceste fisiere pentru fiecare componenta, am scris un generator de componente gql la indemana. Sa o folosim pentru a crea o componenta numita UserInfo:

$ npx @ hellsquirrel / create-gql-component create app / javascript / components / UserInfo

Nota: Stilurile sunt omise din articol pentru a-l mentine scurt, dar puteti gasi toate stilurile in repozitia GitHub. Stilurile vor fi adaugate automat daca utilizati generatorul nostru.

Acesta este aspectul structurii dvs. de fisiere:

Structura componentelor

Componenta UserInfo este responsabila pentru functionalitatea „Conectare” si afisarea numelui de utilizator curent la autentificare. Sa adaugam mai intai intrebarile API de care avem nevoie pentru aceste functii la operatii.graphql:

interogati-ma {me {id fullName}} mutatie SignMeIn ($ email: String!) {signIn (e-mail: $ email) {token user {id fullName}}

Definim operatia SignMeIn cu argumentul de e-mail $ necesar pentru un tip String, care

„executa” mutatia signIn si returneaza un jeton de autentificare si informatiile curente ale utilizatorului atunci cand reuseste. Este posibil sa fi observat unele repetari in operatiunile Me si SignMeIn – nu va faceti griji, va vom arata cum sa scapati de el mai tarziu!

Sa deschidem index.js si sa definim componenta noastra folosind operatiunile definite mai sus. Vrem sa incarcam informatiile utilizatorului mai intai si sa afisam formularul „Conectare” numai daca utilizatorul nu este autentificat:

<Query query = {Me}> {({data, loading}) => {if (loading) return “… Se incarca”; if (! data.me) {// Afisare returnare formular formular de autentificare; } const {nume complet} = data.me; return <div className = {cs.info}> ???? {FULLNAME} </ div>; }} </Query>

Pentru a afisa formularul, ar trebui sa utilizam componenta de mutatie si sa trecem operatia SignMeIn ca proprietate de mutatie:

<Mutation mutation = {SignMeIn}> {(signIn, {loading: autentification}) => autentificare? (“…”): (<formular onSubmit = {() => signIn (/ * email aici * /)}> <input type = “email” /> <input type = “submit” value = “Autentificare “/> </form>)} </Mutation>

Nu uitati sa importati componentele carligului, interogare si mutatie useRef, precum si intrebarile pe care le utilizam in aceasta componenta:

import React, {useRef} din „reactioneaza”; import {Query, Mutation} din „react-apollo”; import {Me, SignMeIn} din “./operations.graphql”;

Acest cod arata similar cu componenta de biblioteca pe care am creat-o mai devreme. Prop-ul de redare a componentei de mutatie accepta o functie care executa mutatia ca prim argument (signIn), in timp ce al doilea argument este un obiect cu obiectul rezultat al mutatiei care contine date de retur, starea de incarcare si multe altele.

Pentru a trece un e-mail la mutatie, trebuie sa-l preluam de la intrare (folosind ref), sa-l introducem in variabila si sa executam mutatia:

const UserInfo = () => {const input = useRef (null); // … return (<formular onSubmit = {event => {event.preventDefault (); signIn ({variabile: {email: input.current.value}});}}> <input ref = {input} tip = “email” className = {cs.input} placeholder = “e-mailul tau” /> </form>); };

Cand apelam mutatii in JavaScript, leagam valorile la variabile in felul urmator, folosind acelasi nume ca in operatii, dar fara prefixul $, de exemplu, signIn ({variabile: {email: ‘…’}}).

Variabilele de mutatie

Sa ne asiguram ca stocam tokenul undeva pentru a-l reutiliza pentru solicitarile ulterioare si reincarcarile paginii:

<formular onSubmit = {event => {event.preventDefault (); signIn ({variabile: {email: input.current.value},}), apoi (({date: {signIn: {token}}})) => {if (jeton) {localStorage.setItem (‘mlToken’, jeton) );}}); }}>

Dupa ce efectuam actiunea „Conectare”, ar trebui sa actualizam informatiile utilizatorilor (prin interogarea Me).

Tratarea cu cache

Datele pe care le scriem in cache trebuie sa fie valide in contextul unei scheme grafice date.

Exista doua optiuni pentru a face acest lucru:

  1. relua interogarea me (putem folosi proprietatea refetchQueries pe componenta mutatiei) cand mutatia este finalizata – aceasta poate fi utila, dar o putem face mai bine
  2. Asteptati finalizarea mutatiei si actualizati manual cache-ul. apollo-cache-inmemory ofera functia writeQuery pentru asta. Componenta de mutatie din biblioteca react-apollo are o proprietate speciala numita actualizare. Accepta memoria cache ca prim argument si rezultatul mutatiei ca al doilea. Vrem sa adaugam manual o noua intrare in cache folosind o metoda writeQuery. Este ca si cum ai spune „Hei, Apollo! Iata cateva date, pretindeti ca le-ati primit de la server. ”
<Mutation mutation = {SignMeIn} update = {(cache, {data: {signIn}}) => {cache.writeQuery ({query: Me, data: {me: signIn.user},}); }}>

Acesta este aspectul componentei UserInfo finale:

import React, {useRef} din “reactioneaza”; import {Query, Mutation} din „react-apollo”; import {Me, SignMeIn} din “./operations.graphql”; import cs din „./styles”; const UserInfo = () => {const input = useRef (null); return (<div className = {cs.panel}> <Query query = {Me}> {({data, loading}) => {if (loading) return “… Se incarca”; if (! data.me) {return (<Mutare mutare = {SignMeIn} actualizare = {(cache, {data: {signIn}}) => {cache.writeQuery ({interogare: Me, date: {me: signIn.user}});}} > {(signIn, {loading: autentification}) => autentificare? (“…”): (<div className = {cs.signIn}> <formular onSubmit = {event => {event.preventDefault (); signIn ({variabile: {email: input.current.value}}). atunci (({date: {signIn: {token}}}))> {if (token) {localStorage.setItem (“mlToken”, jeton); }}); }}> <input ref = {input} type = “email” className = {cs.input} placeholder = “e-mailul tau” /> <input type = “submit” className = {cs.button} value = “Autentificare” /> </form> </div>)} </Mutation>); } const {nume complet} = data.me; return <div className = {cs.info}> ???? {FULLNAME} </ div>; }} </Query> </div>); }; export implicit UserInfo; div className = {cs.info}> ???? {FULLNAME} </ div>; }} </Query> </div>); }; export implicit UserInfo; div className = {cs.info}> ???? {FULLNAME} </ div>; }} </Query> </div>); }; export implicit UserInfo;

Felicitari! Tocmai am cumparat un bilet la un tren hype numit „React Hooks” adaugand useRef la componenta noastra.

Este mai bine sa impartiti UserInfo in doua componente separate. Primul pentru logica „Conectare” si al doilea pentru reprezentarea informatiilor pentru utilizatori. Simte-te liber sa o faci singur!

Nu uitati sa adaugati componenta la /javascript/packs/index.js:

// app / javascript / packs / index.js import Reacteaza de la “reactioneaza”; import {render} din “react-dom”; import Furnizor din „../components/Provider”; importati biblioteca din „../components/Library”; import UserInfo din „../components/UserInfo”; redare (<Provider> <UserInfo /> <Library /> </Provider>, document.querySelector (“# root”));

Adaugarea de jetoane la clientul Apollo

Sa rulam aplicatia noastra si sa incercam sa va autentificati folosind o adresa de e-mail valida (adica existenta).

Totul ar trebui sa functioneze bine daca nu reincarcati pagina – veti vedea din nou formularul de autentificare, chiar daca ne-am conectat cu succes mai devreme! Explicatia este destul de simpla: am depozitat jetonul in browser, dar nu l-am „invatat” pe Apollo sa-l foloseasca. Sa rezolvam asta!

Uitati-va la utils / apollo.js:

// app / javascript / utils / apollo.js // … const getToken = () => document.querySelector (‘meta [nume = “csrf-token”]’). getAttribute (“continut”); const token = getToken (); const setTokenForOperation = async operation => operation.setContext ({anteturi: {“X-CSRF-Token”: token}});

Avem deja un jeton CSRF trimis serverului. Sa adaugam una noua – jetonul „Autorizare”:

// app / javascript / utils / apollo.js // … const getTokens = () => {const tokens = {“X-CSRF-Token”: document .querySelector (‘meta [name = “csrf-token”) ] ‘) ​​.getAttribute (“continut”)}; const authToken = localStorage.getItem (“mlToken”); returnati authToken? {… jetoane, Autorizare: authToken}: jetoane; }; const setTokenForOperation = operatie async => {return operation.setContext ({anteturi: {… getTokens ()}}); };

Ce se intampla daca incercam sa va conectam folosind un e-mail eronat? Nimic, pentru ca backend-ul nostru returneaza doar datele goale! Nu va faceti griji, ne vom ocupa de el in urmatoarea parte.

Incercati sa va autentificati din nou si sa reincarcati pagina – ar trebui sa vedeti numele utilizatorului in panoul de informatii! „Calea noastra norocoasa” pare sa functioneze. Fluxul de autentificare ✅

Mutarea bibliotecii

Acum vom adauga mai multe mutatii – nimic nou aici, dar avem nevoie de aceasta pentru ca aplicatia noastra de exemplu sa arate mai bine si pentru a obtine mai multe practici.

Sa adaugam o mutatie pentru adaugarea de noi articole in colectie. Ca de obicei, trebuie sa definim argumentele si tipul de returnare:

# app / graphql / mutations / add_item_mutation.rb module Clasa de mutatii AddItemMutation <Mutatii :: Argument BaseMutation: title, String, necesar: argument adevarat: descriere, String, necesar: fals argument: image_url, String, obligatoriu: false field: item, Tipuri: ItemType, null: adevarat camp: erori, [String], null: false def rezolvare (titlu:, descriere: nil, image_url: nil) daca context [: current_user] .nil? ridicati GraphQL :: ExecutionError, „Trebuie sa va autentificati pentru a efectua aceasta actiune” end item = Item.new (title: title, description: description, image_url: image_url, user: context [: current_user]) if item.save {item: item} else {errors: item.errors.full_messages} end end end end end end

Exista cateva aspecte de care trebuie sa fii atent in acest cod:

  • Verificam prezenta contextului [: current_user] si ridicam o exceptie daca nu este setata.
  • Tipul nostru de retur contine doua campuri: element si erori. De ce nu folositi salvati! si ridica o exceptie? Erorile de validare ale utilizatorilor nu trebuie considerate exceptii; aplicatia noastra front-end ar trebui sa le trateze ca un raspuns valid si sa ofere feedback utilizatorului.

Toate celelalte arata ca o actiune veche #create intr-un regulator tip Rails. Si #update analog este, de asemenea, foarte simplu:

# app / graphql / mutations / update_item_mutation.rb module Clasa de mutatii UpdateItemMutation <Mutatii :: Argument BaseMutation: id, ID, obligatoriu: argument adevarat: title, String, necesar: argument adevarat: descriere, sir, necesar: fals argument: image_url, String, obligatoriu: false field: item, Types :: ItemType, null: true field: errors, [String], null: false def resol (id:, title:, description: nil, image_url: nil) if context [: current_user ].zero? Ridicare GraphQL :: ExecutionError, “Trebuie sa va autentificati pentru a efectua aceasta actiune” end item = Item.find (id) daca item.update (title: title, description: description, image_url: image_url) {item: item} else {erori : item.errors.full_messages} end end end end end end

Scrierea specificatiilor pentru mutatii noi este un exercitiu optional.

Poate ai observat ca avem multe repetari in aceste doua clase – fara griji, a treia parte a acestei serii va acoperi tehnicile de refactorizare pe care le putem folosi pentru a remedia acest lucru.

In cele din urma, inregistrati noile noastre mutatii in MutationType:

# app / graphql / types / mutation_type.rb module Clase de tipuri MutationType <Tipuri :: BaseObject # … camp: add_item, mutatie: Mutatii :: AddItemMutation camp: update_item, mutatie: Mutations :: UpdateItemMutation end end

Actualizarea componentei Bibliotecii

Inainte de a incepe, re-generam componenta noastra de biblioteca pentru a respecta noua noastra arhitectura (extragem operatiuni, adaugam stiluri):

$ npx @ hellsquirrel / create-gql-component create app / javascript / components / Library

Sa introducem urmatoarea interogare in operatii.graphql:

interogare LibraryQuery {elemente {id title imageUrl descriere utilizator {id email}}}

Si „reimprospatati” implementarea componentelor noastre de biblioteca

// app / javascript / components / Library import React, {useState} din “reactioneaza”; import {Query} din „react-apollo”; import {LibraryQuery} din „./operations.graphql”; import cs din „./styles”; const Library = () => {const [element, setItem] = useState (null); return (<Query query = {LibraryQuery}> {({data, loading}) => (<div className = {cs.library}> {loading ||! data.items? “loading …”: data.items .map (({title, id, user, imageUrl, description}) => (<buton key = {id} className = {cs.plate} onClick = {() => setItem ({title, imageUrl, id, descriere })}> <div className = {cs.title}> {title} </div> <div> {description} </div> {imageUrl && <img src = {imageUrl} className = {cs.image} /> } {user? (<div className = {cs.user}> adaugat de {user. email} </div>): null} </button>))} </div>)} </Query>); }; export biblioteca implicita;

Retineti ca infasuram fiecare articol in elementul HTML al butonului: dorim sa fie clic pentru a afisa formularul de actualizare. Acum aplicatia noastra frontala arata mult mai frumoasa. Sa adaugam cateva lucruri stralucitoare!

Adaugarea componentelor formularului

Sa adaugam alte componente pentru crearea si editarea articolelor.

filme porno xnxxx https://www.counterwelt.com/charts/click.php?user=14137&link=https://adult69.ro/
filme porno eleve https://secure.dbprimary.com/service/util/logout/CookiePolicy.action?backto=https://adult69.ro/
filme porno gay bear https://www.iguides.ru/bitrix/redirect.php?event1=topcat&event2=click&event3=+%2F+&goto=https://adult69.ro/
siteuri porno romanesti http://my.faso.com/r/?d=2013-03-18&ad=2&id=12931&u=https://adult69.ro/filme-porno/amatori
porno spion https://en.shindanmaker.com/c/jump?url=https://adult69.ro/filme-porno/anal
porno cu homosexuali https://email.purehost.com/atmail/parse.pl?redirect=https://adult69.ro/filme-porno/asiatice
porno cu batrani http://adserver.merciless.localstars.com/track.php?ad=525825&target=https://adult69.ro/filme-porno/beeg
romani porno http://www.esva.net/news/ccwave.cgi?adult69.ro/filme-porno/blonde
magyar szinkronos porno http://timemapper.okfnlabs.org/view?url=https://adult69.ro/filme-porno/brazzers
filme porno cu scufita rosie http://wannsee.gdw-berlin.de/cgi-bin/koha/tracklinks.pl?uri=https://adult69.ro/filme-porno/brunete
filme porno cu inna http://rss.planex.co.jp/service/redirect.php?id=2136&url=https://adult69.ro/filme-porno/chaturbate
penis porno https://secure.tickmill.co.uk/redirect/index.php?cii=132&cis=1&lp=https://adult69.ro/asa-pula-monstroasa-nimeni-nu-o-poate-suge
porno milf italian http://www.webclap.com/php/jump.php?url=https://adult69.ro/pizda-menajerei-este-inecata-in-sperma
porno greny http://emailing.journalauto.com/con_auto.php?code=6423044400&sec=1101064581&id=jta&date=2017-04-18&lettre=1&redirect=https://adult69.ro/tineri-amatori-se-fut-pe-plaja-de-nudisti
film porno cu minore http://www.reservations-page.com/linktracking/linktracking.ashx?trackingid=TRACKING_ID&mcid=&url=https://adult69.ro/cele-mai-perverse-orgii-se-intampla-in-familie
porno cu pule mari https://www.midomi.com/index.php?action=main.redir&url=https://adult69.ro/orgie-cu-lezbiene-si-o-sigura-pula-mare
film porno cu batrani http://painfoundation.org/__media__/js/netsoltrademark.php?d=adult69.ro/sotia-nerabdatoare-ii-suge-scula-prin-chiloti
free porno pictures http://as.inbox.com/AC.aspx?id_adr=262&link=https://adult69.ro/barbatii-mexicani-stiu-cum-sa-si-futa-fiicele
mature milf porno https://www.drehscheibe-online.de/ds_cms/banner.php?ret=https://adult69.ro/se-vrea-atinsa-de-o-scula-batrana
porno cu negre https://www.bug.hr/ads/go.aspx?id=2763&l=bughrarticlerightmiddle&url=https://adult69.ro/tanara-creativa-obtine-orgasme-fara-o-pula-si-fara-vibrator

Aceste componente sunt atat de similare incat putem pune cea mai mare parte a logicii in componenta reutilizabila ProcessItemForm.

$ npx @ hellsquirrel / create-gql-component create app / javascript / components / ProcessItemForm

Acesta este codul nostru de componente:

// app / javascript / components / ProcessItemForm / index.js import React, {useState} din “reactioneaza”; import cs din „./styles”; const ProcessItemForm = ({initialTitle = “”, initialDescription = “”, initialImageUrl = “”, onProcessItem, buttonText, loading}) => {const [title, setTitle] = useState (initialTitle); const [descriere, setDescription] = useState (initialDescription); const [imageUrl, setImageUrl] = useState (initialImageUrl); return (<div className = {cs.form}> <input type = “text” placeholder = “title” value = {title} className = {cs.input} onChange = {e => setTitle (e.currentTarget.value) } /> <input type = “text” placeholder = “description” value = {description} className = {cs.input} onChange = {e => setDescription (e.currentTarget.value)} /> < input type = “text” placeholder = “url” value = {imageUrl} className = {cs.input} onChange = {e => setImageUrl (e.currentTarget.value)} /> {loading? (“… Se incarca”): (<buton onClick = {() => onProcessItem ({title, descriere, imageUrl})} className = {cs.button}> {buttonText} </button>)} </div> >); }; export implicit ProcessItemForm;

Singurul lucru pe care trebuie sa il adaugam este sa cream formular de articol – haideti sa-l numim AddItemForm

$ npx @ hellsquirrel / create-gql-component create app / javascript / components / AddItemForm

Ar trebui sa adaugam AddItemMutation la operatiile.graphql

# /app/javascript/components/AddItemForm/operations.graphql mutatie AddItemMutation ($ title: String! $ description: String $ imageUrl: String) {addItem (title: $ title, description: $ description, imageUrl: $ imageUrl) {item {id title description imageUrl user {id email}}}}

Si folositi-l in index.js

import Reacteaza din „reactioneaza”; import {Mutation} din „react-apollo”; import {AddItemMutation} din “./operations.graphql”; importati ProcessItemForm din „../ProcessItemForm”; const AddItemForm = () => (<Mutatie mutatie = {AddItemMutation}> {(addItem, {loading}) => (<ProcessItemForm buttonText = “Adaugare articol” loading = {loading} onProcessItem = {({title, description, imageUrl }) => addItem ({variabile: {title, descriere, imageUrl}})} />)} </Mutation>); export implicit AddItemForm;

Nu uitati sa adaugati formularul la /javascript/packs/index.js:

import Reacteaza din „reactioneaza”; import {render} din “react-dom”; import Furnizor din „../components/Provider”; importati biblioteca din „../components/Library”; import UserInfo din „../components/UserInfo”; import AddItemForm din „../components/AddItemForm”; redare (<Provider> <UserInfo /> <AddItemForm /> <Library /> </Provider>, document.querySelector (“# root”));

Acum am intalnit aceeasi problema ca in componenta UserInfo. Trebuie sa spunem aplicatiei noastre ca LibraryQuery ar trebui sa fie actualizat. Prin urmare, trebuie sa reimprospatati memoria cache citind intreaga lista si setand o noua lista cu noul nostru element concatenat in lista.

Sa schimbam javascript / components / AddItemForm / index.js:

// javascript / components / AddItemForm / index.js // … import {LibraryQuery} din ‘../Library/operations.graphql’; // … <ProcessItemForm // … // Actualizarea interogarii bibliotecii dupa mutarea va fi finalizata peProcessItem = {({title, descriere, imageUrl}) => addItem ({variabile: {title, description, imageUrl,}, // adaugarea celui de-al doilea argument la actualizarea metodei „addItem”: (cache, {data: {addItem}}) => {const item = addItem.item; if (element) {const currentItems = cache.readQuery ({query: LibraryQuery }); cache.writeQuery ({interogare: LibraryQuery, date: {items: [item] .concat (currentItems.items),},});}},})} // …

Si s-a terminat! Acum vom vedea noul articol adaugat la lista din pagina.

Sa adaugam inca o componenta pentru actualizarea articolelor numite UpdateItemForm. Codul este foarte asemanator cu AddItemForm. Rulati generatorul:

$ npx @ hellsquirrel / create-gql-component create app / javascript / components / UpdateItemForm

Aceasta este ceea ce avem in fisierul de operatii:

mutatie UpdateItemMutation ($ id: ID! $ title: String! $ descriere: String $ imageUrl: String) {updateItem (id: $ id title: $ title title: $ description imageUrl: $ imageUrl) {item {id title description imageUrl} }}

Aceasta este ceea ce avem in fisierul nostru de componente:

// / app / javascript / components / UpdateItemForm import Reactioneaza de la „reactioneaza”; import {Mutation} din „react-apollo”; import {UpdateItemMutation} din “./operations.graphql”; importati ProcessItemForm din „../ProcessItemForm”; import cs din „./styles”; const UpdateItemForm = ({id, initialTitle, initialDescription, initialImageUrl, onClose}) => (<div className = {cs.overlay}> <div className = {cs.content}> <Mutation mutation = {UpdateItemMutation}> {(updateItem , {loading}) => (<ProcessItemForm initialImageUrl = {initialImageUrl} initialTitle = {initialTitle} initialDescription = {initialDescription} buttonText = “Actualizare articol” loading = {loading} onProcessItem = {({title, descriere, imageUrl}) => {updateItem ({variabile: {id, titlu, descriere, imageUrl}}); onClose (); }} />)} </Mutation> <button className = {cs.close} onClick = {onClose}> Inchidere </button> </div> </div>); export implicit UpdateItemForm;

Si adaugati UpdateItemForm in biblioteca noastra (imediat dupa buton):

// /app/javascript/components/Library/index.js // … import UpdateItemForm din „../UpdateItemForm”; // … <buton />; {item! == null && (<UpdateItemForm id = {item.id} initialTitle = {item.title} initialDescription = {item.description} initialImageUrl = {item.imageUrl} onClose = {() => setItem (null)} />)} // …

Acum, daca facem clic pe placa Element si schimbam un articol, acesta va fi actualizat magic. De ce este asta?

La preluarea unei liste de elemente, raspunsul a fost normalizat si fiecare articol a fost adaugat in cache. apollo genereaza o cheie $ {object__typename}: $ {objectId} pentru fiecare entitate care are __typename si id. Cand mutatia este finalizata, obtinem obiectul cu acelasi __penpename si id, apollo il gaseste in cache si face modificari (componentele sunt redate si ele).

Putem face si mai bine? Da!

De ce ar trebui sa asteptam raspunsul serverului? Daca avem suficient de incredere in serverul nostru, putem folosi o actualizare optimista. Sa adaugam inca un argument la functia updateItem:

// / app / javascript / components / UpdateItemForm // … updateItem ({variabile: {// …}, // adaugarea celui de-al doilea argument la metoda “updateItem” optimisticResponse: {__typename: “Mutatie”, updateItem: {__typename: “UpdateItemMutationPayload”, item: {id, __typename: “Item”, titlu, descriere, imageUrl}}}}); // ..

Asta e totul pentru ziua de azi! Am invatat diferenta dintre mutatii si interogari, am invatat cum sa le implementam in back-end si cum sa le folosim din front-end. Acum aplicatia noastra accepta conectarea utilizatorului si gestionarea bibliotecii, asa ca este aproape gata sa fie dislocata pentru productie! Cu toate acestea, codul pare un pic greoi, avand loc pentru refactorizare – asta vom face in partea a treia, impreuna cu adaugarea altor imbunatatiri, cum ar fi actualizarile in timp real si o mai buna gestionare a erorilor. Ramaneti aproape!

Partea 1 | Partea a 2-a | Partea a 3-a