Javascript React 组件的延迟渲染

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/30803440/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 05:46:07  来源:igfitidea点击:

Delayed rendering of React components

javascriptreactjs

提问by michalvalasek

I have a React component with a number of child components in it. I want to render the child components not at once but after some delay (uniform or different for each of the children).

我有一个 React 组件,其中包含许多子组件。我想不是立即渲染子组件,而是在一些延迟之后(每个子组件统一或不同)。

I was wondering - is there a way how to do this?

我想知道 - 有没有办法做到这一点?

回答by Michael Parker

I think the most intuitive way to do this is by giving the children a "wait" prop, which hides the component for the duration that was passed down from the parent. By setting the default state to hidden, React will still render the component immediately, but it won't be visible until the state has changed. Then, you can set up componentWillMountto call a function to show it after the duration that was passed via props.

我认为最直观的方法是给孩子一个 "wait" prop,它在从父级传递下来的持续时间内隐藏组件。通过将默认状态设置为隐藏,React 仍会立即渲染组件,但在状态更改之前它是不可见的。然后,您可以设置componentWillMount在通过 props 传递的持续时间之后调用一个函数来显示它。

var Child = React.createClass({
    getInitialState : function () {
        return({hidden : "hidden"});
    },
    componentWillMount : function () {
        var that = this;
        setTimeout(function() {
            that.show();
        }, that.props.wait);
    },
    show : function () {
        this.setState({hidden : ""});
    },
    render : function () {
        return (
            <div className={this.state.hidden}>
                <p>Child</p>
            </div>
        )
    }
});

Then, in the Parent component, all you would need to do is pass the duration you want a Child to wait before displaying it.

然后,在 Parent 组件中,您需要做的就是传递您希望 Child 在显示之前等待的持续时间。

var Parent = React.createClass({
    render : function () {
        return (
            <div className="parent">
                <p>Parent</p>
                <div className="child-list">
                    <Child wait={1000} />
                    <Child wait={3000} />
                    <Child wait={5000} />
                </div>
            </div>
        )
    }
});

Here's a demo

这是一个演示

回答by goulashsoup

Another approach for a delayed component:

延迟组件的另一种方法:

Delayed.jsx:

Delayed.jsx

import React from 'react';
import PropTypes from 'prop-types';

class Delayed extends React.Component {

    constructor(props) {
        super(props);
        this.state = {hidden : true};
    }

    componentDidMount() {
        setTimeout(() => {
            this.setState({hidden: false});
        }, this.props.waitBeforeShow);
    }

    render() {
        return this.state.hidden ? '' : this.props.children;
    }
}

Delayed.propTypes = {
  waitBeforeShow: PropTypes.number.isRequired
};

export default Delayed;

Usage:

用法:

 import Delayed from '../Time/Delayed';
 import React from 'react';

 const myComp = props => (
     <Delayed waitBeforeShow={500}>
         <div>Some child</div>
     </Delayed>
 )

回答by gcedo

In your father component <Father />, you could create an initial state where you track each child (using and id for instance), assigning a boolean value, which means render or not:

在您的父亲组件中<Father />,您可以创建一个初始状态,您可以在其中跟踪每个孩子(例如 using 和 id),分配一个布尔值,这意味着是否渲染:

getInitialState() {
    let state = {};
    React.Children.forEach(this.props.children, (child, index) => {
        state[index] = false;
    });
    return state;
}

Then, when the component is mounted, you start your timers to change the state:

然后,当组件被挂载时,你启动你的计时器来改变状态:

componentDidMount() {
    this.timeouts = React.Children.forEach(this.props.children, (child, index) => {
         return setTimeout(() => {
              this.setState({ index: true; }); 
         }, child.props.delay);
    });
}

When you render your children, you do it by recreating them, assigning as a prop the state for the matching child that says if the component must be rendered or not.

当你渲染你的孩子时,你通过重新创建他们来做到这一点,将匹配的孩子的状态分配为道具,说明是否必须渲染组件。

let children = React.Children.map(this.props.children, (child, index) => {
    return React.cloneElement(child, {doRender: this.state[index]});
});

So in your <Child />component

所以在你的<Child />组件中

render() {
    if (!this.props.render) return null;
    // Render method here
}

When the timeout is fired, the state is changed and the father component is rerendered. The children props are updated, and if doRenderis true, they will render themselves.

当超时被触发时,状态会发生变化并重新渲染父组件。子道具被更新,如果doRendertrue,它们将呈现自己。

回答by Markus

Depends on your use case.

取决于您的用例。

