TypeScript
Você pode adicionar tipagem estática para o JavaScript para melhorar a produtividade do desenvolvimento e a qualidade do código graças ao TypeScript.
Material-UI requer como versão mínima o TypeScript 3.2.
Dê uma olhada no exemplo Create React App com TypeScript.
Para que os tipos funcionem, você tem que pelo menos ter as seguintes opções habilitadas no seu tsconfig.json
:
{
"compilerOptions": {
"lib": ["es6", "dom"],
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true
}
}
As opções do modo strict são as mesmas que são necessárias para todos os tipos de pacote publicados no namespace @types/
. Usando uma tsconfig.json
menos rigorosa ou omitindo algumas das bibliotecas podem causar erros. Para obter a melhor experiência com os tipos, recomendamos configurar "strict": true
.
Uso de withStyles
Utilizando withStyles
no TypeScript pode ser um pouco complicado, mas há alguns utilitários que tornam a experiência menos dolorosa.
Utilizando createStyles
para evitar a ampliação de tipo (type widening)
Uma fonte frequente de confusão é a ampliação de tipos (type widening) do TypeScript, que faz com que este exemplo não funcione como o esperado:
const styles = {
root: {
display: 'flex',
flexDirection: 'column',
}
};
withStyles(styles);
// ^^^^^^
// Os tipos da propriedade 'flexDirection' são incompatíveis.
// Tipo 'string' não pode ser atribuído para o tipo '"-moz-initial" | "inherit" | "initial" | "revert" | "unset" | "column" | "column-reverse" | "row"...'.
O problema é que o tipo da propriedade flexDirection
é convertido como string
, no qual é o tipo mais conveniente. Para corrigir isto, você pode passar o objeto de estilos diretamente para withStyles
:
withStyles({
root: {
display: 'flex',
flexDirection: 'column',
},
});
No entanto, a ampliação de tipos continuará a causar dores de cabeça se você tentar fazer com que os estilos dependam do tema:
withStyles(({ palette, spacing }) => ({
root: {
display: 'flex',
flexDirection: 'column',
padding: spacing.unit,
backgroundColor: palette.background.default,
color: palette.primary.main,
},
}));
Isso ocorre pois o TypeScript amplia o retorno de tipos de expressões de função.
Por causa disso, é recomendado usar a função utilitária createStyles
para construir seu objeto de regras de estilo:
// Estilos sem dependência
const styles = createStyles({
root: {
display: 'flex',
flexDirection: 'column',
},
});
// Estilos com dependência do tema
const styles = ({ palette, spacing }: Theme) => createStyles({
root: {
display: 'flex',
flexDirection: 'column',
padding: spacing.unit,
backgroundColor: palette.background.default,
color: palette.primary.main,
},
});
createStyles
é apenas a identidade da função; ela não "faz nada" em tempo de execução, apenas auxilia a inferência de tipos em tempo de compilação.
Consultas de mídia
withStyles
permite utilizar um objeto de estilos de nível superior com consultas de mídia assim:
const styles = createStyles({
root: {
minHeight: '100vh',
},
'@media (min-width: 960px)': {
root: {
display: 'flex',
},
},
});
No entanto, para permitir que estes estilos passem pelo TypeScript, as definições devem ser ambíguas em relação aos nomes de classes CSS e nomes de propriedades CSS. Devido a isso, evite utilizar nomes de classes iguais a propriedades do CSS.
// erro porque TypeScript acha que `@media (min-width: 960px)` é o nome da classe
// e `content` é a propriedade css
const ambiguousStyles = createStyles({
content: {
minHeight: '100vh',
},
'@media (min-width: 960px)': {
content: {
display: 'flex',
},
},
});
// funciona corretamente
const ambiguousStyles = createStyles({
contentClass: {
minHeight: '100vh',
},
'@media (min-width: 960px)': {
contentClass: {
display: 'flex',
},
},
});
Aumentando suas propriedades utilizando WithStyles
Desde que um componente seja decorado com withStyles(styles)
, ele recebe uma propriedade injetada classes
, você pode querer definir estas propriedades de acordo com:
const styles = (theme: Theme) => createStyles({
root: { /* ... */ },
paper: { /* ... */ },
button: { /* ... */ },
});
interface Props {
// propriedades comuns
foo: number;
bar: boolean;
// propriedades de estilo injetadas
classes: {
root: string;
paper: string;
button: string;
};
}
No entanto isto não é muito elegante de acordo com o princípio de software DRY, porque requer que você mantenha os nomes das classes ('root'
, 'paper'
, 'button'
, ...) em dois locais diferentes. Nós fornecemos um operador de tipo WithStyles
para ajudar com isso, assim você pode apenas escrever:
import { WithStyles, createStyles } from '@material-ui/core';
const styles = (theme: Theme) => createStyles({
root: { /* ... */ },
paper: { /* ... */ },
button: { /* ... */ },
});
interface Props extends WithStyles<typeof styles> {
foo: number;
bar: boolean;
}
Decorando componentes
Aplicando withStyles(styles)
como uma função, nos dá o resultado como o esperado:
const DecoratedSFC = withStyles(styles)(({ text, type, color, classes }: Props) => (
<Typography variant={type} color={color} classes={classes}>
{text}
</Typography>
));
const DecoratedClass = withStyles(styles)(
class extends React.Component<Props> {
render() {
const { text, type, color, classes } = this.props
return (
<Typography variant={type} color={color} classes={classes}>
{text}
</Typography>
);
}
}
);
Infelizmente devido a uma limitação atual dos decoradores do TypeScript, withStyles(styles)
não pode ser usado como decorador no TypeScript.
Customização de tema
Ao adicionar propriedades customizadas ao Theme
, você pode continuar a utilizá-lo de uma maneira fortemente tipada, explorando o conceito de extensão de módulos do TypeScript (TypeScript's module augmentation).
O exemplo a seguir adiciona uma propriedade appDrawer
que é mesclada na que foi exportada pelo material-ui
:
import { Theme } from '@material-ui/core/styles/createTheme';
import { Breakpoint } from '@material-ui/core/styles/createBreakpoints';
declare module '@material-ui/core/styles/createTheme' {
interface Theme {
appDrawer: {
width: React.CSSProperties['width']
breakpoint: Breakpoint
}
}
// permitir configuração usando `createTheme`
interface ThemeOptions {
appDrawer?: {
width?: React.CSSProperties['width']
breakpoint?: Breakpoint
}
}
}
E uma fábrica customizada de temas com opções padrão adicionais:
./styles/createMyTheme:
import { createTheme, ThemeOptions } from '@material-ui/core/styles';
export default function createMyTheme(options: ThemeOptions) {
return createTheme({
appDrawer: {
width: 225,
breakpoint: 'lg',
},
...options,
})
}
Isso poderia ser usado da seguinte maneira:
import createMyTheme from './styles/createMyTheme';
const theme = createMyTheme({ appDrawer: { breakpoint: 'md' }});
Uso da propriedade component
Muitos componentes do Material-UI permitem que você substitua seu nó raiz através de uma propriedade component
, isto será detalhado na documentação da API do componente. Por exemplo, o nó raiz de um Button pode ser substituído por um Link do React Router, e quaisquer propriedades adicionais que são passados para o Button, como to
, serão propagadas para o componente Link. Para um exemplo de código relativo ao Button e o react-router-dom veja estas demonstrações.
Para poder usar propriedades de determinado componente Material-UI no seu componente próprio, as propriedades devem ser usadas com argumentos de tipo. Caso contrário, a propriedade component
não estará presente nas propriedades do componente Material-UI.
Os exemplos abaixo usam TypographyProps
mas o mesmo funcionará para qualquer componente que tenha propriedades definidas com OverrideProps
.
O componente CustomComponent
a seguir tem as mesmas propriedades que o componente Typography
.
function CustomComponent(props: TypographyProps<'a', { component: 'a' }>) {
/* ... */
}
Agora o CustomComponent
pode ser usado com uma propriedade component
que deve ser definida para 'a'
. Além disso, o CustomComponent
terá todas as propriedades de um elemento HTML <a>
. As outras propriedades do componente Typography
também estarão presentes nas propriedades do CustomComponent
.
É possível ter um componente genérico CustomComponent
que aceitará qualquer componente React, customizado e elementos HTML.
function GenericCustomComponent<C extends React.ElementType>(
props: TypographyProps<C, { component?: C }>,
) {
/* ... */
}
Agora se o GenericCustomComponent
ser usado com uma propriedade component
, ele também deve ter todas as propriedades exigidas pelo componente fornecido.
function ThirdPartyComponent({ prop1 } : { prop1: string }) {
return <div />
}
// ...
<GenericCustomComponent component={ThirdPartyComponent} prop1="algum valor" />;
A prop1
tornou-se necessária para o GenericCustomComponent
como o ThirdPartyComponent
tem ela como um requisito.
Nem todos os componentes suportam totalmente qualquer tipo de componente que você passe. Se você encontrar um componente que rejeita sua propriedade component
no TypeScript por favor abra uma issue. Há um esforço contínuo para corrigir isso fazendo com que a propriedade component seja genérica.
Manipulando value
e manipuladores de eventos
Muitos componentes preocupados com a entrada do usuário oferecem uma propriedade value
ou manipuladores de eventos que incluem o valor atual em value
. Na maioria das situações, value
só é manipulado dentro do React, o que permite que seja de qualquer tipo, como objetos ou matrizes.
No entanto, esse tipo não pode ser verificado em tempo de compilação em situações em que depende de nós filhos do componente, por exemplo, para Select
ou RadioGroup
. Isso significa que a opção mais segura é tipando como unknown
e deixar que o desenvolvedor decida como deseja restringir esse tipo. Não oferecemos a possibilidade de usar um tipo genérico nesses casos, devido as mesmas razões que event.target
não é genérico no React.
As demonstrações incluem variantes tipadas que usam conversão de tipo. É uma troca aceitável porque os tipos estão todos localizados em um único arquivo e são muito básicos. Você tem que decidir por si mesmo se a mesma troca é aceitável para você. A biblioteca de tipos são strict por padrão e loose por meio de opt-in.