Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

React Responsively, Render Responsibly

90 views

Published on

Slides from my talk at React-Next 2018
The Presentation showed performance pitfalls in React apps and how to solve them

Published in: Software
  • Be the first to comment

  • Be the first to like this

React Responsively, Render Responsibly

  1. 1. React Responsively, Render Responsibly Yoav Niran React Responsively, Render Responsibly
  2. 2. React Responsively, Render Responsibly Yoav Niran
  3. 3. React Responsively, Render Responsibly Yoav Niran A little about me Yoav Niran Senior front-end developer @ Cloudinary The Media Full Stack Heavy-duty image & video platform @poeticGeek
  4. 4. React Responsively, Render Responsibly Yoav Niran The crime scene
  5. 5. React Responsively, Render Responsibly Yoav Niran The crime scene App SelectionView footer Header PhotosGrid PhotoItem PhotoItem PhotoItem
  6. 6. React Responsively, Render Responsibly Yoav Niran DEM
  7. 7. React Responsively, Render Responsibly Yoav Niran FPS Indicator CPU Chart Main flame-graph Frames Summary
  8. 8. React Responsively, Render Responsibly Yoav Niran User Timing
  9. 9. React Responsively, Render Responsibly Yoav Niran The road to performance 572.09 ms 13.18 ms
  10. 10. React Responsively, Render Responsibly Yoav Niran The road to performance 1. Data go low 2. Time to leave the nest 3. Spread the love props 4. Just Memoize It TM 5. Open a Window
  11. 11. React Responsively, Render Responsibly Yoav Niran State state = Immutable({ photos: [ { id: "aaa", url: "https://..." width: 2000, height: 2500, selected: true, }, { id: "bbb", url: "https://..." width: 2000, height: 2500, selected: false, }, { id: "ccc", url: "https://..." width: 2000, height: 2500, selected: false, }, ...
  12. 12. React Responsively, Render Responsibly Yoav Niran Clue #1 – You don’t need that data class PhotosGrid extends Component { render() { ... return ( <div className={cx(styles.container, "...")}> ... {photos.map((p) => ( <PhotoItem key={p.url} item={p}/> ))} </div> );}} export default connect( (state) => ({ fetchStatus: selectFetchStatus(state), photos: selectPhotos(state), }), ... PhotosGrid/PhotosGrid.js
  13. 13. React Responsively, Render Responsibly Yoav Niran Fix #1 – Data go low class PhotosGrid extends Component { render() { ... return ( <div className={cx(styles.container, "...")}> ... {photos.map((p) => ( <PhotoItem key={p.url} item={p}/> ))} </div> );}} export default connect( (state) => ({ fetchStatus: selectFetchStatus(state), photos: selectPhotos(state), }), ... class PhotosGrid extends Component { render() { ... return ( <div className={cx(styles.container, "...")}> ... {photos.map((id) => ( <PhotoItem key={id} id={id}/> ))} </div> );}} export default connect( (state) => ({ fetchStatus: selectFetchStatus(state), photos: selectPhotoIds(state), }), ... PhotosGrid/PhotosGrid.js
  14. 14. React Responsively, Render Responsibly Yoav Niran Fix #1 - Data go low const PhotoItem = (props) => { const {id, filename, width, height, selected, price} = props.item, horizontal = props.horizontal; return ( <article> ... </article> ); }; export default connect( null, bindActions, )(RenderCounter(PhotoItem)); const photoItemSelector = (state, props) => { const photo = state.photos .find((p) => p.public_id === props.id); return { item: takePhotoProps(photo), }; }; export default connect( photoItemSelector, bindActions, )(RenderCounter(PhotoItem)); PhotoItem/PhotoItem.js
  15. 15. React Responsively, Render Responsibly Yoav Niran DEM
  16. 16. React Responsively, Render Responsibly Yoav Niran
  17. 17. React Responsively, Render Responsibly Yoav Niran The Journey 1. Data go low 2. Time to leave the nest 3. Spread the love props 4. Just Memoize It TM 5. Open a Window
  18. 18. React Responsively, Render Responsibly Yoav Niran State state = Immutable({ photos: [ { id: "aaa", url: "https://..." width: 2000, height: 2500, selected: true, }, { id: "bbb", url: "https://..." width: 2000, height: 2500, selected: false, }, { id: "ccc", url: "https://..." width: 2000, height: 2500, selected: false, }, ...
  19. 19. React Responsively, Render Responsibly Yoav Niran Clue #2 – Deep in the nest export default createReducer(initialState, { … [TYPES.SET_SELECTED_PHOTO]: (state, {payload}) => { const index = getPhotoIndex(state.photos, payload.id); state = state.setIn(["photos", index, "selected"], payload.selected); ... return state; }, const initialState = Immutable({ ... photos: [], ... }); store/reducers.js
  20. 20. React Responsively, Render Responsibly Yoav Niran Fix #2 – Time to leave the nest export default createReducer(initialState, { … [TYPES.SET_SELECTED_PHOTO]: (state, {payload}) => { const index = getPhotoIndex(state.photos, payload.id); state = state.setIn(["photos", index, "selected"], payload.selected); ... return state; }, const initialState = Immutable({ ... photos: [], ... }); [TYPES.SET_SELECTED_PHOTO]: (state, {payload}) => { const selectedIndex = state.selected.indexOf(payload.id); if (!~selectedIndex) { if (payload.selected) { state = state.set("selected", state.selected.concat(payload.id)); } } else if (!payload.selected) { state = state.set("selected", state.selected.filter((s) => s !== payload.id)); } ... }, const initialState = Immutable({ ... photos: [], selected: [], }); store/reducers.js
  21. 21. React Responsively, Render Responsibly Yoav Niran DEM
  22. 22. React Responsively, Render Responsibly Yoav Niran
  23. 23. React Responsively, Render Responsibly Yoav Niran The Journey 1. Data go low 2. Time to leave the nest 3. Spread the love props 4. Just Memoize It TM 5. Open a Window
  24. 24. React Responsively, Render Responsibly Yoav Niran Clue #3 - Pure & Shallow Equal const getPhotoItemProps = (photo, selected) => { return { item: takePhotoProps({...photo, selected}), }; }; const photoItemSelector = (state, props) => { const photo = state.photos .find((p) => p.public_id === props.id); const selected = !!~state.selected.indexOf(props.id); return getPhotoItemProps(photo, selected); }; export default connect( photoItemSelector, bindActions, )(RenderCounter(PhotoItem)); PhotoItem/PhotoItem.js
  25. 25. React Responsively, Render Responsibly Yoav Niran Fix #3 - Spread the love props const getPhotoItemProps = (photo, selected) => { return { item: takePhotoProps({...photo, selected}), }; }; const photoItemSelector = (state, props) => { const photo = state.photos .find((p) => p.public_id === props.id); const selected = !!~state.selected.indexOf(props.id); return getPhotoItemProps(photo, selected); }; export default connect( photoItemSelector, bindActions, )(RenderCounter(PhotoItem)); PhotoItem/PhotoItem.js const getPhotoItemProps = (photo, selected) => { return { ...takePhotoProps({...photo, selected}), }; }; const photoItemSelector = (state, props) => { const photo = state.photos .find((p) => p.public_id === props.id); const selected = !!~state.selected.indexOf(props.id); return getPhotoItemProps(photo, selected); }; export default connect( photoItemSelector, bindActions, )(RenderCounter(PhotoItem));
  26. 26. React Responsively, Render Responsibly Yoav Niran DEM
  27. 27. React Responsively, Render Responsibly Yoav Niran
  28. 28. React Responsively, Render Responsibly Yoav Niran Yay?
  29. 29. React Responsively, Render Responsibly Yoav Niran
  30. 30. React Responsively, Render Responsibly Yoav Niran Memoization “...memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again…” https://en.wikipedia.org/wiki/Memoization Memoization
  31. 31. React Responsively, Render Responsibly Yoav Niran Clue #4 - Memoization
  32. 32. React Responsively, Render Responsibly Yoav Niran The Journey 1. Data go low 2. Time to leave the nest 3. Spread the love props 4. Just Memoize It TM 5. Open a Window
  33. 33. React Responsively, Render Responsibly Yoav Niran Fix #4 – Just memoize it PhotoItem/PhotoItem.selectors.js import {createSelector} from "reselect"; import {takePhotoProps} from "../../selectors"; const getPhoto = (state, props) => { return state.photos .find((p) => p.public_id === props.id); }; const selectIsSelectedById = (state, props) => !!~state.selected.indexOf(props.id); export const getPhotoByIdSelector = () => createSelector( [ getPhoto, selectIsSelectedById ], (photo, selected) => ({...takePhotoProps(photo), selected}), ); TM
  34. 34. React Responsively, Render Responsibly Yoav Niran const getPhotoItemProps = (photo, selected) => { return { ...takePhotoProps({...photo, selected}), }; }; const photoItemSelector = (state, props) => { const photo = state.photos .find((p) => p.public_id === props.id); const selected = !!~state.selected.indexOf(props.id); return getPhotoItemProps(photo, selected); }; export default connect( photoItemSelector, bindActions, )(RenderCounter(PhotoItem)); PhotoItem/PhotoItem.js import {getPhotoByIdSelector} from "./PhotoItem.selectors const PhotoItem = (props) => { const {id, …, selected, price} = props.item, horizontal = props.horizontal; return ( <article> ... </article> ); }; export default connect( () => { const photoSelector = getPhotoByIdSelector(); return (state, props) => ({ ...photoSelector(state, props), }); }, bindActions, )(RenderCounter(PhotoItem)); Fix #4 – Just memoize it TM
  35. 35. React Responsively, Render Responsibly Yoav Niran DEM
  36. 36. React Responsively, Render Responsibly Yoav Niran
  37. 37. React Responsively, Render Responsibly Yoav Niran Are we there yet? 39.35 ms
  38. 38. React Responsively, Render Responsibly Yoav Niran DEM
  39. 39. React Responsively, Render Responsibly Yoav Niran
  40. 40. React Responsively, Render Responsibly Yoav Niran The Journey 1. Data go low 2. Time to leave the nest 3. Spread the love props 4. Just Memoize It TM 5. Open a Window
  41. 41. React Responsively, Render Responsibly Yoav Niran Clue #5 – Letting it all out class PhotosGrid extends Component { render() { ... return ( <div className={cx(styles.container, "...")}> ... {photos.map((id) => ( <PhotoItem key={id} id={id}/> ))} </div> );}} ... PhotosGrid/PhotosGrid.js
  42. 42. React Responsively, Render Responsibly Yoav Niran Fix #5 – Open a window https://bvaughn.github.io/forward-js-2017
  43. 43. React Responsively, Render Responsibly Yoav Niran Fix #5 – Open a window https://react-window.now.sh https://github.com/bvaughn/react-window
  44. 44. React Responsively, Render Responsibly Yoav Niran Fix #5 – Open a window class PhotosGrid extends Component { render() { ... return ( <div className={cx(styles.container, "...")}> ... {photos.map((id) => ( <PhotoItem key={id} id={id}/> ))} </div> );}} ... PhotosGrid/PhotosGrid.js import {FixedSizeGrid as Grid} from "react-window"; renderItems() { const {height, width, photos} = this.props; const colCount = Math.floor(width / 250), rowCount = photos.length / colCount; return <Grid ref={this.gridRef} columnCount={colCount} columnWidth={250} height={height} rowCount={(rowCount + 1)} rowHeight={270} width={width} onScroll={({scrollTop}) => { this.gridScrollTop = scrollTop; }}> {({columnIndex, rowIndex, style}) => <PhotoItem index={getItemIndex(columnIndex, rowIndex, colCount)} style={style}/>} </Grid> } …
  45. 45. React Responsively, Render Responsibly Yoav Niran DEM
  46. 46. React Responsively, Render Responsibly Yoav Niran
  47. 47. React Responsively, Render Responsibly Yoav Niran DEM
  48. 48. React Responsively, Render Responsibly Yoav Niran
  49. 49. React Responsively, Render Responsibly Yoav Niran The road to performance 1. Data go low 2. Time to leave the nest 3. Spread the love props 4. Just Memoize It TM 5. Open a Window
  50. 50. React Responsively, Render Responsibly Yoav Niran We made it ! ~600 ms ~10 ms
  51. 51. React Responsively, Render Responsibly Yoav Niran Thank y u https://github.com/yoavniran/react-performance-demo @poeticGeek

×