If you want to do some animation of children blending in, use the react animation add-on: https://facebook.github.io/react/docs/animation.htmlOtherwise, make the rendering of the children dependent on props and add the props after some delay.

如果你想做一些儿童融入的动画,使用 react 动画插件:https: //facebook.github.io/react/docs/animation.html否则,让孩子的渲染依赖于 props 并添加一些延迟后的道具。

I wouldn't delay in the component, because it will probably haunt you during testing. And ideally, components should be pure.

我不会延迟组件,因为它可能会在测试过程中困扰你。理想情况下,组件应该是纯的。

回答by Matt Wills

We can solve this using Hooks:

我们可以使用 Hooks 解决这个问题:

First we'll need a timeout hook for the delay.

首先,我们需要一个用于延迟的超时钩子。

This one is inspired by Dan Abramov's useInterval hook (see Dan's blog postfor an in depth explanation), the differences being:

这个是受到 Dan Abramov 的 useInterval hook 的启发(请参阅Dan 的博客文章以获得更深入的解释),不同之处在于:

  1. we use we setTimeout not setInterval
  2. we return a resetfunction allowing us to restart the timer at any time
  1. 我们使用 setTimeout 而不是 setInterval
  2. 我们返回一个reset函数,允许我们随时重新启动计时器

import { useEffect, useRef, useCallback } from 'react';

const useTimeout = (callback, delay) => {
  // save id in a ref
  const timeoutId = useRef('');

  // save callback as a ref so we can update the timeout callback without resetting the clock
  const savedCallback = useRef();
  useEffect(
    () => {
      savedCallback.current = callback;
    },
    [callback],
  );

  // clear the timeout and start a new one, updating the timeoutId ref
  const reset = useCallback(
    () => {
      clearTimeout(timeoutId.current);

      const id = setTimeout(savedCallback.current, delay);
      timeoutId.current = id;
    },
    [delay],
  );

  useEffect(
    () => {
      if (delay !== null) {
        reset();

        return () => clearTimeout(timeoutId.current);
      }
    },
    [delay, reset],
  );

  return { reset };
};

and now we need a hook which will capture previous children and use our useTimeout hook to swap in the new children after a delay

现在我们需要一个钩子来捕获以前的孩子并使用我们的 useTimeout 钩子在延迟后交换新孩子

import { useState, useEffect } from 'react';

const useDelayNextChildren = (children, delay) => {
  const [finalChildren, setFinalChildren] = useState(children);

  const { reset } = useTimeout(() => {
    setFinalChildren(children);
  }, delay);

  useEffect(
    () => {
      reset();
    },
    [reset, children],
  );

  return finalChildren || children || null;
};

Note that the useTimeout callback will always have the latest children, so even if we attempt to render multiple different new children within the delay time, we'll always get the latest children once the timeout finally completes.

请注意, useTimeout 回调将始终具有最新的孩子,因此即使我们尝试在延迟时间内渲染多个不同的新孩子,一旦超时最终完成,我们将始终获得最新的孩子。

Now in your case, we want to also delay the initial render, so we make this change:

现在在您的情况下,我们还希望延迟初始渲染,因此我们进行了以下更改:

const useDelayNextChildren = (children, delay) => {
  const [finalChildren, setFinalChildren] = useState(null); // initial state set to null

  // ... stays the same

  return finalChildren || null;  // remove children from return
};

and using the above hook, your entire child component becomes

并使用上面的钩子,你的整个子组件变成

import React, { memo } from 'react';
import { useDelayNextChildren } from 'hooks';

const Child = ({ delay }) => useDelayNextChildren(
  <div>
    ... Child JSX goes here
    ... etc
  </div>
  , delay
);

export default memo(Child);

or if you prefer: ( dont say i havent given you enough code ;) )

或者,如果您愿意:(不要说我没有给您足够的代码;))

const Child = ({ delay }) => {
  const render = <div>... Child JSX goes here ... etc</div>;

  return useDelayNextChildren(render, delay);
};

which will work exactly the same in the Parent render function as in the accepted answer

这将在父渲染函数中与接受的答案完全相同

...

...

except the delay will be the same on every subsequent render too,

除了延迟在每个后续渲染中也是相同的,

AND we used hooks, so that stateful logic is reusable across any component

并且我们使用了钩子,所以有状态的逻辑可以在任何组件中重用

...

...

...

...

use hooks. :D

使用钩子。:D

回答by djskinner

My use case might be a bit different but thought it might be useful to post a solution I came up with as it takes a different approach.

我的用例可能有点不同,但我认为发布我想出的解决方案可能会很有用,因为它采用了不同的方法。

