Deje que los componentes con estilo también tengan una jerarquía

prefacio

Recientemente, styled-componentscuando , me encontré con algo que me hizo sentir muy incómodo.

Efecto actual

Aunque varios nodos tienen una relación jerárquica en uso, en styled-componentsrealidad no hay soporte relevante y solo se puede declarar un nodo por separado.

import styled from "styled-components";

const Layout = styled.div``;
const LayoutHeader = styled.div``;
const LayoutContent = styled.div``;
const LayoutContentLeft = styled.div``;
const LayoutContentRight = styled.div``;
const LayoutFooter = styled.div``;

function App() {
  return (
    <Layout>
      <LayoutHeader>头部</LayoutHeader>
      <LayoutContent>
        主体
        <LayoutContentLeft>主体的左边</LayoutContentLeft>
        <LayoutContentRight>主体的右边</LayoutContentRight>
      </LayoutContent>
      <LayoutFooter>尾部</LayoutFooter>
    </Layout>
  );
}
复制代码

Esperaba

Espero que los componentes sean jerárquicos, de la siguiente manera:

function App() {
  return (
    <Layout>
      <Layout.Header>头部</Layout.Header>
      <Layout.Content>
        主体
        <Layout.Content.Left>主体的左边</Layout.Content.Left>
        <Layout.Content.Right>主体的右边</Layout.Content.Right>
      </Layout.Content>
      <Layout.Footer>尾部</Layout.Footer>
    </Layout>
  );
}
复制代码

Hay una estructura jerárquica:

  • Disposición
    • Diseño.Encabezado
    • Diseño.Contenido
      • Diseño.Contenido.Izquierda
      • Diseño.Contenido.Derecha
    • Diseño.Pie de página

Análisis de dificultad

No es difícil cambiar una estructura en mosaico a una estructura de árbol, simplemente escriba una función para manejarla.

La dificultad es cómo hacer que el resultado después de la conversión se dé cuenta de la deducción de tipo ts, es decir, de acuerdo con lo que ingresa, indicando qué subniveles tiene el nivel actual

Sí, este artículo está aquí para enseñarte cómo escribir mecanografiado.

提示.png

código completo

Vamos a implementar un styledTiermétodo para hacer esto.

Echemos un vistazo al código completo.

Aplicación.tsx

import { styledTier } from "./utils.ts";

/**
 * 每一级可以单传一个组件也可以传一个对象
 * 如果存在下一层则只能传对象
 **/
const Layout = styledTier({
  _self: styled.div``,
  Header: styled.div``, // 单传一个组件
  Content: {
    // 传一个对象
    _self: styled.div``, // "_self" 属性表示本层组件
    Left: styled.div``,
    Right: styled.div``,
  },
  Footer: styled.div``,
});

export default function App() {
  return (
    <Layout>
      <Layout.Header>头部</Layout.Header>
      <Layout.Content>
        主体
        <Layout.Content.Left>主体的左边</Layout.Content.Left>
        <Layout.Content.Right>主体的右边</Layout.Content.Right>
      </Layout.Content>
      <Layout.Footer>尾部</Layout.Footer>
    </Layout>
  );
}
复制代码

utils.ts

import { StyledComponent } from "styled-components";

interface IHierarchyInput {
  [T: string]: StyledComponent<any, any> | IHierarchyInput;
  _self: StyledComponent<any, any>;
}
type IHierarchyOutput<Input extends IHierarchyInput = any> = Input["_self"] & {
  [Prop in keyof Omit<Input, "_self">]: Input[Prop] extends IHierarchyInput
    ? IHierarchyOutput<Input[Prop]>
    : Input[Prop];
};
export const styledTier = function <O extends IHierarchyInput>(
  input: O
): IHierarchyOutput<O> {
  const { _self: component, ...children } = input;
  const current: any = component;

  for (const [pKey, pItem] of Object.entries(children)) {
    if (pItem._self) {
      current[pKey] = styledTier(pItem);
    } else {
      current[pKey] = pItem;
    }
  }

  return current;
};
复制代码

Explicar con detalle

aporte

IHierarchyInputse utiliza para restringir la entrada

import { StyledComponent } from "styled-components";
interface IHierarchyInput {
  [T: string]: StyledComponent<any, any> | IHierarchyInput;
  _self: StyledComponent<any, any>;
}
复制代码

