React 组件在重新渲染时滚动回顶部

dmi*_*er1 8 reactjs

我的 React 组件有这个问题,我无法弄清楚。每次重新渲染时它都会滚动回顶部。而且,我一开始就不明白为什么它要重新渲染。基本上我有一个祖父母、父母和孙子组件。孙子有一个单击事件,该事件显示祖父母的不同孩子(如果有任何意义的话)。该单击事件会执行一些操作,但它会导致祖组件重新渲染,从而导致父组件滚动到顶部。我将添加我的代码,希望能够澄清我在这里所说的内容。只是想提供一些背景信息。

所以主要问题是滚动到顶部。如果我们能找出它重新渲染的原因并停止它,那就是一个奖励。

祖父母(StaticLine.js):

import React, { useEffect, useState } from 'react';
import StaticOrderColumn from '../ordercolumn/StaticOrderColumn';
import { useGlobalLineTypeActionsContext } from '../../context/GlobalLineTypeContext';
import Details from '../details/DetailsColumn';
import { ModalStyles } from '../modals/ModalStyles';
import {
    useScheduleActionsContext,
    useScheduleContext
} from '../../context/ScheduleContext';
import PalletCount from './PalletCount';
import OrderButtons from './OrderButtons';
import PropTypes from 'prop-types';
import {
    useGlobalShowDetailsActionsContext,
    useGlobalShowDetailsContext
} from '../../context/GlobalShowDetailsContext';
import { useTraceUpdate } from '../../utils/hooks';

const StaticLine = (props) => {
    useTraceUpdate(props);
    console.log('top of StaticLine');
    const { type, setTitle } = props;

    const { orderType, orders } = useScheduleContext();
    const { setOrders, setOrderType } = useScheduleActionsContext();
    const setGlobalLineType = useGlobalLineTypeActionsContext();
    const showDetails = useGlobalShowDetailsContext();
    const setShowDetails = useGlobalShowDetailsActionsContext();

    const [lineID, setLineID] = useState(-1);
    const [lineTitle, setLineTitle] = useState('');
    const [highlightDest, setHighlightDest] = useState(false);

    const ordersCol = React.useRef();

    let lineCountOffset = 0;
    let numLines = 4;
    let pageTitle = '';
    switch (type) {
        case 'bagline':
            pageTitle += 'Bag Lines';
            break;
        case 'toteline':
            pageTitle += 'Tote Lines';
            lineCountOffset = 10;
            break;
        case 'otherline':
            pageTitle += 'Other Lines';
            numLines = 3;
            lineCountOffset = 4;
            break;
        default:
    }

    useEffect(() => {
        setLineID(-1);
    }, [type]);

    const globalLineType = type + 's';
    useEffect(() => {
        setGlobalLineType(globalLineType);
        setTitle(pageTitle);
        const title = `${process.env.REACT_APP_BASE_TITLE ||
            'title'} - ${pageTitle}`;
        document.title = title;
    }, [type, setGlobalLineType, pageTitle, setTitle, globalLineType]);

    const selectLine = (e) => {
        setShowDetails(false);
        setHighlightDest(false);
        const lineNum = e.target.value.substring(4);
        setLineID(parseInt(lineNum, 10));
        setLineTitle(
            orderType.charAt(0).toUpperCase() +
                orderType.slice(1) +
                ' Orders - Line ' +
                parseInt(lineNum, 10)
        );
    };

    const selectOrderType = (e) => {
        const selectedOrderType = e.target.value;
        setOrderType(selectedOrderType);
        setShowDetails(false);
        setLineTitle(
            selectedOrderType.charAt(0).toUpperCase() +
                selectedOrderType.slice(1) +
                ' Orders - Line ' +
                lineID
        );
    };

    const OrderColWithRef = React.forwardRef((props, ref) => (
        <StaticOrderColumn
            {...props}
            title={lineTitle}
            lineID={lineID}
            orders={orders}
            ref={ref}
        />
    ));

    return (
        <div
            className={`staticLines p-1 no-gutters d-flex flex-nowrap${
                orderType === 'completed' ? ' completed' : ''
            }`}
        >
            <div className={'radio-col no-border'}>
                <div className={'radio-container p-2'}>
                    <div className={'radios'}>
                        // lots of irrelevant code here
                    </div>
                </div>
            </div>
            {lineID > -1 && (
                <>
                    <div
                        className={'col lines no-gutters order-col'}
                    >
                        <OrderColWithRef ref={ordersCol} />
                    </div>
                    <div className={'col row lines no-gutters order-details'}>
                        <Details
                            setOrders={setOrders}
                            orders={orders || []}
                            customStyles={ModalStyles}
                            highlightDest={highlightDest}
                            setHighlightDest={setHighlightDest}
                            errLocation={'top-center'}
                        />
                        {orderType === 'completed' && showDetails && (
                            <OrderButtons
                                setLineID={setLineID}
                                setOrders={setOrders}
                                orders
                                lineNum={lineID}
                            />
                        )}
                    </div>

                    <div className={'col lines no-gutters d-flex no-border'}>
                        {orderType === 'scheduled' && (
                            <PalletCount
                                type={'Bag'}
                                lineNum={lineID}
                                orders={orders}
                                setTitle={setTitle}
                                setHighlightDest={setHighlightDest}
                            />
                        )}
                    </div>
                </>
            )}
        </div>
    );
};

