Minha evolução em 3 semestres

Nessa semana finalizei o curso técnico de 3 semestres no IFSP, em cada um desses semestres acabei focando em fazer uma função pra me testar e ver o que conseguia. Agora que finalizei o curso queria compartilhar minha evolução atráves de 3 funções/classes para vocês.

1° semestre - caixa para respostas do console

Na matéria de lógica da programação o professor acabou passando os exercicios do pythonBrasil para fazer no Replit (exercicos que fiz na época)

Quando aprendi sobre funções acabei inventando demais e gastei dezenas de horas pra fazer uma função que coloca a resposta da questão em uma caixa de linha. Na época foi um desafio e tanto e ela acabou ficando meio ruinzinha.

exemplo de uso

//para formatar saida em caixas
const  saida = require('../funções').saida;
let bloco = []  
  //o que vai ter em cada linha
    let paia = [
      "[0-25] tem: ",
      "[26-50] tem: ",
      "[51-75] tem: ",
      "[76-100]  tem: "
    ]
//

let cont = 1
function entrada(){
  let paia = Number(prompt("N"+cont+": "))
  cont++
  return paia
}

let a = [0,0,0,0] 
for(let i = entrada(); i>=0; i = entrada()){
  if(i >= 0 && i<= 25) a[0]++
    else if(i >= 26 && i<= 50) a[1]++
    else if(i >= 51 && i<= 75) a[2]++
    else if(i >= 76 && i<= 100) a[3]++
}


console.log("\n quantos numeros estão no intervalo: ")
saida(bloco, 0, paia, 1, a[0], a[1], a[2], a[3])
saida(bloco, "stop", paia, 1)


/// NO CONSOLE ///
 42. Faça um programa que leia uma quantidade indeterminada de números positivos e conte quantos
 deles estão nos seguintes intervalos: [0-25], [26-50], [51-75] e [76-100]. A entrada de dados
 deve terminar quando for lido um número negativo.
N1: > 2
N2: > 3
N3: > 6
N4: > -1

 quantos numeros estão no intervalo: 
.-------------------. 
| [0-25] tem: 3     | 
| [26-50] tem: 0    | 
| [51-75] tem: 0    | 
| [76-100]  tem: 0  | 
'-------------------' 

Função:

function saida(bloco, stop, conteudo_format, linhas, titulo, cod, a, b){

  
  let l =  [
    conteudo_format[0]+titulo,
    conteudo_format[1]+cod,
    conteudo_format[2]+a,
    conteudo_format[3]+b,
    ]
  
  if(titulo != undefined ) titulo = titulo.toString()
  if(cod != undefined ) cod = cod.toString()
  if(a != undefined ) a = a.toString()
  if(b != undefined ) b = b.toString()
  
  let all = [titulo, cod, a, b]

  //para mostrar os blocos no final
  if(stop == "stop"){
    for(let i = 1; i<=linhas; i ++) {
      for(let j = 0; j<=5; j++){
        console.log(bloco[i][ ("linha"+j) ])
      }
    } 
    return
  }

  //criar os objetos das linhas se n tiverem sido criadas
  if (bloco[linhas] == undefined) {
    bloco[linhas] = {
      linha0: "",
      linha1: "",
      linha2: "",
      linha3: "",
      linha4: "",
      linha5: "",
    } 
  }


  //o tamanho da retangulo
    let tamanho = 0, barras = "-"
           
    for(let i = 0; i<4; i++){
      let paiadnv = 0
      if(all[i] != undefined) paiadnv = all[i] 

      if(paiadnv.length > 10) {if(paiadnv.length > tamanho){tamanho = paiadnv.length}}
        else if(all[i] != undefined) tamanho = l[i].length

    }
    tamanho += 2
    for(let i = 0; i<tamanho; i++) barras += "-"

  //criar um retangulo para as infos
  let format = {
    f1: function(a) {bloco[linhas]["linha"+a] += "| "+l[0].padEnd(tamanho, " ") + "| "},
    f2: function(b) {bloco[linhas]["linha"+b] += "| "+l[1].padEnd(tamanho, " ") + "| " },
    f3: function(c) {bloco[linhas]["linha"+c] += "| "+l[2].padEnd(tamanho, " ") + "| "},
    f4: function(d) {bloco[linhas]["linha"+d] += "| "+l[3].padEnd(tamanho, " ") + "| "},
  }

  let format_puro = {
    f1: function(a) {bloco[linhas]["linha"+a] += "| "+titulo.padEnd(tamanho, " ") + "| "},
    f2: function(b) {bloco[linhas]["linha"+b] += "| "+cod.padEnd(tamanho, " ") + "| " },
    f3: function(c) {bloco[linhas]["linha"+c] += "| "+a.padEnd(tamanho, " ") + "| "},
    f4: function(d) {bloco[linhas]["linha"+d] += "| "+b.padEnd(tamanho, " ") + "| "},
  }


  //verifica se a variavel está vazia e se não estiver formata a informação para o bloco
  
    bloco[linhas].linha0 += "."+barras+". "

    for(let i = 1, j = 1; i<=5; i++){
      if(all[i-1] != undefined) {        
        if(all[i-1].length < 10) {format["f"+i](j); j++}
          else {format_puro["f"+i](j); j++}
      }
      if(i == 5) {
        bloco[linhas]["linha"+(j)] += "'"+barras+"' "
        // for(let a = j+1; a<= 5; a++) bloco[linhas]["linha"+a] = "".padEnd(tamanho, " ")
      }
    }
  }



