reactjs 如何发送请求点击React挂钩的方式?

chhqkbe1  于 2022-12-18  发布在  React
关注(0)|答案(8)|浏览(127)

如何用react钩子在按钮点击时发送http请求?或者,就此而言,如何对按钮点击做任何副作用?
到目前为止,我看到的是一些“间接”的东西,如:

export default = () => {
  const [sendRequest, setSendRequest] = useState(false);

  useEffect(() => {
    if(sendRequest){
       //send the request
       setSendRequest(false);
    }
  },
  [sendRequest]);

  return (
    <input type="button" disabled={sendRequest} onClick={() => setSendRequest(true)}
  );
}

这是正确的方式还是有其他的模式?

iyfjxgzm

iyfjxgzm1#

export default () => {
  const [isSending, setIsSending] = useState(false)
  const sendRequest = useCallback(async () => {
    // don't send again while we are sending
    if (isSending) return
    // update state
    setIsSending(true)
    // send the actual request
    await API.sendRequest()
    // once the request is sent, update state again
    setIsSending(false)
  }, [isSending]) // update the callback if the state changes

  return (
    <input type="button" disabled={isSending} onClick={sendRequest} />
  )
}

这是什么,它会归结为当你想发送一个请求点击和禁用按钮,而它正在发送

更新:

@tkd_aj指出,这可能会给予一个警告:无法对未装入的组件执行React状态更新。这是一个无操作,但它表明应用程序中存在内存泄漏。若要修复,请取消useEffect清理函数中的所有订阅和异步任务。
实际上,所发生的情况是请求仍在处理,而同时您的组件正在卸载,然后它尝试对卸载的组件执行setIsSending(setState)。

export default () => {
  const [isSending, setIsSending] = useState(false)
  const isMounted = useRef(true)

  // set isMounted to false when we unmount the component
  useEffect(() => {
    return () => {
      isMounted.current = false
    }
  }, [])

  const sendRequest = useCallback(async () => {
    // don't send again while we are sending
    if (isSending) return
    // update state
    setIsSending(true)
    // send the actual request
    await API.sendRequest()
    // once the request is sent, update state again
    if (isMounted.current) // only update if we are still mounted
      setIsSending(false)
  }, [isSending]) // update the callback if the state changes

  return (
    <input type="button" disabled={isSending} onClick={sendRequest} />
  )
}
kx1ctssn

kx1ctssn2#

你不需要一个效果来发送一个按钮点击请求,相反,你需要的只是一个处理方法,你可以使用useCallback方法来优化它

const App = (props) => {
   //define you app state here
   const fetchRequest = useCallback(() => {
       // Api request here
   }, [add dependent variables here]);

  return (
    <input type="button" disabled={sendRequest} onClick={fetchRequest}
  );
}

使用变量useEffect跟踪请求不是正确的模式,因为您可以使用useEffect将state设置为调用API,但是由于某些其他更改而导致的额外呈现将导致请求进入循环

64jmpszr

64jmpszr3#

在函数式编程中,任何异步函数都应该被认为是副作用。
当处理副作用时,你需要将副作用的启动逻辑和副作用的结果逻辑分开(类似于redux Saga )。
基本上,按钮职责只是触发副作用,副作用职责是更新DOM。
此外,由于react处理的是组件,因此您需要确保在任何setState之前或每个await之后仍然挂载了组件,这取决于您自己的偏好。
为了解决这个问题,我们可以创建一个定制的钩子useIsMounted,这个钩子可以让我们很容易的检查组件是否还在挂载中

/**
 * check if the component still mounted
 */
export const useIsMounted = () => {
  const mountedRef = useRef(false);
  const isMounted = useCallback(() => mountedRef.current, []);

  useEffect(() => {
    mountedRef.current = true;
    return () => {
      mountedRef.current = false;
    };
  });

  return isMounted;
};

那么您的代码应该如下所示

export const MyComponent = ()=> {
  const isMounted = useIsMounted();
  const [isDoMyAsyncThing, setIsDoMyAsyncThing] = useState(false);

  // do my async thing
  const doMyAsyncThing = useCallback(async () => {
     // do my stuff
  },[])

  /**
   * do my async thing effect
  */
  useEffect(() => {
    if (isDoMyAsyncThing) {
      const effect = async () => {
        await doMyAsyncThing();
        if (!isMounted()) return;
        setIsDoMyAsyncThing(false);
      };
      effect();
    }
  }, [isDoMyAsyncThing, isMounted, doMyAsyncThing]);

  return (
     <div> 
        <button disabled={isDoMyAsyncThing} onClick={()=> setIsDoMyAsyncThing(true)}>
          Do My Thing {isDoMyAsyncThing && "Loading..."}
        </button>;
     </div>
  )
}

