使用.map渲染组件会重置组件内的setTimeout

时间:2019-10-08 02:42:26

标签: reactjs settimeout

我构建了一个“中继器”组件,该组件接受要重复作为道具的组件。有添加,删除和清除功能可添加或删除实例,或清除所有实例。

我正在尝试使用此转发器组件来呈现另一个具有setTimeout的组件。如果添加或删除其他组件,则所有其他组件的setTimouts都会重置,因此它们都在同一时间触发。

我正在使用的转发器组件:

class Repeater extends React.Component {
  state = {
    elements: [],
  };

  component = Notification; // For simplicity - actually dynamic from props
  count = 0;

  getKey = () => {
    return this.count++;
  };

  add = props => {
    const { elements } = this.state;    
    props.key = this.getKey();
    elements.push(props);

    this.setState({ elements });
  };

  remove = key => {
    const { elements } = this.state;
    const newElements = elements.filter(element => {
      return element.key !== key;
    });

    this.setState({ elements: newElements });
  };

  clear = () => {
    this.setState({ elements: [] });
  };

  render() {
    const { elements } = this.state;
    const Component = this.component;

    return (
      <React.Fragment>
        {elements.map(element => {
          return <Component key={element.key} {...element} />;
        })}
      </React.Fragment>
    );
  }
}

我正在渲染的组件:

export function Notification() {
  const {
    isOpen = true,
    message,
    title,
    duration = 4.5,
  } = props;
  const [openState, setOpenState] = React.useState(isOpen);
  let timer;

  const clearTimer = () => {
    window.clearTimeout(timer);
  };

 const handleClose = () => {
    clearTimer();
    console.log('Test');
  };

  const setTimer = () => {
    if (duration) timer = setTimeout(() => handleClose(), duration * 1000);
  };

  if (openState) {
    setTimer();
    return (
      <div
        onMouseEnter={() => clearTimer()}
        onMouseLeave={() => setTimer()}>
        <div>
          <Icon />
        </div>
        <div>{title}</div>
        <div>{message}</div>
      </div>
    );
  }
  return null;
}

2 个答案:

答案 0 :(得分:0)

在这种情况下,使用.map函数是一个问题。您需要分别渲染每个组件,否则映射函数将在每次触发更新时运行,并将元素重置为其初始状态。

我更新了解决方案以使用最新的ES6 +功能。您想要实现的功能和代码的完整上下文仍然有待改进的地方,但这是第一个应该起作用的迭代,因为setState函数引用了先前的状态。

import React, { useState, useEffect } from 'react';

export function Notification({ duration=4500, isOpen=true, message, title }) {
  const [open, setOpen] = useState(isOpen);

  let timer;

  useEffect(() => {
    setTimer();
  });

  const handleClose = () => {
    clearTimer();
  };

  const clearTimer = () => {
    window.clearTimeout(timer);
  };

  const setTimer = () => {
    timer = setTimeout(() => handleClose(), duration);
  };

  if (!open) return null;

  return (
    <div
      onMouseEnter={() => clearTimer()}
      onMouseLeave={() => setTimer()}>
      <div>
        <Icon />
      </div>
      <div>{title}</div>
      <div>{message}</div>
    </div>
  );
};

export function Repeater({ component }) {
  const [elements, setElements] = useState([]);
  const [count, setCount] = useState(0);

  const Component = component ? component : Notification;

  const getKey = () => {
    setCount(count => count + 1);
    return count;
  };

  const add = (props) => {
    setElements((elements) => elements.push({ ...props, key: getKey }));
  };

  const remove = (key) => {
    setElements((elements) => elements.filter((e) => e.key !== key));
  };

  const clear = () => {
    setElements([]);
  };

  const renderedElements = elements.map(({ key, ...rest }) => (
    <Component key={key} {...rest} />
  ));

  return (
    <>
      {renderedElements}
    </>
  );
};

答案 1 :(得分:0)

我能够通过以下方式解决此问题:在第一次加载时将通知组件的呈现时间存储为状态,然后在setTimer方法中找到它与当前时间之间的差异。

...

const [renderTime] = React.useState(Date.now());

...

const setTimer = () => {
  if (duration) {
    const elapsed = Date.now() - openTime;
    const timeout = duration - elapsed;
    timer = setTimeout(() => handleClose(), timeout);
  }
};