Mas a verdade, a verdade desagradável, é que esses designs compactos costumam ser muito úteis. E são, ao mesmo tempo, bastante simples. Isso significa que qualquer pessoa interessada no código em que são usados pode dominá-los e entendê-los.
Nesta postagem, vou dar uma olhada em algumas construções compactas muito úteis (e às vezes crípticas) que você pode encontrar em JavaScript e TypeScript. Depois de estudá-los, você pode usá-los você mesmo, ou pelo menos você pode entender o código dos programadores que os usam.
1. Operador ??
O operador para verificar os valores de
nulle undefined(operador de coalescência nula) se parece com dois pontos de interrogação ( ??). É difícil acreditar que esta, com tal e tal nome, seja a operadora mais popular. Verdade?
O significado desse operador é que ele retorna o valor do operando direito se o valor do esquerdo for igual a
nullou undefined. Isso não está claramente refletido em seu nome, mas tudo bem, isso é - isso é. Veja como usá-lo:
function myFn(variable1, variable2) {
let var2 = variable2 ?? "default value"
return variable1 + var2
}
myFn("this has ", "no default value") // "this has no default value"
myFn("this has no ") // "this has no default value"
myFn("this has no ", 0) // "this has no 0"
Estão envolvidos aqui mecanismos muito semelhantes aos usados para organizar o trabalho do operador
||. Se o lado esquerdo da expressão for igual a nullou undefined, o lado direito da expressão será retornado. Caso contrário, o lado esquerdo será retornado. Como resultado, o operador é ??excelente para uso em situações em que qualquer coisa pode ser atribuída a uma variável, mas você precisa tomar algumas medidas caso isso nullou caia nessa variável undefined.
2. Operador ?? =
Um operador usado para atribuir um valor a uma variável apenas se ela tiver um valor
nullou undefined(operador de atribuição nula lógica) se parece com dois pontos de interrogação seguidos por um sinal de igual ( ??=). Pense nisso como uma extensão da operadora acima ??.
Vamos dar uma olhada no trecho de código anterior, reescrito usando
??=.
function myFn(variable1, variable2) {
variable2 ??= "default value"
return variable1 + variable2
}
myFn("this has ", "no default value") // "this has no default value"
myFn("this has no ") // "this has no default value"
myFn("this has no ", 0) // "this has no 0"
O operador
??=permite que você verifique o valor de um parâmetro de função variable2. Se for igual a nullou undefined, ele gravará um novo valor nele. Caso contrário, o valor do parâmetro não mudará.
Lembre-se de que o design
??=pode parecer incompreensível para quem não está familiarizado com ele. Portanto, se você o estiver usando, pode adicionar um breve comentário com explicações no local apropriado do código.
3. Declaração abreviada de construtores TypeScript
Este recurso é específico para TypeScript. Portanto, se você é um campeão da pureza do JavaScript, está perdendo muito. (Brincadeira, é claro, mas isso realmente não se aplica ao JS normal).
Como você sabe, ao declarar uma classe, eles geralmente listam todas as suas propriedades com modificadores de acesso e, em seguida, no construtor da classe, atribuem valores a essas propriedades. Mas nos casos em que o construtor é muito simples, e nele os valores dos parâmetros passados ao construtor são simplesmente escritos nas propriedades, você pode usar uma construção mais compacta do que a usual.
Isto é o que parece:
// ...
class Person {
private first_name: string;
private last_name: string;
private age: number;
private is_married: boolean;
constructor(fname:string, lname:string, age:number, married:boolean) {
this.first_name = fname;
this.last_name = lname;
this.age = age;
this.is_married = married;
}
}
// , ...
class Person {
constructor( private first_name: string,
private last_name: string,
private age: number,
private is_married: boolean){}
}
Usar a abordagem acima ao criar construtores definitivamente ajuda a economizar tempo. Especialmente quando se trata de uma classe que possui muitas propriedades.
O principal aqui é não se esquecer de adicionar
{}imediatamente após a descrição do construtor, pois se trata de uma representação do corpo da função. Depois que o compilador encontrar essa descrição, ele entenderá tudo e fará o resto sozinho. Na verdade, estamos falando sobre o fato de que tanto o primeiro quanto o segundo fragmentos do código TS serão eventualmente transformados no mesmo código JavaScript.
4. Operador ternário
O operador ternário é uma construção fácil de ler. Este operador é freqüentemente usado em vez de instruções curtas
if…else, pois permite que você se livre de caracteres extras e transforme uma construção de várias linhas em uma construção de uma linha.
// if…else
let isEven = ""
if(variable % 2 == 0) {
isEven = "yes"
} else {
isEven = "no"
}
//
let isEven = (variable % 2 == 0) ? "yes" : "no"
Na estrutura de um operador ternário, o primeiro é uma expressão lógica, o segundo é algo como um comando
returnque retorna um valor se a expressão lógica for verdadeira e o terceiro também é algo como um comando returnque retorna um valor se a expressão lógica for falsa. Embora o operador ternário seja melhor usado no lado direito das atribuições de valor (como no exemplo), ele também pode ser usado de forma autônoma, como um mecanismo para chamar funções quando qual função será chamada ou com quais argumentos uma e a mesma será chamada. a mesma função é determinada pelo valor da expressão lógica. Isto é o que parece:
let variable = true;
(variable) ? console.log("It's TRUE") : console.log("It's FALSE")
Observe que a estrutura da instrução parece a mesma do exemplo anterior. A desvantagem de usar o operador ternário é que se no futuro for necessário expandir uma de suas partes (seja aquela que se refere ao valor verdadeiro da expressão lógica ou aquela que se refere ao seu valor falso), isso significará a necessidade de ir para a instrução usual
if…else.
5. Usando um ciclo de cálculo curto usado pelo operador ||
Em JavaScript (e também em TypeScript), o operador lógico OR (
||) implementa um modelo de computação abreviada. Ou seja, ele retorna a primeira expressão avaliada como truee não verifica as expressões restantes.
Isso significa que se houver a seguinte afirmação
if, onde a expressão expression1contém um valor falso (redutível a false) e expression2- verdadeiro (redutível a true), então apenas expression1e será calculado expression2. Expressões espression3e expression4não serão avaliados.
if( expression1 || expression2 || expression3 || expression4)
Podemos aproveitar essa oportunidade fora da instrução
if, onde atribuímos valores às variáveis. Isso permitirá, em particular, escrever um valor padrão para uma variável no caso de algum valor, digamos, representado por um parâmetro de função, acabar sendo falso (por exemplo, igual undefined):
function myFn(variable1, variable2) {
let var2 = variable2 || "default value"
return variable1 + var2
}
myFn("this has ", " no default value") // "this has no default value"
myFn("this has no ") // "this has no default value"
Este exemplo demonstra como você pode usar um operador
||para gravar em uma variável o valor do segundo parâmetro de uma função ou um valor padrão. No entanto, se você olhar atentamente para este exemplo, verá um pequeno problema nele. O fato é que se houver variable2um valor em 0ou uma string vazia, o var2valor padrão será gravado, já que ambos 0e a string vazia são convertidos em false.
Portanto, se no seu caso você não precisa substituir todos os valores falsos pelo valor padrão, você pode recorrer a um operador menos conhecido
??.
6. Operador bit a bit duplo ~
Os desenvolvedores de JavaScript geralmente não gostam de usar operadores bit a bit. Quem se importa com as representações binárias dos números atualmente? Mas o fato é que, como esses operadores trabalham no nível de bits, eles executam as ações correspondentes muito mais rápido do que, por exemplo, alguns métodos.
Se falarmos sobre o operador bit a bit NOT (
~), ele pega um número, converte-o em um inteiro de 32 bits (descartando os bits "extras") e inverte os bits desse número. Isso faz com que o valor seja xconvertido em valor -(x+1). Por que estamos interessados em tal conversão de números? E o fato de que se você usá-lo duas vezes, ele nos dará o mesmo resultado que uma chamada de método Math.floor.
let x = 3.8
let y = ~x // x -(3 + 1), ,
let z = ~y // y ( -4) -(-4 + 1) - 3
// :
let flooredX = ~~x //
Observe os dois ícones
~na última linha do exemplo. Pode parecer estranho, mas se você tiver que converter muitos números de ponto flutuante em inteiros, esta técnica pode ser muito útil.
7. Atribuição de valores às propriedades do objeto
Os recursos do padrão ES6 simplificam o processo de criação de objetos e, em particular, o processo de atribuição de valores às suas propriedades. Se os valores das propriedades forem atribuídos com base em variáveis que têm os mesmos nomes dessas propriedades, não há necessidade de repetir esses nomes. Anteriormente, era necessário.
Aqui está um exemplo escrito em TypeScript.
let name:string = "Fernando";
let age:number = 36;
let id:number = 1;
type User = {
name: string,
age: number,
id: number
}
//
let myUser: User = {
name: name,
age: age,
id: id
}
//
let myNewUser: User = {
name,
age,
id
}
Como você pode ver, a nova abordagem para atribuir valores às propriedades do objeto permite que você escreva um código mais compacto e simples. E esse código não é mais difícil de ler do que o código escrito de acordo com as regras antigas (o que não pode ser dito sobre o código escrito usando outras construções compactas descritas neste artigo).
8. Retorno implícito de valores das funções de seta
Você sabia que as funções de seta de linha única retornam os resultados dos cálculos realizados em sua linha única?
Usar esse mecanismo permite que você se livre de uma expressão desnecessária
return. Essa técnica é freqüentemente usada em funções de seta passadas para métodos de array, como filterou map. Aqui está um exemplo do TypeScript:
let myArr:number[] = [1,2,3,4,5,6,7,8,9,10]
// :
let oddNumbers:number[] = myArr.filter( (n:number) => {
return n % 2 == 0
})
let double:number[] = myArr.map( (n:number) => {
return n * 2;
})
// :
let oddNumbers2:number[] = myArr.filter( (n:number) => n % 2 == 0 )
let double2:number[] = myArr.map( (n:number) => n * 2 )
Aplicar essa técnica não significa necessariamente tornar o código mais complexo. Construções como essas são uma boa maneira de limpar um pouco seu código e se livrar de espaços desnecessários e linhas extras. Obviamente, a desvantagem dessa abordagem é que, se os corpos de tais funções curtas precisarem ser estendidos, você terá que voltar a usar chaves novamente.
A única peculiaridade que terá que ser levada em consideração aqui é que o que está contido na única linha das funções de seta curta consideradas aqui deve ser uma expressão (ou seja, deve produzir algum resultado que possa ser retornado pela função). Caso contrário, esse design ficará inoperante. Por exemplo, as funções de uma linha acima não podem ser escritas assim:
const m = _ => if(2) console.log("true") else console.log("false")
Na próxima seção, continuaremos falando sobre funções de seta de linha única, mas agora falaremos sobre funções que não podem ser criadas sem chaves.
9. Parâmetros de função, que podem ter valores padrão
ES6 introduziu a capacidade de especificar valores que são atribuídos aos parâmetros de função padrão. Anteriormente, o JavaScript não tinha esses recursos. Portanto, nas situações em que era necessário atribuir valores semelhantes aos parâmetros, era necessário recorrer a algo como um modelo de cálculos reduzidos do operador
||.
Mas agora o mesmo problema pode ser resolvido de forma muito simples:
// 2
// ,
function myFunc(a, b, c = 2, d = "") {
// ...
}
Mecanismo simples, certo? Mas, na verdade, tudo é ainda mais interessante do que parece à primeira vista. O ponto é que o valor padrão pode ser qualquer coisa - incluindo uma chamada de função. Esta função será chamada se o parâmetro correspondente não for passado a ela quando a função for chamada. Isso torna mais fácil implementar o padrão de parâmetro de função necessário:
const mandatory = _ => {
throw new Error("This parameter is mandatory, don't ignore it!")
}
function myFunc(a, b, c = 2, d = mandatory()) {
// ...
}
// !
myFunc(1,2,3,4)
//
myFunc(1,2,3)
Aqui, de fato, está a mesma função de seta de uma linha, ao criar, o que você não pode fazer sem chaves. A questão é que a função
mandatoryusa uma instrução throw. Preste atenção - "instrução", não "expressão". Mas, suponho, este não é o preço mais alto pela capacidade de equipar funções com os parâmetros necessários.
10. Convertendo quaisquer valores em um tipo booleano usando !!
Este mecanismo funciona com o mesmo princípio da construção acima
~~. Ou seja, estamos falando sobre o fato de que, para converter qualquer valor em um tipo lógico, você pode usar dois operadores lógicos NOT ( !!):
!!23 // TRUE
!!"" // FALSE
!!0 // FALSE
!!{} // TRUE
Um operador
!já resolve a maior parte dessa tarefa, ou seja, converte o valor para um tipo booleano e retorna o valor oposto. E o segundo operador !pega o que aconteceu e apenas retorna o oposto disso. Como resultado, obtemos o valor original convertido em um tipo booleano.
Esta construção curta pode ser útil em várias situações. Primeiro, quando você precisa garantir que uma variável seja atribuída a um valor booleano real (por exemplo, se estivermos falando sobre uma variável do tipo TypeScript
boolean). Segundo, quando você precisa realizar uma comparação estrita (usando ===) de algo com trueou false.
11. Destruição e sintaxe de propagação
Os mecanismos mencionados no título desta seção podem ser discutidos e falados. A questão é que, se usados corretamente, podem levar a resultados muito interessantes. Mas aqui não vou muito fundo. Vou te dizer como usá-los para simplificar a solução de alguns problemas.
▍ Objetos de estruturação
Você já enfrentou a tarefa de escrever valores múltiplos de propriedades de objetos em variáveis comuns? Essa tarefa é bastante comum. Por exemplo, quando é necessário trabalhar com esses valores (modificando-os, por exemplo) e ao mesmo tempo não afetar o que está armazenado no objeto original.
O uso da desestruturação de objetos permite que você resolva problemas semelhantes usando a quantidade mínima de código:
const myObj = {
name: "Fernando",
age: 37,
country: "Spain"
}
// :
const name = myObj.name;
const age = myObj.age;
const country = myObj.country;
//
const {name, age, country} = myObj;
Qualquer pessoa que tenha usado o TypeScript viu essa sintaxe nas instruções
import. Ele permite que você importe métodos de biblioteca individuais sem contaminar o namespace do projeto com muitas funções desnecessárias:
import { get } from 'lodash'
Por exemplo, esta instrução permite que você importe
lodashapenas um método da biblioteca get. Ao mesmo tempo, outros métodos desta biblioteca não se enquadram no namespace do projeto. E há muitos deles nele.
▍ Espalhe a sintaxe e crie novos objetos e matrizes com base nos existentes
Usar a sintaxe spread (
…) simplifica a tarefa de criar novos arrays e objetos baseados nos existentes. Agora, essa tarefa pode ser resolvida escrevendo literalmente uma linha de código e sem recorrer a nenhum método especial. Aqui está um exemplo:
const arr1 = [1,2,3,4]
const arr2 = [5,6,7]
const finalArr = [...arr1, ...arr2] // [1,2,3,4,5,6,7]
const partialObj1 = {
name: "fernando"
}
const partialObj2 = {
age:37
}
const fullObj = { ...partialObj1, ...partialObj2 } // {name: "fernando", age: 37}
Observe que usar essa abordagem para combinar objetos irá sobrescrever suas propriedades com o mesmo nome. Isso não se aplica a matrizes. Em particular, se os arrays a serem mesclados tiverem os mesmos valores, todos acabarão no array resultante. Se você precisa se livrar das repetições, pode recorrer ao uso de uma estrutura de dados
Set.
▍ Combinando desestruturação e sintaxe de propagação
A desestruturação pode ser usada em conjunto com a sintaxe de propagação. Isso permite que você obtenha um efeito interessante. Por exemplo, remova o primeiro elemento da matriz e deixe o resto intocado (como no exemplo comum com o primeiro e o último elemento da lista, cuja implementação pode ser encontrada em Python e outras linguagens). E também, por exemplo, você pode até extrair algumas propriedades de um objeto e deixar o resto intacto. Vamos considerar um exemplo:
const myList = [1,2,3,4,5,6,7]
const myObj = {
name: "Fernando",
age: 37,
country: "Spain",
gender: "M"
}
const [head, ...tail] = myList
const {name, age, ...others} = myObj
console.log(head) //1
console.log(tail) //[2,3,4,5,6,7]
console.log(name) //Fernando
console.log(age) //37
console.log(others) //{country: "Spain", gender: "M"}
Observe que os três pontos usados no lado esquerdo da declaração de atribuição devem se aplicar ao último item. Você não pode usar a sintaxe de propagação primeiro e, em seguida, descrever variáveis individuais:
const [...values, lastItem] = [1,2,3,4]
Este código não funcionará.
Resultado
Existem muitos outros designs semelhantes aos de que falamos hoje. Mas, ao usá-los, vale lembrar que quanto mais compacto o código, mais difícil é de lê-lo para quem não está acostumado com as construções abreviadas nele utilizadas. E o propósito de usar tais construções não é reduzir o código ou acelerá-lo. Esse objetivo é apenas remover estruturas desnecessárias do código e tornar a vida mais fácil para quem vai ler o código.
Portanto, para manter todos felizes, é recomendável que você mantenha um equilíbrio saudável entre construções compactas e código legível regular. É sempre bom lembrar que você não é a única pessoa lendo seu código.
Quais construções compactas você usa no código JavaScript e TypeScript?
