-
Notifications
You must be signed in to change notification settings - Fork 28
Chapter 18—Routing the application
angel edited this page Jul 20, 2024
·
2 revisions
Note
|
You can check the code written for this chapter in the #194 pull request. |
The application object expects an options object with an optional router (app.js)
import { destroyDOM } from './destroy-dom'
import { h } from './h'
import { mountDOM } from './mount-dom
++import { NoopRouter } from './router'
--export function createApp(RootComponent, props = {}) {
++export function createApp(RootComponent, props = {}, options = {}) {
let parentEl = null
let isMounted = false
let vdom = null
++ const context = {
++ router: options.router || new NoopRouter(),
++ }
// -- snip -- //
return {
mount(_parentEl) {
if (isMounted) {
throw new Error('The application is already mounted')
}
parentEl = _parentEl
vdom = h(RootComponent, props)
-- mountDOM(vdom, parentEl)
++ mountDOM(vdom, parentEl, null, { appContext: context })
++ context.router.init()
isMounted = true
},
unmount() {
if (!isMounted) {
throw new Error('The application is not mounted')
}
destroyDOM(vdom)
++ context.router.destroy()
reset()
},
}
}
Saving the app context in the component (component.js)
export function defineComponent({
render,
state,
onMounted = emptyFn,
onUnmounted = emptyFn,
...methods
}) {
class Component {
#isMounted = false
#vdom = null
#hostEl = null
#eventHandlers = null
#parentComponent = null
#dispatcher = new Dispatcher()
#subscriptions = []
++ #appContext = null
constructor(props = {}, eventHandlers = {}, parentComponent = null) {
this.props = props
this.state = state ? state(props) : {}
this.#eventHandlers = eventHandlers
this.#parentComponent = parentComponent
}
onMounted() {
return Promise.resolve(onMounted.call(this))
}
onUnmounted() {
return Promise.resolve(onUnmounted.call(this))
}
++ setAppContext(appContext) {
++ this.#appContext = appContext
++ }
++ get appContext() {
++ return this.#appContext
++ }
// -- snip -- //
}
// -- snip -- //
}
Setting the application context to each newly created component (mount-dom.js)
function createComponentNode(vdom, parentEl, index, hostComponent) {
const { tag: Component, children } = vdom
const { props, events } = extractPropsAndEvents(vdom)
const component = new Component(props, events, hostComponent)
component.setExternalContent(children)
++ component.setAppContext(hostComponent?.appContext ?? {})
component.mount(parentEl, index)
vdom.component = component
vdom.el = component.firstElement
}
The router link (router-components.js)
import { defineComponent } from './component'
import { h, hSlot } from './h'
export const RouterLink = defineComponent({
render() {
const { to } = this.props
return h(
'a',
{
href: to,
on: {
click: (e) => {
e.preventDefault()
this.appContext.router.navigateTo(to)
},
},
},
[hSlot()]
)
},
})
The router outlet (router-components.js)
export const RouterOutlet = defineComponent({
state() {
return {
matchedRoute: null,
subscription: null,
}
},
onMounted() {
const subscription = this.appContext.router.subscribe(({ to }) => {
this.handleRouteChange(to)
})
this.updateState({ subscription })
},
onUnmounted() {
const { subscription } = this.state
this.appContext.router.unsubscribe(subscription)
},
handleRouteChange(matchedRoute) {
this.updateState({ matchedRoute })
},
render() {
const { matchedRoute } = this.state
return h('div', { id: 'router-outlet' }, [
matchedRoute ? h(matchedRoute.component) : null,
])
},
})
Exporting the router components (index.js)
export { createApp } from './app.js'
export { defineComponent } from './component.js'
export { DOM_TYPES, h, hFragment, hSlot, hString } from './h.js'
++export { RouterLink, RouterOutlet } from './router-components.js'
++export { HashRouter } from './router.js'
export { nextTick } from './scheduler.js'