function entrada(vezes, vetor, pergunta, paia, a){
  for(i = 0; i<vezes; i++){
    if(paia == 0) vetor[i] = prompt(pergunta+(i+1)+": ")
      else if(paia == 1) vetor[i][a] = prompt(pergunta+(i+1)+": ")
      else vetor[i] = Number(prompt(pergunta+(i+1)+": "))
  }
}


module.exports = {saida, entrada}

No final eu só usei ela 2 vezes porque era chato usar e limitada

2° Semestre - Classe de Inputs (código aberração)

No projeto integrado do semestre 2° Semestre - Era para fazer um sistema para empresas de onibus com admin, catraca e site público - acabei esbarrando em validação de input e tive a ideia de fazer uma classe para reutlizar em todos os form do projeto. O problema é que era a primeira vez fazendo e a classe virou um monstro...

Os métodos eram muito especifico e acabavam sendo raros ser usado e o método principal que é de validação... tem 300 linhas com nomes de variaveis confusos. Eu criei um monstro e na época não sabia que refatora-la era a melhor opção


exemplo de uso


    const cadastrar = new Inputs("#formscadastro");

    cadastrar.allvalidacao({
      cpf: {
        max: 14,
        min: 14,
        caractereNpermitido: ['Nletra', 'Nacentuacao'],
        pattern: [
          "(\\d{3}\\.\\d{3}\\.\\d{3}-\\d{2,5})",
          // "\\d{3}\\.\\d{3}\\.\\d{3}-\\d{2}",
          "cpf valido",
          "cpf invalido",
        ],
        autopontuar: [/(\d{3})(\d{3})(\d{3})(\d{2})/, "$1.$2.$3-$4", /[.-]/g, ""],
        customErro: [
          [/(\d{3}\.\d{3}\.\d{3}-\d{3,6})/, "não existe cpf com mais de 11 numeros"]
        ],
        customEvento: [

          (async (value) => {
            if (((await axios.get("http://localhost:9000/api/user/cadastrar/?cpf=" + value)).data.resultado.cpf)) {
              return "Esse CPF já está cadastrado"
            }
          })
        ],
        btnoff: "required"
      },
      email: {
        max: 64,
        pattern: [
          "^[a-zA-Z0-9\\._+\\-]+@[a-zA-Z0-9\\.\\-]+\\.[a-zA-Z]{2,6}$",
          "email valido",
          "email invalido",
        ],
        customEvento: [(async (value) => {
          if (((await axios.get("http://localhost:9000/api/user/cadastrar/?email=" + value)).data.resultado.email)) {
            return "Esse email já está em uso"
          }
        })],
        btnoff: "required"
      },
    });


    const cadastro = cadastrar.cadastrar("http://localhost:9000/api/user/cadastrar", null,
      erro = () => {},
      success = ({
        data
      }) => {
        localStorage.setItem("email", data)

        location.href = "/login"
      })

  })

