mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-09-13 16:14:36 +08:00
fix(performance): Thread-list re-renders too often
Summary: Freeze threads on their way out of the ThreadStore Don't add an unread attribute to contacts by accident... Don't pass inline functions as props that are looked at by _isEqual Test Plan: Run tests Reviewers: evan Reviewed By: evan Differential Revision: https://review.inboxapp.com/D1367
This commit is contained in:
parent
5826d0dd37
commit
7c298aa158
5 changed files with 45 additions and 19 deletions
|
@ -16,20 +16,20 @@ ThreadListParticipants = React.createClass
|
||||||
if @props.thread.messageMetadata and @props.thread.messageMetadata.length > 1
|
if @props.thread.messageMetadata and @props.thread.messageMetadata.length > 1
|
||||||
count = " (#{@props.thread.messageMetadata.length})"
|
count = " (#{@props.thread.messageMetadata.length})"
|
||||||
|
|
||||||
chips = items.map (item, idx) ->
|
chips = items.map ({spacer, contact, unread}, idx) ->
|
||||||
if item.spacer
|
if spacer
|
||||||
<span key={idx}>...</span>
|
<span key={idx}>...</span>
|
||||||
else
|
else
|
||||||
if item.name.length > 0
|
if contact.name.length > 0
|
||||||
if items.length > 1
|
if items.length > 1
|
||||||
short = item.displayFirstName()
|
short = contact.displayFirstName()
|
||||||
else
|
else
|
||||||
short = item.displayName()
|
short = contact.displayName()
|
||||||
else
|
else
|
||||||
short = item.email
|
short = contact.email
|
||||||
if idx < items.length-1 and not items[idx+1].spacer
|
if idx < items.length-1 and not items[idx+1].spacer
|
||||||
short += ", "
|
short += ", "
|
||||||
<span key={idx} className="unread-#{item.unread}">{short}</span>
|
<span key={idx} className="unread-#{unread}">{short}</span>
|
||||||
|
|
||||||
<div className="participants">
|
<div className="participants">
|
||||||
{chips}{count}
|
{chips}{count}
|
||||||
|
@ -44,10 +44,11 @@ ThreadListParticipants = React.createClass
|
||||||
last = null
|
last = null
|
||||||
for msg in @props.thread.messageMetadata
|
for msg in @props.thread.messageMetadata
|
||||||
from = msg.from[0]
|
from = msg.from[0]
|
||||||
continue unless from
|
if from and from.email isnt last
|
||||||
if from.email isnt last
|
list.push({
|
||||||
from.unread = msg.unread
|
contact: msg.from[0]
|
||||||
list.push(from)
|
unread: msg.unread
|
||||||
|
})
|
||||||
last = from.email
|
last = from.email
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
|
@ -18,6 +18,7 @@ ThreadList = React.createClass
|
||||||
@_getStateFromStores()
|
@_getStateFromStores()
|
||||||
|
|
||||||
componentDidMount: ->
|
componentDidMount: ->
|
||||||
|
@_prepareColumns()
|
||||||
@thread_store_unsubscribe = ThreadStore.listen @_onChange
|
@thread_store_unsubscribe = ThreadStore.listen @_onChange
|
||||||
@thread_unsubscriber = atom.commands.add '.thread-list', {
|
@thread_unsubscriber = atom.commands.add '.thread-list', {
|
||||||
'thread-list:star-thread': => @_onStarThread()
|
'thread-list:star-thread': => @_onStarThread()
|
||||||
|
@ -40,18 +41,29 @@ ThreadList = React.createClass
|
||||||
@body_unsubscriber.dispose()
|
@body_unsubscriber.dispose()
|
||||||
|
|
||||||
render: ->
|
render: ->
|
||||||
|
# IMPORTANT: DO NOT pass inline functions as props. _.isEqual thinks these
|
||||||
|
# are "different", and will re-render everything. Instead, declare them with ?=,
|
||||||
|
# pass a reference. (Alternatively, ignore these in children's shouldComponentUpdate.)
|
||||||
|
#
|
||||||
|
# BAD: onSelect={ (item) -> Actions.selectThreadId(item.id) }
|
||||||
|
# GOOD: onSelect={@_onSelectItem}
|
||||||
|
#
|
||||||
classes = React.addons.classSet("thread-list": true, "ready": @state.ready)
|
classes = React.addons.classSet("thread-list": true, "ready": @state.ready)
|
||||||
|
|
||||||
|
@_itemClassProvider ?= (item) -> if item.isUnread() then 'unread' else ''
|
||||||
|
@_itemOnSelect ?= (item) -> Actions.selectThreadId(item.id)
|
||||||
|
|
||||||
<div className={classes}>
|
<div className={classes}>
|
||||||
<ListTabular
|
<ListTabular
|
||||||
columns={@state.columns}
|
columns={@_columns}
|
||||||
items={@state.items}
|
items={@state.items}
|
||||||
itemClassProvider={ (item) -> if item.isUnread() then 'unread' else '' }
|
itemClassProvider={@_itemClassProvider}
|
||||||
selectedId={@state.selectedId}
|
selectedId={@state.selectedId}
|
||||||
onSelect={ (item) -> Actions.selectThreadId(item.id) } />
|
onSelect={@_itemOnSelect} />
|
||||||
<Spinner visible={!@state.ready} />
|
<Spinner visible={!@state.ready} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
_computeColumns: ->
|
_prepareColumns: ->
|
||||||
myEmail = NamespaceStore.current()?.emailAddress
|
myEmail = NamespaceStore.current()?.emailAddress
|
||||||
|
|
||||||
labelComponents = (thread) =>
|
labelComponents = (thread) =>
|
||||||
|
@ -101,7 +113,7 @@ ThreadList = React.createClass
|
||||||
resolver: (thread) ->
|
resolver: (thread) ->
|
||||||
<span className="timestamp">{timestamp(thread.lastMessageTimestamp)}</span>
|
<span className="timestamp">{timestamp(thread.lastMessageTimestamp)}</span>
|
||||||
|
|
||||||
[c1, c2, c3, c4]
|
@_columns = [c1, c2, c3, c4]
|
||||||
|
|
||||||
_onFocusSelectedIndex: ->
|
_onFocusSelectedIndex: ->
|
||||||
Actions.selectThreadId(@state.selectedId)
|
Actions.selectThreadId(@state.selectedId)
|
||||||
|
@ -145,5 +157,4 @@ ThreadList = React.createClass
|
||||||
_getStateFromStores: ->
|
_getStateFromStores: ->
|
||||||
ready: not ThreadStore.itemsLoading()
|
ready: not ThreadStore.itemsLoading()
|
||||||
items: ThreadStore.items()
|
items: ThreadStore.items()
|
||||||
columns: @_computeColumns()
|
|
||||||
selectedId: ThreadStore.selectedId()
|
selectedId: ThreadStore.selectedId()
|
||||||
|
|
|
@ -41,12 +41,12 @@ class Thread extends Model
|
||||||
@naturalSortOrder: ->
|
@naturalSortOrder: ->
|
||||||
Thread.attributes.lastMessageTimestamp.descending()
|
Thread.attributes.lastMessageTimestamp.descending()
|
||||||
|
|
||||||
fromJSON: (json) =>
|
fromJSON: (json) ->
|
||||||
super(json)
|
super(json)
|
||||||
@unread = @isUnread()
|
@unread = @isUnread()
|
||||||
@
|
@
|
||||||
|
|
||||||
tagIds: =>
|
tagIds: ->
|
||||||
_.map @tags, (tag) -> tag.id
|
_.map @tags, (tag) -> tag.id
|
||||||
|
|
||||||
hasTagId: (id) ->
|
hasTagId: (id) ->
|
||||||
|
|
|
@ -62,6 +62,13 @@ Utils =
|
||||||
object.fromJSON(json)
|
object.fromJSON(json)
|
||||||
object
|
object
|
||||||
|
|
||||||
|
modelFreeze: (o) ->
|
||||||
|
Object.freeze(o)
|
||||||
|
for key, prop of o
|
||||||
|
if !o.hasOwnProperty(key) || typeof prop isnt 'object' || Object.isFrozen(prop)
|
||||||
|
continue
|
||||||
|
Utils.modelFreeze(prop)
|
||||||
|
|
||||||
modelReviver: (k, v) ->
|
modelReviver: (k, v) ->
|
||||||
return v if k == ""
|
return v if k == ""
|
||||||
v = Utils.modelFromJSON(v) if (v instanceof Object && v['object'])
|
v = Utils.modelFromJSON(v) if (v instanceof Object && v['object'])
|
||||||
|
|
|
@ -5,6 +5,7 @@ WorkspaceStore = require './workspace-store'
|
||||||
AddRemoveTagsTask = require '../tasks/add-remove-tags'
|
AddRemoveTagsTask = require '../tasks/add-remove-tags'
|
||||||
MarkThreadReadTask = require '../tasks/mark-thread-read'
|
MarkThreadReadTask = require '../tasks/mark-thread-read'
|
||||||
Actions = require '../actions'
|
Actions = require '../actions'
|
||||||
|
Utils = require '../models/utils'
|
||||||
Thread = require '../models/thread'
|
Thread = require '../models/thread'
|
||||||
Message = require '../models/message'
|
Message = require '../models/message'
|
||||||
_ = require 'underscore-plus'
|
_ = require 'underscore-plus'
|
||||||
|
@ -60,6 +61,11 @@ ThreadStore = Reflux.createStore
|
||||||
for item in items
|
for item in items
|
||||||
item.messageMetadata = results[item.id]
|
item.messageMetadata = results[item.id]
|
||||||
|
|
||||||
|
# Prevent anything from mutating these objects or their nested objects.
|
||||||
|
# Accidentally modifying items somewhere downstream (in a component)
|
||||||
|
# can trigger awful re-renders
|
||||||
|
Utils.modelFreeze(item)
|
||||||
|
|
||||||
@_items = items
|
@_items = items
|
||||||
|
|
||||||
# Sometimes we can ask for a thread that's not in the current set
|
# Sometimes we can ask for a thread that's not in the current set
|
||||||
|
@ -90,6 +96,7 @@ ThreadStore = Reflux.createStore
|
||||||
|
|
||||||
@_autoselectForLayoutMode()
|
@_autoselectForLayoutMode()
|
||||||
|
|
||||||
|
|
||||||
console.log("Fetching data for thread list took #{Date.now() - start} msec")
|
console.log("Fetching data for thread list took #{Date.now() - start} msec")
|
||||||
|
|
||||||
# If we've loaded threads, remove the loading indicator.
|
# If we've loaded threads, remove the loading indicator.
|
||||||
|
|
Loading…
Add table
Reference in a new issue