type Props =
  {Name: string;}

Full name: index.Props
Props.Name: string
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
val sayHelloComponent : props:Props -> 'a

Full name: index.sayHelloComponent
val props : Props
val sayHello : name:'a -> 'b

Full name: index.sayHello
val name : 'a
Multiple items
type SayHelloComponent =
  inherit obj
  new : initialProps:'a -> SayHelloComponent
  override render : unit -> 'a

Full name: index.SayHelloComponent

--------------------
new : initialProps:'a -> SayHelloComponent
val initialProps : 'a
type obj = System.Object

Full name: Microsoft.FSharp.Core.obj
override SayHelloComponent.render : unit -> 'a

Full name: index.SayHelloComponent.render
System.Object.ReferenceEquals(objA: obj, objB: obj) : bool
val unbox : value:obj -> 'T

Full name: Microsoft.FSharp.Core.Operators.unbox
type bool = System.Boolean

Full name: Microsoft.FSharp.Core.bool
val sprintf : format:Printf.StringFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member GetSlice : startIndex:int option * endIndex:int option -> 'T list
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
val map : mapping:('T -> 'U) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.map
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
union case Option.None: Option<'T>
union case Option.Some: Value: 'T -> Option<'T>

React performance in a Fable world


Plan

  1. 🧙🏻‍ How does react work ?
  2. 🔍 Measuring performance
  3. 💫 React in Fable
  4. 👩‍💻 Optimizations

Bonus

  • 🤹‍ Micro optimizations
  • 🚀 The future?
🥚🐣🐤💀

How does react work ?

« Virtual DOM » is a little short for an answer

🧙

DOM

  • An object model for HTML
  • A very imperative API
  • Not always slow but reflow (size recalculation) can be

React 101

  • Written in JS, made by Facebook, Open Source
  • A declarative syntax for the DOM
  • Elements
    • Native elements (HTML)
    • Custom Components
    • Basic types (string, number)
    • Arrays of all the above

Components

  • Props
  • State
  • ~render
  • ~shouldComponentUpdate

Reconciliation

  • Can be seen as an Element tree, distinct from the DOM
  • Triggered by our code
  • The new DOM is diffed with the previous one and change applied

Diff

  • Different types (different component or HTML element) are always re-created
  • HTML elements properties are compared with the previous ones and the changes are applied
  • Component instances are kept and asked if they should be recursed into via shouldComponentUpdate. If true render is called.
  • Elements are always compared in order except if they have a Key prop

What do we want when we optimize

  • Limit the number of React Elements considered
  • Limit the amount of DOM ones returned
  • Ease the work of the diff algorithm with as much hints as we can
  • Limit the changes that really need to happen in the DOM

PureComponent

  • Provided by React in the React.PureComponent base class
  • Act like if shouldComponentUpdate was implemented with a shallow diff of state and props
  • So each field in props is compared by reference with the previous value

Functional components

  • Purely a different syntax to declare a Component
  • No shouldComponentUpdate implemented, they always re-render
  • Still useful versus just-a-function to give hints to React diff algorithm

Elmish

  • Elm-like library to manage web application state
  • Same pattern as Flux then Redux tried to apply in JS
  • We're mostly interested in the render part here

Measuring performance

Measure, Measure, Measure, Optimize?

🔍

User timing

React post performance events to browsers in development builds

Browsers show them when you take a measurement via their developer tools.

Chrome

Also on Edge

  • Very useful, but doesn't work in Firefox and Edge is still inferior. Use Chrome.
  • Chrome view is the best but need a little practice
    • It show both React events and JS callstack as a Flame Graph 👍
    • Finding anything at first is confusing. Zoom on top Timeline then adjust with scroll wheel.
  • Beware of React fiber: a single update can have continuations later

React profiler

Require recent (September 2018) React versions and DevTools

1: 
yarn upgrade react react-dom

Works on Chrome and Firefox

  • Arrows move between renders
  • Grey = not rendered
  • Color = rendered, color coded for time
  • Early stage but extremely promising tool
  • No correlation with JS callstack
  • Start with it then move to a more complex analysis

Introducing the Canary

  • A very visual way to see render() calls
  • A PureComponent that show instance and global render() count
  • Show 🐣 when instance render() is 1, 🐤 when between 2 and 5 and ☠️ otherwise

DEMO Time

React in Fable

A weird syntax

💫

Basic syntax is simple

Most Elmish projects only use it

1: 
2: 
3: 
div [] [
    str "Hello 🐤"
]

Simplest components can be "functional"

1: 
2: 
3: 
4: 
5: 
6: 
type Props = { Name: string }
let sayHelloComponent (props: Props) =
    div [] [ str "Hello "; str props.Name ]

let sayHello name =
    ofFunction sayHelloComponent { Name: name } []
