Skip to content

Commit

Permalink
update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
Brian Kim committed Feb 29, 2024
1 parent cbec13b commit a95d5fc
Show file tree
Hide file tree
Showing 8 changed files with 415 additions and 260 deletions.
4 changes: 1 addition & 3 deletions website/documents/guides/01-getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,4 @@ export default defineConfig({
});
```

## Shovel

A full-stack framework is in the works for Crank. Stay tuned.
## Key Examples
5 changes: 1 addition & 4 deletions website/documents/guides/02-elements.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ title: Elements and Renderers
Crank works with [JSX](https://facebook.github.io/jsx/), a well-supported, XML-like syntax extension to JavaScript.

### Two types of JSX transpilation
Historically speaking, there are two ways to transform JSX: the *classic* and *automatic* transforms. Crank supports both formats.
There are two ways to transform JSX: the *classic* and *automatic* transforms. Crank supports both formats.

The classic transform turns JSX elements into `createElement()` calls.

Expand Down Expand Up @@ -83,7 +83,6 @@ console.log(html); // <div id="element">Hello world</div>

## The Parts of an Element

<!-- TODO: Make this a JSX element -->
![Image of a JSX element](/static/parts-of-jsx.svg)

An element can be thought of as having three main parts: a *tag*, *props* and *children*. These roughly correspond to the syntax for HTML, and for the most part, you can copy-paste HTML into JSX-flavored JavaScript and have things work as you would expect. The main difference is that JSX has to be well-balanced like XML, so void tags must have a closing slash (`<hr />` not `<hr>`). Also, if you forget to close an element or mismatch opening and closing tags, the parser will throw an error, whereas HTML can be unbalanced or malformed and mostly still work.
Expand Down Expand Up @@ -198,5 +197,3 @@ renderer.render(
console.log(document.body.firstChild === div); // true
console.log(document.body.firstChild.firstChild === span); // true
```

**Note:** The documentation tries to avoid the terms “virtual DOM” or “DOM diffing” insofar as the core renderer can be extended to target multiple environments; instead, we use the terms “virtual elements” and “element diffing” to mean mostly the same thing.
62 changes: 44 additions & 18 deletions website/documents/guides/03-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
title: Components
---

So far, we’ve only seen and used *host elements*, lower-case elements like `<a>` or `<div>`, which correspond to HTML. Eventually, we’ll want to group these elements into reusable *components*. Crank uses plain old JavaScript functions to define components. The type of the function determines the component’s behavior.

## Basic Components

So far, we’ve only seen and used *host elements*. By convention, all host elements use lowercase tags like `<a>` or `<div>`, and these elements are rendered as their HTML equivalents.

However, eventually we’ll want to group these elements into reusable *components*. In Crank, components are defined with plain old JavaScript functions, including async and generator functions, which return or yield JSX elements. These functions can be referenced as element tags, and component elements are distinguished from host elements through the use of capitalized identifiers. The capitalized identifier is not just a convention but a way to tell JSX compilers to interpret the tag as an identifier rather than a literal string.

The simplest kind of component is a *function component*. When rendered, the function is invoked with the props of the element as its first argument, and the return value of the function is rendered as the element’s children.

