Pull to refresh

Comments 13

Очень ценная статья, спасибо что поделились! Но у меня остались кое какие вопросы. Для чего в компоненте Figure создавать 3 новых интерфейса для фигур когда можно было бы props под названием type прописать в интерфейсах самих фигур? Таким образом код работал бы точно так же и был бы короче.

Спасибо за комментарий. Параметр type в данном случае является параметром нашего компонента-фабрики и к подкомпонентам не имеет отношения.

Спасибо за ответ! Понятно. Я со временем только понял что это для того что бы продукт не зависил от фабрики и его можно было переиспользовать иначе. А вы случайно не пробовали использовать Emotion?

Спасибо за интересную статью.

Сам я пишу на JavaScript + Vue и никогда не писал ни на TS ни на React. Вы заставили меня с ними поковыряться, потому что привели реальный интересный пример.

Честно говоря я бы предпочел сделать через merge типов (&), для меня это выглядит более лаконичным. Уверен что все подсказки и бенефиты из ваших примеров работают и здесь.

type CircleProps = { radius: number }
const Circle = ({ radius }: CircleProps) => ...

type RectProps = { width: number; height: number }
const Rect = ({ width, height }: RectProps) => ...

type FigureCircle = { type: 'circle' } & CircleProps
type FigureRect = { type: 'rect' } & RectProps

const Figure = (props: FigureRect | FigureCircle) => {
  switch (props.type) {
    case 'circle':
      return <Circle {...props} />

    case 'rect':
      return <Rect {...props} />

    default:
      return null
  }
}

Но вообще мне кажется вы недооцениваете JavaScript. Все тоже самое, со всеми подсказки и плюшками возможно сделать и на JS.

function getFigure() {
  return {
    circle({ radius = 0 }) {
      return <div>I'm a circle {radius}</div>
    },

    rect({ width = 0, height = 0 }) {
      return <div>I'm a rect {width}, {height}</div>
    }
  }
}

export default function App() {
  const type = 'rect'

  return (
    <>
      {getFigure()[type]({ width: 123, height: 321 })}
      {getFigure().circle({ radius: 42 })}
    </>
  )
}

И сюрприз-сюрприз, получать хинты можно прямо в IDE, включая тип number для аргументов. Ниже результат.

А при правильно настроенном ESLint и юнит тестах, разницы с TS вы не заметите, кроме более простого и понятного кода.

Это очень хорошо, что вы решили попробовать это на практике!

Насчет использования &, то здесь скорее нет технической разницы и каждый может выбрать то, что ему по душе. Я, например, предпочитаю использовать type для сущностей (User, Card, Address), функций ((name: string)=>void) или для объединения нескольких interface. А interface использую для всего остального.

Что касается реализации на js, то в статье описаны проблемы, которые возникают на нем. В вашем примере вам даже пришлось отказаться от jsx.

Я лишь хотел рассказать, как можно использовать typescript правильно, если он есть в проекте. Чтобы от него была действительная польза, а не только упоминание в стэке проекта.

Согласен, тут кому что нравится!

Кстати, сначала я не понял зачем вы используете литеральный тип "type", но проигравшись понял что иначе typescript не понимает что речь идёт про окружность например. Хотя казалось бы если указан radius, то ни width ни height уже быть не может.

Простейший пример:

interface Circle { radius: number}
interface Rect { width: number, height: number}

function test (fig: Circle | Rect) {
  if ("radius" in fig) {
    console.log(fig.radius) // code completion gives only radius
  } else {
    console.log(fig.width) // gives only width and height, nice!
    console.log(fig.radius) // compilation error, super!
  }
}

test({radius: 1, width: 2}) // no errors, wtf?

Это странно!

Хотя казалось бы если указан radius, то ни width ни height уже быть не может.

Это особенность структурной типизации в TS. Лишние поля не считаются ошибкой, за исключением ситуации когда объект передаётся как литерал ({ ... }). В этом случае TS предполагает что "явно что-то идёт не так", и пытается бить по рукам за неправомерные поля. Но видимо не сильно вникает в то какие же поля правомерны:


test({radius: 1, width: 2}) // no error
test({radius: 1, wrong: 3, width: 2}) // "wrong" - error
const a = {radius: 1, wrong: 3, width: 2}
test(a) // no error

Понятно, не знал.

Кстати, я поигрался с flow, там есть синтаксис для exact object types {| ... |}, поэтому можно заставить компилятор ругаться на лишние поля.

Exact object types benefits in Flow
Exact object types benefits in Flow

Правда, мое мнение это балоство. В реальности ниразу я не сталкивался с ошибками вида "ой лишние поля".

Использование generic для избавления от boilerplate
Использование generic для избавления от boilerplate

На ночь пришла идея как избавиться от импорта интерфейсов подкомпонентов и обьявления новых интерфейсов(типа IFigureRectangle). При таком виде, если мы хотим добавить новый подкомпонент, то просто добавляем еще одну строчку в IFigure.

Типы ориентированы под функциональный компонент, но можно подправить, если пригодится для классовых.

Круто! Не могли бы дать ссылку codesandbox с обновленной версией?

На этом скрине все изменения, что есть. Codesandbox и статью не стал обновлять, т.к. на интерфейсах легче понять читателю.

Если я правильно понял идею, то посмотрите в сторону React.ComponentProps<typeof Comp>, вместо Parameters<C>[0].

Sign up to leave a comment.

Articles