React pra quem vem do AdvPL

Ricardo Mansano Godoi
TOTVS Developers
Published in
12 min readAug 31, 2019

Já comparamos o Angular com o ADVPL, chegou a vez do React. Este episódio será um flashback do primeiro, mas focado no frame do Facebook.

Nestes links, os vídeos sobre este conteúdo:
Vídeo 1:
https://youtu.be/GJTqDE-18bA
Vídeo 2:
https://youtu.be/yV72DehPkIo

Link para o primeiro post da série:
Angular pra quem vem do AdvPL

TOTVS Developers
https://developers.totvs.com/

Canal do TOTVS Developers no Youtube
https://www.youtube.com/channel/UCTsbIzv43uuxQlMG2yjeJ9w

Um canal onde aprendi muito sobre React foi o DevPleno.

Para acompanhar esse conteúdo, colocando a mão na massa, você vai precisar de:

  1. Um ambiente Protheus configurado
  2. Um ambiente React previamente instalado

Instalando o React

Aqui também usaremos o NPM para criar e manipular o projeto. Caso esteja utilizando Windows, baixe o nodejs do site nodejs.org, dê preferência para versão recomendada, pois é a mais estável.

A instalação segue o modelo next-finish.

Para Linux, instale à partir do comando sudo apt install npm. Obviamente, se utilizar um Linux com o gerenciador de pacotes APT.

Crie uma pasta onde iremos iniciar o projeto, e acesse essa pasta via linha de comando (cmd/terminal). Exemplo: Crie a pasta projs que será a raiz de vários projetos.

Nos comandos a seguir, caso esteja utilizando Windows, apenas retire o sudo.

Vamos instalar o React a partir do comando:

sudo npm install -g create-react-app

Para confirmar a versão:

npm view create-react-app

Para criar o projeto, que no exemplo será o my_proj2, utilizamos o seguinte comando:

O React não aceita letras maiúsculas no nome do projeto.

sudo create-react-app my_proj2

Após criar o projeto entramos no diretório.

cd my_proj2

Faremos a primeira execução através do comando:

npm start

Neste momento o projeto será compilado e exibido no navegador.

Com um Ctrl+C você encerra o servidor. Abra então o VSCode neste mesmo diretório, digitando:

code .

Roteamento

Mais uma vez, o mais importante sobre uma Single Page Application é entender como rotear uma página, trocando partes dela sem a necessidade de recarga.

Instale então o plugin de roteamento.

sudo npm install react-router-dom

Vamos criar dois componentes apenas pra testar o roteamento. No React, você pode criar manualmente os arquivos. Criaremos os arquivos Home.js e o Niver.js. Insira o trecho abaixo em cada um deles, alterando a parte que define a origem do arquivo => Home work’s e Niver work’s.

Importante: No React, a função render() é a responsável pela exibição do conteúdo HTML da aplicação.

import React from ‘react’;export default class Home extends React.Component{
render(){
return ( <div>
<h2>Home work’s</h2>
</div>
)
}
}

Ao terminar, ficara assim…

Abra o arquivo /src/App.js de seu projeto, vamos substituir todo seu conteúdo pelo trecho abaixo, que explicarei na sequência.

import React from 'react';
import './App.css';
// Componentes do roteamento
import { BrowserRouter as Router, Route } from 'react-router-dom'
// Paginas que serao roteadas
import HomeApp from './Home';
import Nivers from './Nivers';
export default class App extends React.Component{
constructor() {
super()
this.state = {
myPage: HomeApp // Funcao sendo executada deve ficar no State
};
}
render() {
return (
<div className="App">
<button onClick={()=>{ this.setState({myPage: HomeApp}) }}> Home </button>
<button onClick={()=>{ this.setState({myPage: Nivers}) }}> Nivers </button>

{/* As paginas sao roteadas aqui */}
<Router>
<Route component={this.state.myPage}/>
</Router>
</div>
);
}
}

Aqui inserimos os componentes de roteamento, e na sequência importamos os componentes que criamos para serem roteados.

// Componentes do roteamento
import { BrowserRouter as Router, Route } from ‘react-router-dom’
// Paginas que serao roteadas
import HomeApp from ‘./Home’;
import Nivers from ‘./Nivers’;

Na criação da classe App vamos utilizar o this.state, ele é um array global, e toda alteração em seus valores dispara atualizações nas páginas que referenciam essas variáveis…

…no nosso exemplo isso acontecerá com a variável myPage.

