Balram Singh
Bring Your Own Project
Venue: CR5Lloyds Banking Group, Sighthill, Edinburgh
Date: 12th April 2018
Time: 18:00 to 21:00
For latest updates please join on meetup
https://www.meetup.com/React-Scotland/
Agenda
React
Re-render everything on every update
Sounds expensive?
Wasted renders
Problem
Re-render everything on every update.
But renders even waste components
Solution
Use shouldComponentUpdate
Initial state
Proposed state
Green = node that rerendered
Ideal update
Default behaviour
Orange = waster renderer
How to get this ideal update
Make shouldComponentUpdate checks fast
shouldComponentUpdate(nextProps) {
// expensive!
return isDeepEqual(this.props, nextProps);
}
const newValue = {
...oldValue
// any modifications you want to do
};
// fast check - only need to check references
newValue === oldValue; // false
// you can also use the Object.assign syntax if you prefer
const newValue2 = Object.assign({}, oldValue);
newValue2 === oldValue; // false
Bad practice
Good Practice
Make shouldComponentUpdate checks easy
Bad practice
shouldComponentUpdate (nextProps) {
// have any of the items changed?
if(!isArrayEqual(this.props.items, nextProps.items)){
return true;
}
// everything from here is horrible.
// if interaction has not changed at all then when can return false (yay!)
if(isObjectEqual(this.props.interaction, nextProps.interaction)){
return false;
}
// at this point we know:
// 1. the items have not changed
// 2. the interaction has changed
// we need to find out if the interaction change was relevant for us
const wasItemSelected = this.props.items.any(item => {
return item.id === this.props.interaction.selectedId
});
const isItemSelected = nextProps.items.any(item => {
return item.id === nextProps.interaction.selectedId
});
// return true when something has changed
// return false when nothing has changed
return wasItemSelected !== isItemSelected;
}
// Data structure with good separation of concerns (normalised data)
const state = {
items: [
{
id: 5,
description: 'some really cool item'
}
],
// an object to represent the users interaction with the system
interaction: {
selectedId: 5
}
};
Good Practice
Use Higher Order Components
Use Normalised state
export class TestContainer extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.callApi();
}
render() {
return (
<div>{this.props.data}</div>
);
}
}
const mapStateToProps = state => ({
data: state.data
});
const mapDispatchToProps = dispatch => ({
callApi: () => dispatch(callApi())
});
export default connect(mapStateToProps, mapDispatchToProps)(TestContainer);
Optimizing React Builds
Bundling
Minification
AOT (Ahead of Time compilation)
Tree Shaking (removing unused code)
Code Splitting (loading chunk of code on demand)
Where should I Code Split
When part of application is highly unlikely to be used
Component is heavy and can be used in multiple apps
Caution
Code splitting is cool, but don’t get carried away. If your entire application is a bunch of lazy components, you are a) defeating the purpose of the bundler, b)prematurely optimizing and c) making your code legitimately harder to read and debug.
class Heroes extends Component {
constructor(props) {
super(props);
this.state = {
...
lazyEditHero: null
}
}
async LoadEditForm() {
const { default : EditHero } = await import('./EditHero');
this.setState({ lazyEditHero: EditHero })
}
handleSelect = async hero => {
await this.LoadEditForm();
this.setState({ selectedHero: hero });
}
handleEnableAddMode = async () => {
await this.LoadEditForm();
this.setState({
addingHero: true,
selectedHero: { id: '', name: '', saying: '' }
});
}
...
render() {
const EditHero = this.state.lazyEditHero;
return (
<div>
...
<div className="editarea">
<button onClick={this.handleEnableAddMode}>Add</button>
{EditHero ? (
<EditHero
addingHero={this.state.addingHero}
onChange={this.handleOnChange}
selectedHero={this.state.selectedHero}
onSave={this.handleSave}
onCancel={this.handleCancel}
/>
) : (
<div></div>
)}
</div>
</div>
);
}
}
ReactJS Anti Patterns
Pure renderer Functions
Pure renderer Immutability
Pure renderer Functions
// BAD
export default (props, context) => {
// ... do expensive compute on props ...
return <SomeComponent {...props} />
}
// GOOD
import { pure } from 'recompose';
// See: https://github.com/acdlite/recompose#composition
// This won't be called when the props DONT change
export default pure((props, context) => {
// ... do expensive compute on props ...
return <SomeComponent someProp={props.someProp} />
})
Pure renderer Immutability
In order to preserve performance one needs to consider the creation of new entities in the render method.
Array methods such as slice, filter, map and reduce are also good example of immutability
Use spread operator (Do not use push() for array)
Spread operator for Objects
Spread operator for Objects
Best Practise
// NEVER do this
render() {
return <MyInput onChange={this.props.update.bind(this)} />;
}
// NEVER do this
render() {
return <MyInput onChange={() => this.props.update()} />;
}
// Instead do this
onChange() {
this.props.doUpdate()
}
render() {
return <MyInput onChange={this.onChange}/>;
}
// NEVER do this, if there are no items, SubComponent will render every time!
render() {
return <SubComponent items={this.props.items || []}/>
}
// This will avoid re-rendering
const EMPTY_ARRAY = []
render() {
return <SubComponent items={this.props.items || EMPTY_ARRAY}/>
}
Virtualize Long Lists
How to measure Performace
React Perf
React Opt
Profiling Components with the Chrome Performance Tab
References