```jsx live
Expand Down Expand Up @@ -73,8 +76,8 @@ In the preceding example, the component’s local state was updated directly whe

Crank allows components to control their own execution by passing in an object called a *context* as the `this` keyword of each component. Contexts provide several utility methods, the most important of which is the `refresh()` method, which tells Crank to update the related component instance in place.

```jsx
function *Timer() {
```jsx live
function *Timer({message}) {
let seconds = 0;
const interval = setInterval(() => {
seconds++;
Expand All @@ -84,22 +87,46 @@ function *Timer() {
try {
while (true) {
yield (
<div>Seconds elapsed: {seconds}</div>
<div>{message} {seconds}</div>
);
}
} finally {
clearInterval(interval);
}
}

renderer.render(<Timer message="Seconds elapsed:" />, document.body);
```

This `<Timer />` component is similar to the `<Counter />` one, except now the state (the local variable `seconds`) is updated in a `setInterval()` callback, rather than when the component is rerendered. Additionally, the `refresh()` method is called to ensure that the generator is stepped through whenever the `setInterval()` callback fires, so that the rendered DOM actually reflects the updated `seconds` variable.
This `<Timer>` component is similar to the `<Counter>` one, except now the state (the local variable `seconds`) is updated in a `setInterval()` callback, rather than when the component is rerendered. Additionally, the `refresh()` method is called to ensure that the generator is stepped through whenever the `setInterval()` callback fires, so that the rendered DOM actually reflects the updated `seconds` variable. Finally, the `<Timer>` component is passed a display message as a prop.

One important detail about the `Timer` example is that it cleans up after itself with `clearInterval()` in the `finally` block. Behind the scenes, Crank will call the `return()` method on an element’s generator object when it is unmounted.

If you hate the idea of using the `this` keyword, the context is also passed in as the second parameter of components.

```jsx
function *Timer({message}, ctx) {
let seconds = 0;
const interval = setInterval(() => {
seconds++;
ctx.refresh();
}, 1000);

try {
while (true) {
yield (
<div>{message} {seconds}</div>
);
}
} finally {
clearInterval(interval);
}
}
```

## The Render Loop

The generator components we’ve seen so far haven’t used props. They’ve also used while (true) loops, which was done mainly for learning purposes. In actuality, Crank contexts are iterables of props, so you can `for...of` iterate through them.
The `<Timer>` component works, but it can be improved. Firstly, while the component is stateful, it would not update the message if it was rerendered with new props. Secondly, the `while (true)` loop can iterate infinitely if you forget to add a `yield`. To solve these issues, Crank contexts are an iterable of the latest props.

```jsx live
import {renderer} from "@b9g/crank/dom";
Expand All @@ -120,23 +147,21 @@ function *Timer({message}) {
}

renderer.render(
<Timer message="Seconds elapsed" />,
<Timer message="Seconds elapsed:" />,
document.body,
);

setTimeout(() => {
renderer.render(
<Timer message="Hello from the timeout" />,
<Timer message="Seconds elapsed (updated in setTimeout):" />,
document.body,
);
}, 4500);
}, 2500);
```

The loop created by iterating over contexts is called the *render loop*. By replacing the `while` loop with a `for...of` loop which iterates over `this`, you can get the latest props each time the generator is resumed.
The loop created by iterating over contexts is called the *render loop*. By replacing the `while` loop with a `for...of` loop, you can get the latest props each time the generator is resumed. It also provides benefits over `while` loops, like throwing errors if you forget to `yield`, and allowing you to write cleanup code after the loop without having to wrap the block in a `try`/`finally` block.

The render loop has additional advantages over while loops. For instance, you can place cleanup code directly after the loop. The render loop will also throw errors if it has been iterated without a yield, to prevent infinite loops.

One Crank idiom you may have noticed is that we define props in component parameters, and overwrite them using a destructuring expression in the `for...of` statement. This is an easy way to make sure those variables stay in sync with the current props of the component. For this reason, even if your component has no props, it is idiomatic to use a render loop.
One Crank idiom you may have noticed is that we define props in function parameters, and overwrite them using a destructuring expression in the `for...of` statement. This is an easy way to make sure those variables stay in sync with the current props of the component. For this reason, even if your component has no props, it is idiomatic to destructure props and use a `for...of` loop.

```jsx live
import {renderer} from "@b9g/crank/dom";
Expand All @@ -160,17 +185,18 @@ renderer.render(<Counter />, document.body);
```

## Default Props
You may have noticed in the preceding examples that we used [object destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring) on the props parameter for convenience. You can further assign default values to specific props using JavaScript’s default value syntax.
Because we use [object destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring), you can further assign default values to specific props using JavaScript’s default value syntax.

```jsx
```jsx live
import {renderer} from "@b9g/crank/dom";
function Greeting({name="World"}) {
return <div>Hello, {name}</div>;
}

renderer.render(<Greeting />, document.body); // "<div>Hello World</div>"
renderer.render(<Greeting />, document.body);
```

This syntax works well for function components, but for generator components, you should make sure that you use the same default value in both the parameter list and the loop. A mismatch in the default values for a prop between these two positions may cause surprising behavior.
For generator components, you should make sure that you use the same default value in both the parameter list and the loop. A mismatch in the default values for a prop between these two positions may cause surprising behavior.

```jsx live
import {renderer} from "@b9g/crank/dom";
Expand Down
24 changes: 12 additions & 12 deletions website/documents/guides/04-handling-events.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ title: Handling Events
Most web applications require some measure of interactivity, where the user interface updates according to input. To facilitate this, Crank provides several ways to listen to and trigger events.

## DOM Event Props
You can attach event callbacks to host element directly using event props. These props start with `on`, are all lowercase, and correspond to the event type (`onclick`, `onkeydown`). By combining event props, local variables and `this.refresh()`, you can write interactive components.
You can attach event callbacks to host element directly using event props. These props start with `on`, are by convention lowercase, and correspond to the event type (`onclick`, `onkeydown`). By combining event props, local variables and `this.refresh()`, you can write interactive components.

```jsx live
import {renderer} from "@b9g/crank/dom";
Expand Down Expand Up @@ -83,13 +83,6 @@ function *Counter() {
renderer.render(<Counter />, document.body);
```

## Event props vs EventTarget
The props-based event API and the context-based EventTarget API both have their advantages. On the one hand, using event props means you can listen to exactly the element you’d like to listen to.

On the other hand, using the `addEventListener` method allows you to take full advantage of the EventTarget API, which includes registering passive event listeners, or listeners which are dispatched during the capture phase. Additionally, the EventTarget API can be used without referencing or accessing the child elements which a component renders, meaning you can use it to listen to elements nested in other components.

Crank supports both API styles for convenience and flexibility.

## Dispatching Events
Crank contexts implement the full EventTarget interface, meaning you can use [the `dispatchEvent` method](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent) and [the `CustomEvent` class](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) to dispatch custom events to ancestor components:

Expand All @@ -110,7 +103,7 @@ function MyButton(props) {

function MyButtons() {
return [1, 2, 3, 4, 5].map((i) => (
<p>
<p>
<MyButton id={"button" + i}>Button {i}</MyButton>
</p>
));
Expand Down Expand Up @@ -179,7 +172,14 @@ function *CustomCounter() {
renderer.render(<CustomCounter />, document.body);
```

Using custom events and event bubbling allows you to encapsulate state transitions within component hierarchies without the need for complex state management solutions used in other frameworks like Redux or VueX.
Using custom events and event bubbling allows you to encapsulate state transitions within component hierarchies without the need for complex state management solutions in a way that is DOM-compatible.

## Event props vs EventTarget
The props-based event API and the context-based EventTarget API both have their advantages. On the one hand, using event props means you can listen to exactly the element you’d like to listen to.

On the other hand, using the `addEventListener` method allows you to take full advantage of the EventTarget API, which includes registering passive event listeners, or listeners which are dispatched during the capture phase. Additionally, the EventTarget API can be used without referencing or accessing the child elements which a component renders, meaning you can use it to listen to elements nested in other components.

Crank supports both API styles for convenience and flexibility.

## Form Elements

Expand Down Expand Up @@ -213,7 +213,7 @@ function *Form() {
renderer.render(<Form />, document.body);
```

If your component is updating for other reasons, you can use the special property `$static` to prevent the input element from updating.
If your component is updating for other reasons, you can use the special property `copy` to prevent the input element from updating.

```jsx live
import {renderer} from "@b9g/crank/dom";
Expand All @@ -237,7 +237,7 @@ function *Form() {
reset = false;
yield (
<form onsubmit={onsubmit}>
<input type="text" value="" $static={currentReset} />
<input type="text" value="" copy={currentReset} />
<p>
<button onclick={onreset}>Reset</button>
</p>
Expand Down
Loading

0 comments on commit a95d5fc

Please sign in to comment.