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