1: 
div [] [ sayHello "World!" ]

More complex ones need the class syntax

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
type Props = { Name: string }

type SayHelloComponent(initialProps) =
    inherit Component<Props, obj>(initialProps)

    override this.render() =
        div [] [ str "Hello "; str this.props.Name ]

let sayHello name =
    ofType<SayHelloComponent, _, _> { Name: name } []
1: 
div [] [ sayHello "World!" ]

Direct mounting in the DOM

1: 
2: 
3: 
4: 
5: 
6: 
7: 
<!doctype html>
<html>
<body>
    <div id="root"></div>
    <script src="bundle.js"></script>
</body>
</html>
1: 
2: 
let reactElement = sayHello "World!"
mountById "root" reactElement

Optimizations

From components to DOM

👩‍💻

A few standard best practices

Easily findable in React documentation

  • Production builds need to use the production build of React (DefinePlugin in Webpack)
  • Minification and dead code removal need to be done
  • Take the habit of using components! Yes it's a little bit uglier than the "elm" style but it's useful all around.

Use PureComponent

For shallow props comparison

Most of your components shouldn't render() without changes.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
type UserInfo = { Name: string }
type UserProps = { Info: UserInfo }

type UserComponent(initialProps) =
    inherit StatelessComponent<UserProps>(initialProps)
    member this.render() =
        div [] [
            str "Welcome "
            createCanary [Name this.props.Info.Name]
        ]
    static member inline Create info =
        ofType<UserComponent, _, _> { Info = info } []

type Model = { User: UserInfo; (* ... *) }
let view model dispatch =
    div [
        UserComponent.create model.User
        // ...
    ]

Tree before

Tree after

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
type UserInfo = { Name: string }
type UserProps = { Info: UserInfo }

type UserComponent(initialProps) =
    inherit PureStatelessComponent<UserProps>(initialProps)
    member this.render() =
        div [] [
            str "Welcome "
            createCanary [Name this.props.Info.Name]
        ]
    static member inline Create info =
        ofType<UserComponent, _, _> { Info = info } []

type Model = { User: UserInfo; (* ... *) }
let view model dispatch =
    div [
        UserComponent.create model.User
        // ...
    ]

Tree before

Tree after

  • Easier if you're already using components
  • Really worth it, do you want the user's name block to update each time someone send a chat message ?
  • Don't use it in Elmish if you need dispatch as it's not a stable reference (Yet ?)
  • There is a good elmish alternative that is easier to read especially in simple cases

Elmish way: lazyViewWith

Nearly the same with less code. Also has a lazyView2With variant to get dispatch.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
type UserInfo = { Name: string }

let userView info =
    div [] [
        str "Welcome "
        createCanary [Name info.Name]
    ]

type Model = { User: UserInfo; (* ... *) }
let view model dispatch =
    div [
        userView model.User
        // ...
    ]

Tree before

Tree before

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
type UserInfo = { Name: string }

let userView info =
    div [] [
        str "Welcome "
        createCanary [Name info.Name]
    ]

let inline refEquals a b = obj.ReferenceEquals(unbox a, unbox b)

type Model = { User: UserInfo; (* ... *) }
let view model dispatch =
    div [
        lazyViewWith refEquals userView model.User
        // ...
    ]

Tree before

Tree before

IMPORTANT

  • Single parameter (model) is a subset of the main model.
  • If it's created in place (tuple for example) it defeat the purpose.
  • Clearly more readable
  • Doesn't have a name so React DevTools is a sea of LazyView and React diff always see the same component
  • Fable curry handling mean that 2 functions are created each render() for refEquals, fast or now depending heavily on JS engine.
  • Perf good enough for 90% of usages

React.memo living on the edge

Pure Component + Functional Component = 🧟‍

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
type UserInfo = { Name: string }

let userComponent = namedMemo "UserComponent" (fun info ->
    div [] [
        str "Welcome "
        createCanary [Name info.Name]
    ]
)

let inline userView name = ofElementType userComponent { Name = name } []

type Model = { User: UserInfo; (* ... *) }
let view model dispatch =
    div [
        userView model.User
        // ...
    ]
  • Need react and react-dom 16.6
  • It's been out for 3 days, short even for JS
  • Fable API presented is not final
  • Name display isn't there in DevTools yet
  • DevTools show them as rendering when they aren't (With a negative time)

