Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/reactjs/24.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 反应无限滚动组件性能_Javascript_Reactjs - Fatal编程技术网

Javascript 反应无限滚动组件性能

Javascript 反应无限滚动组件性能,javascript,reactjs,Javascript,Reactjs,我在React中编写了以下无限滚动组件: import React from 'react' import { uniqueId, isUndefined, hasVerticalScrollbar, hasHorizontalScrollbar, isInt, throttle } from '../../../js/utils'; export default class BlSimpleInfiniteScroll extends React.Component { const

我在React中编写了以下无限滚动组件:

import React from 'react'
import { uniqueId, isUndefined, hasVerticalScrollbar, hasHorizontalScrollbar, isInt, throttle } from '../../../js/utils';

export default class BlSimpleInfiniteScroll extends React.Component {

    constructor(props) {
        super(props)

        this.handleScroll = this.handleScroll.bind(this)

        this.itemsIdsRefsMap = {}

        this.isLoading = false

        this.node = React.createRef()
    }

    componentDidMount() {
        const {
            initialId
        } = this.props
        let id
        if (initialId) {
            if (typeof initialId === "function") {
                id = initialId()
            }
            else {
                id = initialId
            }
            this.scrollToId(id)   
        }
    }

    componentDidUpdate(prevProps) {
        if (
            this.isLoading
            &&
            prevProps.isInfiniteLoading
            &&
            !this.props.isInfiniteLoading
        ) {
            const axis = this.axis()
            const scrollProperty = this.scrollProperty(axis)
            const offsetProperty = this.offsetProperty(axis)
            this.scrollTo(scrollProperty, this.node.current[offsetProperty])
            this.isLoading = false
        }
    }

    itemsRenderer(items) {
        const length = items.length

        let i = 0
        const renderedItems = []
        for (const item of items) {
            renderedItems[i] = this.itemRenderer(item.id, i, length)
            i++
        }
        return renderedItems
    }

    itemRenderer(id, i, length) {
        const {
            itemRenderer,
            isInfiniteLoading,
            displayInverse
        } = this.props

        let renderedItem = itemRenderer(id, i)
        if (isInfiniteLoading) {
            if (!displayInverse && (i == length - 1)) {
                renderedItem = this.standardLoadingComponentWrapperRenderer(id, renderedItem)
            }
            else if (i == 0) {
                renderedItem = this.inverseLoadingComponentWrapperRenderer(id, renderedItem)
            }
        }
        const ref = this.itemsIdsRefsMap[id] || (this.itemsIdsRefsMap[id] = React.createRef())
        return (
            <div className="bl-simple-infinite-scroll-item"
                key={id}
                ref={ref}>
                {renderedItem}
            </div>
        )
    }

    loadingComponentRenderer() {
        const {
            loadingComponent
        } = this.props

        return (
            <div className="bl-simple-infinite-scroll-loading-component"
                key={uniqueId()}>
                {loadingComponent}
            </div>
        )
    }

    loadingComponentWrapperRenderer(id, children) {
        return (
            <div className="bl-simple-infinite-scroll-loading-component-wrapper"
                key={id}>
                {children}
            </div>
        )
    }

    standardLoadingComponentWrapperRenderer(id, renderedItem) {
        return this.loadingComponentWrapperRenderer(id, [
            renderedItem,
            this.loadingComponentRenderer()
        ])
    }

    inverseLoadingComponentWrapperRenderer(id, renderedItem) {
        return this.loadingComponentWrapperRenderer(id, [
            this.loadingComponentRenderer(),
            renderedItem
        ])
    }

    axis() {
        return this.props.axis === 'x' ? 'x' : 'y'
    }

    scrollProperty(axis) {
        return axis == 'y' ? 'scrollTop' : 'scrollLeft'
    }

    offsetProperty(axis) {
        return axis == 'y' ? 'offsetHeight' : 'offsetWidth'
    }

    scrollDimProperty(axis) {
        return axis == 'y' ? 'scrollHeight' : 'scrollWidth'
    }

    hasScrollbarFunction(axis) {
        return axis == 'y' ? hasVerticalScrollbar : hasHorizontalScrollbar
    }

    scrollToStart() {
        const axis = this.axis()
        this.scrollTo(
            this.scrollProperty(axis),
            !this.props.displayInverse ?
                0
                :
                this.scrollDimProperty(axis)
        )
    }

    scrollToEnd() {
        const axis = this.axis()
        this.scrollTo(
            this.scrollProperty(axis),
            !this.props.displayInverse ?
                this.scrollDimProperty(axis)
                :
                0
        )
    }

    scrollTo(scrollProperty, scrollPositionOrPropertyOfScrollable) {
        const scrollableContentNode = this.node.current
        if (scrollableContentNode) {
            scrollableContentNode[scrollProperty] = isInt(scrollPositionOrPropertyOfScrollable) ?
                scrollPositionOrPropertyOfScrollable
                :
                scrollableContentNode[scrollPositionOrPropertyOfScrollable]
        }
    }

    scrollToId(id) {
        if (this.itemsIdsRefsMap[id] && this.itemsIdsRefsMap[id].current) {
            this.itemsIdsRefsMap[id].current.scrollIntoView()
        }
    }

    handleScroll() {
        const {
            isInfiniteLoading,
            infiniteLoadBeginEdgeOffset,
            displayInverse
        } = this.props
        if (
            this.props.onInfiniteLoad
            &&
            !isInfiniteLoading
            &&
            this.node.current
            &&
            !this.isLoading
        ) {
            const axis = this.axis()
            const scrollableContentNode = this.node.current
            const scrollProperty = this.scrollProperty(axis)
            const offsetProperty = this.offsetProperty(axis)
            const scrollDimProperty = this.scrollDimProperty(axis)
            const currentScroll = scrollableContentNode[scrollProperty]
            const currentDim = scrollableContentNode[offsetProperty]
            const scrollDim = scrollableContentNode[scrollDimProperty]

            const finalInfiniteLoadBeginEdgeOffset = !isUndefined(infiniteLoadBeginEdgeOffset) ?
                infiniteLoadBeginEdgeOffset
                :
                currentDim / 2

            let thresoldWasReached = false
            let memorizeLastElementBeforeInfiniteLoad = () => { }
            if (!displayInverse) {
                thresoldWasReached = currentScroll >= (scrollDim - finalInfiniteLoadBeginEdgeOffset)
            }
            else {
                memorizeLastElementBeforeInfiniteLoad = () => {
                    // TODO
                }
                thresoldWasReached = currentScroll <= finalInfiniteLoadBeginEdgeOffset
            }
            if (thresoldWasReached) {
                this.isLoading = true
                memorizeLastElementBeforeInfiniteLoad()
                this.props.onInfiniteLoad()
            }
        }
    }

    render() {
        const {
            items
        } = this.props

        return (
            <div className="bl-simple-infinite-scroll"
                ref={this.node}
                onScroll={this.handleScroll}
                onMouseOver={this.props.onInfiniteScrollMouseOver}
                onMouseOut={this.props.onInfiniteScrollMouseOut}
                onMouseEnter={this.props.onInfiniteScrollMouseEnter}
                onMouseLeave={this.props.onInfiniteScrollMouseLeave}>
                {this.itemsRenderer(items)}
            </div>
        )
    }

}
BlOrderChatBox
,包含
BlOrderChat

import React from 'react'
import BlOrderChat from './BlOrderChat';
import BlAlert from '../../core/alert/BlAlert';
import BlLoadingSpinnerContainer from '../../core/animation/loading/BlLoadingSpinnerContainer';

export default class BlOrderChatBox extends React.Component {

    constructor(props) {
        super(props)

        this.node = React.createRef()
    }

    render() {
        const {
            ordId, currentChat,
            isCurrentChatLoading, currentUser,
            err
        } = this.props

        return (
            <div className="bl-order-chat-box" ref={this.node}>
                <div className="bl-order-chat-box-inner">
                    {
                        (err &&
                            <BlAlert type="error" message={err} />)
                        ||
                        (currentChat && (
                            // ...
                            <div className="bl-order-chat-box-inner-chat-content">
                                <BlOrderChat ordId={ordId}
                                    chat={currentChat}
                                    isChatLoading={isCurrentChatLoading}
                                    onLoadPreviousChatMessages={this.props.onLoadPreviousChatMessages}
                                    currentUser={currentUser} />
                            </div>
                        ))
                        ||
                        <BlLoadingSpinnerContainer />
                    }
                </div>
            </div>
        )
    }

}
我试图删除所有不相关的代码以简化阅读。这里还有一个文件,其中包含
chatSelector
函数(规范化chat array-able对象)和
*ArrayAble*
函数(对于我来说,array-able对象基本上是一个对象,它通过
items
中的id映射对象,并具有一个保持排序的
order
属性):

谢谢你的帮助。如果有什么我应该包括在内的遗漏,请告诉我

更新:多亏了苏丹H.,它有所改进,尽管当我收到服务器的回复时,卷轴仍然会阻塞。请看这里: 有没有关于如何进一步改善这种行为的想法


谢谢

这里是一个解决性能问题的尝试,不希望在计算新状态的Arrow函数中执行任务,在本例中,在
loadPreviousChatMessages
中,您正在计算回调中的内容,在该上下文上设置状态时,可能会导致加载

首选更改,替换
this.setState
在函数中使用此代码,我在这里所做的只是通过将所有任务移出来清除上下文:

            const chat = this.state.chats.items[chatId];
            const messages = arrayToArrayAbleItemsOrder(json.messages);
            const newChat = {
              ...chat,
              messages: mergeArrayAbles(messages, chat.messages);
            }
            const chats = mergeArrayAbles(prevState.chats, newArrayAble(newChat));
            const newState = {
              ...(
                makeChatIdCurrent ?
                  {
                      currentChatId: chatId,
                      focusSendMessageTextarea: true,
                  }
                  :
                  {
                      currentChatId: this.state.currentChatId,
                  }
              ),
              chats,
              isCurrentChatLoading: false,
            };

            this.setState(() => newState);

如果这还不能完全解决问题,你能告诉我是否至少有改进吗?

你能在你的问题中加入
onLoadPreviousChatMessages
函数吗?你想知道它真正的作用是什么吗。如果有其他组件链称为内部,请尝试添加它们。问题可能不在该组件上。好的,我将在今天下午尽快解决并更新我的问题!我会尽力支持的。我更新了我的问题!我正在通读代码,希望得到负载发生的确切位置。谢谢你的建议。是的,它有了一些改进。我对这种方法的担心是,如果以这种方式更新状态,我将丢失一些消息。除了加载以前的消息之外,我还有一些钩子可以接收来自其他用户的新消息,因此,当用户等待以前的消息时,也可能会出现新消息到达的情况。你将如何应对这种情况?我在React文档中读到,使用带有
setState
的回调是从当前最新状态计算新状态的唯一可靠方法。您认为呢?首先,加载旧消息的性能问题是否可以解决?是的,你是对的,使用回调更新状态是最有保证和最可靠的,但在这种情况下,它不会有什么不同,因为我们对状态没有结果更新,我猜加载函数在实际更新一次之前不会出现两次,这段代码,现在让聊天功能因加载旧消息而中断?如果加载旧消息没有问题,那么我们将讨论如何加载新消息!是的,它已经改进了,尽管当JSON响应在
JSON
中可用时,滚动仍然会阻塞。在这里看到->。在加载新邮件时,当我向下滚动时,滚动会跳跃。关于新消息,它们来自WebSocket连接,并附加到底部,而不是添加到顶部(与旧消息一样)。如果我从WS-connection收到一条新消息,而AJAX几乎同时返回旧消息,那么在
setState
回调之外计算新状态不会导致运行条件吗?抱歉,tonix,我想今晚继续这样做,显然我今天筋疲力尽了:lol:,我会再回来的。没问题,谢谢你的帮助,很高兴再次与你讨论这个话题。我只想告诉你,由于这篇文章的缘故,我需要将所有内容包装回
setState
更新程序回调中。我需要保证所有消息将始终保持在状态,并且由于应用程序(AJAX、WebSockets)的异步性,竞争条件可能会发生。为了提高性能,我将渲染组件添加到
BlOrderChatApp
的状态。
import React from 'react'
import BlOrderChat from './BlOrderChat';
import BlAlert from '../../core/alert/BlAlert';
import BlLoadingSpinnerContainer from '../../core/animation/loading/BlLoadingSpinnerContainer';

export default class BlOrderChatBox extends React.Component {

    constructor(props) {
        super(props)

        this.node = React.createRef()
    }

    render() {
        const {
            ordId, currentChat,
            isCurrentChatLoading, currentUser,
            err
        } = this.props

        return (
            <div className="bl-order-chat-box" ref={this.node}>
                <div className="bl-order-chat-box-inner">
                    {
                        (err &&
                            <BlAlert type="error" message={err} />)
                        ||
                        (currentChat && (
                            // ...
                            <div className="bl-order-chat-box-inner-chat-content">
                                <BlOrderChat ordId={ordId}
                                    chat={currentChat}
                                    isChatLoading={isCurrentChatLoading}
                                    onLoadPreviousChatMessages={this.props.onLoadPreviousChatMessages}
                                    currentUser={currentUser} />
                            </div>
                        ))
                        ||
                        <BlLoadingSpinnerContainer />
                    }
                </div>
            </div>
        )
    }

}
import React from 'react'
import { POSTJSON } from '../../../js/ajax';
import config from '../../../config/config';
import { newEmptyArrayAble, arrayToArrayAbleItemsOrder, arrayAbleItemsOrderToArray, mergeArrayAbles, newArrayAble, firstOfArrayAble, isArrayAble } from '../../../js/data_structures/arrayable';

export default class BlOrderChatApp extends React.Component {

    static NEW_CHAT_ID = 0

    static MAX_NUMBER_OF_MESSAGES_TO_LOAD_PER_AJAX = 30

    constructor(props) {
        super(props)

        this.currentUser = globals.USER
        this.lastHandleSendMessagePromise = Promise.resolve()
        this.newMessagesMap = {}
        this.typingUsersDebouncedMap = {}

        // Imagine this comes from a database.
        const chat = {
            // ...
        }

        const initialState = {
            chats: newArrayAble(this.newChat(chat)),
            currentChatId: null,
            shouldSelectUserForNewChat: false,
            newChatReceivingUsers: newEmptyArrayAble(),
            isChatListLoading: false,
            isCurrentChatLoading: false,
            popoverIsOpen: false,
            popoverHasOpened: false,
            err: void 0,
            focusSendMessageTextarea: false,
            newChatsIdsMap: {},
            currentChatAuthActs: {},
            BlOrderChatComponent: null,
        }
        this.state = initialState

        this.handleLoadPreviousChatMessages = this.handleLoadPreviousChatMessages.bind(this)
    }

    POST(jsonData, callback) {
        let requestJSONData
        if (typeof jsonData === "string") {
            requestJSONData = {
                action: jsonData
            }
        }
        else {
            requestJSONData = jsonData
        }
        return POSTJSON(config.ORDER_CHAT_ENDPOINT_URI, {
            ...requestJSONData,
            order_chat_type: this.props.orderChatType,
        }).then(response => response.json()).then(json => {
            this.POSTResponseData(json, callback)
        })
    }

    POSTResponseData(data, callback) {
        if (data.err) {
            this.setState({
                err: data.err
            })
        }
        else {
            callback && callback(data)
        }
    }

    newChat(chat) {
        const newChat = {
            id: (chat && chat.id) || BlOrderChatApp.NEW_CHAT_ID,
            ord_id: this.props.ordId,
            users: (chat && chat.users && (isArrayAble(chat.users) ? chat.users : arrayToArrayAbleItemsOrder(chat.users))) || newEmptyArrayAble(),
            messages: (chat && chat.messages && (isArrayAble(chat.messages) ? chat.messages : arrayToArrayAbleItemsOrder(chat.messages))) || newEmptyArrayAble(),
            first_message_id: (chat && chat.first_message_id) || null,
            typing_users_ids_map: (chat && chat.typing_users_ids_map) || {},
        }
        return newChat
    }

    isChatNew(chat) {
        return (
            chat
            &&
            (chat.id == BlOrderChatApp.NEW_CHAT_ID || this.state.newChatsIdsMap[chat.id])
        )
    }

    loadPreviousChatMessages(chatId, lowestMessageIdOrNull, makeChatIdCurrent) {
        this.POST({
            act: 'loadPreviousChatMessages',
            chat_id: chatId,
            lowest_message_id: lowestMessageIdOrNull,
            max_number_of_messages_to_load: BlOrderChatApp.MAX_NUMBER_OF_MESSAGES_TO_LOAD_PER_AJAX
        }, json => {
            this.setState(prevState => {
                const chat = prevState.chats.items[chatId]
                const messages = arrayToArrayAbleItemsOrder(json.messages)

                const newChat = {
                    ...chat,
                    messages: mergeArrayAbles(messages, chat.messages)
                }
                const chats = mergeArrayAbles(prevState.chats, newArrayAble(newChat))
                return {
                    ...(makeChatIdCurrent ?
                        {
                            currentChatId: chatId,
                            focusSendMessageTextarea: true,
                        }
                        :
                        {
                            currentChatId: prevState.currentChatId,
                        }
                    ),
                    chats,
                    isCurrentChatLoading: false,
                }
            })
        })
    }

    loadPreviousChatMessagesIfNotAllLoaded(chatId) {
        let lowestMessageIdOrNull
        const chat = this.state.chats.items[chatId]
        if (
            !this.isChatNew(chat)
            &&
            (lowestMessageIdOrNull = (chat.messages.order.length && firstOfArrayAble(chat.messages).id) || null)
            &&
            lowestMessageIdOrNull != chat.first_message_id
        ) {
            this.setState({
                isCurrentChatLoading: true
            }, () => {
                this.loadPreviousChatMessages(chat.id, lowestMessageIdOrNull)
            })
        }
    }

    handleLoadPreviousChatMessages(chatId) {
        this.loadPreviousChatMessagesIfNotAllLoaded(chatId)
    }

    // ...

    render() {
        const currentChat = this.state.chats.items[this.state.currentChatId] || null
        const err = this.state.err

        return (
            <div className="bl-order-chat-app">
                <BlOrderChatBox currentUser={this.currentUser}
                    chats={arrayAbleItemsOrderToArray(this.state.chats)}
                    currentChat={currentChat}
                    isCurrentChatLoading={this.state.isCurrentChatLoading}
                    onLoadPreviousChatMessages={this.handleLoadPreviousChatMessages}
                    err={err} />
            </div>
        )
    }

}

import { isUndefined, unshiftArray, findIndex } from "../utils";

export function chatSelector(chat, currentUser) {
    const newChat = { ...chat }
    newChat.messages = arrayAbleItemsOrderToArray(chat.messages).sort((a, b) => {
        const sortByUnixTs = a.sent_unix_ts - b.sent_unix_ts
        if (sortByUnixTs == 0) {
            return a.id - b.id
        }
        return sortByUnixTs
    })
    newChat.users = arrayAbleItemsOrderToArray(chat.users).filter(user => user.id != currentUser.id)
    return newChat
}

/**
 * Given an array-able object, returns its array representation using an order property.
 * This function acts as a selector function.
 * 
 * The array-able object MUST have the following shape:
 * 
 *      {
 *          items: {},
 *          order: []
 *      }
 * 
 * Where "items" is the object containing the elements of the array mapped by values found in "order"
 * in order.
 * 
 * @see https://medium.com/javascript-in-plain-english/https-medium-com-javascript-in-plain-english-why-you-should-use-an-object-not-an-array-for-lists-bee4a1fbc8bd
 * @see https://medium.com/@antonytuft/maybe-you-would-do-something-like-this-a1ab7f436808
 * 
 * @param {Object} obj An object.
 * @param {Object} obj.items The items of the object mapped by keys.
 * @param {Array} obj.order The ordered keys.
 * @return {Array} The ordered array representation of the given object.
 */
export function arrayAbleItemsOrderToArray(obj) {
    const ret = []
    for (const key of obj.order) {
        if (!isUndefined(obj.items[key])) {
            ret[ret.length] = obj.items[key]
        }
    }
    return ret
}

export function arrayToArrayAbleItemsOrder(array, keyProp = "id") {
    const obj = newEmptyArrayAble()
    for (const elem of array) {
        const key = elem[keyProp]
        obj.items[key] = elem
        obj.order[obj.order.length] = key
    }
    return obj
}

export function newEmptyArrayAble() {
    return {
        items: {},
        order: []
    }
}

export function isEmptyArrayAble(arrayAbleObj) {
    return !arrayAbleObj.order.length
}

export function mergeArrayAbles(arrayAbleObj1, arrayAbleObj2, prependObj2 = false) {
    const obj = newEmptyArrayAble()
    for (const key of arrayAbleObj1.order) {
        if (isUndefined(arrayAbleObj1.items[key])) {
            continue
        }
        obj.items[key] = arrayAbleObj1.items[key]
        obj.order[obj.order.length] = key
    }
    for (const key of arrayAbleObj2.order) {
        if (isUndefined(arrayAbleObj2.items[key])) {
            continue
        }
        if (!(key in obj.items)) {
            if (!prependObj2) {
                // Default.
                obj.order[obj.order.length] = key
            }
            else {
                unshiftArray(obj.order, key)
            }
        }
        obj.items[key] = arrayAbleObj2.items[key]
    }
    return obj
}

export function newArrayAble(initialItem = void 0, keyProp = "id") {
    const arrayAble = newEmptyArrayAble()
    if (initialItem) {
        arrayAble.items[initialItem[keyProp]] = initialItem
        arrayAble.order[arrayAble.order.length] = initialItem[keyProp]
    }
    return arrayAble
}

export function lastOfArrayAble(obj) {
    return (
        (
            obj.order.length
            &&
            obj.items[obj.order[obj.order.length - 1]]
        )
        ||
        void 0
    )
}
            const chat = this.state.chats.items[chatId];
            const messages = arrayToArrayAbleItemsOrder(json.messages);
            const newChat = {
              ...chat,
              messages: mergeArrayAbles(messages, chat.messages);
            }
            const chats = mergeArrayAbles(prevState.chats, newArrayAble(newChat));
            const newState = {
              ...(
                makeChatIdCurrent ?
                  {
                      currentChatId: chatId,
                      focusSendMessageTextarea: true,
                  }
                  :
                  {
                      currentChatId: this.state.currentChatId,
                  }
              ),
              chats,
              isCurrentChatLoading: false,
            };

            this.setState(() => newState);