Microsoft To-Do

React practices

👋 my name is Alex

Microsoft To-Do

Just a To-Do list...

React

Redux

Redux-actions

ES6 + TypeScript

eslint plugins

ImmutableJS

...

Reselect

  • Design Principles

  • Passing State vs ICC

  • The art and magic of state passing

  • Performance

  • How To ICC

  • Composable Store

  • What's next?

  • Going Deeper...

Design Principles

Data-Driven Design

Immutable Design (OCP)

Composable abstractions

Domain-Driven Design

Maintenance and Change

Explicitness

Passing state

vs.

ICC

ICC

=

Independently

Connected

Components

We connect

every tiny single component

to the store

Think MVVM or MVP

The art and magic

of state passing

State Passing

Model Passing

Primitives Passing

props.completed
props.todo.completed

Model Passing

Model Passing

  1. Data hierarchy == View hierarchy
  2. Expensive updates
  3. Very sensitive to changes
  4. Where do I add child data?
  5. Derived data dependencies
  6. Does not scale (too many things)

Easy vs. Performant

Primitives Passing

Primitives Passing

  1. Data hierarchy == View hierarchy
  2. Expensive updates
  3. Sensitive to changes
  4. Where do I add child data?
  5. Derived data dependencies
  6. Does not scale (too many things)

Performant vs. Extensible

ICC

ICC

  1. Data hierarchy == View hierarchy
  2. Expensive updates
  3. Sensitive to changes
  4. Where do I add child data?
  5. Derived data dependencies
  6. Does not scale (too many things)
  7. Performance?

Extensible vs. ?

Data vs View

Connector + Component

=

Single Domain Entity

Performance

As if no one asks about performance...

Worry not!

State passing

ICC

Now FIGHT!!!

Huge list = 500 tasks

~20 Drag'n'Drop wrappers

1 ICC Task = 

~7 Components

~50 properties

~25 handlers

1 prim. Task = 

Our context

 Nested Connectors

~1700 ms 😱

ICC
react-redux < v5.0.0

~600 ms 💪

No Batcher + ICC
react-redux < v5.0.0

No Batcher + ICC
react-redux v5.0.0beta3

~500 ms 👌

Single Subscribe
react-redux v5.0.0beta3

~450 ms 👍

Rendering (dev build)

Single Subscribe

react-redux v5.0.0beta3

~500 ms 🤔

ICC + virtualized list
react-redux v5.0.0beta3

~200 ms 👍

Total time (perf build)

More components    =    less overhead

What we have learned?

        25 items                       ~50ms

       500 items                       ~300ms

Our DnD implementation

=

 50% perf overhead

What we have learned?

Every connect()

adds

~0.1ms to initial rendering time

What we have learned?

State Passing is  ~10% faster in our hot rendering path 🔥but ...

Single file with ~50 selectors and 25 handlers 🙀

What we have learned?

Complexity

vs.

a bit of Performance

What we have learned?

Measure in your own case 🔎

Use virtualized lists 📜

Do not create custom Batchers 💩

Compose behaviours with React components mindfully

How to ICC

ICC in the wild

1. Connect is partially applied

2. Index exports (component + connect)

3. Specialization goes to subdirectories

ICC in the wild - connect

import { connect } from 'react-redux'
import ...

const mapStateToProps = createSelector(
  getAddTask,
  ...,
  (addTask, ...) => ({
    title: addTask.title,
    ...
  })
)

const mapActionsToProps = dispatch => ({
  onChange: ({ title }) => dispatch(updateAddTask({ title })),
  ...
})

export default connect(mapStateToProps, mapActionsToProps)

ICC in the wild - Component

import React, { PropTypes } from 'react'
...

export default class AddTask extends React.Component {
  // ...

  render() {
    const { title, onFocus, onPlusClick, isFocused } = this.props
    const placeholder = LocalizedString.getString('placeholder_add_task')

    return (
      <div className="addTask">
        <button
          className="addTask-icon"
          onClick={ onPlusClick }
        >
          <Icon name={ iconName }/>
        </button>
        // ...
      </div>
    )
  }
}

ICC in the wild - default/index

import React from 'react'
import AddTask from '../AddTask'
import connect from '../connect'
import '../addTask.styl'

export default connect(AddTask)

Using ICC

import AddTask from 'components/AddTask'

// Component stuff ...
render() {
  return (
    <AddTask intentOrigin={ intentOrigin } />
  )
}

Another Example

import React, { PropTypes } from 'react'
import TaskItem from '../TaskItem'
import connect from './connect'

import Checkbox from 'components/Checkbox'

const DefaultTaskItem = props =>
  <TaskItem
    primaryActionComponent={ <Checkbox id={ props.id } intentOrigin={ props.intentOrigin }/> }
    { ...props }
  />

DefaultTaskItem.propTypes = {
  id: PropTypes.string.isRequired,
  inToday: PropTypes.bool,
  intentOrigin: PropTypes.string
}

export default connect(DefaultTaskItem)

Composable Store 

Normalized State

Directory structure

===

state structure

State is composed of domains

1. No derived data in domains

2. Single Source of Writes

3. Represents Domain Entities

4. Indexed (byId, byOrder)

Think "a table in relational DB"

Example of a domain

1. Derived Data is in selectors

Crucial Conventions

2. Action is handled by only one reducer

3. Actions have domain-specific names

4. Dependent actions

What's next?

  1. Separate builds for integrations (logic/UI reuse) 🤔

  2. Cyclic dependencies in Webpack  💩

  3. Too much code for derived data

  4. "Where do I put that?" 

More structure

Relationships

Read (domain - domain) dependencies

Transactions

Write (domain->domain) relationships

State machines?

Going deeper...

Basics

Advanced

Black Magic

Thank you!

Questions?

Как Microsoft To-Do использует React (JSFest 2018)

By Alexey Migutsky

Как Microsoft To-Do использует React (JSFest 2018)

JSFest 2018

  • 3,769