StaticLine.propTypes = {
    type: PropTypes.string.isRequired,
    orders: PropTypes.array,
    setTitle: PropTypes.func.isRequired
};

export default StaticLine;
Run Code Online (Sandbox Code Playgroud)

父级 (StaticOrderColumn.js):

import React from 'react';
import PropTypes from 'prop-types';
import StaticOrder from '../order/StaticOrder';
import '../../scss/App.scss';
import { useGlobalSpinnerContext } from '../../context/GlobalSpinnerContext';

const StaticOrderColumn = (props) => {
    const { title, lineID, orders } = props;

    const isGlobalSpinnerOn = useGlobalSpinnerContext();

    const sortedOrdersIDs = orders
        .filter((o) => o.lineNum === lineID)
        .sort((a, b) => a.linePosition - b.linePosition)
        .map((o) => o.id);

    return (
        <div id={'line-0'} className={'col order-column'}>
            <header className={'text-center title'}>
                {title}{' '}
                {sortedOrdersIDs.length > 0 && (
                    <span> ({sortedOrdersIDs.length})</span>
                )}
            </header>
            <div className={'orders'}>
                {orders &&
                    sortedOrdersIDs &&
                    sortedOrdersIDs.map((orderID, index) => {
                        const order = orders.find((o) => o.id === orderID);
                        return (
                            <StaticOrder
                                key={orderID}
                                order={order}
                                index={index}
                            />
                        );
                    })}
                {!sortedOrdersIDs.length && !isGlobalSpinnerOn && (
                    <h3>There are no orders on this line.</h3>
                )}
            </div>
        </div>
    );
};

StaticOrderColumn.propTypes = {
    title: PropTypes.string.isRequired,
    lineID: PropTypes.number.isRequired,
    orders: PropTypes.array.isRequired,
    ref: PropTypes.instanceOf(Element).isRequired
};

export default StaticOrderColumn;
Run Code Online (Sandbox Code Playgroud)

该文件是单击事件发生的位置,并导致重新呈现 StaticLine 并滚动到 StaticOrderColumn 的顶部。孙子(StaticOrder.js):

import React from 'react';
import styled from 'styled-components';
import PropTypes from 'prop-types';
import '../../scss/App.scss';
import { getFormattedDate } from '../../utils/utils';
import { useGlobalActiveOrderActionsContext } from '../../context/GlobalActiveOrderContext';
import { useGlobalShowDetailsActionsContext } from '../../context/GlobalShowDetailsContext';
// import { stringTrunc } from '../../utils/utils';

const MyOrder = styled.div`
    background-color: #193df4;
    transition: background-color 1s ease;
`;

const devMode = true;
// const devMode = false;

