我有一个 React 组件,其中包含许多子组件。我不想立即呈现子组件,而是在一些延迟之后(对于每个子组件都是统一的或不同的)。
我想知道,有没有办法可以做到这一点?
在父组件 <Father />中,您可以创建一个初始状态,在其中跟踪每个子组件(例如 using 和 id) ,分配一个布尔值,这意味着是否呈现:
<Father />
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 />组件中
<Child />
render() { if (!this.props.render) return null; // Render method here }
触发超时时,状态将更改,父组件将重新呈现。儿童道具是更新的,如果 doRender是 true,他们将呈现自己。
doRender
true
我认为最直观的方法是给孩子一个“ wait”prop,它在从父代传递下来的持续时间内隐藏组件。通过将默认状态设置为隐藏,React 仍然会立即呈现组件,但是在状态改变之前组件是不可见的。然后,您可以设置 componentWillMount来调用一个函数,在通过道具传递的持续时间之后显示它。
prop
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 的博客的详细解释) ,不同之处在于:
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中
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>