Architect's Guide
to Frontend Frameworks

Tomasz Ducin  •  @tomasz_ducin  •  bit.ly/fe-arch

Hi, I'm Tomasz

Independent Consultant & Software Architect

Trainer, Speaker, JS/TS Expert

ArchitekturaNaFroncie.pl (ANF)

Warsaw, PL

tomasz (at) ducin.dev

@tomasz_ducin

disclaimer 😉

Update UI:

when and which parts?

DOM

COMPONENTS

STATE

EVENTS

DOM

WIDGET

EVENTS

jQuery

AngularJS

Digest Cycle

DOM

COMPONENTS

STATE

EVENTS

Service

AngularJS

Digest Cycle

async task ⚙️

async task ⚙️

async task ⚙️

Zone (zone.js)

Angular (v2+)

Zone.js-based Change Detection

DOM manipulations

are very expensive

<body>
<div>
<div>
<span>
<div>
<img>
<h1>
<p>

DOM

in browsers

<body>
<div>
<div>
<span>
<div>
<img>
<h1>
<p>

Virtual DOM

in React

export const Heading = (props) =>
  <div>
    <h1>{ props.heading }</h1>
    <img src={ props.imageURL } />
  </div>
function Heading(props) {
  return React.createElement("div", null,
    React.createElement("h1", null, props.heading),
    React.createElement("img", {
      src: props.imageURL
    })
  );
};

JSX / TSX

JS / TS

React Diffing Algorithm (VDOM)

render phase (virtual) + commit phase (DOM repaint)

Incremental DOM

Pre-compiled instructions updating the DOM straight away
No virtual representation, no runtime overhead

Incremental DOM

Pre-compiled instructions updating the DOM straight away
No virtual representation, no runtime overhead

<script>
  const sendLog = console.log
  let amount = 100000;
  let rate = 4.0;
  $: exchange = amount / rate
  $: sendLog(exchange)
</script>

...

<div>exchange: {exchange}</div>
let div2, t6, t7, ...
c(){ // create
  ...
  div2 = element("div");
  t6 = text("exchange: ");
  t7 = text(/*exchange*/ ctx[2]);
},
m(target, anchor) { // mount
  ...
  append(div3, div2);
  append(div2, t6);
  append(div2, t7);
}, ...

UI = fn(state)

View as a Function of State

Immutability

new object reference == change => need to refresh UI

const object = { name: "John Lennon", age: 39 }

// native
const newOne = { ...object, age: object.age + 1 }

// immer.js
const newOne = produce(object, draft => {
  draft.age += 1
})

Skip unnecessary rendering

with stable references

Memo

(silence)

no renders

same props passed

state change!

OnPush Change Detection

Cutting off renders of entire subtree

@Component({
    selector: 'listing',
    changeDetection: ChangeDetectionStrategy.OnPush,
    template: `...`
})
export class ListingComponent  {
 ...
}

Proactive vs Reactive

(Imperative)                       vs      (Declarative, Inversion of Control)

let rate = 3.94;
let amount = 1000;
let exchange = amount / rate; // 253.80

rate = 3.97;
exchange // 🔥 DESYNC, sync manually! INVARIANT BROKEN

Imperative Code

Reactivity redefines
what a variable is

let rate$ = 3.94;
let amount$ = 1000;
let exchange$ = amount$ / rate$; // 253.80

rate$ = 3.97;
exchange$ // 251.89
let rate$ = 3.94;
let amount$ = 1000;
let exchange$ = combineLatest(amount$, rate$,
  (amount, rate) => amount / rate);
let rate$ = 3.94;
let amount$ = 1000;
let exchange$ = combineLatest(amount$, rate$,
  (amount, rate) => amount / rate);

// rate$ == 3.97 ---> exchange$ == 251.89

Reactive Streams 

RxJS, heavily used in Angular

const rate = signal(3.94);
const amount = signal(1000);
const exchange = computed(() => amount() / rate()); // 253.80

rate.set(3.97);
rate.update(old => old + 0.03);
effect(() => sendLog(`exchange: ${exchange()}`)) // 251.89

Signals 

New Reactivity Model: Signals replacing RxJS in Angular

<script>
  const sendLog = console.log
  let amount = 1000;
  let rate = 3.94;
  $: exchange = amount / rate
  $: sendLog(exchange)
</script>

Pre-compiled Reactivity

<input bind:value="{amount}" />
<input bind:value="{rate}" />
<div>exchange: {exchange}</div>

<script>
  const sendLog = console.log
  let amount = 1000;
  let rate = 3.94;
  $: exchange = amount / rate
  $: sendLog(exchange)
</script>

Fundamental in Svelte

const [amount, setAmount] = useState(1000);
const [rate, setRate] = useState(3.94);

const exchange = useMemo(
  () => amount / rate, [amount, rate])

useEffect(() => {
  sendLog(`exchange: ${exchange}`)
}, [exchange])

Hooks 

FP-based, highly declarative new paradigm in React

State Management

private component state or shared

REDUX STORE

dispatch (WRITE)

notify subscribers (READ)

Redux, NGRX

Publisher-Subscriber pattern, Centralized, in-memory Event Sourcing

QUERY CACHE

mutations (invalidate cache)

active queries (subscriptions)

React-Query

Publisher-Subscriber pattern, Centralized

Stale While Revalidate pattern (refreshing data in the background)

Moving FE from the Client (SPA) to the Server

  • MPA: Multi-Page Apps
    (historical, thin client or no client)

  • SPA: Single-Page Apps
    (status quo, client runs everything)

  • SSR: Server-Side Rendering
    (request-time + Hydration)

  • SSG: Static Site Generation
    (build-time + Hydration)

  • (R)SC: (React) Server Components
    (Partial Hydration: interactive UI + non-interactive UI)

  • Qwik: Progressive Hydration / No Hydration

📝 Key Takeaways

  • TypeScript is widely acclaimed, finally

  • Angular is being drastically simplified, finally

  • Entry-level for FE development gets higher

  • Partial Hydration / Progressive Hydration
    will probably revolutionize Webapps Architecture

  • React/Next.js introduces cutting-edge solutions
    hence, increases complexity

  • Svelte & Qwik implement bleeding-edge solutions
    but are rather unlikely to become massively adapted

Thank you

Tomasz Ducin  •  @tomasz_ducin  •  bit.ly/fe-arch

O'Reilly: Architect's Guide to Frontend Frameworks

By Tomasz Ducin

O'Reilly: Architect's Guide to Frontend Frameworks

  • 280