reactjs - ฉันควรใช้ memoization หรือ callback เมื่อส่งฟังก์ชันเป็นพารามิเตอร์ของ custom hook?
ฉันเขียนฮุกแบบกำหนดเองที่รับฟังก์ชันแบบอะซิงค์เป็นพารามิเตอร์
ฮุกที่กำหนดเองจะเรียกฟังก์ชันและคืนค่าผลลัพธ์เมื่อเสร็จสิ้น
ฉันควรจะใช้ memoize หรือ useCallback เมื่อส่งฟังก์ชันไปหรือไม่?
นี่คือตัวอย่างการใช้งานฮุกของฉัน
const {
status,
error,
fire,
data
} = useAsyncOperation(
() => {
return Promise.resolve("Async result");
},
{ autofire: true }
);
สิ่งที่ฉันกลัวคือฟังก์ชันนิรนามแบบ inline จะเปลี่ยนไปในแต่ละครั้งที่เรนเดอร์ และว่า hook จะพิจารณามันเป็นค่าที่ใหม่ (แม้ว่าฟังก์ชันจะเหมือนเดิม)
ฉันควรจัดการเรื่องนี้อย่างไร?
สำหรับข้อมูลของคุณ: ฉันได้ตั้งค่าแซนด์บ็อกซ์โค้ดเพื่อแสดงการใช้งานฮุคที่ปรับแต่งเองของฉัน
และโค้ดของฮุคของฉันคือ:
import { useCallback, useEffect, useState } from 'react';
type UseAsyncOperationResult<TResult> = {
status: 'idle' | 'pending' | 'success' | 'error';
fire: () => Promise<TResult>;
error?: Error;
data?: TResult;
};
type UseAsyncOperationOptions<TResult> = {
onSuccess?: (result: TResult) => void;
onError?: (error: Error) => void;
autofire?: boolean;
};
export const useAsyncOperation = <TResult>(
operation: () => Promise<TResult>,
options: UseAsyncOperationOptions<TResult> = {}
): UseAsyncOperationResult<TResult> => {
// TODO: use reducer to manage state in a cleaner way
const [status, setStatus] = useState<'idle' | 'pending' | 'success' | 'error'>('idle');
const [error, setError] = useState<Error | undefined>();
const [data, setData] = useState<TResult | undefined>();
const [autofired, setAutofired] = useState(false);
const { onSuccess, onError, autofire } = options;
const fire = useCallback(async () => {
setStatus('pending');
try {
const result = await operation();
setStatus('success');
setData(result);
if (onSuccess) onSuccess(result);
return result;
} catch (error) {
setStatus('error');
setError(error as Error);
if (onError) onError(error as Error);
return Promise.reject(error);
}
}, [onError, onSuccess, operation]);
useEffect(() => {
if (autofire && !autofired) {
console.log('autofire');
setAutofired(true);
void fire();
}
}, [autofire, fire, autofired]);
return {
status,
fire,
error,
data,
};
};
คำตอบ #1
ใช่ คุณจำเป็นต้องเก็บการอ้างอิงของ callback ที่คุณส่งไปยัง useAsyncOperation
วิธีในการกำหนดว่าเมื่อใดคุณต้องใช้ useCallback สำหรับฟังก์ชันหรือไม่คือการตรวจสอบว่าฟังก์ชันนั้นถูกติดตามเป็น dependency หรือไม่ และ custom hook ของคุณติดตาม operation ใน useCallback และผลลัพธ์ก็ถูกติดตามใน useEffect ด้วยเช่นกัน
ถ้าคุณไม่เก็บการอ้างอิงของ callback operation ของคุณ fire จะได้รับการอ้างอิงใหม่ในแต่ละการเรนเดอร์ เพราะมันติดตาม operation และนี่จะทำให้เกิดการเรียกใช้ useEffect ด้วยเช่นกัน เพราะมันติดตาม fire
คำตอบ #2
ทุกครั้งที่ operation เปลี่ยนแปลง (เช่น - ฟังก์ชันลูกศรจะเปลี่ยนแปลงในแต่ละการเรนเดอร์) useCallback จะเปลี่ยนแปลงและทำให้ useEffect ถูกเรียกใช้งาน อย่างไรก็ตาม การเรียกใช้ useEffect นั้นไม่เป็นไร เพราะหลังจากการเรียกครั้งแรก ตัวแปร autofired จะป้องกันการเรียกใช้งานครั้งอื่น ๆ
อย่างไรก็ตาม ถ้าคุณยังต้องการหลีกเลี่ยงการสร้างฟังก์ชัน fire แบบ memoized ซ้ำ และคุณไม่ต้องการ (และไม่สามารถ) บังคับให้ผู้ใช้ต้อง memoize operation เสมอ (ผ่านทาง useCallback) ให้ใช้ ref เพื่อเก็บ operation:
const opRef = useRef(operation);
// update opRef on each render
useEffect(() => {
opRef.current = operation;
})
const fire = useCallback(async() => {
setStatus('pending');
try {
const result = await opRef.current(); // call the function that is stored in the ref
setStatus('success');
setData(result);
if (onSuccess) onSuccess(result);
return result;
} catch (error) {
setStatus('error');
setError(error as Error);
if (onError) onError(error as Error);
return Promise.reject(error);
}
}, [onError, onSuccess]); // remove operation