export default class App extends React.Component{
constructor() {
super()
this.state = {
myPage: HomeApp // Funcao sendo executada deve ficar no State
};
}

No render() criei dois botões, e através de uma arrow function (=>) altero a página que desejo carregar em runtime.

Atente que a atualização da variável é feita através do método this.setState(), o que faz o disparo que comentei anteriormente…

render() {
return (
<div className=”App”>
<button onClick={()=>{ this.setState({myPage: HomeApp}) }}> Home </button>
<button onClick={()=>{ this.setState({myPage: Nivers}) }}> Nivers </button>
...

Neste trecho é onde a página é roteada, através do valor definido na variável myPage, alterada pelos botoes que criamos acima.

... 
{/* As paginas sao roteadas aqui */}
<Router>
<Route component={this.state.myPage}/>
</Router>
</div>
);

Agora abra uma sessão do terminal no VSCode e repita o comando abaixo.

npm start

A partir daí, todas as alterações salvas serão automaticamente recarregadas no navegador, facilitando o desenvolvimento.

Sua visualização ficara assim.

Pressione os botões para ver o roteamento funcionando.

E assim roteamos uma aplicação React…

Baixando e executando os projetos

Agora que conhecemos o básico, podemos começar a comparar os projetos.

Baixe aqui o exemplo em React

Vamos criar uma nova pasta e descompactar esse projeto. Exemplo:

niversReact

Você pode inicializar um projeto facilmente, pois todas as suas dependências ficam no arquivo package.json. Vá para a pasta do projeto e digite:

npm install

Na sequência, compile e execute o projeto, lembrando que não poderá abrir dois na mesma porta (a padrão é a 3000):

npm start

Executando o projeto, veremos a tela a seguir:

Navegue pelo menu e pelos botões para conhecer o exemplo:

Chegou a hora do exemplo em AdvPL.

Baixe aqui exemplo em AdvPL

Compile o fonte e as imagens, e execute a função u_niversAdvPL. Aqui vou partir do princípio que você já conhece o ecossistema Protheus.

Comparando o React com o AdvPL

Como no Angular, para facilitar nossa vida durante a comparação, eu inseri tags nos códigos, bastando pesquisar cada uma delas. Todas se iniciam por [* , veja um exemplo:

A criação da barra de menus em AdvPL:

Agora em React:

Algumas dessas tags não terão referência direta no AdvPL, pois falamos de paradigmas de programação bem diferentes.

Material Design — Tag: [*Material_UI]
Essa tag não tem referência em AdvPL.

Aqui habilitamos o Material Design em nosso projeto. Como comentei, todas as dependências estão no arquivo package.json, mas caso precisem criar um projeto do zero é importante estudar o link abaixo:

https://material-ui.com/getting-started/installation/

A seguir os comandos para inclusão das bibliotecas que utilizei:

sudo npm install material-ui@latest
sudo npm install @material-ui/core
sudo npm install @material-ui/icons
sudo npm install @material-ui/styles
sudo npm install @types/material-ui

/src/App.js
É necessário importar cada componente que será utilizado, como AppBar, ícones, menus, etc.

Roteamento — Tag: [*Router]
Essa tag não tem referência em AdvPL.

Como já falamos sobre o roteamento, segue aqui o fonte onde devem procurar a tag para estudá-la:

/src/App.js

Componentes SPA — Tag: [*SPA]
Essa tag não tem referência em AdvPL.

No React o componente é criado manualmente, e por isso deve ser referenciado manualmente no arquivo abaixo:

/src/App.js

// [*SPA] Componentes da aplicacao
import HomeApp from './components/Home';
import Nivers from './components/Nivers';
import Hooks from './components/Hooks'

Formulários — Tag: [*Form]

/src/components/Nivers.js

{/* [*Form] */}
<form onSubmit={this.onClickSubmit}>
<TextField
required margin="small" id="person" value={this.state.person}
label="Aniversariante" onChange={this.onTextChange}
/>
...

niversAdvPL.prw
Em AdvPL utilizamos os componentes do tipo TSay, TGet e TButton (entre outros) para criação do form.

// [*Form] — Formulario para inclusao dos aniversarios
DEFINE MSDIALOG oDlg TITLE “Nivers” FROM 000, 000 TO 320, 500 COLORS 0, 16777215 PIXEL
@ 014, 003 MSGET oGet1 VAR cAniversariante SIZE 060, 010 OF oDlg

Inclusão do registro — Tag: [*Submit]

/src/components/Nivers.js
Ainda na sub-rotina de Aniversários, efetuamos uma “pseudo” inclusão na variável nivers do State.

// [*Submit]
onClickSubmit = (e) => {
this.state.nivers.push({ list: [this.state.birthday, this.state.person] } )
// Limpa o valor do campo depois do submit
this.setState({person: '', birthday: ' '});
e.preventDefault();
}

niversAdvPL.prw
Em AdvPL eu simplesmente inseri um item em um TMultiGet.

static function niversInsert(oMultiGe1, cAniversariante, dNiver)
// [*Submit] Insere aniversarios na lista
oMultiGe1:appendText(chr(10) + trim(cAniversariante) +” | “+ DtoC(dNiver))

Deleção do item — Tag: [*Delete_Item]
Essa tag não tem referência em AdvPL.

/src/components/Nivers.js
A deleção é feita retirando o item do State.

// [*Delete_Item]
delNiver = (idx) => {
this.state.nivers.splice(idx, 1)
this.setState({person: '', birthday: ' '});
}

Loop de criação de componentes — Tag: [*LoopCreation]
Essa tag não tem referência em AdvPL.

/src/components/Nivers.js
No React não temos o ngFor, mas por sua vez existe um facilitador, podemos criar dinamicamente WebComponents diretamente em um return.

No trecho abaixo, faço isso com o componente NiverItem, utilizando o map, que é um FOR para arrays.

{/* [*LoopCreation] Varre os aniversarios preenchidos*/}
{ this.state.nivers.map((nivers, index) => {
return <NiverItem
delNiver={this.delNiver} // [*Interop] Associa a funcao delNiver
idx={index}
birthday={nivers.list[0]}
person={nivers.list[1]}
/>
})
}

Customização de Estilos — Tag: [*CustomButton]

/src/components/Nivers.js
No React podemos criar um componente já definindo seu style. Neste caso, usei para o botão Submit de nosso formulário.

// [*CustomButton] Define padrao de cores
const CustomButton = withStyles((theme: Theme) => ({
root: {
color: theme.palette.getContrastText('#007bff'),
backgroundColor: '#007bff',
'&:hover': {
backgroundColor: '#0069d9'
},
},
}))(Button);

Após criado o botão, é possível instanciá-lo como se fosse um WebComponent, o que facilita o desenvolvimento.

{/* [*CustomButton] Utilizando o padrao de cores */}
<CustomButton type=”submit” size=”large”>
INSERIR
</CustomButton>

niversAdvPL.prw
Em AdvPL, a definição do CSS pode ser feita através do método SetCss.

@ 014, 145 BUTTON oButton1 PROMPT “INSERIR” SIZE 054, 012 OF oDlg PIXEL Action … 
/* [*CustomButton] Definicao do CSS */
oButton1:SetCss(;
“QPushButton {“+;
“ border: none;”+;
“ color: #fff;”+;
“ background-color: #007bff;}”+;
“QPushButton:hover {“+;
“ background-color: #0069d9;}”)

Barra superior — Tag: [*AppBar]

/src/App.js
No React o WebComponent AppBar faz parte da biblioteca do Material.

{/* [*AppBar] */}
<AppBar position="static" style={{maxHeight: 50}}>
<Toolbar style={{padding: 0}}>
<div>
<Tooltip title="Menu principal">
<IconButton color="inherit" style={{paddingTop: 0}}
...

niversAdvPL.prw
Em AdvPL, usamos o componente TBar, já definindo seu estilo e seus botões.

// [*AppBar]
oTBar := TBar():New( oDlg, 25, 32, .T.,,,, .F. )
oTBar:SetCss(“QFrame{background-color: #007bff;}”)
oBtn1 := TButton():New(0,0,””,oTBar,{|| }, 12,12,,,.F.,.T.,.F.,,.F.,,,.F. )

Menu lateral — Tag: [*MainMenu]

/src/app/app.component.html
Aqui criamos o Menu da tela inicial.

{/* [*MainMenu] */}
<Menu
style={{ marginRight: '8vw' }}
open={open}
onClose={this.menuClose}
anchorEl={anchorEl}
anchorOrigin={{ vertical: 'bottom', horizontal: 'Left' }}
getContentAnchorEl={null}
>
<MenuItem onClick={() => this.menuClick(<HomeApp/>)}><Home/>&nbsp;&nbsp;Home</MenuItem>
<MenuItem onClick={() => this.menuClick(<Nivers title='Nivers'/>)}><Cake />&nbsp;&nbsp;Aniversários</MenuItem>
<MenuItem onClick={() => this.menuClick(<Hooks/>)}><DeviceHub />&nbsp;&nbsp;Hooks</MenuItem>
</Menu>

niversAdvPL.prw
Em AdvPL, utilizei o componente TMenu. Infelizmente, não consegui um “print”, pois como ele é nativo, qualquer ação o fecha.

// [*MainMenu]
oMenu := TMenu():New(0,0,0,0,.T.)
oTMenuIte1 := TMenuItem():New(oDlg,”Home”,,,,{|| home() },,,,,,,,,.T.)
oTMenuIte2 := TMenuItem():New(oDlg,”Aniversarios”,,,,{|| nivers() },,,,,,,,,.T.)
oTMenuIte3 := TMenuItem():New(oDlg,”Hooks”,,,,{|| hooks() },,,,,,,,,.T.)
oMenu:Add(oTMenuIte1)

Templates — Tag: [*Template_Item]
Essa tag não tem referência em AdvPL

/src/app/nivers/nivers.component.ts
No trecho abaixo retornamos os itens de aniversários (<NiverItem/>) diretamente pelo render() do WebComponent.

render(){
return(
// [*Template_Item]
<div style={{
margin: 0,
marginBottom: 2,
borderBottomColor: "#ff0000",
borderBottomStyle: "dotted",
borderBottomWidth: 1
}}>
<IconButton color="inherit" size="small"
onClick={this.props.delNiver.bind(this, this.state.idx)}>
<DeleteForever />
</IconButton>
| <FormControlLabel
control={<Switch color="primary" />}
label="Ativo" />
| {this.props.birthday} | {this.props.person}
</div>
);
}

Definição e captura de dados — Tag: [*Getter_and_Setter]

/src/components/Hooks.js
No React podemos usar os hooks como Getter and Setter, porém ele não funciona em uma Classe, apenas em uma Function.

function Hooks(){
// Hook atuando como get/set
const [count, setCount] = useState(0);
return(
<div>
<h2>Hooks</h2>
<p>Você clicou {count} vezes</p>
<button onClick={() => setCount(count + 1)}>
Incrementa Hook->count
</button>
</div>
)
}
export default Hooks;

No trecho abaixo criamos o hook, que definirá automaticamente a variável no State, retornará seu valor através do {count}, e definirá através do setCount(). Veja que criamos “tudo isso” em uma única linha de comando.

Não sei vocês… Mas confesso que rolou uma lagriminha quando vi esse modelo.

const [count, setCount] = useState(0);

A seguir, retornamos e incrementamos o valor da variável, pressionando o botão “Incrementa Hook”.

<p>Você clicou {count} vezes</p><button onClick={() => setCount(count + 1)}>
Incrementa Hook->count
</button>

niversAdvPL.prw
Em AdvPL, recebo a variável nCount por referência e incremento seu valor.

static function hooksINC(oSay1, nCount, cClicksIni)
nCount++ // [*Getter_and_Setter] Incrementa contador
oSay1:setText( StrTran(cClicksIni, “{{count}}”, cValToChar(nCount)))

Interoperação entre classes — Tag: [*Interop]

/src/component/Niver.js
Aqui, vai parecer confuso à primeira vista mas não é, vamos lá…

No interop vamos chamar diretamente a função delNiver() do “pai” do componente <NiverItem/>.

No trecho abaixo, no momento que crio o <NiverItem/>, meu “this” é o componente <Niver/>, assim, associando o {this.delNiver} à propriedade delNiver da classe “filha”, farei automaticamente a chamada da função declarada na classe “pai”.

return <NiverItem 
delNiver={this.delNiver} // [*Interop] Associa a funcao delNiver do Parent
idx={index}

Aqui a função da classe “pai”

// [*Delete_Item]
delNiver = (idx) => {
this.state.nivers.splice(idx, 1)
this.setState({person: ‘’, birthday: ‘ ‘});
}

Compilando e executando seu projeto React

Utilizando o comando npm start, você testou toda aplicação, mas para distribuí-la vai ter que fazer sua compilação.

Para tanto, vai usar o seguinte comando:

npm run build

Para executar a aplicação depois de compilada, você precisará de um servidor HTTP. Existem várias opções, vou deixar aqui duas muito simples de serem instaladas.

A primeira deve ser usada apenas para execução local, basicamente como uma extensão de testes. É um plugin para o Chrome chamado Web Server for Chrome.

Escolha na extensão a pasta onde foi compilado seu projeto, localizando o arquivo index.html. E depois, acesse através do link. Ex: http://127.0.0.1:8887

O segundo server é o http-server, disponível a partir do próprio npm

https://www.npmjs.com/package/http-server

Para sua instalação, utilize o comando:

npm install http-server -g

Para executar, utilize o comando abaixo, apontando para o diretório onde está o index.html, e definindo a porta (-p), sendo que a default é a 8080:

http-server /home/myDir -p 8081

Como sempre, espero que aproveitem o material…

--

--

Ricardo Mansano Godoi
TOTVS Developers

Chief engineer of Front-end and Development Tools on TOTVS, developing software since 88, plugged to the brand new technologies and Nerd to the bone.