Hay tres puntos clave:

  • Definición de propiedades ( _selflas propiedades se definen con tipos especiales)
  • Tipo de unión (otras propiedades se pueden pasar directamente al componente o a un objeto)
  • Tipo de anidamiento de recursión

Definición de propiedades

Construye un objeto simple:

// 属性名为 string,属性值为 number
interface MyObj {
  [T: string]: number;
}

// 属性名为 number,属性值为 number(其实这样没有意义,因为对象在 js 中属性名都会转成 string)
interface MyObj {
  [T: number]: number;
}

// 或者还可以这样(这样只在 ts 中有意义,而在 js 中是没有意义的)
interface MyObj {
  [T: string]: number;
  [T: number]: number;
}
复制代码

Pero, ¿qué pasa si quiero hacer una restricción específica en una de las propiedades, como las siguientes propiedades beforey after:

interface MyTest {
  before: string;
  [T: string]: number | string | Array<any>;
  after: Array<any>;
}
复制代码

Hay dos cosas a tener en cuenta:

  • No hay una secuencia de posiciones, por lo que lo anterior beforey afterefecto de implementación son consistentes
  • [T: string]El tipo correspondiente debe beforecontener afterlos tipos de y

tipo de union

`type StrOrNum = string | number`;
const str: StrOrNum = "abc";
const num: StrOrNum = 123;
复制代码

Los conceptos básicos de mecanografiado, esto no es mucho de qué hablar.

Tipo de anidamiento de recursión

Las definiciones de tipo también pueden usar anidadas recursivamente

type MyArr = Array<number | MyArr>;
const arr: MyArr = [1, 2, 3, [4, 5, 6, [7, 8, 9]]];
复制代码

paradigma funcional

Cómo hacer que la función determine otros parámetros o tipos de valores devueltos según el tipo de parámetros pasados ​​al llamar

这就要用到范型了,基本用法长这样:

function fn<T>(){}

举个简单的例子:

// 定义范型 T,分别用于两个参数的不同位置,用以确定这两个位置最终类型会是一致
function definition<T>(input: T, callback: (result: T) => void) {
  callback(input);
}

definition(2, function (result) {
  console.log(result); // 提示为 number
});

definition("i'm sb", function (result) {
  console.log(result); // 提示为 string
});
复制代码

返回值

IHierarchyOutput 是用来约束返回值的

type IHierarchyOutput<Input extends IHierarchyInput = any> = Input["_self"] & {
  [Prop in keyof Omit<Input, "_self">]: Input[Prop] extends IHierarchyInput
    ? IHierarchyOutput<Input[Prop]>
    : Input[Prop];
};
复制代码

首先,有以下几个知识点:

  • 范型
  • 约束范型的类型(通过 extends 来约束范型只能是某些类型)
  • 属性的定义(通过 inkeyof 遍历别的类型来构造一个对象)
  • 类型判断(通过 extends 还有三目运算符来实现类型的判断和转化)
  • 递归嵌套结构

范型

这里不过多陈述了,都是基础,举个简单的例子吧:

// 定义一个有范型的类型
type MyTuple<A, B> = [A, B];
const t1: MyTuple<string, number> = ["abc", 123];
const t2: MyTuple<number, number> = [456, 123];
复制代码

约束范型的类型

extends 关键字字面意思是“继承”,同时他也可以用来约束范型必须是什么类型,譬如

// A 必须是 string 或者是 number,而 B 必须是 number
type MyTuple<A extends string | number, B extends number> = [A, B];
const t1: MyTuple<number, number> = [456, 123];
const t2: MyTuple<boolean, number> = [false, 123]; // 报错
const t2: MyTuple<number, string> = [456, "abc"]; // 报错
复制代码

属性的定义

关于 inkeyof 的详细介绍可以看我这篇文章,Typescript 关键字

类型判断

关于 extends 的详细介绍可以看我这篇文章,Typescript 关键字

递归嵌套结构

type IHierarchyOutput<Input extends IHierarchyInput = any> = Input["_self"] & {
  // 排除 _self
  [Prop in keyof Omit<Input, "_self">]: Input[Prop] extends IHierarchyInput // 判断当前属性值是否还是对象结构
    ? IHierarchyOutput<Input[Prop]> // 如果是,则把当前属性值作为范型重新传入 IHierarchyOutput 中,开启下一轮循环
    : Input[Prop];
};
复制代码

Supongo que te gusta

Origin juejin.im/post/7078228495255470117
Recomendado
Clasificación