Skip to content

Step 1 of 5 · Reading · ~4 min

Read

Reactive Foundations

Components

A component is a function from props to a vnode tree. After this lesson your program resolves a page full of component usages into plain elements and prints the HTML, which is the step a renderer performs before it compares anything.

Components are functions

The renderer's rule is short. A vnode whose type is a string is a host element (div, button). A vnode whose type is a function is a component: call it with its props, replace the vnode by whatever comes back, and repeat until only host elements and text remain. In JSX the convention follows from that: a capitalised tag (Button) names a component, a lowercase one (button) is a string looked up by the browser. Your first test of any tag is its first letter, A to Z.

An exercise has no functions to pass around, so a component here is a template. It is declared once and used by name:

DEF Button
OPEN button className=btn
TEXT {label}
CLOSE
END

Using OPEN Button label=Save means "run Button with the props label = Save". The {label} holes in the body, in text and in attribute values, are filled from those props, and a hole with no matching prop is empty. The result is <button class="btn">Save</button>. Bodies may use other components, and one definition can be used any number of times; each use gets its own props.

Props in, children through a slot

Props flow in one direction, from the parent that writes them to the component that reads them. A component never edits its props. Nested content arrives as the children, and the body says where they go with a CHILDREN line, the equivalent of props.children:

DEF Card
OPEN section className=card
OPEN h2
TEXT {title}
CLOSE
OPEN div className=body
CHILDREN
CLOSE
CLOSE
END

Using Card title=Profile around a paragraph produces the section, the heading Profile, and the paragraph inside the div. A body may place CHILDREN anywhere, more than once, or not at all.

Whose props does a hole see?

The step that goes wrong most often. A hole is filled from the props of the component whose body it is written in, not from the component it ends up inside.

Suppose Page is used with title=Home, and its body writes a Card title=Inner containing a paragraph {title}. The paragraph says Home. It was written in Page, and the children were built there, as plain data, before Card was ever called. Card only decides where they land. Passing CHILDREN straight on to another component follows the same rule: a wrapper hands along what it received.

So the order for one usage is: resolve its children in the caller's scope, bind the attributes (their values are also filled from the caller's props), then expand the definition's body with the new props and the resolved children.

Escaping happens once

Prop values are data. They are escaped when the final tree is written out, exactly as in lesson 1, and never when they are substituted. Children are already resolved nodes rather than strings, so they are not escaped a second time. If you escape early you get &amp;amp;.

Purity and composition

Because the renderer may call a component more than once for one visible update, or throw a result away, a component must be a pure function of its props. Reuse comes from nesting, not from inheritance: a component that needs another one renders it or takes it as a child. Class components with lifecycle methods still exist but hooks gave plain functions everything they had. Vue and Svelte keep template, logic and style in one file, React splits them; the model underneath is the one you are building.

Common mistakes

  • Filling a child's hole with the wrapper's props instead of the props of the component where it was written.
  • Escaping on substitution, then again at output.
  • Treating the tag Button as an element and printing <Button>, or a lowercase tag as a component.
  • Letting props leak from one use of a component into the next.
  • Forgetting that a hole with no prop is empty, and that an unclosed { is ordinary text.

Your exercise

The starter reads the definitions and the page, and renders the result with the code from lesson 1. You write subst, which fills the holes in one string, and expand, which resolves one node into its final nodes. An undefined component must make the program print ERROR unknown component <Name> instead of HTML.

Up nextJSX to createElement CompilationReactive Foundations

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…