**注意:**最好将副作用的逻辑与触发副作用的逻辑(useEffect)分开
更新日期:

与上面的所有复杂性不同,只需使用react-use库中的useAsyncuseAsyncFn,它会更干净、更直接。

示例:

import {useAsyncFn} from 'react-use';

const Demo = ({url}) => {

  const [state, doFetch] = useAsyncFn(async () => {
    const response = await fetch(url);
    const result = await response.text();
    return result
  }, [url]);

  return (
    <div>
      {state.loading
        ? <div>Loading...</div>
        : state.error
          ? <div>Error: {state.error.message}</div>
          : <div>Value: {state.value}</div>
      }
      <button onClick={() => doFetch()}>Start loading</button>
    </div>
  );
};

0yycz8jy

0yycz8jy4#

您可以像在问题中所做的那样,作为某些状态更改的结果来获取数据,但是您也可以像在类组件中所习惯的那样,直接在click处理程序中获取数据。

示例

const { useState } = React;

function getData() {
  return new Promise(resolve => setTimeout(() => resolve(Math.random()), 1000))
}

function App() {
  const [data, setData] = useState(0)

  function onClick() {
    getData().then(setData)
  }

  return (
    <div>
      <button onClick={onClick}>Get data</button>
      <div>{data}</div>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>

<div id="root"></div>
oiopk7p5

oiopk7p55#

您可以像之前那样在状态中定义布尔值,一旦触发请求,就将其设置为true,当收到响应时,再将其设置回false

const [requestSent, setRequestSent] = useState(false);

const sendRequest = () => {
  setRequestSent(true);
  fetch().then(() => setRequestSent(false));
};

Working example

owfi6suc

owfi6suc6#

你可以创建一个自定义钩子useApi,并返回一个函数execute,当调用该函数时,它将调用api(通常通过一些onClick)。
useApi挂钩:

export type ApiMethod = "GET" | "POST";

export type ApiState = "idle" | "loading" | "done";

const fetcher = async (
    url: string,
    method: ApiMethod,
    payload?: string
  ): Promise<any> => {
    const requestHeaders = new Headers();
    requestHeaders.set("Content-Type", "application/json");
  
    console.log("fetching data...");
    const res = await fetch(url, {
      body: payload ? JSON.stringify(payload) : undefined,
      headers: requestHeaders,
      method,
    });
  
    const resobj = await res.json();
    return resobj;
  };

export function useApi(
  url: string,
  method: ApiMethod,
  payload?: any
): {
  apiState: ApiState;
  data: unknown;
  execute: () => void;
} {
  const [apiState, setApiState] = useState<ApiState>("idle");

  const [data, setData] = useState<unknown>(null);
  const [toCallApi, setApiExecution] = useState(false);

  const execute = () => {
    console.log("executing now");
    setApiExecution(true);
  };

  const fetchApi = useCallback(() => {
    console.log("fetchApi called");
    fetcher(url, method, payload)
      .then((res) => {
        const data = res.data;
        setData({ ...data });
        return;
      })
      .catch((e: Error) => {
        setData(null);
        console.log(e.message);
      })
      .finally(() => {
        setApiState("done");
      });
  }, [method, payload, url]);

  // call api
  useEffect(() => {
    if (toCallApi &&  apiState === "idle") {
      console.log("calling api");
      setApiState("loading");
      fetchApi();
    }
  }, [apiState, fetchApi, toCallApi]);

  return {
    apiState,
    data,
    execute,
  };
}

在某些组件中使用useApi

const SomeComponent = () =>{

const { apiState, data, execute } = useApi(
      "api/url",
      "POST",
      {
        foo: "bar",
      }
    );

}

if (apiState == "done") {
      console.log("execution complete",data);
}

return (
 <button
   onClick={() => {
            execute();
          }}>
Click me
</button>
);
hpxqektj

hpxqektj7#

为此,您可以在ReactJS中使用回调钩子,这是实现此目的的最佳选择,因为useEffect不是正确的模式,因为您可能设置了状态以使用useEffect进行API调用,但由于一些其他更改而导致的额外呈现将导致请求进入循环。

<const Component= (props) => {
       //define you app state here
       const getRequest = useCallback(() => {
           // Api request here
       }, [dependency]);
    
      return (
        <input type="button" disabled={sendRequest} onClick={getRequest}
      );
    }
noj0wjuj

noj0wjuj8#

我的答案很简单,当使用useState钩子时,如果你将状态设置为false,javascript不会允许你传递值,而是在它被设置为true时接受值,所以如果你在usestate中使用false,你必须定义一个带if条件的函数

相关问题