如何发送请求上点击反应钩的方式?

如何发送 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)}
);
}

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

121101 次浏览
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} />
)
}

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

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


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

举个例子

您可以像在问题中那样,通过某些状态变化的影响来获取数据,但是您也可以像在类组件中那样,直接在 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>

您不需要一个效果来发送按钮点击请求,相反,您需要的只是一个处理程序方法,您可以使用 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,但是由于其他更改而产生的额外呈现将导致请求进入循环

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

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

基本上,按钮的责任只是触发副作用,而副作用的责任是更新 Dom。

此外,由于反应是处理组件,您需要确保您的组件仍然挂载在任何 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>
);
};


您可以创建一个自定义钩子 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>
);




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