React顺序渲染挂钩

时间:2019-12-03 14:34:42

标签: reactjs react-hooks

我有一些组件一旦出于某种原因被加载或标记为就绪后就需要顺序渲染。

在一个典型的{things.map(thing => <Thing {...thing} />}示例中,它们都同时渲染,但是我想一个一个地渲染它们,所以我创建了一个钩子来提供仅包含要准备渲染的项的列表。 / p>

我遇到的问题是孩子们需要一个函数来告诉钩子何时将下一个添加到其准备渲染状态。该函数最终每次都会更改,因此会在子组件上产生无限数量的重新渲染。

在下面的示例中,子组件useEffect必须依赖于依赖项done才能通过linter规则-如果我删除了子规则,则它可以按预期工作,因为每当它更改时,都不必担心但这显然不能解决问题。

类似地,我可以将if (!attachment.__loaded) {添加到子组件中,但是如果子组件需要诸如此类的特定实现,则该API对于钩子来说就很糟糕。

我认为我需要的是一种阻止每次重新创建函数的方法,但是我还没有弄清楚该怎么做。

Codesandbox link

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

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

2 个答案:

答案 0 :(得分:4)

使用useCallback在附加的依赖检查层中包装您的回调。这样可以确保跨渲染器的稳定身份

const Component = ({ callback }) =>{
    const stableCb = useCallback(callback, [])

    useEffect(() =>{
        stableCb()
    },[stableCb])
}

请注意,如果需要更改签名,则也应声明依赖项

const Component = ({ cb, deps }) =>{
    const stableCb = useCallback(cb, [deps])
    /*...*/
}

Edit vigorous-beaver-h2es6

答案 1 :(得分:0)

更新示例: https://codesandbox.io/s/wizardly-dust-fvxsl

检查是否(!已加载){.... setTimeout 要么 useEffect与[loaded]);

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