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
Thick Client
Telemetry
e2e tests (state machine)
Unit tests (TDD)
Snapshot tests
Feature Toggles
-
The art and magic of state passing
-
Performance
-
How To ICC
-
Composable Store
-
What's next?
-
Going Deeper...
Passing state
to 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
- Data hierarchy == View hierarchy
- Expensive updates
- Very sensitive to changes
- Where do I add child data?
- Derived data dependencies
- Does not scale (too many things)
Easy vs. Performant
Primitives Passing
Primitives Passing
- Data hierarchy == View hierarchy
Expensive updates- Sensitive to changes
- Where do I add child data?
Derived data dependencies- Does not scale (too many things)
Performant vs. Extensible
ICC
ICC
Data hierarchy == View hierarchyExpensive updatesSensitive to changesWhere do I add child data?Derived data dependenciesDoes not scale (too many things)- Performance?
Extensible vs. ?
Connector + Component
=
Single Domain Entity
(independent of parent context)
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
Using ICC
import AddTask from 'components/AddTask'
// Component stuff ...
render() {
return (
<AddTask intentOrigin={ intentOrigin } />
)
}
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)
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>
)
}
}
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?
-
Separate builds for integrations (logic/UI reuse) 🤔
-
Cyclic dependencies in Webpack 💩
-
Too much code for derived data
-
"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 (DUMP 2018)
By Alexey Migutsky
Как Microsoft To-Do использует React (DUMP 2018)
- 3,229