【发布时间】:2015-08-28 11:39:12
【问题描述】:
我有一个 React 组件,其中包含许多子组件。我不想立即渲染子组件,而是在延迟一段时间后渲染子组件(每个子组件统一或不同)。
我想知道 - 有没有办法做到这一点?
【问题讨论】:
标签: javascript reactjs
我有一个 React 组件,其中包含许多子组件。我不想立即渲染子组件,而是在延迟一段时间后渲染子组件(每个子组件统一或不同)。
我想知道 - 有没有办法做到这一点?
【问题讨论】:
标签: javascript reactjs
我认为最直观的方法是给孩子一个“等待”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>
)
}
});
【讨论】:
show 指的是我创建的将hidden 状态设置为空字符串的函数。这是Child 类的第三个函数。
延迟组件的另一种方法:
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>
)
【讨论】:
我使用 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>
);
};
【讨论】:
在您的父组件<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
}
当超时触发时,状态会改变,父组件会重新渲染。 children 的 props 更新了,如果 doRender 是 true,他们会渲染自己。
【讨论】:
取决于您的用例。
如果你想做一些儿童融入的动画,请使用 react 动画插件:https://facebook.github.io/react/docs/animation.html 否则,让孩子的渲染依赖于道具,并在延迟后添加道具。
我不会延迟组件,因为它可能会在测试期间困扰您。理想情况下,组件应该是纯的。
【讨论】:
我们可以使用 Hooks 解决这个问题:
首先我们需要一个用于延迟的超时挂钩。
这个灵感来自 Dan Abramov 的 useInterval 钩子(请参阅Dan's blog post 以获得深入的解释),区别在于:
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
【讨论】:
使用 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
【讨论】:
我的用例可能有点不同,但我认为发布我提出的解决方案可能会很有用,因为它采用了不同的方法。
本质上,我有一个第三方 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,但仍然适用于我的用例。
【讨论】:
不是立即渲染子组件,而是在延迟一段时间后渲染。
问题是延迟渲染,但如果可以渲染但隐藏...
您可以立即从地图中渲染组件,但使用 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 会稍稍延迟。
【讨论】:
我这里还有一个后备选项,比如 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>
【讨论】:
我正在使用非常好的循环。此功能的一大优点是不再需要时不会重复。
function geniusLoop()
{
console.log("TRYING TO LOOP")
if (iFinishedMyTask == false)
{
setTimeout(function () { geniusLoop(); }, 1000);
console.log("I LOOPED")
DoSomething();
}
}
【讨论】: