DEV Community

Discussion on: How React isn't reactive, and why you shouldn't care

 
peerreynders profile image
peerreynders • Edited

Then if you talk to web developers they will understand something else.

In my estimation that is what Svelte is talking about when it uses the term "reactive". It takes the perspective of the "developer as a user" differentiating between "explicit change propagation" (e.g. setState()) vs. "implicit change propagation" (i.e. "reactive"). From the developer perspective Svelte has two "reactive" systems:

Example: Reactive declaration

const schedule = (delay, fn) => self.setTimeout(fn, delay);

let a = 5;

// non-reactive declaration
let b = a + 3;

// "reactive" declaration
$: bR = a + 3;

console.log(bR === 8);

schedule(100, () => {
  a += 2;
});

schedule(200, () => {
  console.log(a === 7);
  console.log(b === 8);
  console.log(bR === 10);
}); 
Enter fullscreen mode Exit fullscreen mode

So through a little syntax sugar blocks of code can become "reactive - just like you're used to from spreadsheets".
The primary appeal here is that reactivity is gained with very little adjustment of the imperative mindset of managing the flow of control - except that now reactive code automatically stays in sync with its dependencies without any explicit change propagation.
This seems to be the reactivity that most of Svelte's buzz revolves around while the "developer as a user" persona cares very little how it's implemented under the hood.

Example: Stores

import { tick } from 'svelte';
import { derived, readable, get } from 'svelte/store';

const schedule = (delay, fn) => self.setTimeout(fn, delay);

let a = readable(null, set => {
  let value = 5;
  set(value);

  schedule(100, () => {
    value += 2;
    set(value);
  });
});

// non-reactive use
// Note use of `get()` is discouraged - used for demonstration only
let b = get(a) + 3;

// "reactive" use
let bR = derived(a, $a => {
  const value = $a + 3;
  return value;
});

// use reactive declarations to use auto-subscriptions
$: aLast = $a;
$: bRlast = $bR;

tick().then(() => {
  console.log(bRlast === 8);
});

schedule(200, () => {
  console.log(aLast === 7);
  console.log(b === 8);
  console.log(bRlast === 10);
});
Enter fullscreen mode Exit fullscreen mode

Stores require a bit more explicit setup though auto-subscriptions are supposed to make consuming them more convenient. However propagation of change at the source store has to be explicitly managed by developer code - so this requires a departure from the usual "imperative flow of control" coding.

Example: Stores with explicitly managed subscriptions

import { onDestroy } from 'svelte';
import { readable } from 'svelte/store';

const schedule = (delay, fn) => self.setTimeout(fn, delay);

let a = 5;
const aStore = readable(null, set => {
  set(a);
  schedule(100, () => {
    a += 2;
    set(a);
  });
});

// non-reactive
let b = a + 3;

// "reactive" subscription
let bR;
const unsubscribe = aStore.subscribe(value => {
  bR = value + 3;
});

console.log(bR === 8);

schedule(200, () => {
  console.log(a === 7);
  console.log(b === 8);
  console.log(bR === 10);
});

onDestroy(unsubscribe);
Enter fullscreen mode Exit fullscreen mode

Without auto-subscriptions consumers have to explicitly subscribe (and unsubscribe) to stores so we're even further removed from the "imperative flow of control" model.

So stores are available for more complex use cases but would generally be judged as the least wieldy of the two options.

It's also my opinion that the adoption of RxJS was ultimately limited by the need to shift ones coding mindset away from manipulating the flow of control at runtime to writing code that largely scaffolds the transformation and routing of event streams that - once constructed - just work.

The advantage of Svelte's reactive declarations/statements is that such a shift in mindset simply isn't required - it reminds me of how the majority of developers seem to prefer using async/await (maintaining the illusion of synchronous execution) over raw Promises.