Reactjs React顺序渲染挂钩
我有一些组件,一旦它们加载或标记为出于任何原因准备就绪,它们就需要按顺序渲染 在一个典型的Reactjs React顺序渲染挂钩,reactjs,react-hooks,Reactjs,React Hooks,我有一些组件,一旦它们加载或标记为出于任何原因准备就绪,它们就需要按顺序渲染 在一个典型的{things.map(thing=>}示例中,它们都是同时渲染的,但是我想一个接一个地渲染它们,我创建了一个钩子来提供一个列表,其中只包含要渲染的顺序就绪项 我遇到的问题是,子组件需要一个函数来告诉钩子何时将下一个添加到其“准备渲染”状态。该函数每次都会更改,因此会导致子组件上出现无限次的重新渲染 在下面的示例中,子组件useffect必须依赖依赖依赖项done来通过linter规则-如果我删除此项,它将
{things.map(thing=>}
示例中,它们都是同时渲染的,但是我想一个接一个地渲染它们,我创建了一个钩子来提供一个列表,其中只包含要渲染的顺序就绪项
我遇到的问题是,子组件需要一个函数来告诉钩子何时将下一个添加到其“准备渲染”状态。该函数每次都会更改,因此会导致子组件上出现无限次的重新渲染
在下面的示例中,子组件useffect
必须依赖依赖依赖项done
来通过linter规则-如果我删除此项,它将按预期工作,因为无论何时更改,done都不是问题,但显然这并不能解决问题
类似地,我可以在子组件中添加if(!attachment.\uuu loaded){
,但是如果子组件需要这样的特定实现,那么钩子的API就很差
我想我需要的是一种停止每次重新创建函数的方法,但我还没有想出如何做到这一点
useSequentialRenderer.js
import { useReducer, useEffect } from "react";
const loadedProperty = "__loaded";
const reducer = (state, {i, type}) => {
switch (type) {
case "ready":
const copy = [...state];
copy[i][loadedProperty] = true;
return copy;
default:
return state;
}
};
const defaults = {};
export const useSequentialRenderer = (input, options = defaults) => {
const [state, dispatch] = useReducer(options.reducer || reducer, input);
const index = state.findIndex(a => !a[loadedProperty]);
const sliced = index < 0 ? state.slice() : state.slice(0, index + 1);
const items = sliced.map((item, i) => {
function done() {
dispatch({ type: "ready", i });
return i;
}
return { ...item, done };
});
return { items };
};
从“react”导入{useReducer,useffect};
const loadedProperty=“\uuu loaded”;
const reducer=(state,{i,type})=>{
开关(类型){
案例“就绪”:
常量副本=[…状态];
copy[i][loadedProperty]=true;
返回副本;
违约:
返回状态;
}
};
常量默认值={};
导出常量useSequentialRenderer=(输入,选项=默认值)=>{
const[state,dispatch]=useReducer(options.reducer | | reducer,input);
const index=state.findIndex(a=>!a[loadedProperty]);
常量切片=索引<0?state.slice():state.slice(0,索引+1);
常量项=切片的.map((项,i)=>{
函数完成(){
分派({type:“ready”,i});
返回i;
}
返回{…项,完成};
});
返回{items};
};
example.js
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import { useSequentialRenderer } from "./useSequentialRenderer";
const Attachment = ({ children, done }) => {
const [loaded, setLoaded] = useState(false);
useEffect(() => {
const delay = Math.random() * 3000;
const timer = setTimeout(() => {
setLoaded(true);
const i = done();
console.log("happening multiple times", i, new Date());
}, delay);
return () => clearTimeout(timer);
}, [done]);
return <div>{loaded ? children : "loading"}</div>;
};
const Attachments = props => {
const { items } = useSequentialRenderer(props.children);
return (
<>
{items.map((attachment, i) => {
return (
<Attachment key={attachment.text} done={() => attachment.done()}>
{attachment.text}
</Attachment>
);
})}
</>
);
};
function App() {
const attachments = [1, 2, 3, 4, 5, 6, 7, 8].map(a => ({
loaded: false,
text: a
}));
return (
<div className="App">
<Attachments>{attachments}</Attachments>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
import React,{useffect,useState}来自“React”;
从“react dom”导入react dom;
从“/useSequentialRenderer”导入{useSequentialRenderer};
常量附件=({children,done})=>{
const[loaded,setLoaded]=useState(false);
useffect(()=>{
const delay=Math.random()*3000;
常量计时器=设置超时(()=>{
setLoaded(真);
常数i=完成();
log(“多次发生”,i,new Date());
},延误);
return()=>clearTimeout(计时器);
},[完成];
返回{loaded?子项:“loading”};
};
常量附件=道具=>{
const{items}=useSequentialRenderer(props.childrender);
返回(
{items.map((附件一)=>{
返回(
附件.done()}>
{附件.text}
);
})}
);
};
函数App(){
常量附件=[1,2,3,4,5,6,7,8]。映射(a=>({
加载:false,
正文:a
}));
返回(
{附件}
);
}
const rootElement=document.getElementById(“根”);
render(,rootElement);
使用useCallback
将回调包装在一个传统的依赖性检查层中。这将确保在渲染过程中有一个稳定的标识
const Component = ({ callback }) =>{
const stableCb = useCallback(callback, [])
useEffect(() =>{
stableCb()
},[stableCb])
}
请注意,如果签名需要更改,还应该声明依赖项
const Component = ({ cb, deps }) =>{
const stableCb = useCallback(cb, [deps])
/*...*/
}
更新的示例:
检查(!loaded){…setTimeout
或
使用[loaded]的效果
useffect(()=>{
const delay=Math.random()*1000;
常量计时器=设置超时(()=>{
setLoaded(真);
常数i=完成();
log(“多次呈现”,i,new Date());
},延误);
return()=>clearTimeout(计时器);
},[加载];
返回{loaded?子项:“loading”};
};
哇,回答得很好,非常感谢。这确实有效。有没有一种方法可以在不需要在子组件内部执行的情况下应用它?也就是说,以某种方式将这种复杂性拉到钩子中,以隐藏它,使其远离消费者?但我不能在循环中使用useCallback
,因此我不知道需要进行多少次回调,实际上这应该在自定义钩子中完成。我希望可以删除函数的创建(或重用函数)在钩子内部,而不是在每个重渲染器上再次创建它们,从而有望解决导致循环的效果问题。这是一个遗憾,因为API现在相当笨重,但至少它可以工作。非常感谢您的时间和帮助。谢谢,我在问题中提出了这个解决方案。在它工作的同时,它增加了我试图避免的API的复杂性和摩擦。这修复了它,因为它消除了对done
的依赖性,这也是我的问题,也打破了在useEffect回调中使用的linting规则:(
useEffect(() => {
const delay = Math.random() * 1000;
const timer = setTimeout(() => {
setLoaded(true);
const i = done();
console.log("rendering multiple times", i, new Date());
}, delay);
return () => clearTimeout(timer);
}, [loaded]);
return <div>{loaded ? children : "loading"}</div>;
};