-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f13cdff
commit c270f7b
Showing
11 changed files
with
7,110 additions
and
235 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,4 +6,5 @@ yarn-error.log | |
/react | ||
/dist | ||
.rpt2_cache | ||
/coverage | ||
/coverage | ||
.docz |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# Getting started with RVal | ||
|
||
The core of `RVal` is four functions which have all a very simple contract: `val`, `drv`, `sub` and `act`. | ||
Yes, they all have three-letter names. That's kind of cool I think. Not sure yet why. | ||
|
||
## Creating reactive values using `val(initialValue)` | ||
|
||
In RVal, the universe revolves around _reactive values_. | ||
Creating your first reactive value is easy: | ||
|
||
```javascript | ||
import { val } from "rval"; | ||
|
||
const myLuckyNumber = val(13) | ||
``` | ||
|
||
`val` returns a function that returns any value you've put into it. | ||
So `myLuckyNumber` is now a number and we can call it: | ||
|
||
```javascript | ||
console.log(myLuckyNumber()) | ||
// prints: 13 | ||
``` | ||
|
||
Fancy! But what use is it to create a function that just returns the original value? | ||
We'll find out in a bit. | ||
First, there is another trick the function can do: We can call it with a new lucky number, | ||
since the `13` didn't work out after all: | ||
|
||
```javascript | ||
myLuckyNumber(42) | ||
|
||
console.log(myLuckyNumber()) | ||
// prints: 42 | ||
``` | ||
|
||
By passing an argument to the function, we can update it's internal state. | ||
When calling the reactive value function without argumens, it will always return the value we've passed into it the last time. | ||
|
||
You can put any value you like into a reactive value. | ||
But, for all practical purposes, you've should consider this value to be immutable. | ||
This will greatly benefit the understanding of the code base once it grows big enough. | ||
But, more on that later. | ||
|
||
See the [Philosophy](docs-philosophy) for some more background on this idea. | ||
|
||
|
||
Drv / odd numbers | ||
|
||
subscribe | ||
|
||
act | ||
|
||
Forward ref to objects |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
|
||
# The Philosophy of RVal | ||
|
||
Why reactive values? In essence most of our programming work consists of transfering in-memory information from one place to another, transforming the information into new information, that is either human or machine digestable. | ||
Data tranformations always introduces reduces redundant copies of data that need to be kept in sync with the original data. | ||
In very trivial example of this problem might look like: | ||
|
||
```javascript | ||
const user = { | ||
firstName: "Jane", | ||
lastName: "Stanford", | ||
fullName: "Jane Stanford" | ||
} | ||
|
||
document.body.innerHTML = `<h1>Hello ${user.fullName}</h1> | ||
``` | ||
|
||
This simple snippet introduces a redundant copy of the original user's name in the `fullName` property, and in the DOM. | ||
Now it has become the programmers responsibility to make sure any futher changes to the `user` are propagated properly: | ||
```javascript | ||
function updateFirstName(newName) { | ||
user.firstName = newName | ||
user.fullName = user.firstName + " " + user.lastName | ||
document.body.innerHTML = `<h1>Hello ${user.fullName}</h1> | ||
} | ||
``` | ||
This is the problem that any state management abstraction, regardless the framework or paradigm that is used, is trying to solve. | ||
RVal introduces a handful of primitives that help you to solve this problem in any context, by automating the question: | ||
_when_ should _which_ transformation be applied? | ||
Here is a quick overview in how RVal helps solving that problem. | ||
First, we should recognize that imperatively computing new information, such as the DOM represantation, introduces stale values. | ||
However, we can avoid ever storing such information by storing _computations_, rather than _values_. | ||
The process for that is as simple as creating a _thunk_ (argumentless function) that capture the computation, rather than imperatively producing new values: | ||
```javascript | ||
const user = { | ||
firstName: "Jane", | ||
lastName: "Stanford", | ||
fullName: () => user.firstName + " " + user.lastName | ||
} | ||
const rendering = () => `<h1>Hello ${user.fullName()}</h1>` | ||
document.body.innerHTML = rendering() | ||
function updateFirstName(newName) { | ||
user.firstName = newName | ||
document.body.innerHTML = rendering() | ||
} | ||
``` | ||
We've made things slightly better now; we don't have to imperatively update `user.fullName` anymore if the name changes. | ||
Similarly, we could captured the rendered representation of the user in the thunk called `rendering`. | ||
By storing computations instead of values, we have reduced the amount of redundant information. | ||
However, we still have to make sure that our changes are propagated, for example by updating the DOM whenever we change the `firstName` property. | ||
But, what if we could _subscribe_ to our thunks? And thereby avoid the need to manually propagate state changes, and increasing decoupling in the process? | ||
In other words, what if we could write something like: | ||
```javascript | ||
const user = { /* as-is */ } | ||
const rendering = () => `<h1>Hello ${user.fullName()}</h1>` | ||
on(rendering, () => { | ||
document.body.innerHTML = rendering() | ||
}) | ||
function updateFirstName(newName) { | ||
user.firstName = newName | ||
} | ||
``` | ||
Well, here is the good news: This is exactly the kind of things RVal allows you to write, by introducing three concepts: | ||
1. `val(value)` to create pieces of information that can change over time | ||
2. `drv(thunk)` to create thunks that can be subscribed to | ||
3. `sub(something, listener)` to create a listener that fires whenever the provided reactive value or thunk changes | ||
With those concepts, we can rewrite our above listing as a combination of reactive values and thunks, that propagate the changes when needed! | ||
```javascript | ||
import { val, drv, sub } from "rval" | ||
const user = { | ||
firstName: val("Jane"), | ||
lastName: val("Stanford"), | ||
fullName: drv(() => user.firstName() + " " + user.lastName()) | ||
} | ||
const rendering = drv(`<h1>Hello ${user.fullName()}</h1>`) | ||
// subscribe to the 'rendering' thunk | ||
sub(rendering, () => { | ||
document.body.innerHTML = rendering() | ||
}) | ||
function updateFirstName(newName) { | ||
// change the `firstName` reactive value to 'newName' | ||
// rval will make sure that any derivation and subscription impacted by this | ||
// change will be re-evaluated (and nothing more). | ||
user.firstName(newName) | ||
} | ||
``` | ||
## Functions solidifying state | ||
At this point you might be wondering: | ||
"But _why_ is it interesting to trap our state inside these `val` functions?" | ||
By trapping all our pieces of state inside `val` functions, | ||
we achieved a very interesting property: | ||
We've practically forced ourselfs to have a single source of truth. | ||
Instead of passing the _values of our state_ around, we can now pass a _references to our state_ around. | ||
The benefit of this that it will stop us from accidentally creating redundant copies of our state. | ||
|
||
Take for example the following contrived function. | ||
It creates a random number generator, which is more likely to generate our lucky number than any other number: | ||
|
||
```javascript | ||
function createNumberGenerator(luckyNumber) { | ||
return function numberGenerator() { | ||
return Math.random() < 0.5 ? luckyNumber : Math.round(Math.random() * 100) | ||
} | ||
} | ||
|
||
let luckyNumber = 13 | ||
const generator = createNumberGenerator(luckyNumber) | ||
console.log(generator()) // 13 | ||
console.log(generator()) // 50 | ||
console.log(generator()) // 49 | ||
|
||
luckyNumber = 42 | ||
console.log(generator()) // 28 | ||
console.log(generator()) // 13 | ||
console.log(generator()) // 13 | ||
``` | ||
|
||
Now at this point, updating our `luckyNumber` variable doesn't get reflected in the `numberGenerator` anymore. | ||
We are forced now to create a new number generator to reflect the change in our preference. | ||
The problem is that the `luckyNumber` has been "trapped" as argument to `createNumberGenerator`. | ||
The argument is basically a _copy_ of the original `luckyNumber` variable. | ||
|
||
However, it is easy to see that by passing _functions_ around, we avoid this whole problem. | ||
Because `luckyNumber` itself now becomes a `const` reference to the function that traps our lucky number. | ||
(Yes, `let` and `var` really become anti-patterns when using `rval`). | ||
|
||
|
||
```javascript | ||
function createNumberGenerator(luckyNumber) { | ||
return function numberGenerator() { | ||
// luckyNumber get's evaluated lazily when generating numbers | ||
return Math.random() < 0.5 ? luckyNumber() : Math.round(Math.random() * 100) | ||
} | ||
} | ||
|
||
const luckyNumber = val(13) // luckyNumber is a const now! | ||
const generator = createNumberGenerator(luckyNumber) | ||
console.log(generator()) // 13 | ||
console.log(generator()) // 13 | ||
console.log(generator()) // 22 | ||
|
||
luckyNumber(42) // change our minds | ||
console.log(generator()) // 42 | ||
console.log(generator()) // 8 | ||
console.log(generator()) // 42 | ||
``` | ||
|
||
By capturing values in functions, it becomes much more explicit when we want to pass a _reference_, and when a _value_. | ||
If we want our number generator to take a one-time snapshot of the state as `luckyNumber` we can be explicit about it and _explicitly_ pass a copy of the current state `createNumberGenerator(luckyNumber())`. | ||
On the other hand, we can also explicitly pass a reference to the state by just passing the `luckyNumber` function itself as we did above. | ||
|
||
As it turns out, in many cases it is very intersting to pass around a reference instead of a value. | ||
Especially when we are building systems that are supposed to be reactive, such as a user interface. | ||
But that for later sections. | ||
|
||
--- | ||
|
||
Note that the essence of `val` is simply this: | ||
|
||
```javascript | ||
function val(initial) { | ||
let state = initial | ||
return function() { | ||
if (!arguments.length) | ||
return state | ||
state = arguments[0] | ||
} | ||
} | ||
``` | ||
|
||
RVal's implementation is a little more involved, but that is because it is possible to subscribe to the `state` of a `val`. | ||
But the above is how you can conceptually think about them. | ||
|
||
But, let's [get started](http://localhost:3000/docs-getting-started) with the core "rval" api first! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export default { | ||
menu: [ | ||
'Getting started', | ||
'Philosophy' | ||
], | ||
} |
Oops, something went wrong.