const StaticOrder = (props) => {
    const {
        id,
        item,
        desc,
        cust,
        palletsOrd,
        bagID,
        chemicals,
        totalBagsUsed,
        linePosition,
        palletsRem,
        palletCount,
        requestDate,
        orderNumber,
        comments
    } = props.order;

    const setActiveOrder = useGlobalActiveOrderActionsContext();
    const setShowDetails = useGlobalShowDetailsActionsContext();

    const orderID = id + '';

    // show the details section when user clicks an order
    // THIS IS WHERE THE ISSUE IS HAPPENING, WHEN THE ORDER IS CLICKED,
    // THIS FUNCTION RUNS AND THE StaticLine COMPONENT RE-RENDERS AND THE StaticOrderColumn SCROLLS TO THE TOP
    const showDetails = (orderID) => {
        setActiveOrder(parseInt(orderID, 10));
        setShowDetails(true);
    };

    return (
        <MyOrder
            id={orderNumber}
            className={'order static'}
            onClick={(e) => showDetails(orderID, e)}
        >
            {/*<div className={'orderID'}>{id}</div>*/}
            <p className={'item-number'}>
                {item !== '' ? `Item Number: ${item}` : ''}
            </p>
            <p>{desc !== '' ? `NPK: ${desc}` : ''}</p>
            <p>{cust !== '' ? `Customer: ${cust}` : ''}</p>
            <p>
                {palletsOrd !== '' ? `Pallets Ordered: ${palletsOrd}` : ''}
            </p>
            <p>{bagID !== '' ? `Bag ID: ${bagID}` : ''}</p>
            <p>{chemicals !== '' ? `Chemical : ${chemicals}` : ''}</p>
            <p>
                {requestDate !== ''
                    ? `Request Date: ${getFormattedDate(new Date(requestDate))}`
                    : ''}
            </p>
            {devMode && (
                <>
                    <div className={'id-line-num-pos'}>
                        <p>OrderID: {orderNumber}</p>
                    </div>
                </>
            )}
            <div className={'total-bags'}>Total Bags: {totalBagsUsed}</div>
            <div className={'pallets-remaining'}>
                Pallets Left: {palletsRem}
            </div>
            <div className={'pallets-done'}>
                Pallets Done: {palletCount}
            </div>
            <div className={'line-position'}>{linePosition + 1}</div>
            {comments.length > 0 && (
                // bunch of SVG code
            )}
        </MyOrder>
    );
};

StaticOrder.propTypes = {
    order: PropTypes.object,
    id: PropTypes.number,
    index: PropTypes.number,
    title: PropTypes.string,
    orderID: PropTypes.string
};

export default StaticOrder;
Run Code Online (Sandbox Code Playgroud)

编辑:我添加了问题的图片,以帮助大家直观地了解它。订单框位于该图像的左侧。默认情况下,“订单详细信息”是隐藏的。单击左侧的订单时,它会将该订单加载到“订单详细信息”中并显示该组件。发生这种情况时,左侧的订单列会滚动回顶部。在随后的订单点击中,它不会滚动到顶部。仅当显示或隐藏“订单详细信息”时,它才会滚动回顶部。 订单图片

编辑 2:我发现如果我从 StaticLine.js 中取出该const showDetails = useGlobalShowDetailsContext();行和 2 个引用showDetails,这个问题就会消失。所以如果这可以帮助任何人弄清楚一些事情......

编辑3:我慢慢地向前走。showDetails我想出了如何删除文件中的引用之一StaticLine.js。现在,如果有人可以帮助我弄清楚如何从该组件中获取最后一个引用,但保留功能,那就太棒了!提醒一下,这是我正在谈论的参考:

{orderType === 'completed' && showDetails && (
  <OrderButtons
    setLineID={setLineID}
    setOrders={setOrders}
    orders
    lineNum={lineID}
  />
)}
Run Code Online (Sandbox Code Playgroud)

如果需要更多信息或更多代码,请告诉我。任何见解将不胜感激。

Mor*_*hai 8

const OrderColWithRef = React.forwardRef((props, ref) => (
        <StaticOrderColumn
            {...props}
            title={lineTitle}
            lineID={lineID}
            orders={orders}
            ref={ref}
        />
    ));
Run Code Online (Sandbox Code Playgroud)

将其StaticLine作为顶级函数移到外部。

发生了什么

React 足够聪明,可以在仅部分 Dom 更改时避免重新创建 html 元素和安装内容。如果只有一个 prop 发生变化,它将保留该元素并仅更改其值等。这是通过比较element.type.

您实际上所做的是OrderColWithRef在每个渲染上创建一个新函数,因为它是本地函数,所以类型不相等。每次发生任何StaticLine变化时,React 都会卸载并重新安装一个新的 html 元素。

永远不要嵌套组件声明。在函数内声明组件有效的唯一情况是 HOC,即使如此,HOC 函数本身也不是有效元素,只有它的返回值才是有效元素。

希望这能澄清事情。

  • 为什么不将它们作为 props 传递给 `OrderColWithRef` 呢? (2认同)