[go: up one dir, main page]

0% found this document useful (0 votes)
269 views53 pages

Frontend Masters Hard Parts Ui

Download as pdf or txt
Download as pdf or txt
Download as pdf or txt
You are on page 1/ 53

UI Hard Parts

In UI Hard Parts we develop an under-the-hood understanding of building user


interfaces in the web browser.
Will Sentance

CEO & Founder at Codesmith


Frontend Masters

Academic studies
Harvard University
Oxford University

2
Principles of Analytical problem solving Technical communication

Engineering
Can you break down complex challenges and Can I implement your approach just from your
develop a strategy for solving the problem explanation

Engineering approach Non-technical communication


How you handle ‘not knowing’, debugging, code structure, Empathetic and thoughtful communication, supportive
patience and reference to documentation approach to the Codesmith community

JavaScript and programming experience


Concepts like higher order functions, closure, objects and
algorithms

3
Two goals to UI engineering: Display content - state/data -
so the user can ‘view’ it then let them interact with it

The web browser’s ad hoc history has led to the


UI the Hard Parts features that let us do this being spread across

State, View, JavaScript, languages, APIs and environments

DOM & Events


Requires JavaScript and the DOM to combine with help of
Webcore, Web IDL, the DOM API, Event API

But lets us build dynamic interactive experience at the


center of all modern applications

4
Almost every piece of technology
needs a visual ‘user interface’ (UI)

Content displayed on a screen (output from the


computer) that a user can change by doing
something

- Video stream from zoom


- Likes on tweet
- Comments in a chat
- Mario’s position on the screen

What are other UIs and what’s the interactivity?

5
Just two ‘simple’ goals - (1) display
content & (2) let user change it

Goal 1: Display content (data inside


computer/from internet)

Goal 2: Enable the user to interact (tap, click etc)


with the content they see and change it
(presumably change the underlying data -
otherwise it’d be inconsistent)

Let’s start with Goal 1

6
Goal 1 - Display the content

Code to display content ▼

But we still have to think about location


(coordinates), browser & device variations…

7
Solution: A list of elements in C++
app.html ▼ to add to the page - the DOM

What's on your mind?


<input>
<button>Publish!</button>

<div>
<video src="/carpool.mp4">
<p>Love Les Mis!</p>
<p>♡ 7</p>
</div> DOM - ordered list (object) of page (document) elements we can
add to using HTML

- Order/structure set by where we write each HTML line

Layout engine - works out page layout for specific browser/screen

Render engine - produces the composite ‘image’ for graphics card


8
We have ‘semi-visual code’
app.html ▼
Model of Page (DOM - C++) for structure & content
- Text: What’s on your mind?
What's on your mind?
<input> - Input field

<button>Publish!</button> - Button: Publish!

- Division
<div>
<video src="/carpool.mp4"> - Video: /carpool.mp4

<p>Love Les Mis!</p> - Paragraph: Love Les Mis!

<p>♡ 7</p> - Paragraph: ♡ 7


</div>
Create element, Add content, Where to add, Add to
page

But I’m displaying visual content - how does it look


(colors etc)?

9
Adding CSSOM for styling
app.html ▼
Model of Page (DOM - C++) and formatting
- Text: What’s on your mind?
What's on your mind?
<input> - Input field

<button>Publish!</button> - Button: Publish!

- Division
<div>
<video src="/carpool.mp4"> - Video: /carpool.mp4

<p>Love Les Mis!</p> - Paragraph: Love Les Mis!

<p>♡ 7</p> - Paragraph: ♡ 7


</div> - CSS object model (CSSOM) mirrors
- Linked file: app.css
<link rel="stylesheet" href="app.css">
the DOM

Goal 1 (display content/data) complete!


app.css ▼

button {background: slateblue}


10
Just two simple goals - (1) display
content & (2) let user change it

Goal 1: Display the content (data inside


computer/from internet) ✔

- HTML + CSS

Goal 2: Enable the user to interact (tap, click etc)


with the content they see and change it
(presumably change the underlying data -
otherwise it’d be inconsistent)

11
Goal 2: Enable user to change
content they see - but problem

Pixels ≠ Data

- We’re trained to think so by the real world


where metaphysical and epistemic are tied
- ‘Data’ vs ‘View’

But HTML 1x display/paint of content

User can change what they see e.g. input field

