소스 검색

Extract columns area from UI component (#6650)

UI component used to toggle isComposing state by directly manipulating the
DOM element to avoid the expensive rendering.

However, it is hacky, and is not effective for other states. Instead,
this change makes the rendering cheaper by extracting the huge columns
area.
master
Akihiko Odaki 6 년 전
committed by Eugen Rochko
부모
커밋
a07cfee644
1개의 변경된 파일93개의 추가작업 그리고 70개의 파일을 삭제
  1. +93
    -70
      app/javascript/mastodon/features/ui/index.js

+ 93
- 70
app/javascript/mastodon/features/ui/index.js 파일 보기

@@ -1,3 +1,4 @@
import classNames from 'classnames';
import React from 'react';
import NotificationsContainer from './containers/notifications_container';
import PropTypes from 'prop-types';
@@ -84,10 +85,93 @@ const keyMap = {
goToMuted: 'g m',
};

class SwitchingColumnsArea extends React.PureComponent {

static propTypes = {
children: PropTypes.node,
location: PropTypes.object,
onLayoutChange: PropTypes.func.isRequired,
};

state = {
mobile: isMobile(window.innerWidth),
};

componentWillMount () {
window.addEventListener('resize', this.handleResize, { passive: true });
}

componentDidUpdate (prevProps) {
if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) {
this.node.handleChildrenContentChange();
}
}

componentWillUnmount () {
window.removeEventListener('resize', this.handleResize);
}

handleResize = debounce(() => {
// The cached heights are no longer accurate, invalidate
this.props.onLayoutChange();

this.setState({ mobile: isMobile(window.innerWidth) });
}, 500, {
trailing: true,
});

setRef = c => {
this.node = c.getWrappedInstance().getWrappedInstance();
}

render () {
const { children } = this.props;
const { mobile } = this.state;

return (
<ColumnsAreaContainer ref={this.setRef} singleColumn={mobile}>
<WrappedSwitch>
<Redirect from='/' to='/getting-started' exact />
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
<WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
<WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
<WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
<WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} />

<WrappedRoute path='/notifications' component={Notifications} content={children} />
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />

<WrappedRoute path='/statuses/new' component={Compose} content={children} />
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
<WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
<WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />

<WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
<WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
<WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
<WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
<WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />

<WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
<WrappedRoute path='/blocks' component={Blocks} content={children} />
<WrappedRoute path='/mutes' component={Mutes} content={children} />
<WrappedRoute path='/lists' component={Lists} content={children} />

<WrappedRoute component={GenericNotFound} content={children} />
</WrappedSwitch>
</ColumnsAreaContainer>
);
}

}

@connect(mapStateToProps)
@injectIntl
@withRouter
export default class UI extends React.Component {
export default class UI extends React.PureComponent {

static contextTypes = {
router: PropTypes.object.isRequired,
@@ -103,7 +187,6 @@ export default class UI extends React.Component {
};

state = {
width: window.innerWidth,
draggingOver: false,
};

@@ -118,14 +201,10 @@ export default class UI extends React.Component {
}
}

handleResize = debounce(() => {
handleLayoutChange = () => {
// The cached heights are no longer accurate, invalidate
this.props.dispatch(clearHeight());

this.setState({ width: window.innerWidth });
}, 500, {
trailing: true,
});
}

handleDragEnter = (e) => {
e.preventDefault();
@@ -193,7 +272,6 @@ export default class UI extends React.Component {

componentWillMount () {
window.addEventListener('beforeunload', this.handleBeforeUnload, false);
window.addEventListener('resize', this.handleResize, { passive: true });
document.addEventListener('dragenter', this.handleDragEnter, false);
document.addEventListener('dragover', this.handleDragOver, false);
document.addEventListener('drop', this.handleDrop, false);
@@ -214,28 +292,8 @@ export default class UI extends React.Component {
};
}

shouldComponentUpdate (nextProps) {
if (nextProps.isComposing !== this.props.isComposing) {
// Avoid expensive update just to toggle a class
this.node.classList.toggle('is-composing', nextProps.isComposing);

return false;
}

// Why isn't this working?!?
// return super.shouldComponentUpdate(nextProps, nextState);
return true;
}

componentDidUpdate (prevProps) {
if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) {
this.columnsAreaNode.handleChildrenContentChange();
}
}

componentWillUnmount () {
window.removeEventListener('beforeunload', this.handleBeforeUnload);
window.removeEventListener('resize', this.handleResize);
document.removeEventListener('dragenter', this.handleDragEnter);
document.removeEventListener('dragover', this.handleDragOver);
document.removeEventListener('drop', this.handleDrop);
@@ -247,10 +305,6 @@ export default class UI extends React.Component {
this.node = c;
}

setColumnsAreaRef = c => {
this.columnsAreaNode = c.getWrappedInstance().getWrappedInstance();
}

handleHotkeyNew = e => {
e.preventDefault();

@@ -350,8 +404,8 @@ export default class UI extends React.Component {
}

render () {
const { width, draggingOver } = this.state;
const { children } = this.props;
const { draggingOver } = this.state;
const { children, isComposing, location } = this.props;

const handlers = {
help: this.handleHotkeyToggleHelp,
@@ -374,43 +428,12 @@ export default class UI extends React.Component {

return (
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef}>
<div className='ui' ref={this.setRef}>
<div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef}>
<TabsBar />

<ColumnsAreaContainer ref={this.setColumnsAreaRef} singleColumn={isMobile(width)}>
<WrappedSwitch>
<Redirect from='/' to='/getting-started' exact />
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
<WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
<WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
<WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
<WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} />

<WrappedRoute path='/notifications' component={Notifications} content={children} />
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />

<WrappedRoute path='/statuses/new' component={Compose} content={children} />
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
<WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
<WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />

<WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
<WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
<WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
<WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
<WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />

<WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
<WrappedRoute path='/blocks' component={Blocks} content={children} />
<WrappedRoute path='/mutes' component={Mutes} content={children} />
<WrappedRoute path='/lists' component={Lists} content={children} />

<WrappedRoute component={GenericNotFound} content={children} />
</WrappedSwitch>
</ColumnsAreaContainer>
<SwitchingColumnsArea location={location} onLayoutChange={this.handleLayoutChange}>
{children}
</SwitchingColumnsArea>

<NotificationsContainer />
<LoadingBarContainer className='loading-bar' />


불러오는 중...
취소
저장