A classe em si

Não seria uma boa ideia colocar 400 linhas de código seco aqui, então para quem quiser ver o código inteiro está aqui: Pj2-G10-TechPass/frontend/public/js/validacao.js

um trecho do classe:


  // ele retorna um objeto com todos os valores do input do site
  allValues(modificarFormData = {}) {
    const formobj = new FormData(this.forms);
    const valoresPuro = {};

    this.inputs(({ name, value }) => {
      if (value !== "") {
        valoresPuro[name] = value;
      }
    });

    // modificarFormData é um objeto composto de metodos para modificar os valores do input
    for (const key in modificarFormData) {
      if (Object.hasOwnProperty.call(modificarFormData, key)) {
        const valor = formobj.get(key);
        const valorFormatado = modificarFormData[key](valor);

        if (valorFormatado && valorFormatado !== "") {
          formobj.set(key, valorFormatado);
          valoresPuro[key] = valorFormatado;
        }
      }
    }

    const keysNull = [];
    formobj.forEach((value, key) => {
      if (!value || value.size === 0) keysNull.push(key);
    });

    keysNull.forEach((key) => {
      formobj.delete(key);
    });

    for (const [_, value] of formobj.entries()) {
      console.log(_, value);
      if (typeof value == "object") {
        return formobj;
      }
    }

    // modificarFormData é um objeto composto de metodos para modificar os valores do input

    return valoresPuro;
  }

3° Semestre - Classe de Inputs para React Native

No projeto integrado do último semestre - Era para criar um app mobile, totem/admin, backend e um site estático para um parque da minha cidade, a apresentação saiu até como noticia no site da cidade - resolvi fazer uma classe para validacao de input também. E bem... depois de refatorar 4 vezes saiu algo aceitavel.

Fiz essa classe pra cuidar exclusivamente do lógica de post e validacão. Quando tentei estilizar o input nela a classe ficou muito grande e confusa, então acabei refatorando abstraindo todos os estilos para ser passados como props. A classe também usa o useApi (um hook que junta react query com outro hook responsavel por criar uma instancia axios com a lógica de token/refresh token) para lidar com a requisição para a API.

Classe:

const TIMEOUT_VALIDY = 500;

export class FormPaia<C extends string, Inp extends InputMinimoProps> {
  public values = useRef({} as ObjC<C, string>).current;
  protected useApiPaia: UseMutation<typeof this.values>["result"];

  constructor(
    private options: Options<C>,
    private InputType: InputMinimo<Inp>,
    private TemplateInput: ComponentType<TemplateInputProps>
  ) {
    this.useApiPaia = useApi("mutate", options.submitOptions);
  }

  /*
   * função para submeter o formulário
   */
  public submit = () => this.useApiPaia.mutate(this.values);

  /*
   * componente que rerenderiza o que estiver dentro com as informações da tentativa de submissão
   */
  public Escutar = ({ children: C }: EscutarPropsPaia<C>) => (
    <C mutation={this.useApiPaia} />
  );

  /*
   * Componente do Input com toda lógica de validação
   */
  public Input = ({
    campo,
    defaultValue,
    ...props
  }: Omit<Inp, "value" | "onChangeText"> & { campo: C }) => {
    const [valueInput, setValuesInput] = useState(
      this.values[campo] || defaultValue || ""
    );
    const infoValidy = this.useValid(valueInput, campo);

    const { isPending } = this.useApiPaia;

    if (valueInput) this.values[campo] = valueInput;

    return (
      <this.TemplateInput campo={campo} {...infoValidy}>
        <this.InputType
          editable={!isPending}
          selectTextOnFocus={!isPending}
          placeholder={campo}
          {...(props as unknown as Inp)}
          value={valueInput}
          onChangeText={(v) => setValuesInput(v)}
        />
      </this.TemplateInput>
    );
  };

