Skip to content

Commit

Permalink
More docs updates
Browse files Browse the repository at this point in the history
  • Loading branch information
ryansolid committed May 30, 2021
1 parent bfafaf8 commit bd24af8
Show file tree
Hide file tree
Showing 14 changed files with 54 additions and 958 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ Most non-essential arguments on reactive primitives are now living on an options

No longer uses rest parameters for multiple dependencies. Instead pass an array. This facilitates new option to defer execution until dependencies change.

#### Actions renamed to Directives

To remove future confusion with other uses of actions the JSX.Actions interace is now the JSX.Directives interface.

## 0.26.0 - 2021-04-09

This release is about finalizing some API changes on the road to 1.0. This one has one breaking change and not much else.
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,17 @@ For TypeScript remember to set your TSConfig to handle Solid's JSX by:

## Documentation

> This is the documentation for the 1.0.0 RC. THese are significantly slimmed down from what was here previously as we are moving things to the new Docs site. If you wish to see the older docs in the mean time [look here](https://github.com/solidjs/solid/tree/0.x/#documentation).
- [API](https://github.com/solidjs/solid/blob/main/documentation/api.md)
- [FAQ](https://github.com/solidjs/solid/blob/main/documentation/faq.md)
- [Comparison with other Libraries](https://github.com/solidjs/solid/blob/main/documentation/comparison.md)

### Guides
- [Getting Started](https://github.com/solidjs/solid/blob/next/documentation/guides/getting-started.md)
- [Reactivity](https://github.com/solidjs/solid/blob/next/documentation/guides/reactivity.md)
- [Rendering](https://github.com/solidjs/solid/blob/next/documentation/guides/rendering.md)
- [SSR](https://github.com/solidjs/solid/blob/next/documentation/guides/server.md)
- [Getting Started](https://github.com/solidjs/solid/blob/main/documentation/guides/getting-started.md)
- [Reactivity](https://github.com/solidjs/solid/blob/main/documentation/guides/reactivity.md)
- [Rendering](https://github.com/solidjs/solid/blob/main/documentation/guides/rendering.md)
- [SSR](https://github.com/solidjs/solid/blob/main/documentation/guides/server.md)

### Resources

Expand Down
2 changes: 1 addition & 1 deletion documentation/comparison.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ React has had a big influence on Solid. Its unidirectional flow and explicit seg
However, as much as Solid aligns with React's design philosophy, it works fundamentally different. React uses a Virtual DOM, and Solid does not. React's abstraction is top down component partition where render methods are called repeatedly and diffed. Whereas Solid renders each Template once in entirety constructing its reactive graph and afterwords only executes instructions related to fine-grained changes.

#### Advice for migrating:
Solid's update model is nothing like React. Or even React + MobX. Instead of thinking of function components as the `render` function, think of them as a `constructor`. Watch out for destructuring or early property access losing reactivity. You don't need explicit keys on list rows to have "keyed" behavior. Finally, there is no VDOM so imperative VDOM APIs like `React.Children` and `React.cloneElement` make no sense. I encourage finding different ways to solve problems that use these declaratively.
Solid's update model is nothing like React. Or even React + MobX. Instead of thinking of function components as the `render` function, think of them as a `constructor`. Watch out for destructuring or early property access losing reactivity. Solid's primitives have no restrictions like the Hook Rules so you are free to nest them as you see fit. You don't need explicit keys on list rows to have "keyed" behavior. Finally, there is no VDOM so imperative VDOM APIs like `React.Children` and `React.cloneElement` make no sense. I encourage finding different ways to solve problems that use these declaratively.

## Vue

Expand Down
189 changes: 34 additions & 155 deletions documentation/guides/reactivity.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,187 +21,66 @@ const App = () => {
render(() => <App />, document.getElementById("app"));
```

> **For React Users:** This looks like React Hooks, but it is very different. There are no Hook rules, or concern about stale closures because your Component only runs once. It is only the "Hooks" that re-execute. So they always have the latest references.
## Introducing Primitives

## Signals
Solid is made up of 3 primary primitves, Signal, Memo, and Effect. At their core is the Observer pattern where Signals (and Memos) are tracked by
wrapping Memos and Effects.

Signals are the glue that hold the library together. They are a simple primitive that contain values that change over time. Each Signal comes with a separate setter in a tuple. This setter is the only way to update the Signal's value.

With Signals you can track all sorts of changes from various sources in your applications. They are not tied to any specific component and can be used wherever whenever.

```js
import { createSignal, onCleanup } from "solid-js";

function createTick(delay) {
const [getCount, setCount] = createSignal(0),
handle = setInterval(() => setCount(getCount() + 1), delay);
onCleanup(() => clearInterval(handle));
return getCount;
}
```
## Effects

When we want our Signals to affect the world we use Effects. Effects wrap expressions that contain Signals and re-execute them everytime those Signals change. The most common ones, created automatically for us, are JSX bindings, but they can also be created manually.

```js
import { createEffect, createSignal, onCleanup } from "solid-js";

function logTick(delay) {
const [getCount, setCount] = createSignal(0),
handle = setInterval(() => setCount(getCount() + 1), delay);
onCleanup(() => clearInterval(handle));

// logs the count every `delay` interval
createEffect(() => console.log(getCount()));
}
```

Effects are what allow the DOM to stay up to date. While you don't see them, everytime you write an expression in the JSX (code between the parenthesis `{}`), the compiler is wrapping it in a function and passing it to a `createEffect` call.

## Memos

Wrapping a Signal read in a thunk `() => signal()` creates a derived signal that can be tracked as well. The same holds true for accessing props or Solid's reactive State proxies. Want to use state as a signal just wrap it in a function. Any pure function that wraps one ore more Signal executions is also a Signal.
Signals are the simplest primitive. They contain a get and set so we can intercept when they are read and written to.

```js
import { createSignal, createEffect } from "solid-js";

const [count, setCount] = createSignal(1);
const doubleCount = () => count() * 2;

createEffect(() => console.log(doubleCount()));
setCount(count() + 1);

// 2
// 4
const [count, setCount] = createSignal(0);
```

Memos allow us to store and access values without re-evaluating them until their dependencies change. They are very similar to derived Signals mentioned above except they only re-evaluate when their dependencies change and return the last cached value on read.

Keep in mind memos are only necessary if you wish to prevent re-evaluation when the value is read. Useful for expensive operations like DOM Node creation. Otherwise they are not very different from other Signals.
Effects are functions that wrap reads of our signal and re-excute when ever a dependent Signal's value changes. This is useful for creating side effects like rendering.

```js
import { createSignal, createMemo, createEffect } from "solid-js";

const [count, setCount] = createSignal(1);
const expensiveCount = createMemo(() => expensiveCalculation(count()));

createEffect(() => console.log(expensiveCount()));
setCount(count() + 1);
createEffect(() => console.log("The latest count is", count()));
```

Memos also pass the previous value on each execution. This is useful for reducing operations (obligatory Redux in a couple lines example):
Finally, Memos are cached derived values. They share the properties of both Signals and Effects. They track their own dependent Signals and re-execute only when those change and are trackable Signals themselves.

```js
// reducer
const reducer = (state, action = {}) => {
switch (action.type) {
case "LIST/ADD":
return { ...state, list: [...state.list, action.payload] };
default:
return state;
}
};

// initial state
const state = { list: [] };

// redux
const [getAction, dispatch] = createSignal(),
getStore = createMemo(state => reducer(state, getAction()), state);

// subscribe and dispatch
createEffect(() => console.log(getStore().list));
dispatch({ type: "LIST/ADD", payload: { id: 1, title: "New Value" } });
const fullName = createMemo(() => `${firstName()} ${lastName()}`);
```

That being said there are plenty of reasons to use actual Redux.
# How it Works

## State
Signals are event emitters that hold a list of subscriptions. They notify their subscribers whenever their value changes.

Sometimes there is a desire to have deeply nested signals and that is where state comes in. It is composed of many on demand reactive Signals through a proxy object. It is deeply nested reactivity, and lazily creates Signals on demand.
Where things get more interesting is how these subscriptions happen. Solid uses automatic dependency tracking. Updates happen automatically as the data changes.

The advantage is that it is automatically reactive and resembles data structures you may already have. It removes the classic issues with fine-grained reactivity around mapping reactive structures and serializing JSON. And as a structure itself it can be diffed allowing interaction with immutable data and snapshots.

Through the use of proxies and explicit setters it gives the control of an immutable interface and the performance of a mutable one.

```jsx
import { createState } from "solid-js";
import { render } from "solid-js/web";

export default function App() {
const [state, setState] = createState({
user: {
firstName: "John",
lastName: "Smith",
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
});

return (
<div onClick={() => setState("user", "lastName", value => value + "!")}>
{state.user.fullName}
</div>
);
};
```

> Note: State objects themselves aren't reactive. Only the property access on them are. So destructuring in non-tracked scopes will not track updates. Also passing the state object directly to bindings will not track unless those bindings explicitly access properties. Finally, while nested state objects will be notified when new properties are added, top level state cannot be tracked so adding properties will not trigger updates when iterating over keys. This is the primary reason state does benefit from being created as a top level array.
## Managing Dependencies and Updates

Sometimes we want to be explicit about what triggers Effects and Memos to update and nothing else. Solid offers ways to explicitly set dependencies or to not track under a tracking scope at all.
The trick is a global stack at runtime. As a Effect or Memo executes (or re-executes) its developer provided function it pushes itself on to that stack. Then any Signal that is read checks if there is a current listener on the stack and if so adds it to its subscriptions.

You can think of it like this:
```js
const [a, setA] = createSignal(1);
const [b, setB] = createSignal(1);

createEffect(() => {
const v = a();
untrack(() => console.log(v, b()));
}); // 1, 1

setA(2); // 2, 1
setB(2); // (does not trigger)
setA(3); // 3, 2 (still reads latest values)
```

For convenience Solid provides an `on` operator to set up explict dependencies for any computation.
function createSignal(value) {
const subscribers = new Set();

```js
// equivalent to above
createEffect(on(a, v => console.log(v, b())));
```
const read = () => {
const listener = getCurrentListener();
if (listener) subscribers.add(listener);
return value;
}

Solid executes synchronously but sometimes you want to apply multiple changes at once. `batch` allows us to do that without triggering updates multiple times.
const write = (nextValue) => {
value = nextValue;
for (const sub of subscribers) sub.run();
}

```js
batch(() => {
setA(4);
setB(6);
});
return [read, write];
}
```
Now whenever we update the Signal we know which Effects to re-run. Super simple yet effective. The actual implementation is much more complicated but that is the guts of what is going on.

## Cleanup

While Solid does not have Component lifecyles in the traditional sense, it still needs to handle cleaning up reactive dependencies. The way Solid works is that each nested computation is owned by its parent reactive scope. This means that all computations must be created as part of a root. This detail is generally taken care of for you as the `render` method contains a `createRoot` call. But it can be called directly for cases where you need to control disposal outside of this cycle.
# Considerations

Once inside a scope whenever the scope is re-evaluated or disposed of itself, all children computations will be disposed. In addition you can register a `onCleanup` method that will execute as part of this disposal cycle.
This approach to reactivity is very powerful and dynamic. It can handle dependencies changing on the fly through executing different branches of conditional code. It also works through many levels of indirection. Any function executed inside a tracking scope is also being tracked.

_Note: Solid's graph is synchronously executed so any starting point that isn't caused by a reactive update (perhaps an asynchronous entry) should start from its own root. There are other ways to handle asynchronicity as shown in the [Suspense Docs](./suspense.md)_
However, there are some key behaviors and tradeoffs we must be aware of.

## Composition
1. All reactivity is tracked from function calls whether directly or hidden beneath getter/proxy and triggered by property access. This means where you access properties on reactive objects is important.

Solid's primitives combine wonderfully. They encourage composing more sophisticated patterns to fit developer need.
2. Components and callbacks from control flows are not tracking scopes and only execute once. This means destructuring or doing logic top-level in your components will not re-execute. You must access these Signals, State, and props from within other reactive primitives or the JSX for that part of the code to re-evaluate.

```js
// Solid's fine-grained equivalent to React's `useReducer` Hook
const useReducer = (reducer, state) => {
const [store, setStore] = createState(state);
const dispatch = (action) => {
state = reducer(state, action);
setStore(reconcile(state));
}
return [store, dispatch];
};
```
3. This approach only tracks synchronously. If you have a setTimeout or use an async function in your Effect the code that executes async after the fact won't be tracked.
51 changes: 3 additions & 48 deletions documentation/guides/rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import { render } from "solid-js/web";

render(() => <App />, document.getElementById("main"));
```

> **Important** The first argument needs to be a function. Otherwise we can't properly track and schedule the reactive system. This simple ommission will cause your Effects not to run.
## Components

Components in Solid are just Pascal (Capital) cased functions. Their first argument is a props object and they return real DOM nodes.
Expand Down Expand Up @@ -276,51 +278,4 @@ const List = (props) => {
</ul>;
```
**Important:** Solid treats child tags as expensive expressions and wraps them the same way as dynamic reactive expressions. This means they evaluate lazily on `prop` access. Be careful accessing them multiple times or destructuring before the place you would use them in the view. This is because Solid doesn't have the luxury of creating Virtual DOM nodes ahead of time and then diffing them, so resolution of these `props` must be lazy and deliberate. Use `children` helper if you wish to do this as it memoizes them.
## Lifecycle
All lifecycles in Solid are tied to the lifecycle of the reactive system.
If you wish to perform some side effect on mount or after update use `createEffect` after render has complete:
```jsx
import { createSignal, createEffect } from "solid-js";

function Example() {
const [count, setCount] = createSignal(0);

createEffect(() => {
document.title = `You clicked ${count()} times`;
});

return (
<div>
<p>You clicked {count()} times</p>
<button onClick={() => setCount(count() + 1)}>Click me</button>
</div>
);
}
```
For convenience if you need to only run it once you can use `onMount` which is the same as `createEffect` but it will only run once. Keep in mind that Solid's cleanup is independent of this mechanism. So if you aren't reading the DOM you don't need to use these.
If you wish to release resources on the Component being destroyed, simply wrap in an `onCleanup`.
```jsx
const Ticker = () => {
const [state, setState] = createState({ count: 0 }),
t = setInterval(() => setState({ count: state.count + 1 }), 1000);

// remove interval when Component destroyed:
onCleanup(() => clearInterval(t));

return <div>{state.count}</div>;
};
```
## Web Components
Since change management is independent of code modularization, Solid Templates are sufficient to act as Components. Solid fits easily into other Component structures like Web Components.
[Solid Element](https://github.com/solidjs/solid/tree/main/packages/solid-element) Provides an out of the box solution for wrapping your Solid Components as Custom Elements.
**Important:** Solid treats child tags as expensive expressions and wraps them the same way as dynamic reactive expressions. This means they evaluate lazily on `prop` access. Be careful accessing them multiple times or destructuring before the place you would use them in the view. This is because Solid doesn't have the luxury of creating Virtual DOM nodes ahead of time and then diffing them, so resolution of these `props` must be lazy and deliberate. Use `children` helper if you wish to do this as it memoizes them.
Loading

0 comments on commit bd24af8

Please sign in to comment.