延迟渲染React组件

时间:2015-06-12 12:45:04

标签: javascript reactjs

我有一个包含许多子组件的React组件。我希望不是一次性渲染子组件,而是在延迟一段时间后(每个孩子都统一或不同)。

我在想 - 有办法怎么做?

10 个答案:

答案 0 :(得分:43)

我认为最直观的方法是给孩子们一个“等待”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>
        )
    }
});

然后,在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

答案 1 :(得分:10)

在父组件<Father />中,您可以创建一个初始状态,跟踪每个子项(例如,使用和id),分配一个布尔值,这意味着是否呈现:

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

然后,在安装组件时,启动计时器以更改状态:

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

当你渲染你的孩子时,你可以通过重新创建它们,为匹配的孩子指定一个状态,指出是否必须渲染组件。

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

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

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

触发超时后,将更改状态并重新呈现父组件。子道具已更新,如果doRendertrue,则会自行呈现。

答案 2 :(得分:8)

我已经使用 Hooks和TypeScript

创建了Delayed组件
import React, { useState, useEffect } from 'react';

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

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

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

  return isShown ? children : null;
};

export default Delayed;

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

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

答案 3 :(得分:6)

延迟组件的另一种方法:

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;

用法:

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

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

答案 4 :(得分:2)

取决于您的使用案例。

如果你想做一些混合儿童的动画,请使用反应动画插件:https://facebook.github.io/react/docs/animation.html 否则,让孩子的渲染依赖于道具,并在延迟一段时间后添加道具。

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

答案 5 :(得分:1)

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

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

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

最终我决定稍微延迟(重新)渲染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
}

可以像这样使用:

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

对我来说感觉非常hacky但是仍适用于我的用例。

答案 6 :(得分:1)

使用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

答案 7 :(得分:0)

不立即渲染子组件,而是延迟一段时间。

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

您可以直接从地图上渲染组件,但可以使用CSS动画来延迟它们的显示。

public static class Demo {
    public static String demoName = "xyz";

    public String getDemoName() {
        return demoName;
    }
}

public static class Demos extends Demo {

    private String demoNameOverride;

    @Override
    public String getDemoName() {
        return demoNameOverride;
    }

}

现在,子div互相跟随了一段时间。

答案 8 :(得分:0)

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

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

这是由Dan Abramov的useInterval挂钩启发的(有关详细说明,请参见Dan's blog post),区别在于:

  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 };
};

现在我们需要一个钩子,该钩子将捕获以前的子代,并使用我们的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;
};

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

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

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

  // ... stays the same

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

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

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);

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

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

  return useDelayNextChildren(render, delay);
};

在父代渲染功能中与在接受的答案中完全一样

...

除了延迟在以后的每个渲染上也一样,

我们使用了钩子,因此状态逻辑可以在任何组件之间重用

...

...

使用挂钩。 :D

答案 9 :(得分:0)

我这里还有一个像 Suspense 这样的后备选项

import { useState, useEffect } from "react";

export default function FakeSuspense(props) {
  const { children, delay, fallback } = props;
  const [isShown, setIsShown] = useState(false);

  useEffect(() => {
    setTimeout(() => {
      setIsShown(true);
    }, delay);
  }, [delay]);

  return isShown ? children : fallback;
}

然后使用它

<FakeSuspense delay={1700} fallback={<Spinner />}>
  <Component />
</FakeSuspense>