如何在 useEffect 中设置 state 后调用函数?

4
我希望仅在customEffect完成设置isReady状态后才运行customFunction。同时,只要customFunction在设置后运行过一次,无论isReady是否被设置为falsetrue,都应该只运行一次。
import customFunction from 'myFile';

export const smallComponent = () => {
  const [isReady, setIsReady] = useState(false);

  useEffect(() => {
    const customEffect = async () => {
      try {
        const response = await get(
          `some-api.com`,
        );
        return setIsReady(response); // response can be true or false
      } catch {
        return null;
      }
    };

    customEffect();

    customFunction();
  }, []);

  return (
    <>Hello World</>
  )

}



我尝试将isReady作为第二个useEffect参数添加,但是在customEffect完成之前我的customFunction被运行,然后在设置isReady之后再次运行。我还尝试了在另一个useEffect中使用它,但似乎仍然在customEffect完成之前运行。

customEffect中更新了isReady值时,customFunction是否依赖于它? - Drew Reese
在我的情况下是的。我正在 customFunction 中使用 isReady 的值。但是看起来您和 p1uton 的解决方案似乎可以完成这项工作 :) - OFFLlNE
没错,我意识到把 customFunction 调用推入 customEffect 调用中并不像想象的那么简单。我在下面更新了我的解决方案,并添加了一个保护条件,在 customFunction 被调用后,防止它在组件重新渲染时再次被调用。 - Drew Reese
4个回答

2
将初始值设置为null,并像Kevin建议的那样使用单独的useEffect(仅不检查isReady true/false)。
在这种情况下,setIsReady将把isReady从null更改为true/false,并调用第二个useEffect
import customFunction from 'myFile';

export const smallComponent = () => {
    const [isReady, setIsReady] = useState(null);

    useEffect(() => {
        const customEffect = async () => {
            try {
                const response = await get(
                    `some-api.com`,
                );
                return setIsReady(response);
            } catch {
                return null;
            }
        };

        customEffect();
    }, []);

    useEffect(() => {
       if (null === isReady) {
          return;
       }
       customFunction();
    }, [isReady]);

    return (
        <>Hello World</>
    )
}

我喜欢这个,因为它只是单一状态,但第二个效果仍在组件挂载时运行。在 isReady 上添加额外的空值检查,只有在其非空时才调用 customFunction - Drew Reese
是的,我尝试过,而且很惊讶它被调用了两次。空值检查可能会解决这个问题。 - OFFLlNE
是的,忘记了这个。 - p1uton

1

由于您想要在设置isReady状态后触发效果,而isReady的值无关紧要,因此可以使用第二个状态值来指示第一个效果和状态更新已完成。

这将触发第二个效果来调用customFunction,但您不希望组件保持在这种状态下,因为从这里开始,每当组件重新呈现时,条件仍将得到满足。您需要第三个“状态”来指示第二个效果已被触发。在这里,您可以使用React ref来指示这一点。

export const smallComponent = () => {
  const [readySet, setReadySet] = useState(false);
  const [isReady, setIsReady] = useState(false);
  const customFunctionRunRef = useRef(false);

  useEffect(() => {
    const customEffect = async () => {
      try {
        const response = await get(
          `some-api.com`,
        );

        setReadySet(true); // to trigger second effect callback
        return setIsReady(response); // response can be true or false
      } catch {
        return null;
      }
    };

    customEffect();
  }, []);

  useEffect(() => {
    if (readySet && !customFunctionRunRef.current) {
      // won't run before readySet is true
      // won't run after customFunctionRunRef true
      customFunction();
      customFunctionRunRef.current = true;
    }
  }, [readySet]);

  return (
    <>Hello World</>
  );
}

更好的解决方案来自@p1uton。使用null的isReady状态来指示customFunction不应该被调用,并使用ref来防止它在之后被调用。
export const smallComponent = () => {
  const [isReady, setIsReady] = useState(null);
  const customFunctionRunRef = useRef(false);

  useEffect(() => {
    const customEffect = async () => {
      try {
        const response = await get(
          `some-api.com`,
        );

        return setIsReady(response); // response can be true or false
      } catch {
        return null;
      }
    };

    customEffect();
  }, []);

  useEffect(() => {
    if (isReady !== null && !customFunctionRunRef.current) {
      // won't run before isReady is non-null
      // won't run after customFunctionRunRef true
      customFunction();
      customFunctionRunRef.current = true;
    }
  }, [isReady]);

  return (
    <>Hello World</>
  );
}

是的,我也有类似的想法,但现在没有感觉。我猜你的代码应该在调用 setIsReady 后调用 setReadySet。但我认为这是最清晰的前进方式。 - OFFLlNE
@OFFLINE 不好意思,我在编辑的时候丢失了一部分内容,实际上我是在更新isReady状态的同时设置readySet的。 - Drew Reese

0

我不确定我是否正确理解了你的意思,但这是我如何使用单独的useEffect

import customFunction from 'myFile';

export const smallComponent = () => {
    const [isReady, setIsReady] = useState(false);

    useEffect(() => {
        const customEffect = async () => {
            try {
                const response = await get(
                    `some-api.com`,
                );
                return setIsReady(response);
            } catch {
                return null;
            }
        };

        customEffect();
    }, []);

    useEffect(() => {
        if (!isReady) {
            return;
        }

        customFunction();
    }, [isReady]);

    return (
        <>Hello World</>
    )
}

好的,我会尽量添加更多上下文:)API 可以响应 true 或 false。如果 API 执行了 setIsReady(false),我仍希望它能继续运行。 - OFFLlNE

0

你尝试过使用这个包isMounted吗?

我在我的项目中使用了它。

import React, { useState, useEffect } from 'react';
import useIsMounted from 'ismounted';
import myService from './myService';
import Loading from './Loading';
import ResultsView from './ResultsView';
 
const MySecureComponent = () => {
  const isMounted = useIsMounted();
  const [results, setResults] = useState(null);
 
  useEffect(() => {
    myService.getResults().then(val => {
      if (isMounted.current) {
        setResults(val);
      }
    });
  }, [myService.getResults]);
 
  return results ? <ResultsView results={results} /> : <Loading />;
};
 
export default MySecureComponent;

https://www.npmjs.com/package/ismounted


我想相信这是可能的,而无需外部软件包。但是谢谢,我会看一下那个 :) - OFFLlNE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接