javascript - Utöka tema med material-ui@next och typescript
När jag skapade mitt tema för material-ui lade jag till två nya palettalternativ som ger mig ett bättre utbud av ljusa och mörka färger. Jag har utökat Theme-typen för att indikera detta
import {Theme} from "material-ui/styles";
import {Palette} from "material-ui/styles/createPalette";
export interface ExtendedTheme extends Theme {
palette: ExtendedPalette
}
export interface ExtendedPalette extends Palette {
light: Color,
dark: Color,
}
Problemet uppstår när jag försöker använda dessa ytterligare alternativ i WithStyles-renderhjälparen
const styles = (theme : ExtendedTheme) => ({ root: {color: theme.light['100'] }});
export interface MyProps {classes: {[index: string] : string}};
const MyComponent = (props : MyProps) => {...};
// Type ExtendedTheme is not assignable to Theme
export default withStyles(styles : StyleRulesCallback)(MyComponent);
Funktionellt fungerar min kod bra i ren JavaScript, men eftersom typerna är olika så genererar den ett fel. Typningarna för material-ui förväntar sig en typ av Theme som det enda argumentet till stilåteranropsfunktionen:
export type StyleRulesCallback<ClassKey extends string = string> = (theme: Theme) => StyleRules<ClassKey>;
Jag trodde att att utvidga ett gränssnitt skulle fungera på ett polymorfiskt sätt så att ExtendedTheme skulle implementera Theme
Svar #1
Problemet kan lösas med hjälp av modulaugumentering:
declare module '@material-ui/core' {
interface Theme {
colors: {
success: {
dark: string,
light: string,
}
}
}
}
Dessutom kan du deklarera modulen i din App-komponent samt omsluta barnen i en ThemeProvider:
import { createMuiTheme, ThemeProvider, colors, ThemeOptions } from '@material-ui/core';
declare module '@material-ui/core' {
interface Theme {
colors: {
success: {
dark: string,
light: string,
}
}
}
}
const App = () => {
const theme = createMuiTheme({
colors: {
success: {
dark: colors.green[600],
light: colors.green[300],
},
} as ThemeOptions,
});
return (
<ThemeProvider theme={theme}>
<a href="https://material-ui.com/customization/theming/">Theming</a>
</ThemeProvider>
)
Svar #2
Det enda svaret jag har kommit på är att göra mina egna alternativ valfria på följande sätt
export interface ExtendedPalette extends Palette {
light?: Color,
dark?: Color,
}
Sedan i min styles callback måste jag kontrollera att de alternativen finns, vilket är lite besvärligt, men jag tror inte att det finns någon annan lösning
const styles = (theme : ExtendedTheme) => {
let light = theme.palette.light[100];
if(light === undefined) light = theme.common.white;
{ root: {color: light }}
};
Anledningen till detta är att Theme-objektet skickas till callbacken när jag använder withStyles, men typningen för denna callback använder Theme-typen eftersom de inte kan veta något om min ExtendedTheme-typ. Konflikten uppstår när ExtendedTheme måste ha alternativ som Theme inte har någon kännedom om. Genom att göra dessa extra alternativ valfria kan Theme fortfarande uppfylla ExtendedTheme. Grundläggande kan ett utökat gränssnitt skickas dit där dess förälder förväntas, men dess förälder kan inte skickas dit där det utökade gränssnittet förväntas, om inte det utökade gränssnittet är utökat på ett sätt som gör att föräldern fortfarande kan uppfylla det.
Ett enklare exempel är lärorikt.
export interface Foo {foo: string};
export interface Bar extends Foo {bar: string}
function getFoo(f : Foo) {console.log(f.foo)}
function getBar(b : Bar) {console.log(b.bar)}
function getFooBar(fb: Bar) {console.log(fb.foo, fb.bar)}
const f : Foo = {foo: 'foo'}
const b : Bar = {foo: 'foo', bar: 'bar'}
getFoo(f) // foo
getFoo(b) // foo
getBar(f) // Error Incompatible Type
getBar(b) // bar
getFooBar(f) // Error Incompatible Type
getFooBar(b) // foo bar
getFoo(b) fungerar eftersom Bar garanterat har åtminstone allt som Foo har. getBar(f) och getFooBar(f) misslyckas båda eftersom kompilatorn ser att typen Foo inte har nyckeln bar
Genom att omdefiniera Bar på detta sätt
export interface Bar extends Foo {bar? : string}
Kompilatorn vet nu att Foo matchar de minimala kvalifikationerna för Bar-typen, men du måste kontrollera för en implicit null. Så detta kommer att fungera
getBar(f)
Men kompilatorn kommer att skrika om implicita nullvärden, vilket är bra, eftersom f.bar är odefinierat. Så du måste omdefiniera din funktion så här
function getBar(b : Bar) {
let bar = b.bar
if(bar === undefined) bar = b.foo;
console.log(bar);
}
getBar(b) // bar
getBar(f) // foo
