代码之家  ›  专栏  ›  技术社区  ›  tlt

如何通过点击方式发送请求?

  •  0
  • tlt  · 技术社区  · 6 年前

    如何使用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)}
      );
    }
    

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

    3 回复  |  直到 6 年前
        1
  •  52
  •   DoXicK    5 年前
    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状态更新。这是一个no op,但它表明应用程序中存在内存泄漏。要修复,请取消useEffect清理功能中的所有订阅和异步任务。”

    实际上,发生的情况是,请求仍在处理中,而与此同时,组件将卸载。然后它试图 setIsSending (设置状态)在未安装的组件上。

    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} />
      )
    }
    
        2
  •  27
  •   Shubham Khatri    6 年前

    你不需要在点击按钮时发送请求,相反,你需要的只是一个处理程序方法,你可以使用它进行优化 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将状态设置为调用api,但由于某些其他更改而进行的额外渲染将导致请求进入循环

        3
  •  6
  •   Fareed Alnamrouti    3 年前

    在函数式编程中,任何异步函数都应该被视为副作用。

    在处理副作用时,你需要将副作用开始的逻辑和副作用结果的逻辑分开(类似于redux传奇)。

    基本上,按钮的责任只是触发副作用,副作用的责任是更新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 )

    更新:

    使用 useAsync useAsyncFn react-use 图书馆,它更干净,更直接。

    例子:

    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>
      );
    };
    
    
        4
  •  4
  •   Tholle    6 年前

    您可以像在问题中那样,通过某些状态更改来获取数据,但也可以像在类组件中使用的那样,直接在单击处理程序中获取数据。

    实例

    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>
        5
  •  2
  •   Teneff    6 年前

    您可以像以前一样在状态中定义布尔值,并在触发请求后将其设置为 true 当你收到回复时,将其设置回 false :

    const [requestSent, setRequestSent] = useState(false);
    
    const sendRequest = () => {
      setRequestSent(true);
      fetch().then(() => setRequestSent(false));
    };
    

    Working example

        6
  •  0
  •   GorvGoyl    3 年前

    可以创建自定义挂钩 useApi 并返回一个函数 execute 调用时将调用api(通常通过一些 onClick ).

    乌萨皮 钩子:

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

    使用 乌萨皮 在某些组件中:

    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>
    );
    
    
    
        7
  •  0
  •   Jamal Ashraf    3 年前

    为此,您可以在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}
          );
        }