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
- 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. ?
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?
-
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 (JSFest 2018)
By Alexey Migutsky
Как Microsoft To-Do использует React (JSFest 2018)
JSFest 2018
- 3,789