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

Relaterade artiklar