【问题标题】:Delayed rendering of React componentsReact 组件的延迟渲染
【发布时间】:2015-08-28 11:39:12
【问题描述】:

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

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

【问题讨论】:

    标签: javascript reactjs


    【解决方案1】:

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

    【讨论】:

    • 你从哪里得到的显示属性?这不是 React 的一部分吗?
    • show 指的是我创建的将hidden 状态设置为空字符串的函数。这是Child 类的第三个函数。
    • 对我来说似乎很老套。为什么要强制等待异步操作...您可以完全通过这种方式劫持性能,而不是以某种方式延迟加载并行组件?
    • 如果您对问题中描述的问题有更好的解决方案,非常欢迎您发布。
    • 是否需要清除componentWillUnmount中的Timeout?
    【解决方案2】:

    延迟组件的另一种方法:

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

    【讨论】:

    • 这太棒了。应标记为解决方案。
    【解决方案3】:

    我使用 Hooks 和 TypeScript

    创建了延迟组件
    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 const LoadingScreen = () => {
      return (
        <Delayed>
          <div />
        </Delayed>
      );
    };
    

    【讨论】:

    【解决方案4】:

    在您的父组件&lt;Father /&gt; 中,您可以创建一个初始状态来跟踪每个孩子(例如使用和 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]});
    });
    

    所以在你的&lt;Child /&gt; 组件中

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

    当超时触发时,状态会改变,父组件会重新渲染。 children 的 props 更新了,如果 doRendertrue,他们会渲染自己。

    【讨论】:

    • 非常感谢您的回答!尽管我选择了 Michael 的答案(因为它更适合我的情况),但您的答案也正确,并且可能很适合阅读本文的其他人。非常感谢您的宝贵时间!
    • 总是乐于提供帮助......无论如何它可能会促进任何未来的想法:)
    • 谢谢你,很好的回答。这个应该被认为是正确的,因为它实际上延迟了子组件的渲染而不是隐藏渲染的组件。在渲染繁重的组件和关注初始页面加载时间时非常有用。
    • 这在我更新项目时不起作用,它没有更新,因为布尔值已经设置为 true。
    【解决方案5】:

    取决于您的用例。

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

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

    【讨论】:

      【解决方案6】:

      我们可以使用 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

      【讨论】:

        【解决方案7】:

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

        【讨论】:

          【解决方案8】:

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

          本质上,我有一个第三方 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,但仍然适用于我的用例。

          【讨论】:

            【解决方案9】:

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

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

            您可以立即从地图中渲染组件,但使用 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);
             }
            }
            

            现在,子 div 会稍稍延迟。

            【讨论】:

              【解决方案10】:

              我这里还有一个后备选项,比如 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>
              

              【讨论】:

                【解决方案11】:

                我正在使用非常好的循环。此功能的一大优点是不再需要时不会重复。

                function geniusLoop()
                {
                    console.log("TRYING TO LOOP")
                    if (iFinishedMyTask == false)
                    {
                        setTimeout(function () { geniusLoop(); }, 1000);
                        console.log("I LOOPED")
                        DoSomething();
                    }
                }
                

                【讨论】:

                  猜你喜欢
                  • 2019-01-10
                  • 2011-07-07
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2017-01-18
                  • 2021-04-21
                  • 1970-01-01
                  相关资源
                  最近更新 更多