12
Typing in the input field
app.html ▼
Model of Page (DOM - C++) changes DOM ‘data’
- Text: What’s on your mind?
What's on your mind?
<input> - Input field

<button>Publish!</button> - Button: Publish!

- Division
<div>
<video src="/carpool.mp4"> - Video: /carpool.mp4

<p>Love Les Mis!</p> - Paragraph: Love Les Mis!

<p>♡ 7</p> - Paragraph: ♡ 7


</div>
Does change data from from “ “ -> “Hi!”

However:

1. Doesn’t display anywhere else


2. We can’t run any code in DOM
13
Goal 2: Enable user to change
content they see - but problem

Step 2 impossible w/ our approach to Step 1

- HTML 1x display/paint of content


- DOM - Display ‘data’ but can’t run code

We have to use JavaScript to:

- Create & save content/data


- Run code to change it

14
Data (and code to change it) is
app.html ▼
only available in JavaScript
1 <script src="app.js"></script>

JS file of code added using html code

JavaScript is an entire runtime

- Thread of execution to run code


app.js
- Memory for data
1
- Call stack & event loop to allow code
let post = "Hey 👋"
(functions) to run asynchronously
2 post = "Hi!"
3 console.log(post) We have data now - but how do we display it?
With WebIDL & WebCore

15
Webcore gives JavaScript
app.html ▼
access to the DOM & pixels
1 <input/>
Goal 1: Display content/data to the user
2 <div></div>
3 <script src="app.js"></script> 1. Data created & stored in JS: post = "Hi!"