Essentially I have a third party Popover component that takes an anchor DOM element as a prop. The problem is that I cannot guarantee that the anchor element will be there immediately because the anchor element becomes visible at the same time as the Popover I want to anchor to it (during the same redux dispatch).

本质上,我有一个第三方 Popover 组件,它将锚定 DOM 元素作为道具。问题是我不能保证锚元素会立即出现,因为锚元素与我想要锚定到它的 Popover 同时变得可见(在同一个 redux 调度期间)。

One possible fix was to place the Popover element deeper into the component tree than the element it was to be anchored too. However, that didn't fit nicely with the logical structure of my components.

一种可能的解决方法是将 Popover 元素放置在组件树中比它要锚定的元素更深的位置。但是,这与我的组件的逻辑结构不太匹配。

Ultimately I decided to delay the (re)render of the Popover component a little bit to ensure that the anchor DOM element can be found. It uses the function as a child pattern to only render the children after a fixed delay:

最终我决定稍微延迟(重新)渲染 Popover 组件,以确保可以找到锚点 DOM 元素。它使用该函数作为子模式,仅在固定延迟后渲染子项:

import { Component } from 'react'
import PropTypes from 'prop-types'

export default class DelayedRender extends Component {
    componentDidMount() {
        this.t1 = setTimeout(() => this.forceUpdate(), 1500)
    }

    componentWillReceiveProps() {
        this.t2 = setTimeout(() => this.forceUpdate(), 1500)
    }

    shouldComponentUpdate() {
        return false
    }

    componentWillUnmount() {
        clearTimeout(this.t1)
        clearTimeout(this.t2)
    }

    render() {
        return this.props.children()
    }
}

DelayedRender.propTypes = {
    children: PropTypes.func.isRequired
}

It can be used like this:

它可以像这样使用:

<DelayedRender>
    {() =>
        <Popover anchorEl={getAnchorElement()}>
            <div>Hello!</div>
        </Popover>
    )}}
</DelayedRender>

Feels pretty hacky to me but works for my use case nevertheless.

对我来说感觉很hacky,但仍然适用于我的用例。

回答by R.Meredith

render the child components not at once but after some delay .

不是立即渲染子组件,而是在一些延迟后渲染。

The question says delay render but if it is ok to render but hide...

问题是延迟渲染,但如果可以渲染但隐藏...

You can render the components from a map straight away but use css animation to delay them being shown.

您可以立即从地图渲染组件,但使用 css 动画来延迟它们的显示。

@keyframes Jumpin {
 0% { opacity: 0; }
 50% { opacity: 0; }
 100% { opacity: 1; }
}
// Sass loop code
@for $i from 2 through 10 {
 .div .div:nth-child(#{$i}) {
  animation: Jumpin #{$i * 0.35}s cubic-bezier(.9,.03,.69,.22);
 }
}

The child divs now follow each other with a slight delay.

子 div 现在彼此跟随,略有延迟。

回答by anoNewb

Using the useEffect hook, we can easily implement delay feature while typing in input field:

使用 useEffect 钩子,我们可以在输入字段中输入时轻松实现延迟功能:

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

function Search() {
  const [searchTerm, setSearchTerm] = useState('')

  // Without delay
  // useEffect(() => {
  //   console.log(searchTerm)
  // }, [searchTerm])

  // With delay
  useEffect(() => {
    const delayDebounceFn = setTimeout(() => {
      console.log(searchTerm)
      // Send Axios request here
    }, 3000)

    // Cleanup fn
    return () => clearTimeout(delayDebounceFn)
  }, [searchTerm])

  return (
    <input
      autoFocus
      type='text'
      autoComplete='off'
      className='live-search-field'
      placeholder='Search here...'
      onChange={(e) => setSearchTerm(e.target.value)}
    />
  )
}

export default Search

回答by Black

I have created Delayed component using Hooks and TypeScript

我使用 Hooks 和 TypeScript 创建了延迟组件

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

type Props = {
  children: React.ReactElement;
  waitBeforeShow?: number;
};

const Delayed = ({ children, waitBeforeShow = 500 }: Props) => {
  const [isShown, setIsShown] = useState(false);

  useEffect(() => {
    console.log(waitBeforeShow);
    setTimeout(() => {
      setIsShown(true);
    }, waitBeforeShow);
  }, [waitBeforeShow]);

  return isShown ? children : null;
};

export default Delayed;

Just wrap another component into Delayed

只需将另一个组件包装到 Delayed

export function LoadingScreen = ({ children }: Props) => {
  return (
    <Delayed>
      <div />
    </Delayed>
  );
};