如何实现泛型组件并允许外部自定义函数获取组件内变量?
Hey there! Let's break down how to turn your Adder component into a flexible generic component that works with any data type, keeps your callbacks returning values ready for downstream processing, and lets other developers customize the onDelete function to access the correct val type.
Step 1: Define a Generic Props Interface
First, we'll create a type-safe props interface using TypeScript generics. This lets the component adapt to any data type T, and ensures callbacks receive the correct value type while allowing flexible return values for downstream handling.
// Define generic props for the Adder component interface AdderProps<T> { // The data value passed to the component, can be any type T data: T; // onAdd callback: takes a T type value, returns any type (flexible for downstream use) onAdd: (val: T) => unknown; // onDelete callback: same structure, lets developers customize logic while getting the correct val type onDelete: (val: T) => unknown; }
Step 2: Refactor the Adder Component to Use Generics
Now update your Adder component to accept the generic props. TypeScript will automatically infer the type T based on the data prop passed in, so callbacks get type-safe access to val.
import React from 'react'; // Generic Adder component const Adder = <T,>(props: AdderProps<T>) => { // Example internal handlers that trigger the callbacks const handleAddClick = () => { // Call onAdd with the typed data, capture the return value for downstream processing const addResult = props.onAdd(props.data); // You can add logic here to use addResult (e.g., update state, call an API) }; const handleDeleteClick = () => { // Call the custom onDelete callback with the typed data const deleteResult = props.onDelete(props.data); // Similarly, use deleteResult as needed }; return ( <div className="adder-component"> <button onClick={handleAddClick}>Add Item</button> <button onClick={handleDeleteClick}>Delete Item</button> </div> ); }; export default Adder;
Step 3: Using the Generic Component (with Custom onDelete)
Other developers can now use the component with any data type, and define a custom onDelete function that gets the correctly typed val out of the box. Here are a couple examples:
Example 1: Using a String Type
const myString = "My Todo Item"; // Custom onDelete that logs the string and returns its length const customDeleteHandler = (val: string) => { console.log(`Deleting item: ${val}`); return val.length; // Return value can be used for downstream logic (e.g., update stats) }; <Adder data={myString} onAdd={(val) => { console.log(val); return val; }} onDelete={customDeleteHandler} />
Example 2: Using a Custom Object Type
// Define a custom data type interface Product { id: number; name: string; price: number; } const myProduct: Product = { id: 123, name: "Laptop", price: 999 }; // Custom onDelete that returns the product ID for API calls const productDeleteHandler = (val: Product) => { console.log(`Deleting product: ${val.name}`); return val.id; // Return ID to pass to a delete API endpoint }; <Adder data={myProduct} onAdd={(val) => { console.log(val); return val; }} onDelete={productDeleteHandler} />
Bonus: Strict Return Type Control (Optional)
If you want to enforce specific return types for the callbacks (e.g., a Promise for async operations), you can extend the props interface with additional generics:
interface AdderProps<T, RAdd = unknown, RDelete = unknown> { data: T; onAdd: (val: T) => RAdd; onDelete: (val: T) => RDelete; } // Update the component to use the extended generics const Adder = <T, RAdd = unknown, RDelete = unknown>(props: AdderProps<T, RAdd, RDelete>) => { const handleAddClick = () => { const addResult: RAdd = props.onAdd(props.data); // If RAdd is a Promise, you can await it here if (addResult instanceof Promise) { addResult.then(result => console.log("Add completed:", result)); } }; // ... rest of component };
This gives you even more control over what the callbacks return, while keeping the component flexible.
内容的提问来源于stack exchange,提问作者user16516348