2. Get DOM element (a link in JS): document.querySelector('div’)

3. Store link to element, in JS: const jsDiv


app.js

4. Use built-in ‘property-methods’ to add content/data from JS to DOM


// document - object of methods for DOM access element: jsDiv.textContent = post
1 let post = "Hi!"
5. DOM automatically displays content on page (the ‘view’)
2 const jsDiv = document.querySelector('div')

3 jsDiv.textContent = post Success! But SO much harder than <div>7</div>


4 console.log(jsDiv) // extremely misrepresentative! - How to technically communicate lines 2 and 3?
-

16
app.html ▼ Solving for Goal 2: Users can change
what they see (& underlying data) by
1 <input/>
interacting
2 <div></div>
<script src="app.js"></script>
3
User interaction (typing, clicking etc) is ‘registered’ by
DOM elements (they know when they’re tapped)
app.js
But how can this cause a change in data which is
elsewhere (in Javascript)?
1 let post = ""
2 DOM events and handler functions - lets user
3 const jsInput = document.querySelector('input')
action/input (‘events’) change data in JavaScript
4 const jsDiv = document.querySelector('div')
5 jsInput.oninput = handleInput
6 function handleInput(){
By saving a function definition to the DOM element -
7 post = jsInput.value
when the user action (‘event’) happens, the function will
8 jsDiv.textContent = post
be ‘called back’ into JS and executed automatically
9 }
10 So what?
11 jsInput.oninput = handleInput

17
app.html ▼

1 <input/>
We’ve solved for UI goals 1 & 2!
2 <div></div>
<script src="app.js"></script>
3 User actions can trigger running of JS code (!) to:

- (1) Save/change underlying data (in JS)


- (2) Change what data is displayed (in the DOM)
app.js - Note that we have to manually re-update
the DOM with our data - there’s no
1 let post = ""
‘propagation’
2
const jsInput = document.querySelector('input')
3 We have a full User interface (UI)
const jsDiv = document.querySelector('div')
4
function handleInput(){
Goal 1: Display content (data inside computer/from
5
6
post = jsInput.value internet) as the ‘view’ for users to see ✔
jsDiv.textContent = post
7
} Goal 2: Enable the user to interact (tap, click etc) with
8
9 jsInput.oninput = handleInput
the ‘view’ they see and change it (by changing the
underlying data & updating the view) ✔
18
HTML + the DOM work together for one-time display
Our remarkably intuitive HTML adds elements to the DOM exactly where we
write them in code then the layout and render engines display the content

Now we can display Apps let users interact (change what they see)
But this requires changing underlying data - which is only available in JavaScript - we

content and let our get to add that data to the DOM (and do many other things) with WebCore & WebIDL

users change it Events and handlers let users run JavaScript


Which can then get info on the user’s action and (1) make change to underlying

JavaScript, the DOM, Events & Handlers give data then (2) redisplay the new data - so the user sees the consequence

us our interactive applications


From HTML to JavaScript - descriptive to imperative
With HTML we could state (describe) our displayed content in intuitive ordered code but
without data it was not interactive. Adding data added interactivity but also significant
complexity. The rest of UIHP (and much of UI engineering) aims to reduce that complexity

19
Popular paradigm for tackling the essential UI
engineering challenge - data/view consistency

UI the Hard Parts


Invaluable at scale (state/view with 1000s of points of
One-way Data Binding interaction) & implemented in React, Angular & Vue

Enables us to build scalable apps with or without


frameworks, debug complex UIs & answer the toughest
interview Qs

20
app.html ▼
1 <input/>

2 <div></div> Let’s expand our UI to be more


3 <script src="app.js"></script> sophisticated

app.js
1 let post = ""
2 const jsInput = document.querySelector('input')

3 const jsDiv = document.querySelector('div')

4
jsInput.value = "What's on your mind?" // affect view!
5
6 function handleInput(){ Adding another handler - this one for clicks
7 post = jsInput.value

8 jsDiv.textContent = post // affect view! We’re changing our ‘view’ based on multiple possible user
9 } interactions. In practice there will be 1000s that can all
10 function handleclick(){
affect data/view
jsInput.value = "" // affect view!
11
}
12 We need a way to make these changes as predictable
13 jsInput.oninput = handleInput as we possibly can - how?
14 jsInput.onclick = handleclick
21
app.html ▼
1 <input/>
2 <div></div> Restrict every change to view to be via
3 <script src="app.js"></script>
(1) An update of ‘data’ and
app.js (2) A run of a single dataToView
1 let post = undefined // Data convertor function
2 const jsInput = document.querySelector('input')

3 const jsDiv = document.querySelector('div')

4
function dataToView(){ // affect view!
5
jsInput.value =
6 post == undefined ? "What's on your mind?" : post
7 jsDiv.textContent = post
8 }

9 function handleclick(){
10 post = "" // Update data
11 dataToView() // Convert data to view - This is just one approach - but an immensely
}
12 popular one (React, Next, Vue, Angular, Svelte)
13 function handleInput(){
14 post = jsInput.value // Update data - Tees us up for semi-visual coding with a virtual
15 dataToView() // Convert data to view
}
DOM
16
17 jsInput.onclick = handleclick
jsInput.oninput = handleInput
18
19 22
dataToView()
app.html ▼
1 <input/>
2 <div></div> Added benefit that we can declare our
3 <script src="app.js"></script>
data/view relationship once and when
app.js the data updates the view will update!
1 let post = undefined // Data
2 const jsInput = document.querySelector('input')
3 const jsDiv = document.querySelector('div')
4
5 function dataToView(){ // affect view!

6 jsInput.value =
post == undefined ? "What's on your mind?" : post
7
jsDiv.textContent = post
8
}
9
10 function handleInput(){ Remember our goals of UI: Display content
11 post = jsInput.value
12
} (underlying data) as ‘view’ and enable the user to
function handleclick(){
13 post = "" change it (both the underlying data and
14 }
corresponding view of it)
15 jsInput.oninput = handleInput
16 jsInput.onclick = handleclick
- We need to run the dataToView convertor on
17
18 setInterval(dataToView, 15) repeat but we’re able to automate the progress
23
1000s of ways for users to interact in a modern app
Each piece of ‘view’ (element on page) can be clicked/typed/moused over - all of
which need to change data and view

We’ve added We need a predictable structure

predictability to our Better to be restricted but predictable than overly flexible and it be impossible to
identify which line of code caused our view/data to change

Data and View flow


Every interaction must change JS data and then update all
UI application complexity grows exponentially dependent views
Changes to views via (1) An update of ‘data’ and (2) A run of a single dataToView convertor
with # of elements - one-way data binding can
ensure predictability and ease of reasoning
One-way data binding
Restricting all our user’s changes to flow to associated underlying data and all changes of
view to flow through a single dataToView convertor is a restrictive but powerful step to
simplify our reasoning about UI by order of magnitude

24
What’s tomorrow?

More flexible Virtual DOM & Functional components & State hooks, Diffing & DOM
UI Composition properties reconciliation
JavaScript is not at all visual or We’ll get visual/declarative development but We need to solve the performance crisis we
declarative - we’ll make it more so with added flexibility & reusability create by solving the first 3 hard parts

25
Most misunderstood concept in UI
development

UI the Hard Parts


Enables us to have a more visual (or declarative)
Virtual DOM experience of coding UIs with JavaScript

Requires significant optimizations (diffing,


reconciliation) to be performative

26
app.html ▼
1 <input/> Returning to our full UI with auto
2 <div></div>
updating views (from data)
3 <script src="app.js"></script>

app.js
1 let post = "" // Data
2 const jsInput = document.querySelector('input')
3 const jsDiv = document.querySelector('div')
4
5 function dataToView(){ // affect view!

6 jsInput.value = post

7 jsDiv.textContent = post - We’re describing a key part of the UI in


}
8 dataToView:
9 function handleInput(){
10 post = jsInput.value - The contents (data) and how to display it
if (post == "Will"){ jsDiv.remove() } // affect view!
11 - But even our ‘containers’ are ‘data’ of sorts
}
12
13 jsInput.oninput = handleInput - What if we also described the element as well
14 then our dataToView convertor is a complete
15 setInterval(dataToView, 15)
description of the data + view?
27
app.html ▼
1 <script src="app.js"></script> Function that fully creates element & relates
data/view is known as a UI component
app.js
1 let post = ""; let jsInput; let jsDiv // Data
2
3 function dataToView(){ // affect view!
4 jsInput = document.createElement('input')

5 jsDiv = post == "Will" ? "" : document.createElement('div')


6
7 jsInput.value = post
8 jsDiv.textContent = post

9 jsInput.oninput = handleInput - Combines everything into 1 function - Creates


10
document.body.replaceChildren(jsInput, jsDiv)
elements, sets their data/contents, attaches
11
} handlers & displays via DOM
12
13
function handleInput(){ - Ties data to view - handles how user can change
14
post = jsInput.value
15 } data (here, via input) then re-runs to update view
16
with new data
17 setInterval(dataToView, 15)
- React, Angular, Vue, Svelte all have these
28
[📝 JS + Webcore]

mypage.html ►
String interpolation gives us
‘visual’ code mypage.js ▼

1 let name = "Jo"


- Known as Template literals 2
3 let textToDisplay = "Hello, "
- The closer our code can be to ‘visual’ 4 textToDisplay = textToDisplay.concat(name)
(mirroring what its visual/graphic 5 textToDisplay = textToDisplay.concat("!") // "Hello, Jo!"
output will look) the easier for us as 6

developers 7 // alternative
8
- Could we do the something similar with 9 textToDisplay = `Hello, ${name}!` // "Hello, Jo!"
our main code creating visual
elements?

29
[📝 JS + Webcore]

mypage.html ►
Could we emulate HTML with
semi-visual coding? mypage.js ▼

1 let name = "Jo"


2
Starting with a ‘unit of code’ representing each
3 const divInfo = ['div', `Hi, ${name}!`]
piece of ‘view’ 4
5 function convert(node){
- JavaScript array with all the details
6 const elem = document.createElement(node[0])
- Element 0 the type, element 1 the
7 elem.textContent = node[1]
contents
8 return elem
And a function that takes in that list and 9 }
produces a DOM element as output 10
11 const jsDiv = convert(divInfo)
- A ‘mirror’ in our code the actual DOM 12
element 13

Puts all our element details in an array and our


‘create element, add contents’ instructions into
a convert function
30
[📝 JS + Webcore]
mypage.html ►

mypage.js ▼

1 let name = ""; let jsInput; let jsDiv; let vDOM


Creating a JavaScript 2

(‘virtual’) DOM 3 function createVDOM (){


4 return [['input', name, function handle (){name = jsInput.value}],
5 ['div', `Hello, ${name}!`]]
}
1. Blocks of code representing each 6
piece of ‘view’ 7 function updateDOM() {
8 vDOM = createVDOM()
- JavaScript array with all the 9 jsInput = convert(vDOM[0]) // jsInput
details 10 jsDiv = convert(vDOM[1]) // outputDiv
11 document.body.replaceChildren(jsInput, jsDiv) // DOM
- Function that takes in that array }
12
and produces a DOM element as
13 function convert(node){
output
14 const element = document.createElement(node[0])
- convert
15 element.textContent = node[1]
16 element.value = node[1]
17 element.oninput = node[2]
2. Then where we place them in the 18 return element
}
code mirrors the order they show up on 19
the page 20 setInterval(updateDOM, 15)

31
Declarative UI as a programming paradigm
Goal: See a JS nested list representation1 of your actual DOM elements (and contents) so you can get semi-visual coding 🥲
- Via nested objects or arrays of info (as we default to in UIHP) - or functional components in React

For it to be guaranteed accurate (& not out of sync) - you need your data in JS to be the only source of truth
- You can only do a JS ‘DOM’ if you’ve set on ‘one-way data binding’2 - otherwise it’s not guaranteed to be reflective of
the actual DOM (which has been altered by the user!)

So we literally take any change to the DOM (e.g. user action like typing letters in an input field) and immediately replace it by
- (i) sending the info to ‘data’ store in JS
- (ii) have that update contents of the JS DOM3
- (iii) convert that to the DOM (view)

We have semi-visual coding - our JS DOM contents and positioning on the JS page is reflected in our actual view
- We ‘declare’ (a) what page structure we want and (b) how we want its contents to depend on our data4
- And then it appears on the page (even an empty form field is the propagation of data - our ‘empty string’)

1. This is the ‘virtual DOM’. You don’t have one by default - only objects representing access to the respective DOM elements
2. Every bit of view at all times depends on underlying JS state - not whatever a user has started typing in
3. [I need to update the schema to include this ‘JS DOM’ step
32
4. Note there’s two parts to the ‘declaration’: structure/positioning & contents
Our JavaScript code mirrors our output with our JS
DOM - we can make it flexible by mapping over it

UI the Hard Parts


Composition & We can use reusable functions to create JS (V) DOM

Functional components elements each with specific data

We get UI composition with units of UI (data, view and


handlers) that we can reuse and combine flexibly to
build out our applications

33
Lists are central to UI mypage.html ►

development mypage.js ▼

1 const vDOM = [
We have a list of 2 arrays with their
2 ['input', name, function handle (){name = jsInput.value}],
element details - but in reality it will 3 ['div', `Hello, ${name}!`]
likely be many (49 video elements in a ]
4
zoom meeting, 100 tweets in a timeline)

We need tools for dealing with lists

- To apply code (functionality) to


each element - here to take the
info and connect to a DOM
element vDOM.map()

To convert an array elements to


individual inputs (arguments) to
a function (for functions that
don’t take in arrays) spread
syntax ...

34
[📝 JS + Webcore]

mypage.html ►

‘Mapping’ over our vDOM mypage.js ▼

1 let name = ''


2 const vDOM = [
3 ['input', name, function handle (){name = jsInput.value}],
- Now adding new ‘elements’
4 ['div', `Hello, ${name}!`]
doesn’t require any new code 5 ]
6
- The map call will handle as
7 function convert(node){
many elements in the vDOM as 8 const element = document.createElement(node[0])
there are 9 element.textContent = node[1]
10 element.value = node[1]
11 element.oninput = node[2]
12 return element
}
13
const elems = vDOM.map(convert)

35
[📝 JS + Webcore]

mypage.html ►

Spread syntax for arrays & mypage.js ▼

objects 1 const vDOM = [


2 ['input', name, handle],
3 ['div', `Hello, ${name}!`]
To add our array of DOM elements we 4 ]
need to convert them to individual 5

arguments 6 function handle (){name = jsInput.value}


7
- Spread syntax allows an iterable 8 function convert(node){
9 const element = document.createElement(node[0])
such as an array expression or
10 element.textContent = node[1]
string to be expanded element.value = node[1]
11
12 element.oninput = node[2]
- To work with the append method
return element
13
(which can’t handle arrays) }
14
const elems = vDOM.map(convert)
// convert(vDOM[0]) - push to new elems array
// convert(vDOM[1]) - push to new elems array
document.body.replaceChildren(...elems);
// spread out elems array into individual arguments
36
mypage.html ► [📝 JS + Webcore]

mypage.js ▼

1 let name = ''; let vDOM; let elems


With our new 2

element-flexible code we 3 function createVDOM (){


return [["input", name, function handle (e){name = e.target.value}],
4
can ‘compose’ our code 5 ["div", `Hello, ${name}!`],
["div", "Great job”]]
6
}
- Adding to our list of arrays each 7
8 function updateDOM() {
new element we want to see in
9 vDOM = createVDOM()
order on our page
10 elems = vDOM.map(convert)
11 document.body.replaceChildren(...elems)
- They’ll show up on the page }
12
exactly as they’re placed in code
13 function convert(node){

We’re getting semi-visual coding! 🙌 14 const element = document.createElement(node[0])


15 element.textContent = node[1]
16 element.value = node[1]
17 element.oninput = node[2]
18 return element
}
19
20 setInterval(updateDOM, 15)

37
mypage.html ► [📝 JS + Webcore]

mypage.js ▼

1 let posts = ["Gez", "Gerry", "Fen", "Ursy"]; let vDOM; let elems;

Even more composition - 2


function Post(msg){ return ["div", msg] }
creating multiple (similar) 3
4
VDOM elements each with 5 function createVDOM (){

different data 6 return [["input", name, handle],


...posts.map(Post)]
7
}
8 function handle (e){posts.push(e.target.value)}

- We can produce our VDOM 9


10 function updateDOM() {
elements with a function so that
11 vDOM = createVDOM()
we can easily add new elements elems = vDOM.map(convert);
12
document.body.replaceChildren(...elems)
- Post defines data -> view 13 }
14
relationship where the data (each function convert(node){
15
element in the array posts) const element = document.createElement(node[0])
16
changes for each of the posts element.textContent = node[1]; element.value = node[1]
17
element.oninput = node[2]
18
- Can also add more logic to the return element
19 }
Post/Input functions to make 20
setInterval(updateDOM, 15)
them more powerful 21

38
All great and awesome but we need efficiency now
We love the vDOM for semi-visual coding - however performance is a nightmare - we need some
improvements

Hooks

We’re running this updateDOM function every 15 ms - CSS animations and smooth scrolling are at risk (and
user action handlers get blocked even!).

We could only run on data change - but now our VDOM is not ‘real’ - it’s only rendered ‘as is’ if we’ve
remembered to run the updateDOM function on every data change (including from servers etc) - which is
unlikely - so introduce a ‘state hook’

Diffing

Only some of our DOM elements need recreating from scratch - we can compare archived VDOMs and
workout what changes to actually make using an algorithm

39
mypage.html ► [📝 JS + Webcore]

mypage.js ▼
1 let name = ""; let vDOM; let elems
2
Automatic update on data
3 function updateName(value){
change without looping 4 name = value

5 } updateDOM()
6
function createVDOM (){
- Instead of directly updating name, 7
return [["input", name, function handle (e){updateName(e.target.value)}],
8
we run a function ‘updateName’ to ["div", `Hello, ${name}!`],
9
do so ["div", "Great job!"]]
10 }

- And of course it’s low key running 11


function updateDOM() {
12
updateDOM for us vDOM = createVDOM()
13
elems = vDOM.map(convert);
- As long as we restrict our team 14
document.body.replaceChildren(...elems);
15 }
from ever changing data directly
16 function convert(node){
and only let them do so using the
17 const element = document.createElement(node[0])
update function then we’re fine 18 element.textContent = node[1]; element.value = node[1]
19 element.oninput = node[2]
- We’d likely lock down name so that
20 return element
it can’t be accessed directly }
21
22 updateDOM()

40
mypage.html ► [📝 JS + Webcore]

mypage.js ▼
1 const data = {name: ''}; let vDOM; let elems
2
Turning it into something we
3 function updateData(label, value){
can use with any data 4 data[label] = value

5 } updateDOM()
6
function createVDOM (){
- We can make our updatename 7
return [["input", data.name, handle],
8
function an updateData function ["div", `Hello, ${data.name}!`],
9
and have it work for any piece of ["div", "Great job!"]]
10 }
data in our app - we just hook into 11
it function handle (e){updateData('name', e.target.value)}
12
13
- So they call it a hook… function updateDOM() {
14
vDOM = createVDOM()
15
- We could switch to running elems = vDOM.map(convert);
16 document.body.replaceChildren(...elems);
requestAnimationFrame rather
17 }
than updateDOM directly on data 18
function convert(node){
change - so it never prioritizes over 19 const element = document.createElement(node[0])
animations (CSS etc) - truly 20 element.textContent = node[1]; element.value = node[1]
optimized wrt other priorities now 21 element.oninput = node[2]
22 return element
}
41
updateDOM()
mypage.html ► [📝 JS + Webcore]

mypage.js ▼

1 let name = ''


All the benefit of 2

composition - but it’s still 3 function createVDOM (){


4 return [["input", name, function handle (e){name = e.target.value}],
dangerously inefficient 5 ["div", `Hello, ${name}!`],
6 ["div", "Great job!"]]

- Only some of our elements need 7 }


8
recreating from scratch
9 function findDiff(prevVDOM, currentVDOM){
10 for (let i = 0; i < currentVDOM.length; i++) {
- Couldn’t we recreate our vDOM
11 if(JSON.stringify(prevVDOM[i]) !== JSON.stringify(currentVDOM[i])){
from scratch to give us
12 // change the actual DOM element related to that vDOM element!
‘composition’ but then: }
13
14 }
- Write an ‘algorithm’ (smart
15 }
instructions) to check what
16
elements actually differ - and
17 const vDOM1 = createVDOM()
only change the DOM elements 18 name = 'Will'
that need updating 19 const vDOM2 = createVDOM()
20
21 findDiff(vDOM1, vDOM2)
42
mypage.html ► [📝 JS + Webcore]

mypage.js ▼
1 let name = ''; let vDOM = createVDOM(); let prevVDOM; let elems
2
function createVDOM (){ return [
3
Finally - composable UI 4
["input", name, handle],
["div", `Hello, ${name}!`],

that’s not a disaster! 5


6
}
["div", "Great job!"]]

7
function handle (e){name = e.target.value}
8
9 function updateDOM() {
- Integrating this ‘diffing 10 if (elems == undefined){

11 elems = vDOM.map(convert)
algorithm’ makes our code for document.body.append(...elems)
12 }
building user interfaces else {
13
prevVDOM = [...vDOM]
‘semi-visual’ (enabling 14 vDOM = createVDOM()
composition) 15 findDiff(prevVDOM, vDOM)
}
16 }
- And yet not untenably inefficient 17
function convert(node) {
18
const element = document.createElement(node[0]);
19
element.textContent = node[1]
20
element.value = node[1]
21
element.oninput = node[2]
22
return element
23 }
24
function findDiff(prevVDOM, currentVDOM){
25
for (let i = 0; i < currentVDOM.length; i++) {
26 if(JSON.stringify(prevVDOM[i]) !== JSON.stringify(currentVDOM[i])){ 43
elems[i].textContent = currentVDOM[i][1]
Single source of truth for our data
Everything the user sees stems from the data. It’s restrictive but desperately
predictable - and enables data propagation to the view

Enables UI composition with JavaScript

We have a A JavaScript DOM that enables a semi visual form of coding our UIs - where units of code that
describe units of view can be positioned in the code to indicate their order on the webpage

groundbreaking
approach to UI
Makes reasoning about state/view much easier
The downside of semi-visual code is it takes the data and info on the view at this moment
and creates it in full (that’s why it’s so easy to reason about - we don’t have to figure out the
change from the previous data/view - just current data generating current view).

But requires techniques like hooks & VDOM diffing for efficiency
If we rebuilt the DOM itself every time any data changed that would be entirely unnecessary.
Instead we can spot the actual differences and only change those (DOM reconciliation)

44
UIHP ARCHIVE
Slide 12/13
Slide 5 Slide 7

Slide 8

Slide 9
Slide 13

📊Slide 12/13

46
Slide 14

📊Slide 14

📊Slide 15 -> 16 Here the previous view is


one of two (I)ndependent
sources of truth (ie we
have to reason about this
part of the view and how
📊Slide 15 to change it if necessary)

Here the previous view (user


typing ‘Jo’) updates our data but
then our data regenerates our
entire view - our prev is of no
relevance

47
Slide 26

48
mypage.html ► [📝 JS + Webcore]

mypage.js ▼
Part 1 - Event delegation 1 let name = ''; let vDOM; let elems
2
Now what if we wanted more function createVDOM(){
3
control over placement - 4
return [['div', [
['p', "Gez", function handle1 (e){name = e.target.textContent}],
nested elements 5
['p', "Ginger", function handle2 (e){name = e.target.textContent}],
6
["p", `${name}!`, function handle3 (e){name = e.target.textContent}]]]]
- We can use ‘sub-arrays’ to visually 7 }
8
show on page of code that we function updateDOM() {
9
want these ‘p’ elements to be the vDOM = createVDOM()
10
content of the ‘div’ on our elems = vDOM.map(convertNested);
11 document.body.replaceChildren(...elems);
webpage itself 12 }
13
- Our code layout determines our function convertNested(node){
14
const element = document.createElement(node[0])
actual layout (semi-visual coding!) 15 if (node[1] instanceof Array){
16 const childElems = node[1].map(convertNested);
- We have to expand our
17 element.append(...childElems)
`createDOMElement` function to 18 }

handle a array of elements as the else { element.textContent = node[1] }


19
element.onclick = node[2]
‘content’ of another element 20
return element
21 }
- 22
23 setInterval(updateDOM, 15)
49
mypage.html ► [📝 JS + Webcore]

mypage.js ▼
1 let name = ''; let vDOM; let elems
2
Introducing event delegation function createVDOM(){
3
for performance/efficiency 4
return [['div', [
['p', "Gez"],
5
['p', "Ginger"],
6
['p', `${name}!`]],
- In practice we’ll have 20, 50, 100+ 7
function handle (e){name = e.target.textContent}]]
8 }
elements each needing their own
9
event handler function - totally
10 function updateDOM() {
inefficient 11 vDOM = createVDOM()
elems = vDOM.map(convertNested);
CUT 12
- Fortunately the DOM Event feature document.body.replaceChildren(...elems);
13 }
also includes ‘event bubbling’ - the
14
event floats ‘up’ and each parent function convertNested(node){
15
on the DOM tree gets hit with the const element = document.createElement(node[0])
16
if (node[1] instanceof Array){
event - and if it has a handler 17
const childElems = node[1].map(convertNested);
function that function will run in 18
element.append(...childElems)
19 }
JavaScript
20 else { element.textContent = node[1] }
21 element.onclick = node[2]
22 return element
}
23
50
24 setInterval(updateDOM, 15)
mypage.html ► [📝 JS + Webcore]

mypage.js ▼

1 let name = ''; let vDOM; let elems;


Let’s try the decoration 2

approach - *directives* 3 function createVDOM () {


return [["input", name, function handle (e){name = e.target.value}],
4
5 } ["div", `Hello, ${name}!`, , setColor]]

6
- Each element on the page has a 7 function setColor(el, data) { if (data == "Will") {el.style.color = "lightgreen" } }
8
chance to ‘do’ something in the function updateDOM() {
9
user’s eyes vDOM = createVDOM()
10
elems = vDOM.map(convert)
- In reality that ‘doing’ is 11
document.body.replaceChildren(...elems)
12 }
happening in JavaScript (e.g.
13
checking a conditional, a loop function convert([type, contents, handler, fn]){
14
etc) and then some updating of const element = document.createElement(type)
15
element.textContent = contents
the view (DOM) 16
element.value = contents
17
element.oninput = handler
- We can make our vDOM 18
if (fn) { fn(element, name) }
elements store the functionality 19 return element
20 }
21
22 setInterval(updateDOM,15)
51
Could we augment our VDOM elements to include
additional functionality?
Couple of interesting ways we could try:

1. Write statements that evaluate directly in the array

2. Create functions that take in our element and add functionality to it (‘decorate’ it) then return it -
Directives

3. Create our elements with a function that returns them out but before it does, it can run code to
determine exactly what it returns out - functional components

52
mypage.html ► [📝 JS + Webcore]

mypage.js ▼
1 let name = ''; let vDOM; let elems;
2
Alternatively produce our
3 function Input(){
elements with functions 4}
return ['input', name, function handle (e){name = e.target.value}]

5
function Notifcation(){
6
let className = 'darkred'
- Let’s us add logic/code/ 7
if (name == "Will"){className = 'lightgreen'}
8 return ["div", `Correct ${name}!`, , className]
functionality before we return out }
9
the VDOM element
10 function updateDOM() {

- We still get to have declarative UI 11 vDOM = [Input(), Notifcation()]


12 elems = vDOM.map(convert);
(see line 11) - but now with some document.body.replaceChildren(...elems)
13 }
logic added
14
15 function convert(node){
const element = document.createElement(node[0])
16
element.textContent = node[1]
17
element.value = node[1]
18
element.oninput = node[2]
19 element.classList.add(node[3])
20 return element
}
21
22 setInterval(updateDOM, 15)

53

You might also like