  /**
   *  Hook que cuida de validar os valores do input
   */
  private useValid(value: string, campo: C) {
    const [validy, setValidy] = useState<StateValues<typeof State>>(
      State.EMPTY
    );

    useEffect(() => {
      setValidy(State.LOADING);
      const validadoresDoCampo = this.options.campos?.[campo];
      if (!validadoresDoCampo) return setValidy(State.EMPTY); // não existe validacao para esse campo

      const timeoutId = setTimeout(async () => {
        for await (const [verify, avisoDoErro] of validadoresDoCampo) {
          const isValidy = await verify(value);
          const isValidyMsg = typeof isValidy === "string" && isValidy;
          if (isValidy) {
            return setValidy(isValidyMsg || avisoDoErro); // ERROR
          }
        }

        // se passou nos testes seta null como sucesso
        setValidy(State.SUCCESS);
      }, TIMEOUT_VALIDY);

      return () => clearTimeout(timeoutId);
    }, [value]);

    const isError = typeof validy == "string";

    return {
      aviso: isError ? validy : null,
      isLoading: validy == State.LOADING,
      isValid: validy === State.SUCCESS,
    };
  }
}

Não coloquei as tipagens por que o código ia ficar muito grande. O arquivo completo com as tipagens está aqui

Classe que extende a anterior com componentes Tamagui:

A parte do constructor está complicada porque realmente perdi a paciência tentando deixar o camposAdd opcional com valor padrão. Typescript é uma maravilha na zona de conforto, mas fora dela...

Pois bem, eu acabei criando essa classe extendendo a outra para não ter que ficar definindo os componentes do input e dos avisos toda vez que for usar a classe. Também coloquei validações padrão (o validsPaia) e um botão de submit como método.

type ValidKeys = keyof typeof validsPaia;
// class extendida do FormPaia com os Input e AvisoType definido
export class FormPaiado<C extends string = ValidKeys> extends FormPaia<
  C,
  GetProps<typeof InputPaia>
> {
  constructor(
    submitOptions: Options<C>["submitOptions"],
    camposAdd?: Record<C, ValidacoesCampo>
  ) {
    const campos = camposAdd ?? (validsPaia as Record<C, ValidacoesCampo>);

    super({ campos, submitOptions }, InputPaia, AvisoPaia);
  }

  /**
   * ButtonSubmit
   */
  public ButtonSubmit = (props: { text: string }) => {
    const { isPending } = this.useApiPaia;

    return (
      <ButtonCustom
        disabled={isPending}
        marginLeft="-2%"
        mt={"$4"}
        width="104%"
        onPress={this.submit}
      >
        {isPending ? "aguarde" : props.text}
        {isPending && <Spinner size="large" color={"$blue10Dark"} />}
      </ButtonCustom>
    );
  };
}

/**
 * VIEW que vai em volta do input
 */
function AvisoPaia(props: TemplateInputProps) {
  const { aviso, campo, children, isValid, isLoading } = props;

  const [color, texto] = (isValid && ["green", `${campo} valido`]) ||
    (isLoading && ["#FFA500", "validando..."]) ||
    (aviso && ["red", aviso]) || ["", " "]; // pratico, mas feio

  return (
    <YStack w="100%" mb={"$2"}>
      <Text fontSize={"$2"} color="green" mb="$1.5" fontFamily={"$outfitBold"}>
        {campo.toUpperCase()}:
      </Text>
      {children}
      <Text fontSize={"$4"} mt={"$1"} color={color}>
        {texto}
      </Text>
    </YStack>
  );
}

/**
 * Modelo padrao de input
 */
export function InputPaia(props: InputProps) {
  return (
    <Input
      size="$4.5"
      borderWidth={1.7}
      borderColor="green"
      backgroundColor="#e8f5e9"
      color="black"
      marginLeft="-2%"
      width="104%"
      focusStyle={{ borderColor: "green" }}
      hoverStyle={{ borderColor: "green" }}
      {...props}
    />
  );
}

