首页 > 解决方案 > 如何在 typescript 中为 css 样式创建递归对象类型?

问题描述

React.CSSProperties用于创建 css 变量,所以我可以创建像

let css: React.CSSProperties = {
  position: "relative",
  display: "inline-block",
  background: "red"
}

我正在尝试创建一个主题,该主题将在根级别具有css变量或嵌套在修饰符中,例如 size(sm,md,lg) 或 variant(light,dark) 或其他如下

let theme: MyTheme = {
  background: "red",       // css
  color: "white",          // css  

  button: {                // custom: component name Level1
    background: "red",     // css
    color: "white",        // css

    light: {               // custom: modifier variant Level2
      background: "red",   // css
      color: "white",      // css

      hollow: {            // custom: modifier Level3
        borderColor: "red",
        color: "red",
        background: "transparent",
      }
    }
    dark: {
      background: "red",
      color: "white",

      hollow: {
        borderColor: "black",
        color: "black",
        background: "transparent",

        modfierLevel4: {
          some: "css"

          modfierLevel5: {
            some: "css"

            modfierLevel6: {
              some: "css"
            }
          }
        }
      }
    }
  }
}

基本上我正在寻找如下打字稿中的递归对象类型,但最终以循环引用结束?

type Modifiers = 'button' | 'dark' | 'light' | 'otherModifiers'

interface Theme extends React.CSSProperties {
 [k in Modifiers]?: Theme;
}

我能够找到接近它的答案(如下)。

type Theme = React.CSSProperties & { [keys in Modifiers]?: Theme }

但面临一些问题。如果给定的修饰符名称无效,例如“btn”而不是“button”

我应该如何为上述创建类型?

标签: cssreactjstypescriptrecursion

解决方案


假设您使用的是 TS3.4 或以下版本:

这是一个已知问题的结果,其中未对嵌套的交叉点类型(Theme. 从技术上讲,TypeScript 中的类型通常是“开放的”或“可扩展的”,这意味着类型的值可以具有比其类型定义中提到的更多的属性。interface A { a: string }您可以使用and之类的东西来依赖它interface B extends A { b: string },其中 typeB的值也是 type 的值A

但是通常当人们像您一样使用对象文字时,添加额外属性是错误的......尤其是在您的情况下,“额外”属性只是可选属性的印刷错误。所以编译器决定当你将一个新的对象字面量分配给一个类型时,额外的属性是一个错误。也就是说,新鲜对象文字的类型是“封闭的”或“精确的”......

...好吧,或者他们应该是,除了这个带有嵌套交叉点的错误。在 TypeScript 3.4 或更低版本中,行为如下:

type Foo = { baseProp: string, recursiveProp?: Foo };
const foo: Foo = {
    baseProp: "a",
    recursiveProp: {
        baseProp: "b",
        extraProp: "error as expected" // error 
    }
}

type Bar = { baseProp: string } & { recursiveProp?: Bar };
const bar: Bar = {
    baseProp: "a",
    recursiveProp: {
        baseProp: "b",
        extraProp: "no error?!" // no error in TS3.4 
    }
}

游乐场链接

幸运的是,TypeScript 3.5 已修复此错误,该版本应2019 年 5 月 30 日发布,或者您可以通过以下方式试用 TypeScript 的夜间版本typescript@next

如果您不能等到那个时候或者不能很快升级,您可以通过使用映射类型将交集替换为看起来更正常的对象来解决此问题:

type Id<T> = { [K in keyof T]: T[K] };
type Bar = Id<{ baseProp: string } & { recursiveProp?: Bar }>;
const bar: Bar = {
    baseProp: "a",
    recursiveProp: {
        baseProp: "b",
        recursiveProp: {
            baseProp: "c",
            extraProp: "error yay" 
        }
    }
}

游乐场链接

出现了预期的错误,世界一切正常。希望有帮助;祝你好运!


推荐阅读