如何使用反应钩子在按钮点击时发送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)}
);
}
这是正确的方法还是有其他模式?
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} />
)
}
您不需要在单击按钮时发送请求的效果,而只需要一个可以使用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
的变量跟踪请求不是正确的模式,因为您可以将状态设置为使用 useImpact 调用 api,但由于其他一些更改而导致的额外渲染将导致请求进入循环
在函数式编程中,任何异步函数都应被视为副作用。
在处理副作用时,您需要将启动副作用的逻辑和副作用结果的逻辑分开(类似于 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
库中的useAsync
和useAsyncFn
,它更加干净和直接。
例:
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>
);
};
您可以像在问题中所做的那样,将数据作为某些状态更改的结果来获取数据,但您也可以像在类组件中习惯的那样直接在单击处理程序中获取数据。
例
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>
您可以像以前一样在状态中定义布尔值,一旦触发请求,请将其设置为true
,并在收到响应时将其设置回false
:
const [requestSent, setRequestSent] = useState(false);
const sendRequest = () => {
setRequestSent(true);
fetch().then(() => setRequestSent(false));
};
工作示例
您可以创建自定义钩子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}
);
}
我的答案很简单,如果使用 useState 钩子,javascript 不允许你在将状态设置为 false 时传递值。当值设置为 true 时,它接受该值。因此,如果您在使用状态中使用 false,则必须使用 if 条件定义一个函数