As validações ficam em um arquivo separado para melhor organização:

Estas são as validações que utilizo no FormPaiado (uma classe estendida do FormPaia). A lógica de validação do FormPaia é assíncrona e sequencial, ou seja, a próxima verificação só será executada se a verificação anterior retornar false (false significa que não houve erro/validação deu certo). Com isso, a função verifyEmail só será chamada se todas as validações anteriores (do email) retornarem false

async function verifyEmail(email: string) {
  try {
    const { data } = await axios.get(
      `${baseURL}/usuario/verify/?email=${email}`,
      { timeout: 2000 }
    );

    return !data.emailDisponivel && data.message;
  } catch (error) {
    return true;
  }
}

/**
 * validações de campos pre definidas
 */
export const validsPaia = {
  email: [
    [(t) => t.length < 1, "Insira um email"],
    [(t) => t.length > 64, "Máx. 64 caracteres"],
    [(t) => !/^[a-zA-Z0-9._%+-]+/.test(t), "Caractere inválido no início"],
    [(t) => !/@[a-zA-Z0-9.-]+\./.test(t), "Formato de email inválido"],
    [(t) => !/[a-zA-Z]{2,}$/.test(t), "Terminação inválida"],
    [verifyEmail, "Erro ao verificar o email"],
  ],
  apelido: [
    [(t) => t.length < 1, "Insira um apelido/nome"],
    [(t) => t.length < 3 || t.length > 40, "Entre 3 e 40 caracteres"],
    [
      (t) => !/^[a-zA-Z\s\dÀ-ú]{3,40}$/.test(t),
      "Apenas letras, números e espaços",
    ],
  ],
  nome: [
    [(t) => t.length < 1, "Insira um nome"],
    [(t) => t.length < 3 || t.length > 40, "Entre 3 e 40 caracteres"],
    [(t) => !/^[a-zA-Z\s\dÀ-ú]{3,40}$/.test(t), "Apenas letras e espaços"],
  ],
  senha: [
    [(t) => t.length < 1, "Insira uma senha"],
    [(t) => t.length < 4 || t.length > 40, "Entre 4 e 40 caracteres"],
  ],
} as Record<string, ValidacoesCampo>;

Sim, me arrependi de colocar um timeout de 2s. A internet do lugar da apresentação era lenta...

Exemplo de uso

import { FormPaia } from "@/components/FormClass";
import { FormPaiado } from "@/components/FormConfigs";
import FormFooter from "@/components/FormFooter";
import TAuth from "@/components/templateAuth";
import { storeAuth } from "@/lib/logicAuth";
import { router } from "expo-router";

export default function Cadastrar() {
  const login = storeAuth((s) => s.login);

  const SignUp = new FormPaiado((axios) => ({
    mutationFn: async (allValues) => {
      await axios.post("/usuario", allValues); //se der erro n continua

      const { apelido, ...credentials } = allValues;

      const { data } = await axios.post("/usuario/login", credentials);

      login(data.token);

      router.replace("/(app)/(home)");
    },
  }));

  return (
    <TAuth subTitulo="Cadastra-se" titulo="CADASTRAR">
      <SignUp.Input campo="apelido" />
      <SignUp.Input campo="email" textContentType="emailAddress" />
      <SignUp.Input campo="senha" secureTextEntry textContentType="password" />
      <SignUp.ButtonSubmit text="CADASTRAR" />
      <FormFooter
        link={{
          href: "/(auth)/login",
          text: "Já está cadastrado?",
          textLink: "Entre aqui!",
        }}
      />
    </TAuth>
  );
}

Tela de cadastrar:
gif da tela cadastrar


Desculpe qualquer erro de português ou na estrutura do texto, não tenho costume de escrever tanto.


LINKS:

  • Exercicios que fiz: REPLIT
  • Repositorio do projeto integrado do 2° Semestre: GitHub
  • Repositorio do projeto integrado do app (o backend e totem estão em outros repos) do 3° Semestre: GitHub
  • Meu github: GitHub