Nuclo दस्तावेज़
Nuclo इंस्टॉल करने, स्पष्ट अपडेट के साथ UI बनाने, टाइपयुक्त CSS से स्टाइल करने और सर्वर पर HTML रेंडर करने के लिए व्यावहारिक संदर्भ।
npm create nuclo@latestअवलोकन
#Nuclo TypeScript के लिए एक छोटी DOM लाइब्रेरी है। आप पृष्ठ को div() और button() जैसे साधारण फ़ंक्शन के साथ बनाते हैं, स्थिति को सामान्य चर में रखते हैं, और जब पृष्ठ बदलना चाहिए तो update() पर कॉल करते हैं।
Nuclo में कोई वर्चुअल DOM, कोई कंपाइलर, कोई प्रॉक्सी और कोई सिग्नल नहीं है।
import 'nuclo' let count = 0 const App = () => div( h1(() => `Count: ${count}`), button('Add one', { onClick: () => { count++; update() } }), ) render(App, document.getElementById('app')!)
मानसिक मॉडल
- टैग बिल्डरों के साथ UI बनाएं।
- स्टेट पर निर्भर प्रत्येक मान के लिए एक फ़ंक्शन पास करें।
- साधारण JavaScript के साथ स्थिति बदलें।
update()पर कॉल करें। Nuclo फिर से फ़ंक्शन चलाता है और DOM को पैच करता है।
किसी वेरिएबल को बदलने से DOM पर असर नहीं पड़ता है। पेज तभी बदलता है जब आप update() पर कॉल करते हैं।
त्वरित शुरुआत
#create-nuclo एक Vite और TypeScript प्रोजेक्ट तैयार करता है जो Nuclo के लिए तैयार है:
यह प्रोजेक्ट का नाम पूछता है। प्रॉम्प्ट को छोड़ने के लिए, नाम और फ़्लैग पास करें। npm के साथ, फ़्लैग से पहले -- लगाएं:
$ npm create nuclo@latest my-app -- --template basic --yes
फिर डेव सर्वर स्थापित करें और प्रारंभ करें:
$ cd my-app $ npm install $ npm run dev
विकल्प
[project-name]: लक्ष्य फ़ोल्डर. डिफ़ॉल्टnuclo-appहै.-t, --template <name>: कॉपी करने के लिए टेम्पलेट। आज केवलbasicमौजूद है।-y, --yes: कभी संकेत न दें, डिफ़ॉल्ट का उपयोग करें।-f, --force: ऐसे फ़ोल्डर में लिखें जो खाली न हो।-h, --help: सहायता प्रिंट करें।
इंस्टॉलेशन और इम्पोर्ट
#किसी मौजूदा प्रोजेक्ट में Nuclo जोड़ने के लिए:
$ npm install nuclo
या pnpm add nuclo, yarn add nuclo या bun add nuclo का उपयोग करें। Nuclo की कोई निर्भरता नहीं है।
रूटिंग कोर पैकेज का हिस्सा नहीं है। इसे अलग से nuclo-router के रूप में भेजा जाता है, जिसे आप जरूरत पड़ने पर nuclo के बगल में स्थापित करते हैं।
ग्लोबल्स
import 'nuclo' इन्हें globalThis पर रखता है:
- 175 टैग बिल्डर्स: HTML के लिए 112 (
div,span,button, ...) और SVG के लिए 63,Svgके साथ प्रत्यय (svgSvg,pathSvg,…)। टैग बिल्डर्स देखें। - 16 सहायक:
update,scope,when,list,region,into,on,render,hydrate,forceUpdate,css,cx,createCss,variants,keyframesऔरglobalStyle।
आयात किसी भी कोड से पहले चलना चाहिए जो बिल्डर को कॉल करता है। ईएस मॉड्यूल आयात क्रम में चलते हैं, इसलिए यह विफल रहता है:
import { App } from './app' // app.ts calls div() → ReferenceError: div is not defined import 'nuclo'
अपनी प्रविष्टि फ़ाइल में import 'nuclo' को पहले रखें, या इसे ग्लोबल्स का उपयोग करने वाली प्रत्येक फ़ाइल में आयात करें।
नामित आयात
सहायकों को निर्यात नाम भी दिया गया है। वे ग्लोबल्स के समान ही कार्य हैं। getCssText() और resetStyles() को केवल निर्यात नाम दिया गया है। टैग बिल्डर्स केवल ग्लोबल हैं, इसलिए import { div } from 'nuclo' काम नहीं करता है।
import { render, update, getCssText } from 'nuclo'
प्रवेश बिंदु
nuclo: ग्लोबल्स को पंजीकृत करता है और 16 हेल्पर्स प्लसgetCssTextऔरresetStylesको निर्यात करता है।nuclo/ssr:renderToString,renderManyToString,renderToStringWithContainerऔरgetCssText। सर्वर रेंडरिंग देखें।nuclo/polyfill: नोड और बन के लिए एक छोटा DOM। nuclo/polyfill देखें।nuclo/types: केवल प्रकार। TypeScript देखें।
TypeScript
#Nuclo अपने स्वयं के प्रकार शिप करता है। आपको दो चीज़ें चाहिए:
"moduleResolution": "bundler".node16औरnodenextअभी समर्थित नहीं हैं।DOMlib. जब आपlibसेट नहीं करते हैं तो TypeScript इसे डिफ़ॉल्ट रूप से शामिल करता है।
यदि प्रोजेक्ट में कोई फ़ाइल 'nuclo' आयात करती है, तो प्रत्येक फ़ाइल वैश्विक प्रकार देखती है। तुम्हें और कुछ नहीं चाहिए. यदि कोई फ़ाइल इसे आयात नहीं करती है, उदाहरण के लिए परीक्षणों के लिए एक अलग tsconfig में, प्रकार स्वयं जोड़ें:
{ "compilerOptions": { "target": "ES2020", "module": "ESNext", "moduleResolution": "bundler", "strict": true, "types": ["nuclo/types"] } }
या .d.ts फ़ाइल में /// <reference types="nuclo/types" /> जोड़ें।
उपयोगी प्रकार
ये प्रकार वैश्विक हैं, इसलिए आप इन्हें आयात के बिना उपयोग करते हैं:
NodeModFn<Tag>: टैग बिल्डर क्या रिटर्न देता है। इसे एक घटक के रिटर्न प्रकार के रूप में उपयोग करें।NodeModLike<Tag>: टैग बिल्डर किसी भी चीज़ को तर्क के रूप में स्वीकार करता है।ExpandedElement<Tag>:render()औरhydrate()क्या लौटाते हैं।ElementTagName: कोई भी HTML टैग नाम।ExpandedElementAttributes<Tag>: वह विशेषता ऑब्जेक्ट जिसे टैग बिल्डर स्वीकार करता है, जिसमेंon*हैंडलर,onMountऔरonDestroyशामिल हैं। इसका उपयोग उन घटकों के लिए करें जो विशेषताओं को पार करते हैं।MountCallbackऔरDestroyCallback:onMountऔरonDestroyहैंडलर।WhenBuilder,ListModifier:when()औरlist()क्या रिटर्न देते हैं।RegionOptions,RegionType: विकल्पregion()लेता है।StyleResult,Style,ThemeConfig,CssInstanceऔर अन्य स्टाइल प्रकार। आप इन्हें भी आयात कर सकते हैं:import type { StyleResult } from 'nuclo'.
function Badge(text: string): NodeModFn<'span'> { return span(text) }
ExpandedElement<Tag> प्रत्येक DOM सदस्य को वैकल्पिक के रूप में चिह्नित करता है। जब आपको पूर्ण तत्व प्रकार की आवश्यकता हो तो इसे कास्ट करें: render(App, root) as HTMLDivElement।
टैग बिल्डर
#प्रत्येक HTML टैग एक ही नाम के साथ एक वैश्विक फ़ंक्शन है: div, span, input, ... एक अपवाद <var> है, जो var_ है क्योंकि var एक आरक्षित शब्द है। प्रत्येक SVG टैग में एक Svg प्रत्यय होता है: svgSvg, pathSvg, circleSvg, ...
टैग बिल्डर को कॉल करने से अभी तक कोई तत्व नहीं बना है। div('Hi') एक builder लौटाता है: एक फ़ंक्शन जो बाद में तत्व बनाता है। जब आप इसे render() के साथ माउंट करते हैं या किसी अन्य बिल्डर, when() या list() के अंदर रखते हैं तो Nuclo इसे कॉल करता है। प्रत्येक प्रयोग एक नया तत्व बनाता है।
let name = 'Ada' const Card = () => div( { id: 'card', className: 'card' }, h2('Profile'), p(() => `Hello, ${name}`), button('Rename', { onClick: () => { name = 'Grace'; update() } }), ) render(Card, document.body)
update() कॉल करता है।खोलें ↗Profile
Hello, Ada
तर्क
एक बिल्डर किसी भी संख्या में तर्क लेता है और उन्हें बाएं से दाएं लागू करता है:
- string, संख्या, bigint: एक टेक्स्ट नोड।
() => value: टेक्स्ट जोupdate()पर बदलता है। डायनामिक मान देखें।null,undefined: छोड़ दिया गया।true,false: पाठ को "सही" या "false" के रूप में प्रस्तुत किया गया। शर्तों के लिएwhen()का उपयोग करें।- A साधारण ऑब्जेक्ट: विशेषताएँ। Attributes देखें।
- A
css(),cx()याvariants()परिणाम: अपनी कक्षाएं जोड़ता है। on(...): एक इवेंट श्रोता या जीवनचक्र हुक।- एक अन्य बिल्डर,
when()याlist(): एक बच्चा। - A DOM
Node: जैसा है वैसा ही जोड़ा गया। (el, index) => …: एक कस्टम संशोधक। नीचे देखें।
गोचास
div(isAdmin && span('Admin'))"false" टेक्स्ट प्रस्तुत करता है।div(when(() => isAdmin, span('Admin')))लिखें.- ऐरे चाइल्ड नोड नहीं हैं। उन्हें फैलाएं:
ul(...names.map((n) => li(n))). बदलने वाली सूचियों के लिए,list()का उपयोग करें। - टैग नाम वाला एक स्थानीय वैरिएबल बिल्डर को छुपाता है।
function Row(label: string)के अंदर,label(...)कॉल करने योग्य नहीं है।label,title,data,p,aऔरiजैसे स्थानीय नाम बदलें। - ब्राउज़र हर उस तत्व को उजागर करते हैं जिसमें
idएक वैश्विक चर के रूप में होता है। यदि Nuclo लोड होने से पहले पृष्ठ में<main id="main">शामिल है, तो Nuclo उस वैश्विक स्थान को छोड़ देता है, इसलिएmain(...)एकTypeErrorफेंकता है। अन्य आईडी का प्रयोग करें.
कस्टम संशोधक
एक फ़ंक्शन जो कम से कम एक पैरामीटर घोषित करता है, तर्क क्रम में एक बार चलता है, जबकि तत्व बनाया जा रहा है। इसका पहला तर्क तत्व है। दूसरा एक आंतरिक सूचकांक है, इसलिए इस पर भरोसा न करें। यदि यह एक स्ट्रिंग या संख्या लौटाता है, तो वह टेक्स्ट बन जाता है। एक Node जोड़ा गया है। किसी ऑब्जेक्ट को विशेषताओं के रूप में लागू किया जाता है।
const field = input((el) => { el.autocomplete = 'off' })
तत्व प्राप्त करना
render() मूल तत्व लौटाता है। किसी ट्री के अंदर, on("mount", (el) => …) या एक कस्टम संशोधक का उपयोग करें। अलग तत्व बनाने के लिए आप बिल्डर फ़ंक्शन को स्वयं भी कॉल कर सकते हैं:
const el = div('Hello')() as HTMLDivElement document.body.append(el)
इसे अगले update() से पहले संलग्न करें। एक नोड जो दस्तावेज़ में नहीं है जब update() चलता है तो हमेशा के लिए अपडेट होना बंद हो जाता है।
SVG
SVG बिल्डर्स भी यही तर्क देते हैं। Nuclo प्रत्येक SVG विशेषता को setAttribute के साथ सेट करता है, इसलिए "stroke-width" जैसे नाम लिखित रूप में काम करते हैं।
const Icon = () => svgSvg( { viewBox: '0 0 24 24', width: 24, height: 24, className: 'icon' }, circleSvg({ cx: 12, cy: 12, r: 10, fill: 'currentColor' }), )
सभी बिल्डर्स
HTML: a abbr address area article aside audio b base bdi bdo blockquote body br button canvas caption cite code col colgroup data datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 head header hgroup hr html i iframe img input ins kbd label legend li link main map mark menu meta meter nav noscript object ol optgroup option output p picture pre progress q rp rt ruby s samp script search section select slot small source span strong style sub summary sup table tbody td template textarea tfoot th thead time title tr track u ul var_ video wbr
SVG: aSvg animateSvg animateMotionSvg animateTransformSvg circleSvg clipPathSvg defsSvg descSvg ellipseSvg feBlendSvg feColorMatrixSvg feComponentTransferSvg feCompositeSvg feConvolveMatrixSvg feDiffuseLightingSvg feDisplacementMapSvg feDistantLightSvg feDropShadowSvg feFloodSvg feFuncASvg feFuncBSvg feFuncGSvg feFuncRSvg feGaussianBlurSvg feImageSvg feMergeSvg feMergeNodeSvg feMorphologySvg feOffsetSvg fePointLightSvg feSpecularLightingSvg feSpotLightSvg feTileSvg feTurbulenceSvg filterSvg foreignObjectSvg gSvg imageSvg lineSvg linearGradientSvg markerSvg maskSvg metadataSvg mpathSvg pathSvg patternSvg polygonSvg polylineSvg radialGradientSvg rectSvg scriptSvg setSvg stopSvg styleSvg svgSvg switchSvg symbolSvg textSvg textPathSvg titleSvg tspanSvg useSvg viewSvg
कंपोनेंट
#एक घटक एक साधारण फ़ंक्शन है जो एक बिल्डर लौटाता है, जैसे button(...)। कोई घटक API नहीं है। जब तक आप इसे प्रस्तुत नहीं करते या इसे किसी ट्री पर नहीं रखते तब तक कुछ भी नहीं बनता है।
function Counter(label: string) { let count = 0 // one per call return button(() => `${label}: ${count}`, { onClick: () => { count++; update() }, }) } render(() => div(Counter('A'), Counter('B')))
स्थिति उस उदाहरण से संबंधित है। बाहर घोषित स्टेट हर उदाहरण द्वारा साझा किया जाता है। forceUpdate() घटकों को फिर से कॉल करता है, जो उनके अंदर घोषित स्थिति को रीसेट करता है।
चाइल्ड नोड सिर्फ तर्क हैं:
function Card(heading: string, ...children: NodeModLike<'div'>[]) { return div({ className: 'card' }, h2(heading), ...children) } render(() => Card('Hi', p('one'), p('two')))
विशेषताएँ सेट करने के लिए एक साधारण ऑब्जेक्ट पास करें। आप कई ऑब्जेक्ट पास कर सकते हैं. समान कुंजी के लिए, className और style को छोड़कर, बाद वाला मान जीतता है, जो विलीन हो जाते हैं। एक मान स्थिर हो सकता है, या बिना किसी पैरामीटर वाला एक फ़ंक्शन हो सकता है जो प्रत्येक update() पर फिर से चलता है।
let busy = false const save = button( { type: 'submit', disabled: () => busy, 'aria-busy': () => String(busy), 'data-id': 42 }, 'Save', )
update() कॉल करता है; Save के disabled और aria-busy उसी के अनुसार बदलते हैं।खोलें ↗कोई मान कैसे सेट किया जाता है
- यदि HTML तत्व में उस नाम (
value,checked,disabled,htmlFor,tabIndex,…) के साथ एक संपत्ति है, तो Nuclo संपत्ति सेट करता है। अन्यथा यहsetAttribute(key, String(value))को कॉल करता है, उदाहरण के लिए"aria-*"और"data-*"के लिए। - SVG तत्वों पर, Nuclo हमेशा
setAttributeको कॉल करता है। nullऔरundefinedको छोड़ दिया गया है। एक गतिशील मान जोnullयाundefinedलौटाता है, पुराना मान रखता है। मान साफ़ करने के लिए, बूलियन प्रॉपर्टी के लिए"", याfalseलौटाएँ।- बूलियन्स DOM बूलियन गुणों के लिए काम करते हैं, जैसे एक बटन पर
disabled,checked,hiddenऔरrequired। कहीं और,trueऔरfalseको स्ट्रिंग "true" और "false" के रूप में लिखा गया है। "data-*"कुंजियों का उपयोग करें।datasetसमर्थित नहीं है.innerHTMLबच नहीं पाया है, और सर्वर रेंडरिंग इसे आउटपुट नहीं करता है। उससे बचिए।
कक्षाएं
className का उपयोग करें। स्टेटिक क्लास स्ट्रिंग्स, css() परिणाम और एक गतिशील className सभी विलीन हो जाते हैं। एक गतिशील className स्थिर कक्षाएं रखता है, और जब यह "" लौटाता है तो केवल स्थिर कक्षाएं ही रहती हैं।
let active = false const base = css({ p: 8 }) const item = li(base, { className: 'item' }, { className: () => (active ? 'active' : '') }, 'Home') // class="<base> item", then "<base> item active" after active = true; update()
HTML तत्वों पर class कुंजी का उपयोग न करें। यह कच्ची विशेषता सेट करता है और css() क्लास सहित, इससे पहले लागू प्रत्येक वर्ग को प्रतिस्थापित करता है।
इनलाइन शैलियाँ
style camelCase कुंजियों के साथ एक ऑब्जेक्ट लेता है, या एक फ़ंक्शन जो एक लौटाता है।
let progress = 30 const bar = div({ style: () => ({ width: `${progress}%`, backgroundColor: 'teal' }) })
- Nuclo कोई इकाई नहीं जोड़ता।
"12px"लिखें,12नहीं। इकाई रहित गुण जैसेopacityऔरzIndexसंख्याएँ स्वीकार करते हैं। "",nullऔरundefinedएक संपत्ति साफ़ करें।- एक गतिशील शैली केवल वही कुंजी सेट करती है जो वह लौटाती है। जिस कुंजी को वह लौटाना बंद कर देता है, उसका पुराना मान बना रहता है, इसलिए उसे साफ़ करने के लिए
""लौटाएँ। - स्टाइल स्ट्रिंग्स (
"color: red") और कस्टम गुण ("--gap") समर्थित नहीं हैं। इसके बजायcss()का उपयोग करें। यहrawके माध्यम से कस्टम गुण लेता है।
इवेंट
#घटनाओं को सुनने के दो तरीके हैं: on* विशेषताएँ और on() संशोधक।
on* विशेषताएँ
एक विशेषता ऑब्जेक्ट में, onClick (on जिसके बाद एक बड़ा अक्षर होता है) जैसी कुंजी के तहत एक फ़ंक्शन एक ईवेंट हैंडलर है, गतिशील मान नहीं।
let query = '' const search = input({ placeholder: 'Search', value: () => query, onInput: (e) => { query = e.currentTarget.value; update() }, onKeyDown: (e) => { if (e.key === 'Escape') { query = ''; update() } }, })
- TypeScript कुंजी और तत्व से ईवेंट प्रकार का अनुमान लगाता है।
e.currentTargetऔरthisतत्व हैं। - Nuclo मूल संपत्ति सेट करता है, इसलिए
onClickel.onclickबन जाता है। प्रति इवेंट एक हैंडलर होता है: बाद वालाonClickपहले वाले की जगह लेता है। onDoubleClickऔरonDblClickदोनों का मतलबdblclickहै।onclickजैसी लोअरकेस कुंजियाँ प्रकार की त्रुटियाँ हैं।- एक नाम जो ज्ञात घटना नहीं है, उसके लोअरकेस रूप को सुनता है:
onMyEvent"myevent"को सुनता है। - हैंडलर द्वारा फेंकी गई त्रुटियाँ पकड़ में नहीं आतीं।
Type something, then press Escape.
on()
on() एक संशोधक लौटाता है जो तत्व पर addEventListener को कॉल करता है।
const panel = div( on('scroll', onScroll, { passive: true }), on('click', logClick), on('click', closeMenu), )
- प्रत्येक
on()एक श्रोता जोड़ता है। वे सभी क्रम से चलते हैं। optionsसीधेaddEventListenerपर जाता है:capture,once,passive,signal।- सीधे बिल्डर के पास
on()पास करें और तत्व प्रकार का अनुमान लगाया जाता है:input(on('input', (e) => e.currentTarget.value))। - यदि कोई श्रोता फेंकता है, तो Nuclo
console.errorके साथ त्रुटि लॉग करता है, और अन्य श्रोता अभी भी चलते हैं। - Nuclo तत्व को हटाते समय श्रोता को हटा देता है। सर्वर पर,
on()कुछ नहीं करता है। - आप कई तत्वों पर एक
on()मान का पुन: उपयोग कर सकते हैं।
एक कस्टम इवेंट के लिए, इवेंट प्रकार को सामान्य के रूप में पास करें:
type PickEvent = CustomEvent<{ id: number }> const picker = ul(on<'pick', PickEvent>('pick', (e) => choose(e.detail.id)))
कौन सा उपयोग करना है
सामान्य स्थिति के लिए on* विशेषताओं का उपयोग करें। जब आपको किसी इवेंट के लिए एक से अधिक श्रोता, श्रोता विकल्प, या "my-event" जैसे कस्टम इवेंट नाम की आवश्यकता हो तो on() का उपयोग करें।
onMount एक बार चलता है। onDestroy एक बार चलता है, जब Nuclo तत्व हटा देता है। उन्हें विशेषताओं के रूप में या on() के साथ लिखें। दोनों फॉर्म समान व्यवहार करते हैं, और तत्व उसके टैग से टाइप किया जाता है।
let seconds = 0 const Timer = () => span( { onMount: () => { const id = setInterval(() => { seconds++; update() }, 1000) return () => clearInterval(id) // runs when the span is removed }, }, () => `${seconds}s`, ) const search = input(on('mount', (el) => el.focus()))
onMount इंटरवल शुरू करता है और उसका क्लीनअप फ़ंक्शन लौटाता है। अनमाउंट होने पर क्लीनअप चलता है, इसलिए टाइमर दोबारा माउंट होने तक गिनती रुक जाती है।खोलें ↗जब वे चलते हैं
onMountउसrender(),hydrate(),update()याforceUpdate()कॉल के अंत में चलता है जिसने तत्व डाला था। ट्री के निर्माण के दौरान यह कभी नहीं चलता।- यदि
onMountएक फ़ंक्शन लौटाता है, तो तत्व केonDestroyकॉलबैक के बाद, वह फ़ंक्शन नष्ट हो जाता है। onDestroyतब चलता है जब Nuclo तत्व को हटा देता है: एकlist()पंक्ति हटा दी जाती है, एकwhen()शाखा स्विच हो जाती है, एकinto()अपना क्षेत्र छोड़ देता है, याforceUpdate()इसे छोड़ देता है। यह हटाने से ठीक पहले चलता है, इसलिए तत्व अभी भी दस्तावेज़ में है।
रॉ DOM हटाना
यदि आप node.remove() या innerHTML = '' के साथ किसी तत्व को स्वयं हटाते हैं, तो इसका onDestroy और क्लीनअप कभी नहीं चलता है, और टाइमर चलते रहते हैं। Nuclo में कोई unmount() नहीं है। किसी ऐप को क्लीनअप से हटाने के लिए, उसे when() में लपेटें:
let mounted = true render(() => div(when(() => mounted, App())), root) mounted = false update() // App's onDestroy hooks and cleanups run
आदेश और त्रुटियाँ
- आप किसी भी रूप में जितने चाहें उतने हुक पंजीकृत कर सकते हैं। वे पंजीकरण क्रम में चलते हैं।
onDestroyहमेशा पहले चाइल्ड नोड को चलाता है, फिर माता-पिता को।- तत्व अपने पहले
onMountहुक के क्रम में माउंट होते हैं, जो तर्क क्रम का पालन करता है, और प्रत्येक तत्व अपने सभी हुक को एक साथ चलाता है। यदि माता-पिता उन पर निर्भर हैं तो अपने चाइल्ड नोड से पहले माता-पिता की जिम्मेदारी छोड़ें। - एक हुक जो फेंकता है उसे
console.errorके साथ लॉग किया जाता है। अन्य हुक अभी भी चल रहे हैं. - कॉलबैक को कुछ भी नहीं या क्लीनअप फ़ंक्शन लौटाना चाहिए, इसलिए इसे
asyncन बनाएं। लौटाए गए वादे को कभी भी सफाई के रूप में उपयोग नहीं किया जाता है। इसके अंदर async कार्य प्रारंभ करें:{ onMount: (el) => { void load(el) } }। - हुक कभी भी सर्वर पर नहीं चलते। क्लाइंट पर, वे
hydrate()के बाद चलते हैं।
Nuclo कभी भी पेज को अपने आप अपडेट नहीं करता है। अपना स्टेट बदलें, फिर update() पर कॉल करें।
let count = 0 const Counter = () => button(() => `Clicked ${count} times`, { onClick: () => { count++; update() }, }) render(Counter)
update() चलने पर टेक्स्ट अपडेट होता है।खोलें ↗Clicked 0 times
जितना चाहें उतना स्टेट बदलें, फिर एक बार update() पर कॉल करें:
let first = 'Ada' let last = 'Lovelace' render(() => p(() => `${first} ${last}`)) first = 'Grace' last = 'Hopper' update() // one pass, both changes
इवेंट हैंडलर, टाइमर, फ़ेच कॉलबैक या onMount से update() पर कॉल करें। इसे डायनामिक मान के अंदर से न बुलाएं।
नो पैरामीटर वाला एक फ़ंक्शन एक डायनामिक मान है। Nuclo तत्व बनाते समय इसे एक बार चलाता है, और फिर प्रत्येक update() पर। टेक्स्ट चिल्ड्रेन, विशेषता मान, style और className के लिए गतिशील मानों का उपयोग करें। दिखाई देने वाले, गायब होने वाले या दोहराए जाने वाले चाइल्ड नोड के लिए, when() और list() का उपयोग करें।
let name = 'Alice' let busy = false const Profile = () => div( h1('Welcome'), // static p(() => `Hello, ${name}`), // dynamic text button({ disabled: () => busy }, 'Save'), )
update() पर दोबारा चलता है। forceUpdate() ट्री दोबारा बनाता है, इसलिए स्टैटिक टेक्स्ट भी अपडेट होता है।खोलें ↗static: Hello, Alice
dynamic: Hello, Alice
Nuclo पैरामीटर गिनती (fn.length) की जाँच करता है। एक फ़ंक्शन जो पैरामीटर घोषित करता है वह एक कस्टम संशोधक है और केवल एक बार चलता है। (x = 0) => … और (...args) => … की लंबाई 0 है, इसलिए वे गतिशील हैं।
गतिशील पाठ
- एक स्ट्रिंग, संख्या, bigint या बूलियन
String(value)के रूप में प्रस्तुत होता है।() => ok && 'Saved'"false" दिखाता है, इसलिए() => (ok ? 'Saved' : '')लिखें। nullऔरundefinedखाली टेक्स्ट के रूप में प्रस्तुत होते हैं।- A
css()याcx()परिणाम पहले रन पर फ़ंक्शन को एक गतिशील वर्ग बनाता है, टेक्स्ट नहीं।cx()देखें। - एक नोड, बिल्डर, ऐरे या साधारण ऑब्जेक्ट कुछ भी प्रस्तुत नहीं करता है, और कभी अपडेट नहीं होता है।
गतिशील विशेषताएँ
- Nuclo कोई मान तभी लिखता है जब वह Nuclo द्वारा लिखे गए अंतिम मान से भिन्न हो। इसकी तुलना लाइव DOM से नहीं की जाती है, इसलिए यदि कोई उपयोगकर्ता इनपुट टाइप करता है और आपका फ़ंक्शन पहले जैसा ही मान लौटाता है, तो उनके द्वारा टाइप किया गया टेक्स्ट बना रहता है।
nullयाundefinedका परिणाम कुछ भी नहीं लिखता है, इसलिए पुराना मान बना रहता है।classNameअपवाद है: कोई भी गलत परिणाम गतिशील वर्गों को हटा देता है और स्थिर वर्गों को रखता है।- एक
styleफ़ंक्शन प्रत्येक अपडेट पर लौटाए गए ऑब्जेक्ट को लागू करता है।
त्रुटियाँ
यदि कोई गतिशील मान फेंकता है, तो Nuclo त्रुटि पकड़ता है और शेष अद्यतन अभी भी चलता है। टेक्स्ट खाली हो जाता है या अपना पुराना मान रखता है, और एक विशेषता अपना पुराना मान रखता है। दोनों अगले update() पर ठीक हो जाते हैं जो सफल होता है। अपवाद: एक टेक्स्ट फ़ंक्शन जो अपने first रन पर फेंकता है वह हमेशा के लिए खाली रहता है।
update()
#पृष्ठ पर प्रत्येक गतिशील मान पर एक सिंक्रोनस पास चलाता है। आईडी के साथ, यह केवल उन scope() आईडी के साथ चिह्नित तत्वों के अंदर क्या अपडेट करता है। प्रत्येक पास इन चरणों को क्रम में चलाता है:
list(): आइटम को दोबारा पढ़ता है और पंक्तियों को जोड़ता, स्थानांतरित या हटाता है।when(): शर्तों की दोबारा जाँच करता है। एक शाखा का पुनर्निर्माण तभी होता है जब कोई दूसरी शाखा सक्रिय हो जाती है।- गतिशील विशेषताएँ, जिनमें
styleऔरclassNameशामिल हैं। - गतिशील पाठ.
onMount।
- यह सब कुछ चलाता है। प्रत्येक डायनामिक मान प्रत्येक कॉल पर चलता है, भले ही उसका डेटा नहीं बदला हो। केवल DOM लिखना छोड़ दिया गया है। गतिशील मूल्यों को तेज़ और दुष्प्रभावों से मुक्त रखें।
- दस्तावेज़ में केवल नोड्स. यदि
update()चलने पर दस्तावेज़ में कोई नोड नहीं है, तो Nuclo इसे ट्रैक करना बंद कर देता है। आपके द्वारा इसे संलग्न करने के बाद भी यह कभी भी दोबारा अपडेट नहीं होता है। यहlist()औरwhen()पर भी लागू होता है। दस्तावेज़ में मौजूद कंटेनर में प्रस्तुत करें। - स्थैतिक मान कभी नहीं बदलते। तत्व निर्मित होने पर साधारण स्ट्रिंग और विशेषता मान तय हो जाते हैं। उन्हें फिर से बनाने के लिए,
forceUpdate()का उपयोग करें। - Errors. टेक्स्ट और विशेषता त्रुटियाँ पकड़ी जाती हैं, और पास जारी रहता है। यदि कोई
when()स्थिति खराब हो जाती है, तो त्रुटि लॉग हो जाती है औरwhen()अपडेट होना बंद हो जाता है। यदि कोईlist()itemsयाrenderItemफ़ंक्शन फेंकता है, तो त्रुटिupdate()से बच जाती है और शेष चरण नहीं चलते हैं। - Server.
update()सर्वर पर कुछ नहीं करता है।
update() सभी डायनेमिक मान चलाता है, भले उनका डेटा न बदला हो। इस पेज के दूसरे डेमो भी काउंटर बढ़ाते हैं।खोलें ↗This dynamic value has run 1 times.
scope()
#तत्व देता है इसे एक या अधिक स्कोप आईडी में पास किया जाता है। update('id') तब केवल वही अपडेट करता है जो उस तत्व के अंदर है, जिसमें तत्व की अपनी विशेषताएं भी शामिल हैं।
let cartItems: string[] = [] let user = 'Ada' const App = () => div( header(() => `Hi, ${user}`), aside(scope('cart'), span(() => `Items: ${cartItems.length}`)), ) render(App) cartItems.push('apple') user = 'Grace' update('cart') // "Items: 1", the header still says "Hi, Ada" update() // everything, the header now says "Hi, Grace"
- एक तत्व में कई आईडी हो सकती हैं:
scope('cart', 'sidebar')। update('a', 'b')aयाbके अंदर सब कुछ अपडेट करता है। यदि कई तत्व एक ही आईडी का उपयोग करते हैं, तो वे सभी अपडेट हो जाते हैं।- नेस्टेड स्कोप के साथ,
update('outer')में आंतरिक स्कोप शामिल है।update('inner')बाहरी हिस्से को नहीं छूता है। - एक आईडी जिसमें दस्तावेज़ के किसी भी तत्व को nothing अपडेट किया गया है और कोई त्रुटि नहीं है, इसलिए वर्तनी की जांच करें।
scope()सर्वर पर कुछ नहीं करता है।
एक शर्त के आधार पर सामग्री प्रस्तुत करता है। Nuclo प्रत्येक update() पर स्थितियों की दोबारा जाँच करता है। पहली सच्ची स्थिति जीतती है। यदि कोई भी सत्य नहीं है, तो .else() सामग्री प्रस्तुत होती है। .else() के बिना, कुछ भी प्रस्तुत नहीं होता।
let status: 'loading' | 'error' | 'ready' = 'loading' const Status = () => div( when(() => status === 'loading', p('Loading…')) .when(() => status === 'error', p('Something went wrong')) .else(p('Ready')), )
else() रेंडर होता है। नई शाखा शुरू से बनती है।खोलें ↗Loading…
शर्तें
- स्टेट के लिए, एक फ़ंक्शन पास करें। एक साधारण बूलियन एक बार पढ़ा जाता है:
when(open, …)कभी नहीं बदलता। - TypeScript में फ़ंक्शन को एक बूलियन लौटाना होगा।
() => items.length > 0या() => user != nullलिखें.
क्या अपडेट
- जबकि एक ही शाखा सक्रिय रहती है, इसके तत्व बने रहते हैं। इसके अंदर गतिशील मान, सूचियाँ और
when()ब्लॉक अपडेट होते रहते हैं। - जब एक अलग शाखा सक्रिय हो जाती है, तो पुरानी सामग्री हटा दी जाती है और उसके
onDestroyहुक चलते हैं। नई शाखा एकदम नए सिरे से बनाई गई है और इसकेonMountहुक चलते हैं। पुराने तत्वों की स्थिति, जैसे इनपुट टेक्स्ट या फ़ोकस, खो जाती है।
शाखा सामग्री
चाइल्ड नोड को एक शाखा में रखें: टेक्स्ट, डायनेमिक टेक्स्ट, बिल्डर्स, list() और नेस्टेड when()। p('Hi') जैसे बिल्डरों को पास करें, DOM नोड्स को नहीं: एक DOM नोड अपनी शाखा के एक बार छिपने के बाद अपडेट होना बंद कर देता है।
विशेषता ऑब्जेक्ट और on() मूल तत्व पर लागू होते हैं, और वे शाखा के छिपने के बाद भी बने रहते हैं। एक सशर्त विशेषता के लिए, एक गतिशील विशेषता का उपयोग करें: { className: () => (active ? 'active' : '') }।
चेनिंग
.when() और .else() एक नया बिल्डर लौटाते हैं और मूल को नहीं बदलते हैं। यदि आप .else() को दो बार कॉल करते हैं, तो अंतिम कॉल जीत जाती है।
list()
#प्रति आइटम एक पंक्ति प्रस्तुत करता है। प्रत्येक update() पर, Nuclo फिर से items() को कॉल करता है और पहचान के आधार पर नए आइटम को मौजूदा पंक्तियों से मिलाता है (===)।
let fruits = ['Apple', 'Banana'] const Fruits = () => ul(list(() => fruits, (fruit) => li(fruit))) render(Fruits) fruits.push('Cherry') update()
पंक्तियों का मिलान कैसे किया जाता है
- एक आइटम जो अभी भी वहां है, अपनी पंक्ति बनाए रखता है। यदि आवश्यक हो तो Nuclo पंक्ति को स्थानांतरित करता है, और
renderItemको दोबारा कॉल नहीं करता है। - एक नए आइटम को एक नई पंक्ति मिलती है। हटाया गया आइटम अपनी पंक्ति खो देता है, और उसके
onDestroyहुक चलते हैं। - डुप्लिकेट आइटमों में से प्रत्येक को अपनी पंक्ति मिलती है।
- आप सरणी को उसके स्थान पर बदल सकते हैं (
push,splice,sort) या एक नई सरणी लौटा सकते हैं।
एक रखी गई पंक्ति को दोबारा कभी प्रस्तुत नहीं किया जाता है, इसलिए बदलने वाले फ़ील्ड के लिए गतिशील मानों का उपयोग करें:
const todos = [{ text: 'Write docs', done: false }] const Todos = () => ul( list(() => todos, (todo) => li({ className: () => (todo.done ? 'done' : '') }, () => todo.text), ), ) render(Todos) todos[0].done = true update() // same <li>, its class is now "done"
प्रतियां नए आइटम हैं, इसलिए सरणी को todos.map((t) => ({ ...t })) से बदलने से प्रत्येक पंक्ति का पुनर्निर्माण होता है।
renderItem क्या लौटाता है
- एक तत्व लौटाएं, जैसे
li(...),tr(...)या SVG बिल्डर। किसी आइटम को छोड़ने के लिएnullयाundefinedलौटें। स्ट्रिंग्स, टेक्स्ट नोड्स और टुकड़े कुछ भी प्रस्तुत नहीं करते हैं। - सीधे
when()याlist()वापस न करें। उन्हें पंक्ति तत्व के अंदर रखें:li(todo.text, when(() => todo.done, ' ✓'))। - आइटम छिपाने के लिए,
items()में फ़िल्टर करें,renderItemमें नहीं। renderItemको दुष्प्रभावों से मुक्त रखें। Nuclo एक ही आइटम के लिए इसे एक से अधिक बार कॉल कर सकता है।
सूचकांक
index आइटम की वह स्थिति है जब उसकी पंक्ति बनाई गई थी। पंक्तियों के हिलने पर यह अद्यतन नहीं होता है। वर्तमान स्थिति के लिए, एक गतिशील मान का उपयोग करें: li(() => `${names.indexOf(name) + 1}. ${name}`)।
पुनरावर्तनीय
कोई भी पुनरावर्तनीय कार्य: सरणियाँ, Set, जनरेटर और map.values()। हर बार एक ही सरणी या Set लौटाना ठीक है। एक पुनरावर्तक, जैसे जनरेटर या map.values(), का उपयोग एक पास के बाद किया जाता है, इसलिए इसे प्रत्येक कॉल पर items() के अंदर बनाएं। Map को पुनरावृत्त करने से हर बार सीधे नए [key, value] सरणियाँ बनती हैं, इसलिए प्रत्येक पंक्ति का पुनर्निर्माण किया जाता है। इसके बजाय map.values() का उपयोग करें।
प्लेसमेंट
पंक्तियाँ वहाँ दिखाई देती हैं जहाँ list() दो टिप्पणी मार्करों के बीच अपने भाई-बहनों के बीच बैठता है। माता-पिता के अन्य चाइल्ड नोड को छुआ नहीं जाता है। list() SVG बिल्डर्स के अंदर भी काम करता है।
region() ट्री में एक नामित स्थान को चिह्नित करता है। into() इसे कहीं और से, किसी भी गहराई से भर देता है। लेआउट सामग्री को प्रोप के रूप में प्राप्त नहीं करता है, और सामग्री बनाने वाले घटक को यह नहीं पता होता है कि वह कहां पहुंचती है।
const Layout = () => div( aside(nav(a({ href: '/' }, 'Home'))), main(region({ id: 'main', empty: p('Nothing open.') })), ) // Anywhere in the tree, before or after the region const Article = () => into('main', article(h1('Hello'))) render(() => div(Layout(), Article()))
यह कैसे काम करता है
into()केवल एक टिप्पणी छोड़ता है जहां यह लिखा जाता है। इसकी सामग्री क्षेत्र के मार्करों के बीच प्रस्तुत होती है।- ऑर्डर कोई मायने नहीं रखता। अपने क्षेत्र से पहले निर्मित एक
into()प्रतीक्षा करता है और उस क्षण को दिखाता है जब क्षेत्र का निर्माण होता है। दृश्य उसी क्रम में दिखते हैं जिस क्रम में वे लिखे गए थे। - सामग्री तब तक जीवित रहती है जब तक दृश्य रहता है। जब
into()रखने वालाwhen()बंद हो जाता है, या इसकीlist()पंक्ति हटा दी जाती है, तो सामग्री क्षेत्र छोड़ देती है। इसे दोबारा खोलने पर एक प्रति जुड़ती है, दो नहीं। - जब कोई क्षेत्र चला जाता है, उदाहरण के लिए उसका
when()बंद हो जाता है, तो उसके दृश्य फिर से प्रतीक्षा करते हैं। बाद में उसी आईडी से बनाया गया क्षेत्र उन्हें दिखाता है। - एक
into()जिसकी आईडी किसी भी क्षेत्र से मेल नहीं खाती है, कुछ भी प्रस्तुत नहीं करती है और कोई त्रुटि उत्पन्न नहीं करती है, इसलिए वर्तनी की जांच करें। - आईडी ट्री में अद्वितीय हैं। लाइव आईडी के अंतर्गत निर्मित दूसरा क्षेत्र एक चेतावनी लॉग करता है।
- क्षेत्र सर्वर रेंडरिंग के साथ काम करते हैं।
hydrate()पर, प्रत्येक दृश्य सर्वर द्वारा इसे दोबारा बनाने के बजाय प्रस्तुत किए गए HTML का दावा करता है।
विकल्प
id: क्षेत्र को खोजने के लिएinto()नाम का उपयोग किया जाता है।type:'latest'(डिफ़ॉल्ट) केवल नवीनतम दृश्य दिखाता है।'stack'प्रत्येक दृश्य को आगमन क्रम में दिखाता है।empty: उस समय प्रस्तुत किया गया जब इस क्षेत्र में कोई दृश्य नहीं है।
स्टैक्ड क्षेत्र
type: 'stack' के साथ, एक दृश्य जो दूसरे के ऊपर खुलता है वह नीचे वाले का पुनर्निर्माण नहीं करता है। इसका DOM, फोकस और फॉर्म स्टेट स्टे है। किसी पृष्ठ पर संवाद जैसी परत के लिए इसका उपयोग करें।
let open = false const App = () => div( region({ id: 'main', type: 'stack' }), into('main', form(input({ placeholder: 'Keeps its text' }))), when(() => open, into('main', div('On top'))), )
एक साथ कई क्षेत्र
एक into() से एक से अधिक क्षेत्रों को भरने के लिए एक ऑब्जेक्ट पास करें। एक मान एक बच्चा या चाइल्ड नोड की एक श्रृंखला हो सकता है। कोई भी रूप घोंसला बनाता है: किसी अन्य दृश्य की सामग्री के अंदर एक into() अपने स्वयं के क्षेत्र को लक्षित करता है।
const Page = () => into({ main: article(h1('Eager route')), sidebar: [h2('Related'), ul(li('One'), li('Two'))], })
ट्री की गहराई से एक संवाद खुला
ओवरले क्षेत्र मूल पर बैठता है. कोई भी घटक, किसी भी गहराई पर, into() को when() में लपेटकर एक संवाद खोलता है। when() को बंद करने से क्षेत्र से संवाद हट जाता है।
let confirming = false const App = () => div( Page(), div({ className: 'overlay' }, region({ id: 'overlay' })), ) // Deep inside Page() const DeleteButton = () => div( button('Delete', { onClick: () => { confirming = true; update() } }), when(() => confirming, into('overlay', dialog({ open: true }, p('Delete this item?'), button('Cancel', { onClick: () => { confirming = false; update() } }), ), ), ), )
टोस्ट
list() की प्रत्येक पंक्ति एक दृश्य है। एक स्टैक्ड क्षेत्र उन्हें क्रम में दिखाता है, और सरणी से किसी आइटम को हटाने से उसका टोस्ट हट जाता है।
const toasts: { text: string }[] = [] const notify = (text: string) => { toasts.push({ text }) update() } const dismiss = (toast: { text: string }) => { toasts.splice(toasts.indexOf(toast), 1) update() } const App = () => div( button('Save', { onClick: () => notify('Saved.') }), div({ className: 'toasts' }, region({ id: 'toasts', type: 'stack' })), list(() => toasts, (toast) => into('toasts', div(toast.text, button('×', { onClick: () => dismiss(toast) }))), ), )
पृष्ठ द्वारा निर्धारित एक पृष्ठ शीर्षक
डिफ़ॉल्ट type: 'latest' के साथ, नवीनतम दृश्य जीतता है। लेआउट शीर्षक का स्वामी होता है, और जो भी पृष्ठ खुला होता है उसे नाम देता है। बिना into('title', …) वाला पेज empty टेक्स्ट छोड़ता है।
let page: 'home' | 'settings' = 'home' const Layout = () => div( h1(region({ id: 'title', empty: 'My app' })), when(() => page === 'home', Home()).else(Settings()), ) const Home = () => p('Welcome.') const Settings = () => div( into('title', 'Settings'), form(/* … */), )
गतिशील मान
into() की सामग्री सामान्य Nuclo सामग्री है। इसमें फ़ंक्शन स्टेट-निर्भर मान हैं और क्षेत्र जहां भी हो, update() पर ताज़ा होते हैं।
const selected = new Set<string>() const Layout = () => div( header(region({ id: 'status' })), Table(), ) const Table = () => div( into('status', () => `${selected.size} selected`), list(() => rows, (row) => label( input({ type: 'checkbox', onChange: (e) => { e.currentTarget.checked ? selected.add(row.id) : selected.delete(row.id) update() } }), row.name, ), ), )
एक क्षेत्र जो आता और जाता है
डैशबोर्ड का into() एक बार लिखा है। जबकि उपयोगकर्ता साइन आउट है, कोई main क्षेत्र नहीं है, इसलिए दृश्य प्रतीक्षा करता है। साइन इन करने से क्षेत्र बनता है और दृश्य दिखता है। साइन आउट करने से क्षेत्र हट जाता है और दृश्य फिर से प्रतीक्षा करता है।
let user: { name: string } | null = null const App = () => div( when(() => user !== null, main(region({ id: 'main' })), ).else( LoginForm(), ), into('main', Dashboard()), )
अपने क्षेत्र के साथ एक दृश्य
एक दृश्य में region() हो सकता है, और अन्य दृश्य इसे लक्षित कर सकते हैं। यहां संपादक पृष्ठ अपने साथ एक tabs क्षेत्र लाता है, और प्रत्येक टैब इसे भरता है। जब संपादक बंद हो जाता है, तो उसका क्षेत्र उसके साथ चला जाता है।
let tab: 'code' | 'preview' = 'code' const Editor = () => into('main', div( nav( button('Code', { onClick: () => { tab = 'code'; update() } }), button('Preview', { onClick: () => { tab = 'preview'; update() } }), ), section(region({ id: 'tabs' })), ), when(() => tab === 'code', into('tabs', textarea())) .else(into('tabs', iframe({ src: '/preview' }))), )
लाइव उदाहरण
import 'nuclo' let page: 'home' | 'article' | null = 'home' let dialog = false const go = (next: typeof page) => () => { page = next dialog = false update() } const Layout = () => div( nav( button('Home', { onClick: go('home') }), button('Article', { onClick: go('article') }), button('Close page', { onClick: go(null) }), ), main( region({ id: 'main', type: 'stack', empty: p('Nothing open.') }), ), aside(region({ id: 'side', empty: p('No related links.') })), ) // Pages fill the layout's regions from outside it. const Home = () => into('main', input({ placeholder: 'Type here, then open the dialog' }), button('Open dialog', { onClick: () => { dialog = true; update() }, }), ) const Article = () => into({ main: article(h2('Article'), p('One into() fills both regions.')), side: [h2('Related'), ul(li('One'), li('Two'))], }) // Stacks over Home, so Home keeps its DOM and the text you typed. const Dialog = () => into('main', div( p('On top of Home.'), button('Close', { onClick: () => { dialog = false; update() } }), ), ) export const RegionApp = () => div( Layout(), when(() => page === 'home', Home()), when(() => page === 'article', Article()), when(() => dialog, Dialog()), )
into() से दोनों रीजन भरता है।खोलें ↗No related links.
update() केवल गतिशील मान चलाता है। स्थैतिक मान, जैसे कि h1(labels[lang].title) में पाठ, ट्री के निर्माण के समय तय किए जाते हैं। forceUpdate() आपके घटकों को फिर से कॉल करता है और लाइव DOM को मिलान करने के लिए पैच करता है, स्थिर मान शामिल हैं। भाषा बदलने जैसे दुर्लभ, ऐप-व्यापी परिवर्तनों के लिए इसका उपयोग करें। बाकी सभी चीज़ों के लिए update() का उपयोग करें।
const labels = { en: { title: 'Hello' }, pt: { title: 'Olá' } } let lang: keyof typeof labels = 'en' const App = () => div(h1(labels[lang].title)) render(App, root) lang = 'pt' forceUpdate() // same <h1> element, now "Olá"
update() केवल डायनेमिक lang पंक्ति अपडेट करता है। forceUpdate() App को दोबारा कॉल करके उसी <h1> को पैच करता है। स्पष्ट रूप केवल इसी ट्री को दोबारा बनाता है, पूरे पेज को नहीं।खोलें ↗lang = 'en'
Hello
Static text, fixed on build.
बिना किसी तर्क के, forceUpdate() render(App) या hydrate(App) के साथ रेंडर किए गए प्रत्येक रूट का पुनर्निर्माण करता है। render(App()) के साथ रेंडर किए गए रूट छोड़ दिए गए हैं। यदि कोई घटक फेंकता है, तो त्रुटि लॉग हो जाती है और वह रूट अपना पुराना DOM रखता है। बाद में जब नई बिल्ड लागू की जाती है तो एक त्रुटि भी लॉग की जाती है, लेकिन पहले से पैच किए गए हिस्से पैच किए हुए रहते हैं।
क्या रहता है और क्या बदलता है
- चाइल्ड नोड का मिलान क्रम से, टैग द्वारा किया जाता है। एक मिलान तत्व का पुन: उपयोग किया जाता है, इसलिए फोकस और टाइप किए गए इनपुट मान बने रहते हैं, और इसके
onMountहुक दोबारा नहीं चलते हैं। एक बार जब एक चाइल्ड नोड का टैग अलग हो जाता है, तो उस चाइल्ड नोड और उसके बाद के प्रत्येक भाई-बहन का निर्माण नए सिरे से होता है। - टेक्स्ट, विशेषताएँ, वर्ग, शैलियाँ, श्रोता,
when()शाखाएँ औरlist()पंक्तियाँ नए निर्माण का अनुसरण करती हैं। - नए तत्व बनाए जाते हैं, और उनके
onMountहुक चलते हैं। जो तत्व नया निर्माण नहीं करता है उन्हें हटा दिया जाता है, और उनकेonDestroyहुक चलते हैं। list()पंक्तियों का मिलान स्थिति से भी किया जाता है, आइटम से नहीं, इसलिए पुन: उपयोग किया गया पंक्ति तत्व एक अलग आइटम दिखा सकता है।- नई बिल्ड विशेषताएँ अब सेट नहीं हटाई जाती हैं।
स्पष्ट फॉर्म. forceUpdate(App(), parent) parent में एक ट्री का पुनर्निर्माण करता है और उसकी जड़ लौटाता है। यह बाद की कॉल के लिए रूट को पंजीकृत नहीं करता है। hydrate() की तरह, यह उम्मीद करता है कि रूट parent में पहला नोड होगा। यदि पहले नोड में एक अलग टैग है, तो उसे बदल दिया जाता है।
सर्वर पर, forceUpdate() कुछ नहीं करता है।
render()
#आपका ट्री बनाता है और उसके मूल तत्व को parent में जोड़ता है। parent डिफ़ॉल्ट रूप से document.body है। यह मूल तत्व लौटाता है।
import 'nuclo' import { App } from './app.ts' render(App, document.getElementById('app')!)
- आप घटक (
render(App)) या इसे लौटाने वाले बिल्डर (render(App())) को पास कर सकते हैं। DOM वही है. केवलrender(App)forceUpdate()को बाद में रूट का पुनर्निर्माण करने देता है, इसलिए इसे प्राथमिकता दें। - यह जुड़ता है। यह
parentको साफ़ नहीं करता है, इसलिए इसे दो बार कॉल करने से दो प्रतियां जुड़ जाती हैं। render()के वापस आने पर ट्री में प्रत्येकonMountचल चुका होता है।- दस्तावेज़ में मौजूद तत्व में प्रस्तुत करें।
update()देखें। - रूट एक तत्व होना चाहिए, जैसे
div(...),when()याlist()नहीं। indexआंतरिक है। यह यह नहीं चुनता कि तत्व कहाँ डाला गया है। उसे छोड़ दो।
render(App) के रूप में पारित एक घटक को कोई पैरामीटर नहीं लेना चाहिए। एक आवश्यक पैरामीटर एक प्रकार की त्रुटि है. एक वैकल्पिक टाइप-चेक करता है, लेकिन render() रनटाइम पर थ्रो करता है। एक घटक लपेटें जो प्रॉप्स लेता है:
const Page = (props: { name: string }) => div(h1('Hi ', props.name)) render(() => Page({ name: 'Ana' }), root)
hydrate()
#ने renderToString() द्वारा निर्मित HTML का अधिग्रहण किया। यह मौजूदा तत्वों का पुन: उपयोग करता है, ईवेंट श्रोताओं को जोड़ता है, और गतिशील मान, when() और list() को जोड़ता है, इसलिए update() बाद में काम करता है। फिर यह onMount हुक चलाता है। parent डिफ़ॉल्ट रूप से document.body है। यह मूल तत्व लौटाता है।
import 'nuclo' import { App } from './app.ts' hydrate(App, document.getElementById('root')!)
- सर्वर के समान घटक और समान डेटा का उपयोग करें।
- एक समर्पित कंटेनर में हाइड्रेट करें। ऐप का रूट उसका पहला नोड होना चाहिए, हालांकि व्हाइटस्पेस ठीक है। यदि कोई अन्य नोड पहले आता है, तो Nuclo उस नोड को बदल देता है और सर्वर कॉपी को पीछे छोड़ देता है।
render()की तरह,Appपास करें,App()नहीं, इसलिएforceUpdate()बाद में रूट को फिर से बना सकता है।Appको कोई पैरामीटर नहीं लेना चाहिए। एक वैकल्पिक के साथ,hydrate()चुपचाप कुछ भी नहीं जोड़ता है। एक घटक लपेटें जो प्रॉप्स लेता है:hydrate(() => Page(props), root)।- यदि कोई सर्वर HTML नहीं है, तो
render()का उपयोग करें।
जब सर्वर और क्लाइंट भिन्न होते हैं
ग्राहक जीतता है:
- एक अलग रूट टैग: उस नोड को ताज़ा DOM से बदल दिया गया है। एक खाली कंटेनर: ताज़ा DOM जोड़ा गया है।
- अलग पाठ पैच किया गया है। विभिन्न
list()आइटम औरwhen()शाखाएँ ग्राहक का अनुसरण करती हैं। - अतिरिक्त सर्वर चिल्ड्रेन हटा दिए जाते हैं, और गायब चिल्ड्रन बनाए जाते हैं।
- विशेषताएँ जो सर्वर ने लिखीं लेकिन क्लाइंट ने सेट नहीं कीं, उन्हें वैसे ही छोड़ दिया गया है।
renderToString() HTML टिप्पणियों को मार्कर के रूप में लिखता है, जैसे प्रत्येक टेक्स्ट नोड से पहले <!-- text-0 -->। hydrate() को उनकी आवश्यकता है, इसलिए सर्वर HTML से टिप्पणियाँ न हटाएँ। मार्करों के बिना, हाइड्रेशन list() पंक्तियों को हटा देता है, और list() और when() अपडेट करना बंद कर देते हैं।
सर्वर रेंडरिंग
#सर्वर पर एक घटक को HTML स्ट्रिंग में प्रस्तुत करें। ब्राउज़र में, उस HTML को इंटरैक्टिव बनाने के लिए उसी घटक के साथ hydrate() पर कॉल करें। सर्वर रनटाइम में कोई document नहीं है, इसलिए पहले nuclo/polyfill आयात करें।
let count = 0 const heading = css({ color: 'red' }) export const App = () => main( h1(heading, () => `Count: ${count}`), button({ onClick: () => { count++; update() } }, '+1'), )
import 'nuclo/polyfill' import 'nuclo' import { renderToString, getCssText } from 'nuclo/ssr' import { App } from './app.ts' const html = renderToString(App) const page = `<!doctype html> <html> <head><style id="nuclo-styles">${getCssText()}</style></head> <body> <div id="root">${html}</div> <script type="module" src="/client.js"></script> </body> </html>`
import 'nuclo' import { App } from './app.ts' hydrate(App, document.getElementById('root')!)
स्टाइल टैग पर id="nuclo-styles" रखें। क्लाइंट उस तत्व का पुन: उपयोग करता है और केवल नए नियम जोड़ता है। getCssText() देखें।
सर्वर पर क्या चलता है
- प्रत्येक
renderToString()कॉल ट्री बनाता है और प्रत्येक गतिशील मान को एक बार चलाता है। - इवेंट हैंडलर,
onMountऔरonDestroyकभी नहीं चलते।update(),scope()औरforceUpdate()कुछ नहीं करते। - रेंडरिंग सिंक्रोनस है। पहले अपना डेटा लोड करें, फिर रेंडर करें।
- रेंडर के बाद कुछ भी पंजीकृत नहीं रहता है, इसलिए एक प्रक्रिया कई अनुरोधों को पूरा कर सकती है। अनुरोध डेटा को तर्क के रूप में पास करें, जैसे
renderToString(Page(user))। मॉड्यूल-स्तरीय चर सभी अनुरोधों द्वारा साझा किए जाते हैं। - सर्वर पर
render()को कॉल न करें। यह साझाdocument.bodyसे जुड़ता है।
renderToString()
एक बिल्डर (App()), एक घटक (App) या एक DOM नोड लेता है। null और undefined "" लौटाते हैं। जड़ एक तत्व होना चाहिए. renderToString(Page) के रूप में पारित घटक को कोई पैरामीटर नहीं लेना चाहिए, इसलिए डेटा लेने वाले घटक के लिए Page(user) पास करें।
renderToString(p('Hello')) // '<p><!-- text-0 -->Hello</p>'
- टेक्स्ट और विशेषता मान HTML-एस्केप्ड हैं।
script()औरstyle()के अंदर टेक्स्ट वैसे ही लिखा गया है, केवल क्लोजिंग टैग टूटे हुए हैं, इसलिए वहां कभी भी यूजर इनपुट न डालें। - यह फेंकता नहीं है। यदि घटक, एक
list()फ़ंक्शन या एकwhen()स्थिति विफल हो जाती है, या पॉलीफ़िल गायब है, तो यह त्रुटि लॉग करता है और""लौटाता है। यदि अनुरोध विफल हो जाए तो खाली परिणाम की जाँच करें। एक टेक्स्ट या विशेषता फ़ंक्शन जो फेंकता है वह केवल खाली प्रस्तुत करता है।
renderManyToString()
inputs.map(renderToString) के समान।
renderToStringWithContainer()
आउटपुट को एक तत्व में लपेटता है। containerTag डिफ़ॉल्ट रूप से "div" पर और containerAttrs पर डिफ़ॉल्ट रूप से {} पर सेट होता है। विशेषता मान बच गए हैं. गुण नाम दिए गए अनुसार लिखे गए हैं, इसलिए class जैसे HTML नामों का उपयोग करें। उपयोगकर्ता इनपुट को कभी भी टैग के रूप में पास न करें.
renderToStringWithContainer(p('Hi'), 'section', { id: 'main', class: 'page' }) // '<section id="main" class="page"><p><!-- text-0 -->Hi</p></section>'
nuclo/polyfill
#एक छोटा DOM ताकि Nuclo नोड और बन में ट्री बना सके। अपने पहले रेंडर से पहले इसे एक बार आयात करें। अधिकांश ऐप्स को केवल साधारण आयात की आवश्यकता होती है।
- जब कोई
windowनहीं होता है, तो यहglobalThis.document,Node,ElementऔरHTMLElementसेट करता है, लेकिन केवल वे जो गायब हैं। यहwindowनहीं बनाता है. - किसी ब्राउज़र में, या jsdom के अंतर्गत, यह कुछ नहीं करता है।
EventऔरCustomEventरनटाइम के स्वयं के कंस्ट्रक्टर हैं।Node,ElementऔरHTMLElementपॉलीफ़िल क्लास के उपनाम हैं।
यह ट्री बनाने के लिए DOM है, ब्राउज़र नहीं। इसमें कोई innerHTML नहीं है और कोई getElementById नहीं है, querySelector() रिटर्न null है, और इवेंट श्रोताओं को नजरअंदाज कर दिया जाता है। HTML प्राप्त करने के लिए renderToString() का उपयोग करें।
एक टाइप की गई स्टाइल ऑब्जेक्ट को जेनरेट किए गए CSS क्लास में बदल देता है। परिणाम को किसी भी टैग बिल्डर को भेजें। ब्राउज़र में, नियम एक <style id="nuclo-styles"> तत्व में जाते हैं। सर्वर पर, getCssText() उन्हें लौटाता है।
const card = css({ p: 16, rounded: 8, bg: '#fff7ed', hover: { bg: '#ffedd5' }, }) const Card = () => div(card, 'Simple card')
css()आधार घोषणाओं के लिए एक वर्ग बनाता है, साथ ही प्रत्येक छद्म कुंजी, चयनकर्ता और मीडिया ब्लॉक के लिए एक वर्ग बनाता है। उपरोक्त कार्ड को दो वर्ग मिलते हैं।- क्लास नाम सामग्री के हैश हैं। समान शैलियाँ एक वर्ग साझा करती हैं, और सर्वर और क्लाइंट समान नाम उत्पन्न करते हैं।
- मॉड्यूल स्तर पर एक बार शैलियों को परिभाषित करें।
css()ऑब्जेक्ट द्वारा कैश होता है, इसलिए प्रत्येक कॉल पर एक नया ऑब्जेक्ट हर बार फिर से संसाधित होता है। - किसी स्टाइल ऑब्जेक्ट को
css()में पास करने के बाद उसे बदलने से कुछ नहीं होता है। - प्रत्येक शैली पृष्ठ या सर्वर प्रक्रिया के जीवनकाल के लिए पंजीकृत रहती है। उन मानों के लिए जो प्रति आइटम बदलते हैं, जैसे डेटा से चौड़ाई,
styleविशेषता का उपयोग करें।
नामांकित शैलियाँ
पठनीय वर्ग नाम पाने के लिए पहले एक नाम पास करें:
const pageTitle = css('page-title', { text: 24, hover: { color: 'red' } }) pageTitle.className // "page-title page-title-<hash>"
- बेस क्लास बिल्कुल नाम है। अन्य ब्लॉकों को
name-<hash>मिलता है। - नाम एक वैध वर्ग नाम होना चाहिए। अन्यथा Nuclo चेतावनी देता है और एक उत्पन्न करता है।
- नाम वैश्विक हैं। यदि दो अलग-अलग शैलियाँ एक ही नाम का उपयोग करती हैं, तो Nuclo चेतावनी देता है।
परिणाम का उपयोग करना
const card = css({ p: 8 }) div(card) // best div(card, { className: 'featured' }) // className merges div(card, { class: 'featured' }) // replaces: only "featured" is left
String(card) और card.className दोनों वर्ग नाम लौटाते हैं।
स्टाइल ऑब्जेक्ट
#आप जिस ऑब्जेक्ट को css() पर पास करते हैं, वह camelCase में सामान्य CSS गुणों, शॉर्टहैंड का एक सेट और छद्म स्टेटों, चयनकर्ताओं और मीडिया प्रश्नों के लिए नेस्टेड कुंजियों का उपयोग करता है। TypeScript उन सभी को स्वत: पूर्ण करता है। टाइप किए गए सेट के बाहर की संपत्तियाँ, जैसे borderTopLeftRadius, fill या paddingInline, raw में जाती हैं (नीचे देखें)।
मान
- संख्याएँ
0और इकाई रहित संपत्तियों को छोड़करpxबन जाती हैं:zIndex,opacity,fontWeight,lineHeight,flex,flexGrow,flexShrink,order,aspectRatio,gridColumnऔरgridRow।rawमें,zoom,scale,column-count,orphans,widows,tab-sizeऔरanimation-iteration-countभी यूनिटलेस हैं। - स्ट्रिंग्स का उपयोग लिखित रूप में किया जाता है।
null,undefinedऔरfalseको छोड़ दिया गया है। lineHeight(औरleading) इकाई रहित है, इसलिएleading: 24का अर्थ फ़ॉन्ट आकार का 24 गुना है। पिक्सेल के लिए"24px"लिखें.
css({ p: 16, m: 0, opacity: 0.5, leading: 1.5, z: 10, w: '50%' }) // padding:16px; margin:0; opacity:0.5; line-height:1.5; z-index:10; width:50%
शॉर्टहैंड
| कुंजी | CSS |
|---|---|
p, pt, pr, pb, pl | पैडिंग, और प्रत्येक साइड |
px / py | पैडिंग बाएँ और दाएँ / ऊपर और नीचे |
m, mt, mr, mb, ml, mx, my | the मार्जिन |
w, h, minW, maxW, minH, maxH | चौड़ाई, ऊंचाई और उनकी न्यूनतम और अधिकतम |
size | चौड़ाई और ऊंचाई |
bg | पृष्ठभूमि |
text / font / weight | font-size / font-family / font-weight |
leading / tracking | line-height / letter-spacing |
align / items / justify | text-align / align-items / justify-content |
z / rounded / shadow / select | z-index / border-radius / box-shadow / user-select |
ये कुंजियाँ true लेती हैं:
| कुंजी | CSS |
|---|---|
row | display: flex; flex-direction: row |
col | display: flex; flex-direction: column |
center | align-items: center; justify-content: center (डिस्प्ले सेट नहीं है) |
truncate | overflow: hidden; text-overflow: ellipsis; white-space: nowrap |
छद्म स्टेट
hover, focus, focusVisible, focusWithin, active, visited, disabled, enabled, checked, required, invalid, valid, readOnly, first (:first-child), last (:last-child), only (:only-child), odd, even, empty, placeholderShown, placeholder, before, after, selection, marker, firstLine और firstLetter। वे घोंसला बनाते हैं: hover: { focus: {…} } :hover:focus देता है।
चयनकर्ता और नियम
एक कुंजी जो & से शुरू होती है वह एक चयनकर्ता है, और प्रत्येक & वर्ग बन जाता है। एक कुंजी जो @ से शुरू होती है वह एक नियम है: @media, @container या @supports।
const menu = css({ p: 8, '& > li': { py: 4 }, '& > li:hover': { color: 'red' }, '&:is(.dark *)': { color: 'white' }, '@media (min-width: 768px)': { p: 16 }, })
- कुंजी
&से शुरू होनी चाहिए।".dark &"जैसी कुंजी को नजरअंदाज कर दिया जाता है, इसलिए"&:is(.dark *)"लिखें। - दो नेस्टेड
@mediaब्लॉकandके साथ संयोजित होते हैं। मीडिया प्रकार (print,only screen) के नेतृत्व वाली क्वेरी को पहले रखा जाता है, क्योंकि व्याकरण केवल शुरुआत में ही प्रकार की अनुमति देता है। एक जोड़ी जिसे समतल नहीं किया जा सकता - दो मीडिया प्रकार, या एक अग्रणीnot- केवल आंतरिक क्वेरी को चेतावनी देता है और उसका उपयोग करता है। कोई भी अन्य नेस्टेड एट-रूल बाहरी को प्रतिस्थापित कर देता है।
raw
किसी भी संपत्ति के लिए raw का उपयोग करें, नाम बिल्कुल CSS जैसा लिखा गया है। नंबर अभी भी px मिलते हैं, इसलिए स्ट्रिंग पास करें:
css({ raw: { '--gap': '8px', '-webkit-line-clamp': '2' } })
किसी भी अन्य फ़्लैट कुंजी को कबाब-केस में बदल दिया जाता है और उसे वैसे ही लिखा जाता है, इसलिए colr: 'red' जैसी टाइपो एक घोषणा बन जाती है जिसे ब्राउज़र ड्रॉप कर देता है। किसी कुंजी के अंतर्गत एक नेस्टेड ऑब्जेक्ट जो एक छद्म स्थिति, एक स्क्रीन, या एक & या @ कुंजी नहीं है, को नजरअंदाज कर दिया जाता है। TypeScript दोनों की रिपोर्ट करता है।
शैलियों को जोड़ती है। जब दो इनपुट समान गुण सेट करते हैं, तो बाद वाला इनपुट जीत जाता है।
const base = css({ p: 8, color: 'red' }) const blue = css({ color: 'blue' }) cx(base, blue) // padding: 8px; color: blue cx(base, false, null, 'extra') // base's classes plus "extra" div(cx(base, blue), 'Blue text')
- गलत इनपुट को छोड़ दिया जाता है, एरे को चपटा कर दिया जाता है, और स्ट्रिंग्स को विभाजित और डी-डुप्लिकेट कर दिया जाता है।
- एक ही ब्लॉक (आधार, एक ही छद्म, एक ही मीडिया क्वेरी) के लिए जेनरेट की गई कक्षाएं एक नई कक्षा में विलय हो जाती हैं। अन्य वर्ग के नाम अपरिवर्तित रहते हैं।
- केवल
cx()तर्क क्रम जानता है।div(base, blue)के साथ, दोनों वर्ग जोड़े जाते हैं, और जो नियम पिछली बार बनाया गया था वह जीत जाता है। एकcx()कॉल में एक-दूसरे को ओवरराइड करने वाली शैलियाँ डालें। - जब एक नामित शैली का ब्लॉक किसी अन्य इनपुट के ब्लॉक के साथ विलय हो जाता है, तो मर्ज किया गया वर्ग
name-<hash>होता है, न कि मूल नाम। जो ब्लॉक मर्ज नहीं होते हैं वे अपनी कक्षा बनाए रखते हैं, इसलिएcx(pageTitle, 'extra')में अभी भीpage-titleहै।
गतिशील कक्षाएं
एक फ़ंक्शन पास करें जो हमेशा cx(...) लौटाता है:
const baseButton = css({ px: 12, py: 8, border: '1px solid #ccc' }) const activeButton = css({ bg: '#ff3f00', color: 'white' }) let active = false const Toggle = () => button( () => cx(baseButton, active && activeButton), () => (active ? 'Active' : 'Inactive'), { onClick: () => { active = !active; update() } }, )
फ़ंक्शन को अपने पहले रन पर एक स्टाइल परिणाम लौटाना होगा। () => active && activeButton एक क्लास के बजाय "false" टेक्स्ट को प्रस्तुत करता है।
एक थीम से बंधे अपने स्वयं के css, cx, variants, keyframes और globalStyle लौटाता है। एक थीम मानों को नाम देती है: नीचे दी गई थीम के साथ, borderColor: 'border' #e5e7eb लिखता है, और TypeScript नामों को स्वतः पूर्ण करता है। screens में प्रत्येक प्रविष्टि मीडिया क्वेरी के लिए एक शैली कुंजी बन जाती है, जैसे md: {…}।
export const { css, cx, variants } = createCss({ colors: { primary: '#ff3f00', border: '#e5e7eb' }, radii: { card: '12px' }, screens: { md: '(min-width: 768px)' }, }) const panel = css({ p: 12, rounded: 'card', border: '1px solid', borderColor: 'border', md: { p: 24 }, })
| थीम कुंजी | |
|---|---|
colors | bg, backgroundColor, color, borderColor और प्रत्येक पक्ष, outlineColor, caretColor, textDecorationColor |
fonts | font, fontFamily |
shadows | shadow, boxShadow |
radii | rounded, borderRadius |
screens | नई स्टाइल कुंजियाँ, जैसे md: {…} |
- एक थीम नाम तभी हल होता है जब वह संपूर्ण मान हो।
border: "1px solid primary"का समाधान नहीं हुआ। @के बिना एक स्क्रीन मान@media <value>हो जाता है।@से शुरू होने वाला मान लिखित रूप में उपयोग किया जाता है, उदाहरण के लिए"@container (min-width: 400px)"।- स्क्रीन केवल
createCss()से बने इंस्टेंस पर मौजूद हैं। वैश्विकcss()में कोई नहीं है, लेकिन यह@mediaकुंजी स्वीकार करता है। - सभी उदाहरण और वैश्विक सहायक एक स्टाइलशीट साझा करते हैं। स्क्रीन मीडिया नियम स्क्रीन घोषित होने के क्रम में आउटपुट होते हैं, न कि उस क्रम में जिस क्रम में आप उनका उपयोग करते हैं, इसलिए
min-widthस्क्रीन को छोटे से बड़े तक सूचीबद्ध करें। प्रति ऐप एकcreateCss()इंस्टेंस का उपयोग करें, ताकि वह ऑर्डर आपकी थीम में से एक हो। themeवह विषय है जिसे आपने उत्तीर्ण किया है।
एक फ़ंक्शन बनाता है जो विकल्पों के एक सेट के लिए एक शैली लौटाता है, जैसे कि बटन का इरादा और आकार।
const buttonClass = variants({ base: { px: 12, py: 8, rounded: 6 }, variants: { intent: { primary: { bg: '#2563eb' }, danger: { bg: '#dc2626' } }, size: { sm: { text: 12 }, lg: { text: 18 } }, block: { true: { w: '100%' } }, }, defaultVariants: { intent: 'primary', size: 'sm' }, compoundVariants: [{ intent: 'danger', size: 'lg', css: { weight: 700 } }], }) button(buttonClass(), 'Save') // primary, sm button(buttonClass({ intent: 'danger', size: 'lg', block: true }), 'Delete') // bold, full width
- शैलियाँ इस क्रम में विलीन हो जाती हैं:
base, प्रत्येक समूह का चुना हुआ मान, फिर प्रत्येक मिलान यौगिक। बाद की शैलियाँ प्रति संपत्ति जीतती हैं। - एक अनुपलब्ध,
undefinedयाnullविकल्प डिफ़ॉल्ट का उपयोग करता है। बिना किसी डिफ़ॉल्ट वाले समूह को छोड़ दिया जाता है। - केवल
trueऔरfalseकुंजियों वाला एक समूह एक बूलियन लेता है। - एक कंपाउंड तब लागू होता है जब इसमें सूचीबद्ध प्रत्येक समूह मेल खाता है, डिफ़ॉल्ट शामिल हैं।
- प्रत्येक चयन एक बार बनाया जाता है और कैश किया जाता है।
keyframes() एक एनीमेशन पंजीकृत करता है और उसका उत्पन्न नाम लौटाता है। फ़्रेम कुंजियाँ from, to और प्रतिशत हैं, जैसे "50%" या "0%, 100%"। फ़्रेम केवल समतल गुण लेते हैं. समान फ़्रेम समान नाम लौटाते हैं.
const fadeIn = keyframes({ from: { opacity: 0 }, to: { opacity: 1 }, }) const panel = css({ animation: `${fadeIn} 200ms ease-out` })
globalStyle() किसी भी चयनकर्ता के लिए एक नियम जोड़ता है:
globalStyle('*, *::before, *::after', { boxSizing: 'border-box' }) globalStyle('body', { m: 0, font: 'system-ui, sans-serif' })
- चयनकर्ता का उपयोग बिल्कुल वैसे ही किया गया है जैसा लिखा गया है।
- केवल फ्लैट संपत्तियां ही काम करती हैं। छद्म कुंजियाँ, स्क्रीन और
@कुंजियाँ नजरअंदाज कर दी जाती हैं, इसलिएglobalStyle()मीडिया नियम नहीं लिख सकता। - एक ही नियम एक बार जोड़ा जाता है। एक ही चयनकर्ता के लिए एक अलग शैली दूसरा नियम जोड़ती है, और बाद वाला जीतता है।
getCssText() और resetStyles()
#getCssText() हर नियम लौटाता है जो css(), cx(), variants(), keyframes() और globalStyle() ने इस प्रक्रिया में बनाया है: पहले आधार नियम, फिर @media, @container और @supports ब्लॉक। सर्वर रेंडरिंग के लिए इसका उपयोग करें।
- इसे
renderToString()के बाद कॉल करें। रेंडर के दौरान बनाए गए नियम शामिल हैं। - यह एक पेज या अनुरोध को नहीं बल्कि पूरी प्रक्रिया को कवर करता है, और यह केवल बढ़ता है। यह सुरक्षित है, क्योंकि वर्ग के नाम सामग्री हैश हैं।
- इसे
<style id="nuclo-styles">में डालें। क्लाइंट उस तत्व का पुन: उपयोग करता है और उसमें पहले से मौजूद नियमों को छोड़ देता है। किसी अन्य आईडी के साथ, या किसी भी आईडी के साथ, क्लाइंट प्रत्येक नियम के साथ फिर से एक दूसरी स्टाइलशीट जोड़ता है। - टेक्स्ट बच नहीं पाया है, इसलिए शैली मानों में कभी भी अविश्वसनीय इनपुट न डालें।
resetStyles() एक परीक्षण सहायक है। यह प्रत्येक नियम और #nuclo-styles तत्व को हटा देता है। आपके द्वारा पहले बनाई गई शैलियाँ अपने वर्ग के नाम तो रखती हैं लेकिन अपना CSS खो देती हैं। इसे किसी ऐप या सर्वर पर कॉल न करें.
import { resetStyles } from 'nuclo' beforeEach(() => resetStyles())
nuclo-router Nuclo कोर पैकेज का हिस्सा नहीं है। इसे nuclo के बगल में स्थापित करें। यह एक पूर्वावलोकन रिलीज़ है, इसलिए इसका API अभी भी बदल सकता है।Nuclo के लिए रूटिंग: सर्वर रेंडरिंग, हाइड्रेशन, कोड-स्प्लिट रूट और निष्क्रिय प्रीलोडिंग, list() और update() पर निर्मित। 4.9 KB के बारे में gzipped, Nuclo से परे कोई निर्भरता नहीं है और कोई साइड इफेक्ट नहीं है, इसलिए अप्रयुक्त होने पर यह ट्री से हिल जाता है।
$ npm install nuclo nuclo-router
या समान दो नामों के साथ pnpm add, yarn add या bun add का उपयोग करें। इसे सहकर्मी निर्भरता के रूप में nuclo 0.3 या बाद के संस्करण की आवश्यकता है।
राउटर what को लोड करने का निर्णय लेता है। ऐप कहता है where router.outlet() के साथ: परत स्टैक पर एक list(), प्रति परत एक पंक्ति, इसलिए एक साधारण पृष्ठ एक पंक्ति है। सर्वर और क्लाइंट एक ही ट्री बनाते हैं, इसलिए hydrate() सर्वर के नोड्स को बदलने के बजाय उन पर दावा करता है। बाकी सब कुछ history है, एक प्रत्यायोजित click श्रोता और requestIdleCallback है।
एक आयात
export const router = createRouter(routes) // define once, import anywhere await router.start() // resolve the current URL render(App, document.getElementById('app')!) // App places router.outlet() and reads router.path, router.go(), …
start() मिलान रूट के मॉड्यूल को वापस लौटने से पहले हल करता है, इसलिए पहला ट्री सर्वर और क्लाइंट दोनों पर पूरा हो जाता है। उसके बाद राउटर उस रूट को पढ़ता है जो उसने शुरू किया था: router.path, router.pending, router.go()।
एक संपूर्ण ऐप
// isomorphic: imported by the browser and the server import { createRouter } from 'nuclo-router' export const router = createRouter({ '/': () => import('./pages/Home.ts'), '/docs': () => import('./pages/Docs.ts'), '/blog/:slug': () => import('./pages/Post.ts'), '*': () => import('./pages/NotFound.ts'), }) export const App = () => div( header(a({ href: router.href('/') }, 'Home'), a({ href: router.href('/docs') }, 'Docs')), main( when(() => router.pending, Spinner()), when(() => router.error !== null, ErrorView()), router.outlet(), // where the pages land ), footer('© nuclo'), )
import type { RouteContext } from 'nuclo-router' import { router } from '../routes.ts' // A page is plain content export default function Post(ctx: RouteContext) { // A query-only change keeps the page, so the query is read live. return article(h1(ctx.params.slug), p(() => (router.search.has('preview') ? 'draft' : ''))) }
import 'nuclo' import { router, App } from './routes.ts' await router.start() hydrate(App, document.getElementById('app')!) // or render() without SSR
import 'nuclo/polyfill' import 'nuclo' import { renderToString } from 'nuclo/ssr' import { router, App } from './routes.ts' // one Route per request const route = await router.start(request.url) const html = route.run(() => renderToString(App))
यह संपूर्ण एकीकरण है। लिंक काम करते हैं क्योंकि <a href> सिर्फ एक <a href> है। document पर एक प्रत्यायोजित श्रोता राउटर के स्वामित्व वाले लिंक को रोकता है और ब्राउज़र पर हर दूसरे लिंक को छोड़ देता है।
सर्वर पर, start() प्रति अनुरोध एक Route लौटाता है और राउटर को अकेला छोड़ देता है, इसलिए समवर्ती अनुरोध एक दूसरे का मार्ग नहीं देख सकते हैं। route.run() उस रूट को बनाता है जो राउटर, और इसलिए आउटलेट, रेंडर की लंबाई के लिए पढ़ता है। ब्राउज़र में प्रति पेज लोड एक रूट होता है, और start() इसे राउटर का अपना बनाता है। कोई run() की आवश्यकता नहीं है.
एक पृष्ठ को इसका RouteContext और इसका PageProps, (ctx, { layer, data, outlet, beforeLeave }) के रूप में प्राप्त होता है, और कम तर्क ले सकता है। यह list() द्वारा स्वीकार की गई कोई भी चीज़ लौटाता है। लेआउट के अन्य भाग को भी भरने के लिए, पृष्ठ के अंदर से एक into() लौटाएँ, जैसे div(article(…), into('sidebar', nav(…)))। वह सामग्री पृष्ठ के साथ चली जाती है। Pages & क्षेत्र देखें।
| नमूना | माचिस | params |
|---|---|---|
/, /docs/intro | बिल्कुल वही path | {} |
/blog/:slug | /blog/hello | { slug: 'hello' } |
/u/:id/edit | /u/7/edit | { id: '7' } |
/files/*rest | /files/a/b.txt | { rest: 'a/b.txt' } |
* | Zकुछ भी बेजोड़, आपका 404 | { '*': '…' } |
प्राथमिकता स्थिर है, फिर घोषणा क्रम में गतिशील है, फिर पहले सबसे लंबे उपसर्ग के साथ सभी को पकड़ें। तो '*' केवल वही मेल खाता है जो कोई और नहीं करता है और तालिका में कहीं भी बैठ सकता है। ट्रेलिंग स्लैश, डुप्लिकेट स्लैश और प्रतिशत-एन्कोडिंग को सामान्यीकृत किया जाता है, और परम मान डिकोड किए जाते हैं। मिलान प्रति पैटर्न एक खंड तुलना है। कोई रेगेक्स नहीं बनाया या चलाया जाता है।
सापेक्ष मार्ग
./ के साथ लिखा गया एक पैटर्न इसे घोषित करने वाले माता-पिता के सापेक्ष है, और ./ के साथ लिखा गया href सक्रिय मार्ग के सापेक्ष है। वे मिलकर एक पुन: प्रयोज्य अनुभाग बनाते हैं जिसका अर्थ प्रत्येक माता-पिता के लिए समान होता है। किसी तालिका का मान या तो एक लोडर होता है या कोई अन्य तालिका, जिसकी कुंजियाँ उससे संबंधित होती हैं:
import type { RouteTable } from 'nuclo-router' // a section, with no idea where it lives export const recordSection: RouteTable = { '/': () => import('./Record.ts'), // the parent path itself './preview': () => import('./Preview.ts'), './preview/raw': () => import('./Raw.ts'), }
// declare it wherever it belongs createRouter({ '/': () => import('./pages/Home.ts'), '/invoices/:id': recordSection, '/customers/:id': recordSection, '*': () => import('./pages/NotFound.ts'), })
वे दो पंक्तियाँ सभी छह पैटर्न उत्पन्न करती हैं जिनमें कोई पथ दो बार नहीं लिखा होता है। माता-पिता दोनों को समान लोडर ऑब्जेक्ट मिलते हैं, इसलिए वे एक मॉड्यूल कैश और एक import() साझा करते हैं।
- एक चाइल्ड टेबल के अंदर,
'./x','/x'और'x'सभी का मतलब एक ही है,'/'का मतलब मूल पथ ही है, और'*rest'अनुभाग को अपना स्वयं का कैच-ऑल देता है। घोंसला बनाना आप जितना चाहें उतना गहरा हो जाता है। - शीर्ष-स्तरीय कुंजियों का उपयोग लिखित रूप में किया जाता है, इसलिए
'*''*'रहता है। *catch-allमाता-पिता के अंतर्गत एक बच्चा एकcreateRouter()त्रुटि है, क्योंकि कैच-ऑल को अपना पैटर्न समाप्त करना होता है।
मूल पृष्ठ और outlet()
एक माता-पिता और उसके चाइल्ड नोड एक वास्तविक पदानुक्रम हैं। पेरेंट रेंडर करता है, और उसका बच्चा इसके अंदर रेंडर करता है, जहां भी वह outlet() कहता है:
import type { PageProps, RouteContext } from 'nuclo-router' // a parent page takes its outlet from (ctx, { outlet }) export default function Record(ctx: RouteContext, { outlet }: PageProps) { return div(Header(ctx), input({ id: 'notes' }), main(outlet())) }
चाइल्ड नोड के बीच नेविगेट करने से माता-पिता का पुनर्निर्माण नहीं होता है। इसका DOM नोड एक ही नोड है, इसलिए इसका फोकस, इसकी स्क्रॉल स्थिति, इसका आधा टाइप किया गया इनपुट और इसके बंद होने की कोई भी स्थिति सभी जीवित रहती है।
- बिना किसी चाइल्ड रूट वाला पेज अपने आउटलेट से कुछ भी प्रस्तुत नहीं करता है, इसलिए एक लेआउट इसे बिना शर्त कॉल कर सकता है। एक माता-पिता जिसके पास एक बच्चा है और वह कभी भी
outlet()को नहीं बुलाता है, वह उसे निगल जाता है। - कोई सूचकांक मार्ग नहीं हैं।
'/'स्वयं लेआउट है, इसलिए मूल पथ लेआउट को उसके अंदर एक अलग इंडेक्स पेज के बजाय एक खाली आउटलेट के साथ प्रस्तुत करता है। लैंडिंग सामग्री को लेआउट में रखें, या इसे वास्तविक चाइल्ड पथ दें।
फिर वही कॉल माता-पिता में से किसी एक से और एक ऐसे पेज से काम करती है जो यह नहीं जानता कि यह किसमें है:
router.href('./preview') // '/invoices/42/preview' or '/customers/7/preview' await router.go('./preview') await layer.push('./preview') // the usual shape: a child layer
./x, ../x, . और .. वर्तमान रूट के पथ के विरुद्ध समाधान करते हैं, URL की "निर्देशिका" के विरुद्ध नहीं। /invoices/42 से, ./preview /invoices/42/preview है, जहां एक ब्राउज़र ने /invoices/preview कहा होगा, और URL पर एक पिछला स्लैश कुछ भी नहीं बदलता है। ../ एक खंड तक चलता है और मूल पर रुकता है। क्वेरी और हैश को आगे बढ़ाया जाता है, base को फिर से जोड़ा जाता है, और हल किया गया निरपेक्ष पथ इतिहास में चला जाता है। यह URL रिज़ॉल्यूशन से वही विचलन है जो रिएक्ट राउटर <Link to="./x"> के लिए करता है।
- केवल
./और../मार्ग-सापेक्ष हैं। एक href में एक नंगेpreviewको सामान्य ब्राउज़र नियमों के लिए छोड़ दिया गया है। - हाथ से
href="./preview"लिखने के बजायrouter.href('./preview')के साथ सापेक्ष लिंक बनाएं।href()रेंडर समय पर हल हो जाता है, इसलिए जो मार्कअप भेजा जाता है वह क्रॉलर के लिए, मध्य-क्लिक के लिए और JavaScript अक्षम होने पर भी पूर्ण और सही होता है।
क्या रखा है, और क्या बनाया गया है
| नोड | नेविगेशन पर |
|---|---|
| a पैरेंट जिसका पैटर्न और पथ अपरिवर्तित है | kept: वही नोड, और इसका load() नहीं है |
| मिलान किया गया पृष्ठ | kept जब इसका पैटर्न और पथ अपरिवर्तित होता है और इसमें कोई load() नहीं होता है, तो एक क्वेरी या हैश परिवर्तन इसे रखता है। प्रत्येक विज़िट पर लोडर वाला मार्ग ताज़ा हो जाता है। |
| एक अभिभावक जिसका मार्ग बदल गया | , इसके तहत सब कुछ के साथ, फिर से बनाया गया |
| a पुश लेयर | अकेले मिलान किए गए पृष्ठ को प्रस्तुत करता है। इसके माता-पिता पहले से ही इसके नीचे की पंक्ति में आरूढ़ हैं। |
एक रखा हुआ पेज उस ctx को रखता है जिसके साथ इसे बनाया गया था। हर जगह ctx का यही अर्थ है: जीवंत मूल्यों के लिए router.* पढ़ें। क्वेरी-संचालित स्थिति, जैसे फ़िल्टर, पेजिनेशन और टैब, एक गतिशील मान के अंदर router.search को पढ़ता है और router.go('?page=2', { replace: true }) के साथ लिखता है। पेज, उसका फोकस और उसका आधा-टाइप इनपुट यथावत रहता है।
पेज और रीजन
#router.outlet() पेज को एक स्थान पर रखता है। जिस पृष्ठ को लेआउट में कहीं और साइडबार, टूलबार या शीर्षक की भी आवश्यकता होती है, उसे into() से भर दिया जाता है। लेआउट region() के साथ स्थान को चिह्नित करता है, और पृष्ठ अपनी सामग्री के आगे एक into() लौटाता है। लेआउट कभी भी पृष्ठ सामग्री को प्रोप के रूप में प्राप्त नहीं करता है, और नेविगेशन कभी भी इसका पुनर्निर्माण नहीं करता है।
export const App = () => div( aside( input({ placeholder: 'Filter' }), // keeps its text across navigations region({ id: 'sidebar', empty: p('Nothing to show.') }), ), main(router.outlet()), )
import type { PageProps, RouteContext } from 'nuclo-router' export default function Post(_ctx: RouteContext, { data: post }: PageProps<Post>) { return div( article(h1(post.title), p(post.body)), into('sidebar', nav(h2('In this post'), ul(list(() => post.headings, (h) => li(h))))), ) }
पृष्ठ की सामग्री आउटलेट के list() की एक पंक्ति है, और एक into() तब तक जीवित रहता है जब तक वह पंक्ति इसे धारण करती है। तो साइडबार सामग्री पृष्ठ के साथ आती है और उसके साथ चली जाती है: into('sidebar', …) के बिना किसी पृष्ठ पर नेविगेट करने से क्षेत्र की empty सामग्री फिर से दिखाई देती है। पेज दोनों भागों के लिए अपने ctx और data का उपयोग कर सकता है, क्योंकि दोनों एक ही फ़ंक्शन द्वारा बनाए गए हैं।
नेविगेशन पर क्या होता है
- A रखा गया पेज अपने क्षेत्रों को रखता है। एक क्वेरी या हैश परिवर्तन जो पेज को रखता है क्षेत्र की सामग्री को भी नहीं छूता है। यदि साइडबार इस पर निर्भर करता है तो डायनामिक मान के अंदर
router.searchपढ़ें। - A पुनर्निर्मित पृष्ठ उन्हें पुनः भर देता है। जब मार्ग बदलता है, तो पुरानी पंक्ति हटा दी जाती है और नए पृष्ठ के
into()दृश्य आ जाते हैं। कुछ भी पीछे नहीं छूटा. - A पुश की गई परत एक दृश्य जोड़ती है। परत आउटलेट की बाद की पंक्ति है, इसलिए इसका
into()उसी क्षेत्र का एक नया दृश्य है। डिफ़ॉल्टtype: 'latest'के साथ यह परत बंद होने तक पृष्ठ के दृश्य को कवर करता है, और फिर पृष्ठ का दृश्य फिर से दिखाई देता है।type: 'stack'के साथ दोनों शो। - A मूल पृष्ठ क्षेत्रों को भी भर सकता है। इसके दृश्य तब भी बने रहते हैं जब इसके चाइल्ड नोड बदलते हैं, क्योंकि मूल पृष्ठ का पुनर्निर्माण नहीं किया जाता है।
- सर्वर रेंडरिंग वैसे ही काम करता है जैसे सर्वर
route.run()के अंदर पेज बनाता है, इसलिए इसकेinto()दृश्य HTML में हैं, औरhydrate()उनका दावा करता है।
एक पृष्ठ से कई क्षेत्र
एक बार में एक से अधिक क्षेत्र भरने के लिए किसी ऑब्जेक्ट को पास करें। पृष्ठ की मुख्य सामग्री अभी भी आउटलेट के माध्यम से जाती है, इसलिए इसे into() के पास लौटा दें:
export default function Report(ctx: RouteContext) { return div( table(…), into({ toolbar: [button('Export'), button('Print')], sidebar: nav(a({ href: router.href('./summary') }, 'Summary')), }), ) }
किस काम के लिए कौन सा टूल
outlet()एक चाइल्ड रूट के लिए है: यूआरएल द्वारा तय की गई सामग्री, उसके मूल पृष्ठ के अंदर प्रस्तुत की गई है। नेस्टेड रूट देखें।into()मिलान किए गए पेज के लिए है जो पूरे लेआउट में फैला हुआ है। सामग्री पृष्ठ से संबंधित है और उसके डेटा का उपयोग करती है।matches()अकेले यूआरएल से लेआउट तय करने के लिए है, इसकी अपनी छोटी तालिका है और इसमें कोई पेज शामिल नहीं है।matches()देखें।
ये सभी एक सुमेलित मार्ग से संबंधित हैं। राउटर एक यूआरएल से दो स्वतंत्र क्षेत्रों को नहीं चलाता है, इसलिए अपने स्वयं के मार्गों के साथ एक साइडबार वह क्षेत्र है जिसे एक पृष्ठ भरता है, दूसरा आउटलेट नहीं। एक साइडबार जो वास्तव में अपना स्वयं का राउटर चाहता है, लोडिंग और फ़ॉलबैक को अनस्टार्ट रखता है और match() के साथ हल करता है - मल्टीपल राउटर्स & कंबाइंड लोडिंग देखें।
एक पेज मॉड्यूल एक load फ़ंक्शन निर्यात कर सकता है। यह पेज बनने से पहले, उस रूट के हर नेविगेशन पर चलता है, और जो कुछ भी लौटाता है उसे data के रूप में पेज पर भेज दिया जाता है:
import type { DataLoader, PageProps, RouteContext } from 'nuclo-router' export const load: DataLoader<Post> = (ctx) => fetchPost(ctx.params.slug) export default function Post(_ctx: RouteContext, { data: post }: PageProps<Post>) { return article(h1(post.title), p(post.body)) }
रूट तालिका अपरिवर्तित है। लोडर पेज के साथ यात्रा करता है। इसमें मिलान RouteContext मिलता है, इसलिए params, search और path सभी उपलब्ध हैं। यह सिंक या एसिंक हो सकता है, और यह सर्वर पर भी चलता है, इसलिए सर्वर-रेंडर पेज हाइड्रेशन के बाद लाने के बजाय पहले से ही HTML में अपना डेटा भेजता है।
मॉड्यूल कैश्ड है। डेटा नहीं है. एक रूट का हिस्सा एक बार लाया जाता है और पुन: उपयोग किया जाता है, और इसका लोडर हर नेविगेशन पर फिर से चलता है। यही कारण है कि /blog/a और /blog/b को भ्रमित नहीं किया जा सकता है, और एक असफल लोडर को आसानी से पुनः प्रयास किया जा सकता है।
| स्थिति | व्यवहार |
|---|---|
| चलता है | router.pending सच है और जिस पेज पर आप थे वह चालू रहता है |
| it | को router.error पर फेंकता या अस्वीकार करता है। go() अभी भी कभी भी अस्वीकार नहीं करता है, और नेविगेशन का पुनः प्रयास किया जा सकता है। |
start() | के दौरान अस्वीकृति start() से आती है। इसे लगाने के लिए अभी तक कोई रूट नहीं है. |
| a पुश लेयर | अपने स्वयं के डेटा के साथ अपना स्वयं का लोडर चलाता है |
| केवल निष्क्रिय प्रीलोडर | वार्म्स मॉड्यूल। इसमें पारित करने के लिए कोई संदर्भ नहीं है, और यह उस मार्ग के लिए डेटा लाएगा जिसे आप कभी नहीं खोल सकते। |
| उसी URL | पर पुनः नेविगेट करने पर लोडर फिर से चलता है और पंक्ति फिर से बन जाती है। लोडर के बिना एक मार्ग अपनी पंक्ति रखता है। |
पुनः सत्यापित करने के लिए, इतिहास प्रविष्टि जोड़े बिना उस स्थान पर नेविगेट करें जहां आप पहले से हैं:
await router.go(router.url, { replace: true }) // runs load() again
खंड के साथ डेटा प्रारंभ करना
पेज मॉड्यूल द्वारा निर्यात किया गया load केवल उस मॉड्यूल के आने के बाद ही चल सकता है, इसलिए एक ठंडा नेविगेशन खंड, फिर डेटा प्राप्त करता है। दोनों को एक साथ शुरू करने के लिए, लोडर को withLoad() के साथ तालिका में घोषित करें:
import { createRouter, withLoad } from 'nuclo-router' import { loadPost } from './data.ts' // small, in the main bundle createRouter({ '/blog/:slug': withLoad(() => import('./pages/Post.ts'), loadPost), })
यह मॉड्यूल के अपने load को प्रतिस्थापित करता है और हर विजिट पर इसी तरह चलता है। एक अस्वीकृति जबकि हिस्सा अभी भी किसी अन्य की तरह router.error पर लैंड लोड कर रहा है।
डिज़ाइन के अनुसार, HTML में सर्वर डेटा का कोई क्रमांकन नहीं है। क्लाइंट का load फिर से हाइड्रेशन पर चलता है। यदि यह मायने रखता है, तो सर्वर को route.data उत्सर्जित करने दें, उदाहरण के लिए window.__DATA__ के रूप में, और लोडर इसे पहले ले। route.data केवल मिलान किए गए पृष्ठ का डेटा है। माता-पिता का load() परिणाम इस पर नहीं है।
कोड विभाजन और प्रीलोडिंग
एक रूट का मान हमेशा एक फ़ंक्शन होता है जो पृष्ठ घटक लौटाता है, इसलिए घटक तक आलसी तरीके से पहुंचा जाता है:
'/a': () => import('./A.ts'), // default export (+ optional load) '/b': () => import('./B.ts').then(m => m.BPage), // named export '/c': () => CPage, // eager, not split '/d': () => ({ default: DPage, load: loadD }), // eager, module-shaped
केवल मॉड्यूल-आकार वाले फॉर्म ही load निर्यात कर सकते हैं। .then(m => m.BPage) के साथ नामित घटक को चुनने से राउटर को अकेले ही फ़ंक्शन सौंप दिया जाता है।
प्रत्येक मॉड्यूल को एक बार आयात किया जाता है और कैश किया जाता है। एक ही मार्ग पर समवर्ती नेविगेशन एक import() साझा करते हैं। एक बार जब उपयोगकर्ता ने पृष्ठ को idleTimeout (डिफ़ॉल्ट रूप से एक सेकंड; कोई टैप, कुंजी, पहिया या स्क्रॉल नहीं) के लिए अकेला छोड़ दिया है, तो शेष मार्ग पृष्ठभूमि में लोड हो जाते हैं, प्रति निष्क्रिय स्लाइस (requestIdleCallback)। उपयोगकर्ता जिस भी इनपुट, या नेविगेशन की प्रतीक्षा कर रहा है, उसे तब तक रोक देता है जब तक कि पृष्ठ फिर से निष्क्रिय न हो जाए।
एक प्रीलोडेड या उत्सुक मार्ग कभी भी router.pending को फ्लिप नहीं करता है और अगले पेंट से पहले लैंड करता है: कोई स्पिनर फ्रेम नहीं, कोई लेआउट शिफ्ट नहीं। router.pending केवल एक मॉड्यूल या लोडर के लिए सही है जिसके लिए इंतजार करना पड़ता है, और आउटगोइंग पेज तब भी स्क्रीन पर रहता है। एक टुकड़ा जो सतहों को router.error के रूप में लोड करने में विफल रहता है, कभी भी एक अनचाहे अस्वीकृति के रूप में नहीं, और बस पुनः प्रयास किया जा सकता है।
राउटर API
#createRouter(table, options?)
table एक पैटर्न को लोडर, या नेस्टेड टेबल पर मैप करता है। विकल्प:
| विकल्प | गलती करना | |
|---|---|---|
base | '/' | URL उपसर्ग के तहत ऐप परोसा जाता है, जैसे '/docs'। यह केवल खंड सीमा पर मेल खाता है: /docs/intro '/docs' के अंदर है, /docsearch नहीं है। इसके बाहर के यूआरएल किसी भी तरह से मेल नहीं खाते हैं, इसलिए start() अस्वीकार कर देता है (आपका सर्वर-साइड 404) और उनके लिंक ब्राउज़र पर छोड़ दिए जाते हैं। |
preload | true | पृष्ठ निष्क्रिय होने पर शेष मार्गों के मॉड्यूल को पृष्ठभूमि में लोड करें। false इसे बंद कर देता है. |
idleTimeout | 1000 | मिलीसेकंड प्रीलोडिंग शुरू होने से पहले, या उपयोगकर्ता के शुरू होने के बाद फिर से शुरू होता है व्यस्त. |
onNavigate | (ctx, kind) => void, ब्राउज़र में प्रत्येक हल किए गए नेविगेशन के बाद कॉल किया जाता है, जिसमें शुरुआती नेविगेशन भी शामिल है, एक बार DOM पकड़ लिया है. kind 'start', 'push', 'replace', 'pop' (पीछे, आगे, close()) या 'layer' है। document.title, मेटा टैग, फोकस, स्क्रॉलिंग और एनालिटिक्स के लिए हुक। SSR. |
राउटर लौटाता है: start(), match(), outlet(), matches(), और run() को छोड़कर प्रत्येक Route सदस्य, सक्रिय रूट को पढ़ता है। वह वह है जो ब्राउज़र में लौटाया गया अंतिम start() है, या वह जिसका run() सर्वर पर निष्पादित हो रहा है। किसी भी थ्रो के साथ एक सदस्य को पढ़ना। रूट तालिका, संकलित मिलानकर्ता और मॉड्यूल कैश राउटर पर रहते हैं, इसलिए उन्हें सर्वर पर अनुरोधों के बीच साझा किया जाता है।
router.start(url?)
अपने पेज मॉड्यूल में url (ब्राउज़र में डिफ़ॉल्ट: location.href) को हल करता है और एक Promise<Route> लौटाता है। जब कुछ भी मेल नहीं खाता और तालिका में कोई '*' रूट नहीं है तो अस्वीकार कर देता है। एक सर्वर पर, वह आपका 404 है। ब्राउज़र में यह रूटर को सक्रिय बनाता है, popstate और प्रत्यायोजित click श्रोताओं को जोड़ता है और प्रीलोडिंग शेड्यूल करता है। दूसरा start() आपके लिए पिछले रूट को समाप्त कर देता है। सर्वर पर यह साझा की गई किसी भी चीज़ को नहीं छूता है, इसलिए route.run() के अंदर प्रस्तुत करें।
router.match(url?)
कुछ भी लोड किए बिना तालिका के विरुद्ध एक यूआरएल का समाधान करता है: कोई आयात नहीं, कोई इतिहास नहीं, कोई श्रोता नहीं, कोई रूट नहीं। जब URL base के बाहर हो, किसी पैटर्न से मेल न खाता हो या ब्राउज़र में किसी अन्य मूल पर हो, तो RouteContext, या null लौटाता है। केवल पूर्ण URL, क्योंकि ./ रिज़ॉल्यूशन के लिए एक सक्रिय रूट की आवश्यकता होती है।
// 404 before importing a single chunk if (!router.match(request.url)) return new Response(null, { status: 404 }) // "is this link ours?" for a custom link component or a prefetch rule const owned = router.match(href) !== null
router.outlet()
सक्रिय मार्ग के पृष्ठ list() के रूप में। जहाँ भी पन्ने जाते हैं, इसे एक बार रखें। एक धकेली गई परत इसकी एक बाद की पंक्ति है। यह start() के समाधान से पहले और stop() के बाद खाली है। सर्वर पर यह उस रूट को प्रस्तुत करता है जिसका run() निष्पादित हो रहा है।
router.matches(table)
एक दूसरी तालिका का दृश्य प्रस्तुत करता है जो सक्रिय मार्ग के पथ से मेल खाता है, list() के रूप में आप इसे कहीं भी रख सकते हैं। matches() देखें।
Route
run() को छोड़कर प्रत्येक सदस्य राउटर पर भी है, जहां एक ऐप इसे पढ़ता है।
| सदस्य | |
|---|---|
path, pattern, params, search, hash, url | सक्रिय मैच.searchएक हैURLSearchParams. search, hashऔरurlलाइव हैं. |
pending | a नेविगेशन एक मॉड्यूल या लोडर की प्रतीक्षा कर रहा है |
error | अंतिम विफल लोड, अगले नेविगेशन द्वारा साफ़ किया गया |
data | मिलान किए गए पृष्ठ का load() क्या लौटा, सर्वर को क्रमबद्ध करने के लिए। इस पर माता-पिता का नाम नहीं है. |
depth | स्टैक पर कितनी परतें हैं (एक सामान्य पृष्ठ के लिए 1) |
go(href, { replace }) | नेविगेट करें, यदि आवश्यक हो तो मॉड्यूल लोड करें। कभी अस्वीकार नहीं करता: एक मॉड्यूल जो विफल हो जाता है, अस्वीकार करके या फेंककर, error. |
push(href) | एक रूट को शीर्ष पर एक नई परत के रूप में खोलें, जिस पर यह बंद होता है उसके साथ हल करें। ./ स्वीकार करता है। कोई मिलान नहीं होने या विफल मॉड्यूल पर अस्वीकार कर दिया गया। |
href(path) | a({ href }) के लिए base के साथ एक पथ उपसर्ग करें। सक्रिय मार्ग के विरुद्ध ./ और ../ का समाधान करता है। |
stop() | प्रत्येक श्रोता को अलग करें, खुली परतों को खारिज करें, आउटलेट खाली करें। नपुंसक। रुका हुआ रूट अभी भी अपने अंतिम मैच के साथ उत्तर देता है, और go() कुछ नहीं करता है। |
run(fn) | को राउटर के सक्रिय रूट के रूप में इस रूट के साथ fn पर कॉल करें और इसका परिणाम लौटाएं: सर्वर पर route.run(() => renderToString(App))। केवल तुल्यकालिक. fn के वापस आने पर रूट सक्रिय होना बंद हो जाता है। |
RouteContext
एक पृष्ठ का पहला तर्क: path, pattern, params, search, hash, url। यह वह संदर्भ है जिसके साथ पेज बनाया गया था। जीवंत मूल्यों के लिए router.* पढ़ें। वे केवल तभी भिन्न होते हैं जब क्वेरी या हैश पुनर्निर्माण के बिना बदल जाता है।
params डिफ़ॉल्ट रूप से { [name]: string } है। एक पेज इसे Params के साथ अपने पैटर्न से टाइप कर सकता है, जो '/blog/:slug' को { slug: string } में और '/files/*rest' को { rest: string } में बदल देता है:
import type { Params, RouteContext } from 'nuclo-router' export default function Post(ctx: RouteContext<Params<'/blog/:slug'>>) { return article(h1(ctx.params.slug)) // slug: string, nothing else exists }
PageProps
एक पेज का दूसरा तर्क। पृष्ठ को जो चाहिए उसे नष्ट करें।
| प्रॉप | |
|---|---|
layer | जहां पेज स्टैक में बैठता है। नीचे Layer देखें। |
data | रूट का load() क्या लौटा, या undefined बिना किसी के। PageProps<TData>. |
outlet | जहां एक मूल पृष्ठ अपने चाइल्ड रूट को प्रस्तुत करता है। नेस्टेड रूट देखें. |
beforeLeave | इस पृष्ठ को खारिज करने से पहले एक गार्ड से पूछा गया पंजीकरण करता है। देखें गार्ड छोड़ें. |
Layer
जहां एक पेज स्टैक में बैठता है, और उसका रास्ता बाहर आता है।
| सदस्य | |
|---|---|
depth | 0, पुश लेयर्स के लिए 1 और ऊपर |
push(href) | Route.push() |
close(result?) | इस परत और इसके ऊपर की सभी चीज़ों को बंद करें, इसे खोलने वाले push() को हल करें। किसी भी तर्क का मतलब खारिज नहीं किया गया. गहराई पर एक नो-ऑप 0. |
DataLoader और withLoad()
DataLoader (ctx: RouteContext) => TData | Promise<TData> है, जो एक पेज मॉड्यूल का वैकल्पिक load निर्यात है। withLoad(loader, load) एक मार्ग बनाता है जिसका load उसके हिस्से के आयात के बाद के बजाय उसके बगल से शुरू होता है। रूट लोडर देखें।
प्रकार
प्रत्येक प्रकार nuclo-router का नामित निर्यात है, इसलिए एक पेज, एक लोडर या एक गार्ड को पथ से आयात किए बिना टाइप किया जा सकता है।
| टाइप | यह क्या है |
|---|---|
Params<'/blog/:slug'> | एक पैटर्न के लिए पैरामीटर ऑब्जेक्ट। RouteParams अलिखित आकार है, और Match मिलानकर्ता का परिणाम है। |
PageComponent | a पेज: (ctx, { layer, data, outlet, beforeLeave }) => … |
RouteModule | a पेज मॉड्यूल: घटक स्वयं, या import() उपज के रूप में { default, load? } |
RouteLoader | () => RouteModule | Promise<RouteModule>, वैकल्पिक रूप से एक ले जाना load से withLoad() |
RouteTable | तालिका createRouter() लेती है: लोडर के लिए पैटर्न, या एक नेस्टेड तालिका |
RouterOptions | { base?, preload?, idleTimeout?, onNavigate? } |
NavigateOptions | { replace? }, go() |
NavigationKind | 'start' | 'push' | 'replace' | 'pop' | 'layer', onNavigate |
Outlet | का प्रकार outlet() से PageProps |
LeaveGuard | (to) => boolean | void | Promise<…>, beforeLeave() क्या लेता है |
MatchView | matches() दृश्य क्या है: किसी पृष्ठ के पैरामीटर, सामग्री या import() |
push() वर्तमान रूट को बदलने के बजाय उसके शीर्ष पर एक रूट खोलता है, और जो भी परत उसके साथ बंद होती है, उसके साथ उसका समाधान हो जाता है। क्योंकि पेज स्टैक के ऊपर एक एकल list() हैं, एक परत खोलना एक परिशिष्ट है। नीचे दिए गए पृष्ठ को दोबारा प्रस्तुत नहीं किया गया है, इसलिए परत बंद होने पर इसका DOM, इसका फोकस और इसका आधा-भरा फॉर्म सभी अभी भी वहीं हैं।
यह "मुझे एक ऐसे रिकॉर्ड की आवश्यकता है जो अभी तक मौजूद नहीं है" प्रवाह को उस फॉर्म को हटाए बिना काम करता है जिसे उपयोगकर्ता भर रहा था:
// the page underneath const created = await layer.push<Option>('/options/new') if (created) { options.push(created); selected = created.id; update() }
// the pushed page: (ctx, { layer }) layer.close(created) // resolves the push() above with the new option layer.close() // dismissed: resolves with undefined
परतें घोंसला। एक संवाद जिसे अपने स्वयं के संवाद की आवश्यकता होती है वह layer.push() को कॉल करता है और रूट के संदर्भ के बिना, इसकी प्रतीक्षा करता है।
- URL शीर्ष परत है।
push()एक इतिहास प्रविष्टि जोड़ता है, इसलिए एक परत लिंक करने योग्य है और वापस इसे बंद कर देता है, इसकेpush()को खारिज कर दिया गया है, बिल्कुल रद्द की तरह। पीछे औरclose()एक कोड पथ हैं:close()अपना परिणाम रिकॉर्ड करता है और इतिहास को पीछे ले जाता है, औरhistory.stateमें गहराई प्राधिकरण है। ब्राउज़र लगभग 50 प्रविष्टियों को एक टैब में रखते हैं, इसलिए परतों के माध्यम से लंबे समय तक चलने के बाद नीचे की प्रविष्टि गायब हो सकती है: फिरclose()परतों को जगह में बंद कर देता है और वर्तमान प्रविष्टि को दिखाए जाने वाले पृष्ठ पर फिर से लिखता है। - एक साधारण नेविगेशन स्टैक को साफ़ करता है। एक लिंक क्लिक या
go()एक परत नहीं है। यह सब कुछ बदल देता है और हर खुली परत को खारिज कर देता है, इसलिए किसी भी कॉल करने वाले को इंतजार नहीं करना पड़ता है। - A कोल्ड लोड एक स्टैंडअलोन पेज है। URL शीर्ष परत का नाम देता है, लेकिन उसके नीचे के स्टैक का नहीं, इसलिए एक परत का URL खोलने पर यह सीधे
layer.depth === 0और एक निष्क्रियclose()के साथ गहराई 1 पर प्रस्तुत होता है। एक धकेले गए मार्ग को अपने आप में अर्थपूर्ण होना चाहिए। रद्द करें बटन के स्थान पर लिंक आउट की पेशकश करने के लिएlayer.depthपर शाखा करें। - वादा पुनः लोड तक नहीं टिक सकता, क्योंकि यह एक समापन स्थिति में रहता है। परिणाम को प्रवाह के माध्यम से एक शॉर्टकट के रूप में मानें जो लंबे समय तक भी काम करता है।
- प्रतीक्षा में कुछ भी नहीं बचा है।
stop()प्रत्येक खुली परत को खारिज कर देता है, और नेविगेशन द्वारा छोड़ी गई परतundefinedके साथ हल हो जाती है।
गार्ड छोड़ें
एक पृष्ठ जिसमें रखने योग्य कुछ चीज़ होती है, जैसे कि आधा भरा हुआ फॉर्म, एक गार्ड को पंजीकृत करता है। पृष्ठ को त्यागने से पहले यह पूछा जाता है, और false (या इसका वादा) लौटाने से पृष्ठ बना रहता है:
import type { PageProps, RouteContext } from 'nuclo-router' export default function Edit(_ctx: RouteContext, { beforeLeave }: PageProps) { let dirty = false beforeLeave(() => !dirty || confirm('Discard your changes?')) return form(input({ onInput: () => (dirty = true) }), …) }
- केवल जब पेज जाता है। लेआउट के तहत एक चाइल्ड नेविगेशन, या एक क्वेरी या हैश परिवर्तन जो पेज रखता है, नहीं पूछता है। एक गार्ड अपने पेज के साथ जीता और मरता है, इसलिए अपंजीकृत करने जैसा कुछ भी नहीं है।
- हर तरह से बाहर। एक लिंक,
go(), पीछे और आगे, एक परत काclose(), औरgo()एक यूआरएल पर जो राउटर के पास नहीं है (ऐप को छोड़कर, जहांtonullहै)। अस्वीकृत बैक या फ़ॉरवर्ड को वापस चला दिया जाता है, इसलिए पता बार पृष्ठ पर बना रहता है। टैब बंद करना या पुनः लोड करनाbeforeunloadका काम है। toवहRouteContextहै जिस पर नेविगेशन जा रहा है। गार्ड सबसे पहले अंतरतम दौड़ते हैं और पहले इनकार पर रुक जाते हैं। जो फेंकता है वह पृष्ठ रखता है।close(result)गार्ड से भी गुजरता है, इसलिए परिणाम के साथ बंद करने से पहले अपना ध्वज साफ़ करें।- राउटर केवल अपने द्वारा लिखी गई इतिहास प्रविष्टियों को वापस चला सकता है। जिस पर बैक या फॉरवर्ड नहीं किया गया, जैसे कि मूल
#fragmentलिंक या किसी अन्य स्क्रिप्ट काpushState, नहीं पूछा जाता है।
matches() और सब-राउटर
#router.outlet() पृष्ठ प्रस्तुत करता है। router.matches(table) कुछ और प्रस्तुत करता है जो इस पर निर्भर करता है कि आप कहां हैं, जैसे साइडबार, ब्रेडक्रंब या टूलबार, अपनी छोटी तालिका से, बिना रूट लोड किए या पेज जोड़े:
div( aside( router.matches({ '/': () => nav('Home links'), '/blog/new': () => nav('Drafting tips'), '/blog/:slug': ({ slug }) => nav(`More like ${slug}`), '*': () => import('./Sidebar.ts'), // code-split, rendered once loaded }), ), main(router.outlet()), )
- रूट तालिका के समान मिलान। पैटर्न, प्राथमिकता, कच्चे-पथ मिलान और
baseस्ट्रिपिंग राउटर के स्वयं के हैं, इसलिए एक दृश्य और एक पृष्ठ हमेशा यूआरएल के बारे में सहमत होते हैं। यहrouter.pathकी तरह शीर्ष परत का पथ पढ़ता है। - A view
(params, ctx) => contentहै, जहांparamsइसके पैटर्न से टाइप किया गया है औरctxव्यू केpatternऔरparamsके साथRouteContextहै। इसके बजाय यह पेज मॉड्यूल काimport()लौटा सकता है, जिसे लैंड करने के बादctxके साथ कॉल किया जाता है। - कुछ भी मेल नहीं खाता, कुछ भी प्रस्तुत नहीं करता। फ़ॉलबैक के लिए एक
'*'दृश्य जोड़ें। यह आउटलेट की तरहstart()से पहले औरstop()के बाद खाली है। - यह केवल तभी पुन: प्रस्तुत होता है जब मार्ग बदलता है, इसलिए एक दृश्य पृष्ठ को रखने वाली किसी भी चीज़ पर अपना DOM रखता है।
- A आलसी दृश्य अपने मॉड्यूल आने तक कुछ भी प्रस्तुत नहीं करता है। एक विफल
import()कोconsole.errorके साथ लॉग किया गया है,router.errorसेट नहीं करता है, और अगले नेविगेशन पर पुनः प्रयास किया जाता है। एक साधारणupdate()पुनः प्रयास नहीं करता है। - सर्वर पर एक आलसी दृश्य कुछ भी नहीं भेजता है, क्योंकि इसका मॉड्यूल क्लाइंट पर लैंड करता है। पहले HTML में शामिल होने वाली किसी भी चीज़ के लिए एक समकालिक दृश्य का उपयोग करें।
सब-राउटर
रूट्स नेस्ट, इसलिए एक सब-राउटर आमतौर पर सिर्फ एक नेस्टेड टेबल होता है। एक राउटर: सेक्शन का अपना '/' पेज इसका क्रोम है, और इसके चाइल्ड नोड उस पेज के outlet() में रेंडर करते हैं, इसलिए क्रोम सेक्शन के भीतर नेविगेशन से बच जाता है:
createRouter({ '/': …, '/docs': docsRoutes, '*': … })
या फीचर को अपना स्वयं का राउटर दें, बनाया गया लेकिन कभी शुरू नहीं हुआ। माता-पिता के पास एकल कैच-ऑल प्रविष्टि होती है और बच्चा अपने स्वयं के पैटर्न, पैरामीटर और फ़ॉलबैक के अनुसार पूंछ का समाधान करता है:
// parent table '/docs/*rest': () => import('./docs/Shell.ts'),
const docs = createRouter(docsRoutes, { base: '/docs' }) const hit = docs.match() // { pattern: '/:topic', … } const page = await docsRoutes[hit.pattern]() // the app's own lookup
इसे कभी शुरू न करना ही मुद्दा है। start() वह है जो श्रोताओं को जोड़ता है, और दो शुरू किए गए राउटर संघर्ष करते हैं। व्यापार यह है कि चाइल्ड नोड के पास रिज़ॉल्यूशन होता है, इसलिए रूट लोडर, जो एक स्टार्टर राउटर चलाता है, आपको कॉल करना है। यह आकार पूंछ बदलने पर शेल का पुनर्निर्माण भी करता है, क्योंकि राउटर एक मार्ग देखता है और शेल बाकी को स्वयं ही हल कर लेता है। नेस्टेड टेबल वह है जो आपको माता-पिता बनाती है, राउटर माउंटेड रहता है, इसलिए इसे तब तक प्राथमिकता दें जब तक कि फीचर को वास्तव में अपने राउटर, बेस और फ़ॉलबैक की आवश्यकता न हो।
एक ऐप में उतने ही राउटर हो सकते हैं जितने उसके क्षेत्र हैं। नियम जो इसे सरल रखता है: प्रति दस्तावेज़ बिल्कुल एक शुरू करें, और match() के साथ यूआरएल को हल करते हुए, हर दूसरे राउटर को अनस्टार्ट रहने दें। केवल प्रारंभ किया गया राउटर ही यूआरएल, इतिहास और क्लिक श्रोता का स्वामी होता है। बाकी टेबलें हैं जिनके बारे में आप प्रश्न पूछते हैं - और प्रत्येक क्षेत्र अपने स्वयं के स्पिनर के साथ अपनी घड़ी पर लोड कर सकता है।
संयुक्त लोडिंग: लोडर की एक श्रृंखला
मिलान श्रृंखला का प्रत्येक पृष्ठ निर्यात किया जा सकता है load(). चाइल्ड रूट के ठंडे नेविगेशन पर, लेआउट का लोडर और पेज का लोडर चलता है समानांतर में, और एक router.pending पूरी श्रृंखला को कवर करता है: पिछला पृष्ठ जोड़ी के सुलझने तक स्क्रीन पर रहता है, इसलिए आधा भरा हुआ ट्री कभी पेंट नहीं करता। प्रत्येक घटक को अपना स्वयं का प्राप्त होता है data:
'/shop': { '/': () => import('./pages/Shop.ts'), // layout, has a load() './:section': () => import('./pages/Section.ts'), // page, has a load() },
import type { DataLoader, PageProps, RouteContext } from 'nuclo-router' export const load: DataLoader<ShopData> = async () => { await wait(500) // a slow API return { sections: ['toys', 'books', 'music'] } } // the layout gets its own data, and its outlet for the child page export default function Shop(_ctx: RouteContext, { data, outlet }: PageProps<ShopData>) { return div( h1('Shop'), nav(...data.sections.map((s) => a({ href: router.href(`/shop/${s}`) }, s))), main(outlet()), ) }
export const load: DataLoader<SectionData> = async (ctx) => { await wait(900) // slower than the layout's return { section: ctx.params.section, items: catalog[ctx.params.section] } } // the page gets its own data: the two loaders ran beside each other export default function Section(_ctx: RouteContext, { data }: PageProps<SectionData>) { return article(h2(data.section), ul(...data.items.map((i) => li(i)))) }
दोनों लोडर एक के बाद एक नहीं बल्कि एक साथ शुरू होते हैं। router.pending तब सत्य है जब इनमें से किसी एक का इंतजार किया जा रहा है, और false शेष समय में - एक प्रीलोडेड या उत्सुक मार्ग इसे कभी फ़्लिप नहीं करता है। अनुभागों के बीच जाने से लेआउट बना रहता है, इसलिए केवल अनुभाग का लोडर ही दोबारा चलता है; लेआउट का डेटा, इसका DOM और आधा टाइप किया गया इनपुट सभी जीवित हैं। उसी यूआरएल पर दोबारा नेविगेट करने से लीफ का लोडर फिर से चलता है और उसकी पंक्ति फिर से बन जाती है, बिल्कुल एक पेज की तरह।
एकाधिक लोडिंग: अपने स्वयं के स्पिनर के साथ एक दूसरा राउटर
एक स्वतंत्र क्षेत्र - एक साइडबार, एक ट्रे, एक लाइव क्षेत्र - को अपना स्वयं का राउटर मिलता है, बनाया गया लेकिन कभी शुरू नहीं हुआ, और इसका अपना लोडिंग फ़्लैग होता है। router.pending प्रारंभ किए गए राउटर से संबंधित है और इस क्षेत्र के बारे में कुछ नहीं कहता है, इसलिए दोनों साथ-साथ लोड हो सकते हैं और कोई भी पहले समाप्त कर सकता है:
import { createRouter } from 'nuclo-router' // Created but never started: start() is what attaches the history and click // listeners, and only the main router is started. match() attaches nothing. const standIn = () => ({ default: () => div() }) // never rendered const sidebarRouter = createRouter({ '/shop': standIn, '/shop/:section': standIn, '*': standIn, }, { base: '/docs' }) // the same base as the main router export const sidebar = { loading: false, info: null as SidebarInfo | null } export async function refreshSidebar(at: string) { const hit = sidebarRouter.match(at) // the second router resolves if (!hit || (hit.pattern !== '/shop' && hit.pattern !== '/shop/:section')) { sidebar.info = null; update(); return } sidebar.loading = true // its own flag, its own spinner sidebar.info = null; update() const data = await fetchSidebar(hit.params.section) sidebar.loading = false; sidebar.info = data update() }
createRouter(routes, { onNavigate: (ctx, kind) => { announce(kind) refreshSidebar(ctx.url) // every navigation re-resolves the sidebar }, })
दूसरे राउटर के बारे में पूछा गया, लेकिन कभी नहीं बताया गया: match() यूआरएल पढ़ता है और एक संदर्भ देता है, और साइडबार के बारे में कुछ भी मुख्य रूट टेबल में प्रवेश नहीं करता है। onNavigate प्रत्येक हल किए गए नेविगेशन के बाद चलता है, जिसमें प्रारंभिक भी शामिल है, इसलिए यह एक ऐसा स्थान है जो यूआरएल के साथ एक स्वतंत्र क्षेत्र रखता है।
pendingप्रारंभ किए गए राउटर से संबंधित है। एक कभी प्रारंभ न होने वाले राउटर का कोई रूट नहीं है, कोईpendingनहीं है और कोईerrorनहीं है। क्षेत्र अपने स्वयं के फ़्लैग ट्रैक करता है, और दूसरा स्पिनर पहले के बगल में बैठ सकता है। डेमो में साइडबार 800 एमएस के लिए लोड होता है जबकि रूट के लोडर 900 एमएस के लिए चलते हैं, इसलिए पेज साइडबार से पहले या दूसरे तरीके से खत्म हो सकता है।match()कुछ भी नहीं जोड़ता है। किसी भी संख्या में अनस्टार्ट राउटर दस्तावेज़ साझा कर सकते हैं; प्रत्येक की अपनी तालिका, आधार और मॉड्यूल कैश है, और उनमें से कोई भी इतिहास को नहीं छूता है। उनकी कीमत एक टेबल और एक मैचर है।- इसके लोडर कभी नहीं चलते हैं। प्रारंभ किया गया राउटर उससे मेल खाने वाले पृष्ठों के
load()को चलाता है। कभी भी प्रारंभ न किया गया राउटर ही समस्या का समाधान करता है, इसलिए यदि इसके दृश्यों को डेटा की आवश्यकता है, तो इसे स्वयं प्राप्त करें - डेमो काrefreshSidebar()। यदि आप लोडर चाहते हैं, तो एक नेस्टेड टेबल या एक region एक पेज भरता है उस काम को स्टार्ट किए गए राउटर के साथ रखता है। - Guard बासी उत्तर. धीमी साइडबार फ़ेच को उस अनुभाग पर नहीं उतरना चाहिए जहां से आपने नेविगेट किया था। प्रति रिफ्रेश एक जनरेशन काउंटर को टक्कर देता है और उन उत्तरों को छोड़ देता है जो अब मेल नहीं खाते हैं, जिस तरह राउटर खुद ही ओवरटेक किए गए नेविगेशन को रिटायर कर देता है।
- A आलसी
matches()दृश्य में कोईpendingभी नहीं है। यह तब तक कुछ भी प्रस्तुत नहीं करता जब तक इसका मॉड्यूल लैंड नहीं हो जाता। तीन लोडिंग सिस्टम, तीन स्वतंत्र घड़ियाँ: मार्ग काpending, आपके अपने फ़्लैग, और एक आलसी दृश्य जो अभी तक वहाँ नहीं है।
लिंक संभालना
#प्रत्यायोजित श्रोता एक SVG <a> सहित <a href> पर एक बायाँ-क्लिक नेविगेट करता है और एक के अंदर नेस्टेड किसी भी चीज़ पर क्लिक करता है, और बाकी सभी चीज़ों के लिए अलग हट जाता है: संशोधक-क्लिक और मध्य-क्लिक, _self के अलावा एक target, download, rel="external", क्रॉस-ओरिजिन hrefs, वर्तमान पृष्ठ पर हैश-केवल लिंक, base के बाहर पथ, कोई रूट मेल नहीं खाने वाले पथ (इसलिए सर्वर-रेंडर 404 अभी भी पहुंच योग्य है), और किसी भी घटना पर ऐप हैंडलर ने पहले से ही preventDefault() पर कॉल किया है। data-nuclo-router="off" के साथ स्पष्ट रूप से एक लिंक ऑप्ट आउट करें।
स्क्रॉलिंग
एक पुश किया गया नेविगेशन वर्तमान स्क्रॉल स्थिति को सुरक्षित रखता है। जब नए URL में पृष्ठ पर किसी तत्व से मेल खाने वाला #hash होता है, तो वह तत्व दृश्य में स्क्रॉल हो जाता है। popstate ब्राउज़र पर स्क्रॉल करना छोड़ देता है। नए पृष्ठों को शीर्ष पर लाने के लिए, इसे onNavigate में करें, जो बताता है कि नेविगेशन कैसे हुआ:
let shown = '' createRouter(routes, { onNavigate: (ctx, kind) => { // Not on Back/Forward (the browser restores those), nor for a query change on the same page. if (kind === 'push' && ctx.path !== shown && !ctx.hash) window.scrollTo(0, 0) shown = ctx.path }, })
नोट्स
- कुछ भी बरकरार नहीं रखा गया है। रूट पेज फ़ंक्शन और उसके संदर्भ को रखता है, कभी DOM को नहीं। दूर नेविगेट करने से पिछला पृष्ठ रिलीज़ हो जाता है।
stop()श्रोताओं और किसी भी इन-फ़्लाइट लोड को रिलीज़ करता है, औरpendingको साफ़ करता है, इसलिए एक सेवानिवृत्त रूट कभी भी स्पिनर को परेशान नहीं करता है। एक मॉड्यूल जोstop()के बाद हल हो जाता है चुपचाप हटा दिया जाता है। - सर्वर पर समवर्ती-सुरक्षित.
start()प्रति-अनुरोध स्थिति लौटाता है औरrun()इसे एक सिंक्रोनस रेंडर तक सीमित करता है, इसलिए इंटरलीव्ड अनुरोध एक-दूसरे का रूट नहीं देख सकते हैं। केवल मॉड्यूल कैश साझा किया गया है। दायरा ख़त्म हो जाता है जबrun()का फ़ंक्शन वापस आ जाता है.renderToString()समकालिक है, इसलिए यह पर्याप्त है। एक एसिंक्स या स्ट्रीमिंग रेंडर के लिए रूट को पार करने की आवश्यकता होगीawaitएस, जोrun()ऐसा नहीं करते। popstateपर स्क्रॉल पुनर्स्थापन ब्राउज़र का (scrollRestoration) है, राउटर का नहीं।- प्रति दस्तावेज़ एक राउटर प्रारंभ करें। प्रत्येक
start()अपना स्वयं का प्रतिनिधिclickश्रोता जोड़ता है, और दूसरे राउटर काstart()पहले को रिटायर नहीं करता है। उसी राउटर पर केवल दूसराstart()काम करता है। किसी अन्य तालिका के विरुद्ध URL को हल करने के लिए, एक राउटर बनाएं और केवलmatch()पर कॉल करें। यह कुछ भी नहीं जोड़ता है. एक स्वतंत्र क्षेत्र इस तरह मार्ग केpendingके बगल में अपनी लोडिंग चला सकता है। मल्टीपल राउटर्स & कंबाइंड लोडिंग देखें। - डिज़ाइन द्वारा शामिल नहीं है: हैश और मेमोरी इतिहास, क्योंकि रूट पथनाम में रहता है और एक सर्वर को हर रूट की सेवा करनी चाहिए; एक यूआरएल द्वारा संचालित दो स्वतंत्र रूप से रूट किए गए क्षेत्र, क्योंकि एक पेज कई क्षेत्रों को
into()से भर सकता है, लेकिन वे सभी एक मिलान रूट से संबंधित हैं - एक स्वतंत्र क्षेत्र अभी भी अपना स्वयं का अनस्टार्ट राउटर और लोडिंग रख सकता है, जैसा कि मल्टीपल राउटर्स & कंबाइंड लोडिंग में होता है; और होवर प्रीलोडिंग, जो आपकोmouseenterसे रूट के स्वयं के लोडर को कॉल करके मिलती है।
उदाहरण
रिपॉजिटरी में दो रन करने योग्य उदाहरण एक दूसरे के पूरक हैं, इसलिए हर सेटिंग कहीं न कहीं देखने योग्य है:
examples/router: संपूर्ण API का क्लाइंट-साइड टूर। प्रत्येक पैटर्न फॉर्म और प्राथमिकता नियम, लिंक-हैंडलिंग ऑप्ट-आउट, लाइव नियंत्रण के साथ प्रत्येक रूट सदस्य, एक उप-राउटर, लेयर स्टैक तीन गहरी, दो माता-पिता पर एक सापेक्ष अनुभाग, औरpendingऔरerrorके साथ रूट लोडर।preload: falseसेट करता है ताकि वे स्थितियाँ दृश्यमान रहें।examples/router-ssr:renderToString()प्रति अनुरोध, हाइड्रेशन जो सर्वर के नोड्स, वास्तविक 404 स्थिति कोड, एक गैर-रूटbase, निष्क्रिय प्रीलोडिंग का दावा करता है जिसे आप नेटवर्क पैनल में देख सकते हैं, और एक लोडर जिसका डेटा सर्वर से दिया जाता है ग्राहक इसलिए हाइड्रेशन पुनः प्राप्त नहीं करता है।
Nucloकोई गणना नहीं हैAPI. एक साधारण फ़ंक्शन का उपयोग करें, और इसे गतिशील मानों से कॉल करें:
let items = ['apple', 'banana', 'cherry'] let filter = '' const visible = () => items.filter((item) => item.includes(filter)) const Fruits = () => div( p(() => `Showing ${visible().length} of ${items.length}`), ul(list(visible, (item) => li(item))), ) filter = 'an' update()
साधारण async/await का उपयोग करें, और हर बार स्थिति बदलने पर update() पर कॉल करें। एक फ़ेच के लिए, इसका मतलब है एक बार await से पहले और एक बार बाद में:
let status: 'idle' | 'loading' | 'done' | 'error' = 'idle' let result: unknown = null async function loadData() { status = 'loading' update() try { result = await fetch('/api/data').then((r) => r.json()) status = 'done' } catch { status = 'error' } update() }
सुझाए गए तरीके
#- बैच परिवर्तन। आपको आवश्यक सभी स्थिति बदलें, फिर एक बार
update()पर कॉल करें। - गतिशील मूल्यों को तेज और शुद्ध रखें। ये सभी प्रत्येक
update()पर चलते हैं। - छोटे, लगातार अपडेट, जैसे टाइपिंग या टाइमर के लिए
scope()का उपयोग करें। - दस्तावेज़ में प्रस्तुत करें। नोड्स जो दस्तावेज़ में नहीं हैं जब
update()चलता है तो अपडेट करना बंद कर देता है। - उन चाइल्ड नोड के लिए
when()औरlist()का उपयोग करें, न कि कोई फ़ंक्शन जो नोड्स लौटाता है। classNameका उपयोग करें,classका नहीं, और गतिशील क्लास के लिए() => cx(...)या{ className: () => … }का उपयोग करें।- मॉड्यूल स्तर पर शैलियों को एक बार परिभाषित करें।
- सर्वर रेंडरिंग: पहले रेंडर करें, फिर
<style id="nuclo-styles">मेंgetCssText()डालें। अनुरोध डेटा को तर्क के रूप में पास करें, और आउटपुट में HTML टिप्पणियाँ रखें।
API सूचकांक
#प्रत्येक सार्वजनिक समारोह, और यह कहाँ से आता है।
nuclo (वैश्विक और नामित निर्यात)
update(...scopeIds): सभी गतिशील मान, या दिए गए दायरे में चलाएं।scope(...ids):update(id)के लिए एक एलिमेंट स्कोप आईडी दें।when(condition, ...content): सशर्त सामग्री,.when()और.else()के साथ।list(items, renderItem): प्रति आइटम एक पंक्ति, पहचान से मेल खाती है।region(options)औरinto(id, ...content): ट्री में एक नामित स्थान, और सामग्री जो इसे कहीं से भी भरती है।on(type, listener, options?): एक इवेंट श्रोता, याon('mount')/on('destroy')।render(App, parent?): बनाएं और जोड़ें।hydrate(App, parent?): सर्वर HTML पर कब्ज़ा करें।forceUpdate(): घटकों को फिर से कॉल करें और DOM को पैच करें।css(name?, style): एक स्टाइल ऑब्जेक्ट से उत्पन्न कक्षाएं।cx(...inputs): शैलियों को मिलाएं, बाद में जीतें।createCss(theme): थीम वालेcss,cx,variants,keyframesऔरglobalStyle।variants(config): विकल्पों में से शैलियाँ।keyframes(frames)औरglobalStyle(selector, style)।
nuclo (केवल निर्यात नाम से)
nuclo/ssr
renderToString(input),renderManyToString(inputs),renderToStringWithContainer(input, tag?, attrs?)औरgetCssText()।
nuclo/polyfill
- A साइड-इफेक्ट आयात, प्लस
document,Event,CustomEvent,Node,Element,HTMLElementऔरNuclo*कक्षाएं।
nuclo-router (अलग पैकेज)
createRouter(table, options?): राउटर,start(),match(),outlet(),matches(),go(),push()के साथ,href()औरstop()।withLoad(loader, load): एक मार्ग जिसका डेटा लोडर उसके हिस्से के बगल से शुरू होता है।- Types:
RouteContext,PageProps,Params,DataLoader,RouteTableऔर अधिक।
ग्लोबल्स
- 175 टैग बिल्डर्स:
div,span, ...,var_, औरsvgSvg,pathSvg, .... - Types:
NodeModFn,NodeModLike,ExpandedElementऔर अधिक।StyleResultजैसे स्टाइलिंग प्रकार को'nuclo'से भी आयात किया जा सकता है।
विशेष कुंजी
- विशेषताएं:
className,style,on*हैंडलर,onMountऔरonDestroy. - स्टाइल ऑब्जेक्ट: शॉर्टहैंड, छद्म स्टेट,
&चयनकर्ता,@नियम औरraw।
अक्सर पूछे जाने वाले प्रश्न
#क्या स्थिति बदलने से UI अपडेट हो जाता है?
नंबर। DOM तब तक वैसा ही रहता है जब तक आप update() पर कॉल नहीं करते।
क्या Nuclo सिग्नल, प्रॉक्सी या वर्चुअल DOM का उपयोग करता है?
नंबर स्टेट साधारण JavaScript रहता है। जब एक ट्री प्रस्तुत किया जाता है, तो Nuclo वास्तविक तत्व और टेक्स्ट नोड बनाता है। update() प्रत्येक पंजीकृत गतिशील मान, या दिए गए दायरे के अंदर चलाता है, और उन्हीं नोड्स को पैच करता है जहां मान बदल गया है। कोई निर्भरता ग्राफ़ और कोई वर्चुअल ट्री नहीं है।
मेरा पेज "false" क्यों दिखाता है?
बूलियन्स पाठ के रूप में प्रस्तुत करते हैं। div(ok && span('Done')) "false" दिखाता है। चाइल्ड नोड के लिए when(() => ok, span('Done')) और टेक्स्ट के लिए ok ? 'Done' : '' का उपयोग करें।
जब मैं आइटम संपादित करता हूं तो मेरी सूची पंक्ति क्यों नहीं बदलती?
उन आइटमों की पंक्तियाँ जो अभी भी सूची में हैं, रखी जाती हैं, दोबारा प्रस्तुत नहीं की जाती हैं। बदलने वाले फ़ील्ड के लिए () => todo.text जैसे गतिशील मानों का उपयोग करें। list() देखें।
मेरे UI का हिस्सा अपडेट होना क्यों बंद हो जाता है?
आमतौर पर जब update() चलता था तो इसके नोड्स दस्तावेज़ में नहीं होते थे। Nuclo उन नोड्स को ट्रैक करना बंद कर देता है, भले ही आप उन्हें संलग्न कर लें, इसलिए दस्तावेज़ में मौजूद कंटेनर में प्रस्तुत करें। दो अन्य कारण: एक when() जिसकी स्थिति थ्रो अपडेट करना बंद कर देती है, और एक टेक्स्ट फ़ंक्शन जो अपने पहले रन पर थ्रो करता है वह खाली रहता है। update() देखें।
मुझे forceUpdate() का उपयोग कब करना चाहिए?
केवल स्थिर मानों में दुर्लभ परिवर्तनों के लिए, जैसे भाषा बदलना। बाकी सभी चीज़ों के लिए, update() का उपयोग करें। forceUpdate() देखें।
मैं किसी पेज को प्रोप के रूप में पास किए बिना लेआउट में कैसे डालूं?
region({ id: 'main' }) के साथ स्थान को चिह्नित करें और पृष्ठ को into('main', …) पर लौटाएं। region() & into() देखें।
मैं किसी ऐप को कैसे अनमाउंट करूं?
इसे when() में लपेटें और स्थिति को बंद कर दें। DOM को स्वयं हटाने से onDestroy नहीं चलता है। Lifecycle देखें।
क्या Nuclo में राउटर शामिल है?
कोर पैकेज में नहीं है। सर्वर रेंडरिंग, हाइड्रेशन, कोड-स्प्लिट रूट और निष्क्रिय प्रीलोडिंग के साथ जहाजों को nuclo-router के रूप में अलग से रूट करना। इसे npm install nuclo nuclo-router के साथ इंस्टॉल करें। nuclo-router देखें।
क्या Nuclo का प्रभाव है?
नंबर onMount और onDestroy प्रति तत्व एक बार चलते हैं, सम्मिलन और निष्कासन से बंधे होते हैं। कुछ भी दोबारा नहीं चलता क्योंकि कुछ स्टेट बदल गए।