Պետք չէ օգտագործել վերահսկվող մուտքեր կամ ռեֆերատներ
Երբ կարդում եք React-ում ձևերի մշակման վերաբերյալ պաշտոնական փաստաթղթերը, հանդիպում եք վերահսկվող մուտքերի օգտագործման օրինակների: Սա մարդկանց մեծամասնությանը տալիս է պատկերացում, որ դա այն ձևն է, որով դուք պետք է աշխատեք React-ի ձևերի հետ: Այնուամենայնիվ, եթե էջը շրջում եք նույն փաստաթղթի Չվերահսկվող բաղադրիչներ բաժնում, էջի ենթավերնագրում մեծ տառերով հանդիպում եք այս հայտարարությանը.
Շատ դեպքերում մենք խորհուրդ ենք տալիս օգտագործել վերահսկվող բաղադրիչներ՝ ձևաթղթերի իրականացման համար:
Եվ ես նույնպես: Ես շարունակում եմ տեսնել, թե ինչպես են մարդիկ ընկնում, նորից ու նորից, առաջին բանը, որին նրանք հանդիպում են React-ի փաստաթղթերը կարդալիս: Այնուամենայնիվ, փաստաթղթերի երկրորդ մասը, չնայած մեծ բարելավմանը, նույնպես այնքան էլ հիանալի չէ: Օրինակում ցույց տրված ձևը ամենևին էլ ամենապարզը չէ, և մի փոքր հնաոճ վեբ API-ի լավ իմացության շնորհիվ այն կարելի է դարձնել էլ ավելի պարզ (և հեշտ):
Այս հոդվածում ես պատրաստվում եմ ցույց տալ, թե ինչու ձեզ հարկավոր չեն վերահսկվող մուտքեր կամ ռեֆերատներ՝ ձևերի հետ գործ ունենալու համար, և ես նույնիսկ կներկայացնեմ որոշ հավելյալներ՝ ձևերի մշակման որոշ այլ ասպեկտներ ավելի պարզեցնելու և գուցե նաև մի փոքր ավելի հեշտ դարձնելու համար: նույնպես։
Նշենք, որ այս տեխնիկան կարող է օգտագործվել ցանկացած շրջանակում կամ նույնիսկ առանց դրա: Ես այստեղ ներկայացնում եմ միայն React օրինակ, քանի որ դա այն է, ինչին շատերը ծանոթ կլինեն:
Այսպիսով, եկեք սկսենք:
Ձևաթղթի մատուցում
Եկեք սկսենք մեր ձևը ներկայացնելով: Դա կլինի պարզ ձև երեք դաշտերով: Տիպիկ գրանցման ձև՝ անուն, էլ. փոստ և գաղտնաբառ դաշտերով: Մենք չենք ներառի «կրկնել գաղտնաբառի» դաշտը, այլ փոխարենը կներդրենք գաղտնաբառի ցուցադրման կոճակը, որը լուծում է այն խնդիրը, որն ի սկզբանե լուծվել է կրկնվող գաղտնաբառի դաշտում. օգտվողը սխալ է մուտքագրում գաղտնաբառը: Ձեզ երկուսն էլ պետք չեն, ուշադրություն դարձրեք:
import React, { Component } from 'react' import * as ReactDOM from 'react-dom/client' let App = class extends Component { render() { return ( <form> <label> Name: <input type="text" name="name" required /> </label> <label> Email: <input type="email" name="email" required /> </label> <label> Password: <input type="pasword" name="password" minLength="8" required /> </label> <button> Sign up </button> </form> ) } } ReactDOM.createRoot(document.getElementById('app')).render(<App />)
Մենք այստեղ օգտագործում ենք դասի բաղադրիչ, բայց այն աշխատում է ճիշտ նույն կերպ ֆունկցիայի բաղադրիչների դեպքում: Ես կթողնեմ ձեզ, իմ սիրելի ընթերցող, կատարել անհրաժեշտ փոխարկումները:
Նշումը բավականին պարզ է: Մենք օգտագործում ենք նորմալ <form>
թեգը՝ ձևի վերահսկիչները խմբավորելու համար: Ձևի շրջանակներում մենք ունենք երեք դաշտ. Յուրաքանչյուր դաշտ տեքստի հետ կցվում է <label>
պիտակի մեջ: (Եթե չգիտեիք, <input>
-ը <label>
-ում կցելով պիտակը կապում է մուտքագրման հետ՝ առանց հստակորեն դա անելու՝ օգտագործելով id
և for
ատրիբուտները:) Նկատի ունեցեք, որ յուրաքանչյուր մուտքագրին տրվում է name
հատկանիշ, որպեսզի մենք կարողանանք դրանք ավելի ուշ ճանաչել:
Քանի որ դրանք վերահսկվող բաղադրիչներ չեն, մենք չունենք value
հատկանիշը (prop) ոչ մի դաշտում: DOM-ն ինքը կօգտագործվի որպես ճշմարտության միակ աղբյուր:
Ներկայացման գործիքավորումը
Այժմ եկեք գործիքավորենք ձևի ներկայացումը տվյալների արդյունահանման համար:
let App = class extends Component { handleSubmit = ev => { let $form = ev.target ev.preventDefault() fetch('/api/v1/signup/', { method: 'POST', headers: {'content-type': 'application/json'}, body: JSON.stringify( Object.fromEntries(new FormData($form).entries()) ), }) .then(() => $form.reset()) } render() { return ( <form onSubmit={this.handleSubmit}> {/* no changes here */} </form> ) } }
Մենք ստեղծում ենք ավտոմատ կապի մեթոդ handleSubmit
: Սա համեմատաբար նոր շարահյուսություն է, որը հասնում է նույն բանին, ինչ մենք անում էինք անցյալում.
let App = class extends Component { constructor(props) { super(props) this.handleSubmit = this.handleSubmit.bind(this) } handleSubmit(ev) { // ... } }
Մի շփոթվեք դոլարի նշանով $
փոփոխականի անվան տակ: Այդ նիշը վավեր նիշ է փոփոխականների անուններում: Ես պարզապես սիրում եմ DOM հանգույցի հղումներ պահող փոփոխականները նախածանց դնել դոլարի նշանով, որպեսզի դրանք տարբերեմ այլ փոփոխականներից, դա բոլորն է:
Մեր նշագրման հիման վրա ձևը կկատարի GET
հարցում ընթացիկ URL-ին (URL հասցեի տողում) և մուտքագրված արժեքները կվերափոխի հարցման տողի: Օրինակ՝ /?name=test&email=test&password=test
: Սա կանխելու համար զանգում ենք ev.preventDefault()
ին: Դուք միայն դա պետք է անեք, եթե դուք պատրաստվում եք ձևը ներկայացնել՝ օգտագործելով fetch()
, ինչպես այս օրինակում:
Իրադարձության օբյեկտը (ev
այս օրինակում) պարունակում է target
հատկությունը, որը ցույց է տալիս այն տարրը, որի վրա գործարկվել է իրադարձությունը (ձևը): Այսպիսով, ձևի տվյալները ստանալու համար ձևի հղումներ պահելու կարիք չկա: Մենք կարող ենք հանել բոլոր մուտքերի արժեքները՝ օգտագործելով FormData
կոնստրուկտորը: Այս կոնստրուկտորը կընդունի ձևի տարրը որպես մուտքագրում և կվերադարձնի օբյեկտ, որը կարող է օգտագործվել ձևի ցանկացած դաշտի արժեքներին մուտք գործելու համար: entries()
մեթոդը մեզ կտա մուտքագրվող անունների և դրանց արժեքների կրկնվող ցուցակը նույն ձևաչափով, ինչ Object.entries()
: Այսպիսով, մենք կարող ենք օգտագործել Object.fromEntries()
-ը՝ դրանից օբյեկտ կառուցելու համար:
Մնացած մեթոդը ինքնին բացատրելի է, ուստի ես դրան չեմ խորանա: Ես պարզապես նշեմ, որ մենք կանչում ենք $form.reset()
-ը հաջող ներկայացումից հետո (դե, մենք իսկապես չենք ստուգում, թե արդյոք այն հաջող է, բայց դա դուրս է այս հոդվածի շրջանակներից): Մենք կարող ենք դա անել անվտանգ այստեղ, քանի որ մենք գործ չունենք վերահսկվող մուտքերի հետ: Դրանով մենք վերականգնում ենք ձևի դաշտերը իրենց սկզբնական վիճակին (դատարկ): Լավ գաղափար է զրոյացնել ձևը ներկայացնելուց հետո, եթե ձեր ձևը միշտ DOM-ում է և միայն թաքնված կամ ցուցադրվում է անհրաժեշտության դեպքում: Մենք չենք ցանկանում, որ հնացած տեղեկատվությունը մնա մուտքերում:
Որպես կողմնակի նշում, այստեղ պետք է դիտարկել մի բան այն է, թե որքան ենք մենք հասել միայն HTML-ով (լավ, դա բառացիորենկլինի պարզապես HTML, եթե մենք չօգտագործեինք React, բայց դուք հասկանում եք իմ տեսակետը): Այն ամենը, ինչ JavaScript-ը իրականում պետք է աներ՝ կցել իրադարձությունների մշակիչ և ձևի տվյալները փոխակերպել JSON-ի` օգտագործելով կիսագծեր: Սա Նվազագույն իշխանության կանոնի էությունն է, և լավ պատճառ կա, որ այն ներկայացված է W3C կայքում: (Մենք կձևացնենք, որ React-ը, լավ, տեխնիկապես, նրա օգտվողները, սկզբից չի խախտում այս կանոնը, շշշշ 🤐):
Ինչ վերաբերում է մյուս իրադարձություններին:
Մինչ ավելի հեռուն գնալը, ես կանդրադառնամ սովորական սխալին, որը մարդիկ թույլ են տալիս ձևաթղթերը կիրառելիս:
Կարիք չկա ստեղնաշարի իրադարձությունների ունկնդիրներին կցել մուտքագրմանը կամ սեղմել իրադարձությունների ունկնդիրներին ուղարկել կոճակը: Եվ նաև կարիք չկա օգտագործել որևէ այլ բան, բացի <button>
-ից, որպես ուղարկելու կոճակ, խնդրում ենք մի օգտագործեք <div>
-ը:
Եթե դուք օգտագործում եք <form>
պիտակը ձեր մուտքերի շուրջ և այնտեղ կա <button>
(իսկական, խնդրում եմ), դուք անվճար կստանաք հետևյալը.
- Եթե սեղմեք Մուտքագրեք որևէ մուտքագրում, ձևը կներկայացվի:
- Եթե սեղմեք կամ այլ կերպ ակտիվացնեք ուղարկել կոճակը, ձևը կներկայացվի:
- Հեշտ ռեժիմը միացված է ձևի տվյալների որոնման համար:
Կրկին, դուք ստանում եք այս բաները անվճար: Ոչ մի տող կցված չէ: Ձեզ անհրաժեշտ է միայն ուղարկել ներկայացման մշակիչը <form>
տարրին կցել, և ներկայացման մանրակրկիտ աշխատանքը դիտարկվում է ձեր փոխարեն:
Մենք նաև կարիք չունենք փոփոխության իրադարձության ունկնդիրներին կցել առանձին մուտքերի կամ նույնիսկ ձևի վրա և հետևել մուտքային արժեքների փոփոխություններին: Հիշեք, որ մենք խոսում ենք չվերահսկվողմուտքերի մասին: Կա մեկ բացառություն, և մենք դրան կանդրադառնանք հաջորդ բաժնում:
Ձևի վավերացում
Մեկ այլ հատկանիշ, որը մենք ստացել ենք անվճար, ձևի վավերացումն էր: Օգտագործելով այնպիսի ատրիբուտներ, ինչպիսիք են required
և minLength
, մենք կանխում ենք ձևի նույնիսկ ներկայացումը, եթե մուտքերը չեն բավարարում այդ սահմանափակումները: Մինչև մեր handleSubmit
կոդը կանչելը, մենք կարող ենք վստահ լինել, որ բոլոր դաշտերը ճիշտ են լրացված:
Սա կոչվում է սահմանափակման վավերացում, և դրա հետ մենք կարող ենք շատ ավելին անել: Բացի մի շարք ներկառուցված սահմանափակումներից, մենք կարող ենք – օգտագործելով մի փոքր JavaScript – հարմարեցնել սխալի հաղորդագրությունները և նույնիսկ ավելացնել նոր սահմանափակումներ:
Եկեք մի փոքր համեմենք բաները հատուկ գաղտնաբառի վավերացմամբ: Հենց հիմա մենք վավերացնում ենք գաղտնաբառը երկարության համար (8 նիշ կամ ավելի): Որպեսզի գաղտնաբառը վավեր համարվի, մենք ցանկանում ենք, որ այն նաև պարունակի առնվազն մեկ մեծատառ, մեկ փոքրատառ, թիվ և հատուկ նիշ: (Արդյոք դա իրականում գաղտնաբառն ավելի անվտանգ է դարձնում, բոլորովին այլ թեմա է:)
Մենք կօգտագործենք սահմանափակման վավերացման API-ը, որը թույլ է տալիս միանալ նույն վավերացման համակարգին, որն օգտագործվում է բնօրինակ ձևի մուտքագրման միջոցով:
let VALID_CHARS_CONDITIONS = [ /[a-z]/, // lower-case /[A-Z]/, // upper-case /[0-9]/, // number /[`~!@#$%^&*()\-_=+\[\];:'"\\|,<.>/?]/, // special character ], App = class extends Component { handlePasswordChange = ev => { let $passwordInput = ev.target password = $passwordInput.value, isValidPasswordCharacterMix = VALID_CHARS_CONDITIONS.every(r => r.test(password)) if (!isValidPasswordCharacterMix) $passwordInput.setCustomValidity('Please use at least one lower-case and upper-case letter, a number, and a special character') else $passwordInput.setCustomValidity('') } render() { return ( {/* .... */} <label> Password: <input type="pasword" name="password" minLength="8" required onChange={this.handlePasswordChange}/> </label> {/* .... */} ) } }
Եկեք սա փոքր-ինչ քանդենք:
Նախ, մենք ունենք նոր փոփոխական՝ VALID_CHARS_CONDITIONS
, որը պարունակում է կանոնավոր արտահայտությունների մի շարք, որոնք օգտագործվում են գաղտնաբառը վավերացնելու համար: Գաղափարն այն է, որ այս կանոնավոր արտահայտությունները պետք է կիրառվեն գաղտնաբառի վավերացման համար:
handlePasswordChange
իրադարձության ունկնդիրում մենք նախ ստանում ենք հղումը գաղտնաբառի դաշտին, և մենք փորձարկում ենք VALID_CHARS_CONDITIONS
արտահայտությունները՝ տեսնելու, թե արդյոք դրանք բոլորն անցնում են: Եթե արտահայտություններից գոնե մեկը բացասական է, ապա մենք սահմանում ենք հատուկ սխալի հաղորդագրություն (և ես գիտեմ, թե որքան հիմար է դա հնչում հետհամաճարակային ժամանակներում): Հակառակ դեպքում մենք վերակայում ենք հատուկ վավերականությունը դատարկ տողի՝ ոչ մի սխալ:
MDN-ի վերաբերյալ փաստաթղթերն ասում են տարրի վրա reportValidity()
կանչելու անհրաժեշտության մասին: Այստեղ դա անհրաժեշտ չէ, քանի որ դա մեզ համար ավտոմատ կերպով արվում է, երբ ձևը ներկայացվում է: Դա անհրաժեշտ է միայն այն դեպքում, երբ մենք ցանկանում ենք, որ սխալի հաղորդագրությունն անմիջապես ցուցադրվի:
Ցույց տալ/թաքցնել գաղտնաբառը
Օգտագործողները երբեմն կարող են սխալ մուտքագրել գաղտնաբառը: Դա կարող է մեծ անհանգստություն առաջացնել՝ հասկանալու համար, որ գրանցվելուց անմիջապես հետո ձեր գաղտնաբառը չի աշխատում: Նախկինում մենք ունեինք «կրկնել գաղտնաբառը» դաշտը, որտեղ մենք նորից մուտքագրում էինք նույն գաղտնաբառը՝ համոզվելու համար, որ դրանք համընկնում են: Ժամանակի ընթացքում դիզայներներն ու մշակողները ավելի լավ լուծում են գտել՝ պարզապես ցույց տալ պարզ տեքստային գաղտնաբառը:
Գաղափարը պարզ է՝ փոխել մուտքագրման տեսակը: Բայց կա մի փոքր բռնում. Ձևաթղթի ներկայացման ժամանակ մենք պետք է վերադարձնենք մուտքագրման տեսակը գաղտնաբառին, հակառակ դեպքում մուտքագրման արժեքը կավելացվի բրաուզերի ավտոմատ լրացման ցանկում՝ հնարավոր բացահայտելով այն, որտեղ այն չպետք է լինի:
let App = class extends Component { constructor(props) { super(props) this.state = { passwordShown: false } } toggleShowPassword = () => { this.setState(state => ({ passwordShown: !state.passwordShown })) } handleSubmit = ev => { let $form = ev.target ev.preventDefault() $form.elements.password.type = 'password' this.setState({ passwordShown: false }) // ... } render() { let { passwordShown } = this.state return ( <> {/* .... */} <div> <label> Password: <input type={passwordShown ? 'text' : 'password'} name="password" minLength="8" required onChange={this.handlePasswordChange}/> </label> <button type="button" onClick={this.toggleShowPassword}> {passwordShown ? 'Hide' : 'Show'} password </button> </div> {/* .... */} </form> <p aria-live="polite"> {passwordShown ? 'Your password is shown and audible to people around you.' : ''} </p> </> ) } }
Այս անգամ մենք մի փոքր վիճակ ենք մտցնում խառնուրդի մեջ: Մենք օգտագործում ենք Boolean passwordShown
դրոշակ, որը true
է, երբ գաղտնաբառը պետք է լինի… լավ, ցուցադրված է: Այս դրոշն օգտագործվում է գաղտնաբառի մուտքագրման տեսակը դինամիկ փոխելու համար:
Գաղտնաբառի դաշտի կողքին մենք ներառում ենք այս դրոշը փոխելու կոճակ: Նկատի ունեցեք, որ կոճակը գտնվում է դրսում պիտակի վրա, որպեսզի այն չընթերցվի որպես պիտակի մաս էկրանի ընթերցողներին: Նշեք type="button"
հատկանիշը <button>
թեգի վրա: Այս հատկանիշի լռելյայն արժեքը "submit"
է, որն այն դարձնում է ուղարկել կոճակ, որն օգտագործվում է ձևը ներկայացնելու համար: Եթե ցանկանում ենք ավելացնել կոճակ, որն ապահովում է որոշ լրացուցիչ հնարավորություններ, բայց չի ներկայացնում ձևը, մենք պետք է օգտագործենք type="button"
:
Ներկայացման մշակիչում մենք վերադարձնում ենք գաղտնաբառի դաշտի տեսակը password
: Գաղտնաբառի մուտքագրման եղանակը ձևի elements
հատկության միջոցով է, որը HTMLFormControlCollection
օբյեկտ է: Այն ունի հատկություններ, որոնք համապատասխանում են name
հատկանիշին մուտքերում, այնպես որ մենք կարող ենք մուտք գործել այդ մուտքերը անունով: Մենք նաև թարմացնում ենք passwordShown
դրոշը՝ DOM-ի փոփոխությանը համապատասխանելու համար: Մենք չենք կարող պարզապես թարմացնել վիճակը՝ առանց DOM-ի թարմացման, սակայն, քանի որ վիճակի փոփոխությունները կիրառվում են ասինքրոն: Մեզ անհրաժեշտ է type
հատկանիշը՝ անմիջապես թարմացնելու համար, այնպես որ մենք պետք է դա անենք ձեռքով և թարմացնենք վիճակը՝ պարզապես այն համաժամեցված պահելու համար:
Վերջապես, էջի հենց ներքևում մենք ունենք <p>
թեգ, որը պարունակում է «Ձեր գաղտնաբառը ցուցադրվում և լսելի է ձեր շրջապատի մարդկանց» տեքստը, երբ գաղտնաբառը ցուցադրվում է: Այս տարրն ունի aria-live="polite"
հատկանիշ, որը հուշում է էկրանի ընթերցողին կարդալ տեքստը, երբ տարրի բովանդակությունը փոխվում է: CSS-ում մենք թաքցնում ենք այս տարրը, որպեսզի այն միայն կարդացվի, բայց ոչ մի տեղ չցուցադրվի:
p[aria-live] { position: absolute; width: 0; height: 0; overflow: hidden; pointer-events: none; }
Ամեն անգամ, երբ մենք ունենք կոճակ կամ որևէ այլ հսկողություն, որը փոխում է UI-ի վիճակը, մենք պետք է հիշենք, որ ոչ տեսողական օգտատերերը կարող են չգիտեն, թե ինչ է արել կոճակը, մինչև չվերադառնան ազդակիր տարրին: Նման դեպքերում օգտակար է թաքնված կենդանի տարածաշրջանի միջոցով էֆեկտի մասին հայտարարելը:
Սխալի վիճակի ձևավորում
Սովորական է սխալներով մուտքագրել տարբեր ոճավորումներ: Մենք պարզ կպահենք այս օրինակում և պարզապես ձևի դաշտերը ներկենք կարմիրով:
Մինչև ձևը ձևավորելը, այնուամենայնիվ, մենք պետք է որոշենք, թե երբ կիրառել ոճավորումը: Լռելյայնորեն ձևի բոլոր դաշտերը տեխնիկապես անվավեր են: Եթե մենք պարզապես օգտագործենք :invalid
կեղծ ընտրիչը, նրանք կսկսվեն անվավեր վիճակում: Դրա պատճառով մենք կմշակենք invalid
իրադարձությունը ձևի վրա, որն առաջին անգամն է, որ դաշտերը վավերացվում են սկզբնավորումից հետո, և կնշենք դաշտերը որպես պատրաստ՝ ցույց տալու անվավեր վիճակը:
let App = class extends Component { handleInvalid = ev => { ev.target.dataset.invalid = true } handleSubmit = ev => { let $form = ev.target ev.preventDefault() for (let $ of $form.elements) delete $.dataset.invalid // ... } render() { let { passwordShown } = this.state return ( <> <form onSubmit={this.handleSubmit} onInvalid={this.handleInvalid}> {/* ... */} </form> </> ) } }
Մենք ավելացնում ենք handleInvalid
իրադարձությունների ցանկը: Այս ունկնդիրը կկանչվի յուրաքանչյուր մուտքագրման համար, որը չի վավերացվում, չնայած այն կցված է ձևին: Հետևաբար, ev.target
-ը մատնանշում է մուտքը, ոչ թե ձևը: Մենք սահմանել ենք dataset.invalid
հատկությունը մուտքի վրա, երբ կարգավորիչը կանչվում է:
Ներկայացման մշակիչում մենք հեռացնում ենք dataset.invalid
-ը բոլոր մուտքերից՝ կրկնելով $form.elements
օբյեկտը: Ինչպես նախկինում նշեցի, սա HTMLFormControlsCollection
օբյեկտ է և կրկնվող է:
Հաջորդը, եկեք նայենք CSS-ին.
input[data-invalid]:invalid { background: #ffd6d6; border-color: #ff0000; }
Ընտրիչը կիրառվում է ցանկացած մուտքագրման համար, որն ունի data-invalid
հատկանիշ (մեր կոդում սահմանված է dataset.invalid
հատկության միջոցով) և նաև անվավեր վիճակ է (:invalid
կեղծ ընտրիչ): Քանի որ data-invalid
հատկանիշն ուղարկվում է միայն առաջին ներկայացումից հետո և ակտիվ է մնում մինչև բոլոր դաշտերը չանցնեն վավերացումը և ձևը չներկայացվի, սխալների վիճակը չի ցուցադրվում առաջին ներկայացումից առաջ: Հետագայում, եթե օգտատերը շտկում է սխալները, ոճավորումը դադարում է կիրառել, քանի որ :invalid
կեղծ ընտրիչն այլևս չի կիրառվում, թեև data-invalid
հատկանիշը դեռ կա:
Եզրակացություն
React-ը մեզ իսկապես որևէ առավելություն չի տալիս, երբ խոսքը վերաբերում է ձևերի հետ գործ ունենալուն: Նույնիսկ պաշտոնական փաստաթղթերը խորհուրդ են տալիս պարզապես օգտագործել ձևերը այնպես, ինչպես կան: (Հեռացվել է երկար զրպարտությունն այն մասին, թե ինչպես դա չի սահմանափակվում ձևերով:) Ինչպես տեսնում եք օրինակի կոդում, սակայն, այդ ամենի հետ աշխատելը բավականին հեշտ է:
Այնուամենայնիվ, մի մոռացեք ստուգել կապակցված փաստաթղթերը այս հոդվածում օգտագործված API-ների մասին լրացուցիչ տեղեկությունների համար: Շատ ավելին կարող եք անել տարբեր ինտերֆեյսներով, որոնք զննարկիչը բացահայտում է ձևերի և սահմանափակումների վավերացման շուրջ:
Դուք կգտնեք այս օրինակի լիարժեք գործող օրինակ GitHub-ում: Ազատորեն շրջվեք, և եթե գտնում եք այն բարելավելու միջոց, տեղեկացրեք ինձ մեկնաբանություններով կամ PR-ով: 😉