反应组件的延迟呈现

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

我想知道,有没有办法可以做到这一点?

176203 次浏览

在父组件 <Father />中,您可以创建一个初始状态,在其中跟踪每个子组件(例如 using 和 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,他们将呈现自己。

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

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

这是小样

取决于你的用例。

如果你想做一些孩子融入的动画,使用反应动画插件: < a href = “ https://facebook.github.io/response/docs/animation.html”rel = “ nofollow norefrer”> https://facebook.github.io/react/docs/animation.html 否则,使渲染的孩子依赖道具,并添加道具后一些延迟。

我不会延迟组件,因为它可能会在测试期间困扰您。

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

本质上,我有一个第三方 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>

对我来说感觉相当古怪,但仍然适用于我的用例。

不是立即呈现子组件,而是在一些延迟之后呈现。

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

你可以直接从地图渲染组件,但是使用 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);
}
}

现在,子跳水运动员稍微延迟了一点时间。

延迟部分的另一种办法是:

返回文章页面

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

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

首先我们需要一个延迟的超时挂钩。

这篇文章的灵感来源于 Dan Abramov 的 useInterval hook (见 Dan 的博客的详细解释) ,不同之处在于:

  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

我已经使用 钩子和打字脚本创建了延迟组件

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(() => {
const timer = setTimeout(() => {
setIsShown(true);
}, waitBeforeShow);
return () => clearTimeout(timer);
}, [waitBeforeShow]);


return isShown ? children : null;
};


export default Delayed;

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

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

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

我这里还有一个备用选项,比如悬念

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>