- init
vite
project with npm
npm init vite@latest
- select option
vanilla-ts
after, install dependency
npm i
use npm
npm i nvagir
first, write simple example.
// /src/pages/App.ts
import { html } from 'nvagir'
const { el } = html`
<div>
this is first App Page
</div>
`
document.getElementById('app')!.append(el.dom)
html
is a tag function, you can write HTML code as usual.
html<D, C>
return property:
-
el
:{ sign, dom }
,-
sign
is used to determine whether it is a component element. -
dom
is HTMLElement, that is the root HTMLElement.
-
-
doms
: all captured DOM elements,D extends Record<string, HTMLElement> = {}
,doms
type is D -
components
: all components usedC extends Readonly<Component | Component[]> = []
,components
type isTupleMap2NE<C>
note:
html
only return first root HTMLElement
Use n@name="xxx"
to capture HTMLElement
.
Modify the above example.
const { el, doms } = html<{
sonBox: HTMLElement
}>`
<div>
this is first App Page
<div n@name="sonBox">
this is son Box
</div>
</div>
`
doms.sonBox.textContent // this is son Box
Now you can use doms.sonBox
to do anything.
Use n@event=${handlerEvent}
to bind event
Modify the above example.
const handlerClick = () => {
console.log('clicked')
}
const { el, doms } = html<{
sonBox: HTMLElement
}>`
<div>
this is first App Page
<button n@click=${handlerClick}>click</button>
<div n@name="sonBox">
this is son Box
</div>
</div>
`
n@event=${handlerEvent}
equivalent todom.addEventLister(event, handlerEvent)
.
Use reactive(data, reactiveFc)
to react to data changes
Modify the above example.
import { html, reactive } from 'nvagir'
const data = {
text: 'this is son Box',
}
const handlerClick = () => {
console.log('clicked')
proxyData.text = doms.text.value
doms.text.value = ''
}
const { el, doms } = html<{
text: HTMLInputElement
sonBox: HTMLElement
}>`
<div>
<input n@name="text" />
<button n@click=${handlerClick}>modify text</button>
<div n@name="sonBox">${data.text}</div>
</div>
`
const proxyData = reactive(data, (p, data) => {
switch (p) {
case 'text':
doms.sonBox.textContent = data.text
}
})
In the above example, we input after, click button will change sonBox
text content.
reactive(data, reactiveFc) => proxyData
: when we change proxyData, will callreactiveFc(changePropName, changedData)
.
Now encapsulate the above example.
import { html, PageComponent, reactive } from 'nvagir'
const App: PageComponent = () => {
const data = {
text: 'this is son Box',
}
const handlerClick = () => {
console.log('clicked')
proxyData.text = doms.text.value
doms.text.value = ''
}
const { el, doms } = html<{
text: HTMLInputElement
sonBox: HTMLElement
}>`
<div>
<input n@name="text" />
<button n@click=${handlerClick}>modify text</button>
<div n@name="sonBox">${data.text}</div>
</div>
`
const proxyData = reactive(data, (p, data) => {
switch (p) {
case 'text':
doms.sonBox.textContent = data.text
}
})
return { el }
}
document.getElementById('app')!.append(App().el.dom)
export default App
PageComponent
: type is() => { el }
, because this is page render component, i will introduce nvagir-router later.
import { html, NE, PageComponent, reactive } from 'nvagir'
const Son = () => {
const data = {
text: 'this is son Box',
}
const { el, doms } = html<{
sonBox: HTMLElement
}>` <div n@name="sonBox">${data.text}</div> `
const proxyData = reactive(data, (p, data) => {
switch (p) {
case 'text':
doms.sonBox.textContent = data.text
}
})
const component: NE<'son'> = {
name: 'son',
el,
}
return component
}
const App: PageComponent = () => {
const handlerClick = () => {
console.log('clicked')
// proxyData.text = doms.text.value
doms.text.value = ''
}
const { el, doms } = html<{
text: HTMLInputElement
}>`
<div>
<input n@name="text" />
<button n@click=${handlerClick}>modify text</button>
${Son()}
</div>
`
return { el }
}
document.getElementById('app')!.append(App().el.dom)
export default App
We declare a Son
child component, but can not modify Son
text, so we need change to following code to expose and modify Son().proxyData
.
import { DNE, html, PageComponent, reactive } from 'nvagir'
const Son = () => {
const data = {
text: 'this is son Box',
}
const { el, doms } = html<{
sonBox: HTMLElement
}>` <div n@name="sonBox">${data.text}</div> `
const proxyData = reactive(data, (p, data) => {
switch (p) {
case 'text':
doms.sonBox.textContent = data.text
}
})
const component: DNE<'son', typeof proxyData> = {
name: 'son',
el,
proxyData,
}
return component
}
const App: PageComponent = () => {
const handlerClick = () => {
console.log('clicked')
components.son.proxyData.text = doms.text.value
doms.text.value = ''
}
const { el, doms, components } = html<
{
text: HTMLInputElement
},
[typeof Son]
>`
<div>
<input n@name="text" />
<button n@click=${handlerClick}>modify text</button>
${Son()}
</div>
`
return { el }
}
document.getElementById('app')!.append(App().el.dom)
export default App
Child-component will return NE | DNE | MNE | DAMNE
-
NE
isNvagirElement
alias, and hasN
generic type.NE<'son'>
same as{ name: 'son', el }
mean is child component
-
DNE
isDataNvagirElement
alias, and hasN, D
generic type.DNE<'son', typeof proxyData>
same as{ name: 'son', el, proxyData }
mean is child component and expose proxyData
-
MNE
isMethodsNvagirElement
alias, and hasN, M
generic type.MNE<'son', typeof methods>
same as{ name: 'son', el, methods }
mean is child component and expose methods
-
DAMNE
isDataAndMethodsNvagirElement
alias, and hasN, D, M
generic type.DAMNE<'son', typeof proxyData, typeof methods>
same as{ name: 'son', el, proxyData, methods }
mean is child component and expose proxyData and methods
That's all fundamental concepts and basic parent-child component communication.
When we parent component data change, we probably need re-render child component, so let's write a basic demo to learn how to re-render child component.
import { html, NE, PageComponent, render } from 'nvagir'
const Son = (text = 'this is son Box') => {
const data = {
text,
}
const { el } = html` <div>${data.text}</div> `
const component: NE<'son'> = {
name: 'son',
el,
}
return component
}
const App: PageComponent = () => {
const handlerClick = () => {
console.log('clicked')
render(doms.sonBox, Son, components, doms.text.value)
doms.text.value = ''
}
const { el, doms, components } = html<
{
text: HTMLInputElement
sonBox: HTMLElement
},
[typeof Son]
>`
<div>
<input n@name="text" />
<button n@click=${handlerClick}>modify text</button>
<div n@name="sonBox">${Son()}</div>
</div>
`
return { el }
}
document.getElementById('app')!.append(App().el.dom)
export default App
We use render
utility method to re-render we child component.
render(target, childComponent, components, ...childComponentProps) => HTMLElement
-
target
indicates where to render -
childComponent
indicates you will re-render child component -
components
make the corresponding attribute ofcomponents
point to the new component -
...childComponentProps
child component props -
=> HTMLElement
DOM element rendered on the page
We may also use several child components.
import { html, NE, PageComponent, render, renderMaps } from 'nvagir'
const Son = (text = 'this is son Box') => {
const data = {
text,
}
const { el } = html` <div>${data.text}</div> `
const component: NE<'son'> = {
name: 'son',
el,
}
return component
}
const App: PageComponent = () => {
const handlerClick = () => {
console.log('clicked')
renderMaps(doms.sonBox, Son, components, ...new Array(+doms.text.value))
doms.text.value = ''
}
const { el, doms, components } = html<
{
text: HTMLInputElement
sonBox: HTMLElement
},
[typeof Son[]]
>`
<div>
<input n@name="text" />
<button n@click=${handlerClick}>modify text</button>
<div n@name="sonBox">${[Son()]}</div>
</div>
`
return { el }
}
document.getElementById('app')!.append(App().el.dom)
export default App
renderMaps(target, childComponent, components, ...childComponent[]) => HTMLElement[]
- The meaning of the parameters is similar to the render above.