Don't use shouldComponentUpdate for custom comparison

  • Rarely in functional code, PureComponent is an equivalent for immutable data and it's what we use.
  • Only potential use-case is structural equality but use Elmish lazyView (Even if you don't use Elmish otherwise).

Use Elmish lazyView for structural equality

Rarely useful but passing multiple part of state can justify it

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
type UserInfo = { Name: string }
type ConnectionInfo = { IsAway: bool }

let userView (user, connection) =
    let statusText = if connection.IsAway then "away" else "present"
    div [] [
        str "Welcome "
        createCanary [Name info.Name]
        str (sprintf " (You are %s)" statusText)
    ]

type Model = { User: UserInfo; Connection: ConnectionInfo; (* ... *) }
let view model dispatch =
    div [
        userView (model.User, model.Connection)
        // ...
    ]
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
type UserInfo = { Name: string }
type ConnectionInfo = { IsAway: bool }

let userView (user, connection) =
    let statusText = if connection.IsAway then "away" else "present"
    div [] [
        str "Welcome "
        createCanary [Name info.Name]
        str (sprintf " (You are %s)" statusText)
    ]

type Model = { User: UserInfo; Connection: ConnectionInfo; (* ... *) }
let view model dispatch =
    div [
        lazyView userView (model.User, model.Connection)
        // ...
    ]
  • Use only in deep places, potential complexity of structural equality depends on object graph size
  • Don't use because data is streamed from a server, the comparison should be in the state update, not each render.
  • Useful case is when used to pass multiple part of the parent model in Elmish as it involve a temporary Record or Tuple.
  • While more verbose a PureStatelessComponent with dedicated props can be better for performance (Worth it or not depends on your application, Measure !)

Adding keys to elements

« The yield! syndrome »

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
type State = { Canaries: string list }

type KeysComponent(initialProps) =
    override this.render() =
        div [] [
            yield button [OnClick this.Add] [str "🥚 Add"]
            yield! this.state.Canaries
                |> List.map(fun c -> createCanary [Block true; Name c])
        ]

Keys1

Tree before

Tree after

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
type CanaryInfo = { Id: int; Name: string }
type State = { MaxId: int; Canaries: CanaryInfo list }

type KeysComponent(initialProps) =
    override this.render() =
        div [] [
            button [OnClick this.Add] [str "🥚 Add"]
            this.state.Canaries
            |> List.map(fun c ->
                createCanary [Key (string c.Id); Block true; Name c.Name])
            |> ofList
        ]

Keys2

Tree before

Tree after

If ofList is used without key

React key warning

yield! hide this from us

  • Nearly always a good practice, if it's a list it should use ofList or ofArray and have keys
  • Doesn't optimize anything if the list doesn't change (hardcoded, set only once, ...)
  • Also less useful if the list is the last element of a block and is append-only
  • Do it by default, it's simple

Using element types as hints

Present different elements to React to hint that it should replace the DOM instead of uselessly diff.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
if editMode then
    div [] [
        str "Enter the "
        createCanary []
        str " name"
        input [Type "text"] []
    ]
else
    div [] [
        str "Canary: "
        createRandomCanary []
    ]

Tree before

Tree before

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
let editComponent _ =
    div [] [
        str "Enter the "
        createCanary []
        str " name"
        input [Type "text"] []
    ]
let edit () = ofFunction editComponent [] []

let showComponent _ =
    div [] [
        str "Canary: "
        createRandomCanary []
    ]
let show () = ofFunction showComponent [] []

// in a render()
if editMode then edit () else show ()

Tree before

Tree before

  • Complexify the code a little bit
  • Often an occasion to better separate functional blocks in your app
  • Mostly worth it if the change can happen often
  • Not in the previous sample but nobodyTypingComponent is a perfect example of a component that can be Pure and can even return a pre-computed DOM tree (See micro optimizations).

In lists prefer PureComponents

Immutable list elements + PureComponents = 💕

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
type ChatLine = { Id: int; User: string; Text: string }
type ChatProps = { Lines: ChatLine list }

override this.render() =
    div [] [
        this.props.Lines
        |> List.map(fun line ->
            div [Key (string line.Id)] [
                strong line.User; str ": "; str line.Text
            ])
        |> ofList
    ]
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
type ChatLine = { Id: int; User: string; Text: string }
type ChatLineProps = { Key: string; Line: ChatLine }
type ChatProps = { Lines: ChatLine list }

type ChatLineComponent(initialProps) =
    inherit PureStatelessComponent<ChatLineProps>(initialProps)
    member this.render() =
        div [] [ // No key, they are always directly on the element in the list
            strong line.User; str ": "; str line.Text
        ]
    static member inline Create line =
        ofType<ChatLineComponent, _, _> line []

override this.render() =
    div [] [
        this.props.Lines
        |> List.map(fun line ->
            ChatLineComponent.Create { Id = string line.Id; Line = line })
        |> ofList
    ]
  • Important for lists that have quite a few elements and change
    • TODO list where users add/remove items,
    • chat where the server send new lines
  • Don't re-render and force React to diff 1000 of chat lines each time someone say something, except if you want to use more CPU than slack.

Elmish Trick: lazyView doesn't work because it doesn't have a Key but you can wrap it in a fragment

1: 
2: 
3: 
4: 
5: 
6: 
    div [] [
        this.props.Lines
        |> List.map(fun line ->
            fragment [Key (string line.Id)] [lazyView renderLine line])
        |> ofList
    ]

Questions

Micro optimizations

Always measure before

🤹‍

Event handlers without mutating the DOM

Function.prototype.bind()

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
type CounterState = { counter: int }

type Counter(initialProps) =
    inherit Component<obj, CounterState>(initialProps)
    do
        base.setInitState({ counter = 0})

    member this.Add(_:MouseEvent) =
        this.setState(fun state _ -> { counter = state.counter + 1 })

    override this.render() =
        div [] [
            ofInt this.state.counter
            button [OnClick this.Add] [str "👍"]
        ]

Fable 1

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
type CounterState = { counter: int }

type Counter(initialProps) as this =
    inherit Component<obj, CounterState>(initialProps)
    do
        base.setInitState({ counter = 0})

    let add = this.Add

    member this.Add(_:MouseEvent) =
        this.setState(fun state _ -> { counter = state.counter + 1 })

    override this.render() =
        div [] [
            ofInt this.state.counter
            button [OnClick add] [str "👍"]
        ]

Fable 2 (as this is broken)

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
type CounterState = { counter: int }

type Counter(initialProps) =
    inherit Component<obj, CounterState>(initialProps)
    do
        base.setInitState({ counter = 0})

    let mutable add = None

    member this.Add(_:MouseEvent) =
        this.setState(fun state _ -> { counter = state.counter + 1 })

    override this.render() =
        if add.IsNone then add <- Some this.Add
        div [] [
            ofInt this.state.counter
            button [OnClick add.Value] [str "👍"]
        ]
  • OnClick expect a function, but this.Add expect a this parameter so a bind() is done each render, generated a new function and so React mutate the DOM to set the onClick
  • Can often be avoided by not rendering the parent component at all, mutating the DOM each render() if render() is never called is cheap.
  • As the DOM mutation has no risk of affecting visual rendering it's often cheaper for browsers than anything the risk a graphical reflow

Elements are values, store them when you can

They should often be in a PureComponent without any props too. React shouldn't be asked to diff things that never change.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
module WellKnownBlocks =
    let copyright =
        div [ClassName "copyright"] [
            str (sprintf "Copyright %d My Company" COPYRIGHT_YEAR)
        ]

    let notConnected = lazy(
        div [ClassName "user notConnected"] [
            img [ClassName "avatar"; Src "images/shadow_avatar.png"]
            span [ClassName "userName"] [str "Someone"]
        ]
    )

Avoiding sprintf

React can concatenate (And use it's diff algorithm), JavaScript can concatenate, sprintf is slower than both.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
// sprintf is very nice to use
span [] [str (sprintf "User %s is %i years old" name age)]

// Javascript can do it cheaply
span [] [str ("User " + name + " is " + (unbox age) + " years old")

// Using react can limit how much of the DOM is changed
span [] [str "User "; str name; str " is "; ofInt age; " years old"]

Use arrays for collections of elements when possible

Prefer ofArray to ofList and arrays in general.

React use arrays so Fable will need to convert them anyway and JavaScript engines are heavily optimized for them.

Beware that F# doesn't stop you from mutating them but doing so would break PureComponent

Also beware that children of elements are a sequence but it should be a List as Fable optimize that case away.

Avoid extra function creation

An extension of the event handler case.

Fable can create a LOT of intermediate functions to simulate currying.

A frequent case is functional components that end up as <Unknwown> in DevTools due to that.

Decompile hot paths and look at JavaScript.

The future ?

« To Infinity and Beyond! »

🚀

PRs that need to be done

  • Stable dispatch in Elmish 😉
  • memo and friends

A better memo and ofFunc

There should be a way to use functional components that are functions with multiple parameters.

1: 
2: 
let fooComponent = magicMemo "Foo" (fun bar baz -> div [] [str bar; str baz])
let foo bar baz = createMagic fooComponent [] bar baz

Anonymous records

Don Syme is on it, RFC there... it would be great for props.

Plumb the profiler API

  • Provide tracing of why is something happening in React
  • Currently experimental/unstable
  • Need to be imported in Fable.Import
  • Elmish could automatically trace messages triggering re-renders

Import react-window

  • Implement list element virtualization
  • Only render visible elements

Elmish for Components

  • Deep components (Complex text editor) if written in Elmish implies lot of dispatch & re-render of the App for things it would never care about
  • Create a sub-Elmish in a React component with it's model being the React state, and use Elmish inside
  • Props changed are just a different message