Infinite Builder
65,000 React devs
CFP
Prep
Research
+ Survey members
+ Others
class MyComponent extends Component {
componentDidMount() {
this._isMounted = true
}
componentWillUnmount() {
this._isMounted = false
}
getData = () => fetch('/api')
.then(data => {
if (this._isMounted) {
this.setState({data: data.json()})
}
})
// ...
}
.debounce()
.retry()
.window()
.distinctUntilChanged()
aka Reactive Programming
Redux + createState
recycle
frint-react
reenhance-components
recompose
redux-observable
Visicalc (1979 - 1983)
Lotus 1-2-3 (1983-2014)
Excel (1985-?)
"Like a Spreadsheet"
var foo = 1
var bar = 7
var Answer = foo * bar
console.log(Answer) // 7
foo = 6
console.log(Answer) // 7 😢
"Data at rest"
btn.addEventListener('click', handler)
function handler() {
alert('hello world!')
}
consumer (handler) tied to the producer (btn)
function Observable(cb) {
const subscribe = next => cb({ next })
return { subscribe }
}
Barebones Implementation of the spec
const source$ = new Observable(
obs => obs.next('world!')
)
source$.subscribe(
x => console.log('hello', x) // 'hello world!'
)
Basic usage (by convention, add $ to indicate observable stream)
consumer (console.log) decoupled from producer (source$)
Componentizing Event Streams
observers subscribe to observables
listeners subscribe to podcasts
List Credit: Jafar Husain
Note: In academic language,
"Events" = All forms of discrete updates, not just DOM events
v = f(d)
Leaky Abstraction: Data isn't static
v$ = f(d$)
Leaky Abstraction?
🤷♂️
3 months later...
☢️
⚛️
View
Update
0
1
2
Click
Click
class Counter extends Component {
initialState = 0
increment = createHandler(() => 1)
source() {
const source$ = this.increment.$
const reducer = (acc, n) => acc + n
return { source$, reducer }
}
render(state) {
return (
<div>
Count: {state}
<button onClick={this.increment}>+</button>
</div>
)
}
}
DOM
renderStream
MyApp
vDOM
source$
.subscribe()
render
createElement
appendChild
next State
DOM
patch
diff
vDOM
initial/prevState
Red = reactive
UIStream
MyApp
prevvDOM
source$
.subscribe()
render
next State
DOM
patch
diff
vDOM
prevState
renderer/reconciler
scheduler
// import { diff, patch, createElement } from 'virtual-dom'
export function mount(element, container) {
const {source$, initInstance} = renderStream(element)
const rootNode = createElement(initInstance.dom)
container.appendChild(rootNode) // initial mount
return scan( // "stateful loop" similar to Array.reduce
source$,
({instance, state}, nextState) => {
// render function defined elsewhere
const nextinstance = render(element, instance, nextState, state)
const domDiff = diff(instance.dom, nextinstance.dom) // vDom diffing
patch(rootNode, domDiff) // render to screen
return {instance: nextinstance, state: nextState}
}
,{instance: initInstance, state: INITIALSOURCE}
).subscribe() // starts the subscription to the App source stream
}
renderStream
vDOM
source$
render
patch()
diff()
subscribe()
View
Update
Blink!
Blink!
Tick
Tick
(blank)
class Blink extends Component {
source($) {
const reducer = x => !x
const source = Interval(1000) // tick every second
return {source, reducer}
}
render(state) {
const style = {visibility: state ? 'visible' : 'hidden'}
return <div style={style}>Blink!</div>
}
}
Time as a source
instead of clicks
View
Timer Update
-1
Tick (-1)
Tick (-1)
Click Update
Click (+2)
1
0
Click (+2)
2
class CrappyBird extends Component {
initialState = 0
increment = createHandler(() => 2)
source($) {
const click$ = this.increment.$
const time$ = Interval(1000, -1)
render(state) {
return (
<div>
Count: {state}
<button onClick={this.increment}>+</button>
</div>
)
}
}
v$ = f(merge(d$, t$))
const source$ = merge(click$, time$)
const reducer = (acc, n) => acc + n
return { source$, reducer }
}
ok, react can be reactive...
View
Update
Wasted!
View
Update
Update
Wasted!
View
Update
Update
Frame
Frame
e.g. keyUp + onChange in one event, or really fast updates
Poor interoperability
Wasteful/Janky rendering
View
Update
View
Update
Render
View
Update
Render
Frame
Frame
View
Update
Dropped!
Update
Render
View
Frame
Frame
View
Low Pri Update
Dropped!
Low
Render
View
Frame
Frame
High Pri Update
High
Render
View
Data Producers
Data Consumers
Data Flows | Push | Pull |
---|---|---|
Single | Promise | Function Call |
Multiple | Callback/Observable | Generator/Fiber |
promise.then(data => {
//...
})
let data = fn()
source$.subscribe(data => {
//...
})
let gen = generatorFn()
let data = gen.next().value
Fiber :: Generåtor
Pull
Push
Update
View
Update
Queue
+
Render
Push whenever we want into the Queue
Pull whenever we want to render a Frame
Update
Present Day React: Batching updates
Blue = imperative
Red = reactive
React : "UI Virtual Machine" :: Fiber Scheduler : "React runtime system"
Queue
+
Render
v = f*(d$)
Scheduling on The Platform: MDN on requestIdleCallback, cooperative scheduling and the Background Tasks API!
Fiber Scheduling
(more control than generators)
You can use the mental model for so many things....
View
Low Pri
Low
WIP Priority Queue
High
Frame
Frame
High Pri
High
High
+
Low
Time Sliced Rendering
High Pri
Render
Current Queue
Low Pri
Priority Queue
+
Render
Pull
Push
Update
Queue
+
Render
🌀
Frame
Frame
Side Effects
Source
Loaded!
Update
Queue
+
Render
App + Side Effects
React
Today's React needs imperative side effects
Pull
Push
Update
PQueue
+
Render
+
Cache Miss
🌀
Frame
Frame
Side Effects
Source
Loaded!
App
React
Cache Library
PQueue
+
Render
+
Cache Read
Suspense resolves Local Data Deps in Render
Suspend
Resume
You use:
~300 LOC
~2200 LOC
swyx.io/ReactRally
(Slides, Github, Feedback, Further Reading)
⏰