如何在TypeScript中完全禁止接口继承时的属性重写
If you want to block any property override when extending an interface—regardless of whether the new type is compatible with the parent's—here are two reliable solutions tailored to your React component props scenario:
1. Custom ESLint Rule (Automated, Recommended)
Since neither TypeScript nor default ESLint rules enforce this behavior out of the box, a custom rule will catch accidental overrides as you code. Here's how to build it:
Step 1: Install Dependencies
First, add the required TypeScript ESLint utilities:
npm install --save-dev @typescript-eslint/utils @typescript-eslint/eslint-plugin
Step 2: Create the Custom Rule
Make a new file (e.g., eslint-rules/no-interface-property-override.js) with this code:
const { ESLintUtils } = require('@typescript-eslint/utils'); const createRule = ESLintUtils.RuleCreator( () => 'https://your-project-docs.com/rules/no-interface-property-override' // Placeholder URL for your project docs ); module.exports = createRule({ create(context) { const parserServices = ESLintUtils.getParserServices(context); const typeChecker = parserServices.program.getTypeChecker(); return { InterfaceDeclaration(node) { // Skip interfaces without parent extensions if (!node.extends || node.extends.length === 0) return; // Extract property names from the current interface const currentProps = node.body.body .filter(item => item.type === 'TSPropertySignature') .map(item => item.key.name); // Check each inherited parent interface for (const heritageClause of node.extends) { for (const typeNode of heritageClause.types) { const parentType = typeChecker.getTypeAtLocation(typeNode); const parentProps = typeChecker.getPropertiesOfType(parentType) .map(prop => prop.name); // Find overlapping properties and report errors const overlapping = currentProps.filter(prop => parentProps.includes(prop)); overlapping.forEach(prop => { context.report({ node: node.body.body.find(item => item.key.name === prop), messageId: 'forbiddenOverride', data: { prop }, }); }); } } }, }; }, name: 'no-interface-property-override', meta: { type: 'problem', docs: { description: 'Prevent overriding properties when extending an interface', recommended: 'error', }, messages: { forbiddenOverride: 'Property "{{prop}}" is already defined in the parent interface. Avoid overriding inherited properties.', }, schema: [], }, defaultOptions: [], });
Step 3: Configure ESLint
Update your .eslintrc.js to enable the custom rule:
module.exports = { parser: '@typescript-eslint/parser', plugins: ['@typescript-eslint'], rules: { './eslint-rules/no-interface-property-override': 'error', // Use 'warn' if you prefer warnings over errors }, };
Now, any time you define a property in your component props interface that already exists in the inherited HTML props (like color in ButtonProps), ESLint will throw an error immediately.
2. TypeScript Type Guard (Manual Safety Net)
If you prefer a TypeScript-only solution (without ESLint), you can create a type utility that enforces no overlapping properties:
// Type utility to ensure T has no properties overlapping with U type NoInheritedOverrides<T, U> = Exclude<keyof T, keyof U> extends never ? T : never; // Example usage with your React component props type HTMLButtonProps = JSX.IntrinsicElements["button"]; interface ButtonProps extends HTMLButtonProps { // This will trigger a TypeScript error because 'color' exists in HTMLButtonProps color: "primary" | "secondary"; } // Enforce the check: if ButtonProps has overrides, this will be 'never' and throw an error type CheckedButtonProps = NoInheritedOverrides<ButtonProps, HTMLButtonProps>;
This will cause a TypeScript error if your ButtonProps includes any property from HTMLButtonProps, but you'll need to add the CheckedButtonProps type explicitly for each component.
Key Notes
- The ESLint rule is better for long-term use because it works automatically across your entire codebase.
- Both solutions work perfectly for React scenarios where you want to avoid accidentally overriding native HTML element props.
内容的提问来源于stack exchange,提问作者Darkbound

