Criando Componentes Dinâmicos com React
/me
William Correa
@wilcorrea
-> Agenda
- Motor de renderização
- Diagramação
- Componentes dinâmicos
- Abstrações básicas
Cenas fortes de abstração
Pergunte o que e quando quiser
-> Motor de renderização
- Virtual DOM
- Criando elementos
- JSX
-> Virtual DOM
- Reagir à mudanças e aplicar ao DOM
- DOM é lento em tarefas individuais
- É rápido em tarefas em lote
- Criar nós em memória e aplicar várias
- Detectar mudanças de forma eficiente
-> Virtual DOM
- O mundo v-dom é eficiente
- Virtual DOM não é "dev friendly"
- Buscar formas de simplificar isso
-> Criando elementos
- O esquecido "hello world"
class Hello extends React.Component {
render() {
return React.createElement('h1', null, `Hello ${this.props.toWhat}`);
}
}
ReactDOM.render(
React.createElement(Hello, {toWhat: 'World'}, null),
document.getElementById('root')
);
-> Criando elementos
- O "hello world" mais comum
class Hello extends React.Component {
render() {
return <h1>Hello {this.props.toWhat}</h1>;
}
}
ReactDOM.render(
<Hello toWhat="World" />,
document.getElementById('root')
);
-> JSX
- JavaScript XML
- Combina elementos de marcação com JavaScript
- Notação para renderização de HTML
- https://facebook.github.io/jsx/
-> Para levar para casa
- Renderização no cliente vai demandar "tooling"
- Complexidade inerente não é "o problema"
-> Diagramação
- Distribuição de elementos
- Sistemas de grids
- CSS Grid Layout
-> Distribuição de elementos
- Elementos precisam de organização
-> Distribuição de elementos
- HTML Box
- CSS
-> Distribuição de elementos
- Responsividade
-> Sistemas de grids
- Float + porcentagem
- Flex layout
-> Sistemas de grids
- Float + porcentagem
- Flex layout
-> Sistemas de grids
- Aninhamento
<div class="container-fluid">
<div class="row">
<div class="col-9">
<div class="row">
<div class="col-9">
I'm the first element in the nested grid
</div>
<div class="col-3">
I'm the second element in the nested grid
</div>
</div>
<div class="row">
<div class="col-12">
I'm the second row in the nested grid
</div>
</div>
</div>
<div class="col-3">
I'm the second element in the first grid
</div>
</div>
</div>
-> CSS Grid Layout
- Resolve problemas tradicionais nativamente
<div class="container">
<div class="col-9">
I'm the first element in the nested grid
</div>
<div class="col-3">
I'm the second element in the nested grid
</div>
<div class="col-3 height-2">
I'm the second element in the first grid
</div>
<div class="col-12">
I'm the second row in the nested grid
</div>
</div>
.container {
display: grid;
grid-template-columns: repeat(15, 1fr);
grid-gap: 2px;
}
.container > div {
border: 1px solid #D5CFDE;
border-radius: 2px;
background-color: #E7E3EC;
padding: 10px;
}
.col-3 {
grid-column: auto / span 3;
}
.col-9 {
grid-column: auto / span 9;
}
.col-12 {
grid-column: auto / span 12;
}
.height-1 {
grid-row: auto / span 1;
}
.height-2 {
grid-row: auto / span 2;
}
-> CSS Grid Layout
-> CSS Grid Layout
-> Para levar para casa
- Lógica e apresentação caminham de mãos dadas
- Conhecer CSS te dá liberdade
-> Componentes dinâmicos
- Componentes agnósticos
- Usando JSX
- Usando createElement
-> Componentes agnósticos
- Componentes simples
- Parametrização
<AppFormField
label='Nome'
name='name'
value={this.state.filters.name}
onChange={this.handleChange}
placeholder='Informe um nome...'
/>
-> Componentes agnósticos
- DRY < SOLID
- SOLID < KISS
return (
<div className="form-group">
{ label ? labelComponent : '' }
<AppInput valid={valid} invalid={invalid} { ...attrs } />
{ hintText }
{ errorText }
</div>
);
-> Componentes agnósticos
import React from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
const propTypes = {
type: PropTypes.oneOf(['button', 'submit']),
color: PropTypes.oneOf([
'primary', 'secondary', 'success', 'danger',
'warning', 'info', 'light', 'dark', 'link'
]),
removeDefaultClasses: PropTypes.bool
};
const defaultProps = {
type: 'button',
color: 'default',
removeDefaultClasses: false
};
// ...
// ...
const AppButton = (props) => {
let { className, removeDefaultClasses, color, text, ...attrs } = props;
const classes = classNames(
!removeDefaultClasses ? 'btn' : false,
color ? `btn-${color}` : false,
className
);
return (
<button {...attrs} className={classes}>{props.children}</button>
);
}
AppButton.propTypes = propTypes;
AppButton.defaultProps = defaultProps;
export default AppButton;
-> Componentes agnósticos
const AppInput = (props) => {
let { className, valid, invalid, bsSize, clean, ...attrs } = props;
attrs.id = (attrs.hasOwnProperty('id') ? attrs.id : attrs.name);
attrs.autoComplete = (attrs.hasOwnProperty('autoComplete') ?
attrs.autoComplete :'off');
const classes = classNames(
!clean ? 'form-control' : false,
className,
bsSize ? `form-control-${bsSize}` : false,
valid ? `is-valid` : false,
invalid ? `is-invalid` : false,
);
return (
<input type={attrs.type} className={classes} { ...attrs } />
);
}
-> Combinar Recursos
<div className="row">
<div className="col-6">
<AppFormField label='Nome' ...
value={this.state...}
onChange={this.handleChange}
/>
</div>
<div className="col-3">
<AppLabel className='d-none d-md-block'> </AppLabel>
<AppButton color='primary' type='submit'>
<i className="fas fa-search"/> {''}
Buscar
</AppButton>
{
this.state.hasFilter && (
<AppButton color='link' ...>
<i className="fas fa-eraser"/> {''}
Limpar Filtros
</AppButton>
)
}
</div>
-> Componentes dinâmicos
- JSX brilha
import React from 'react'
import './styles.css'
export default function App() {
const Component = 'div'
return (
<Component className="App">
<h1>Hello World</h1>
<h2>My first dynamic component!</h2>
</Component>
)
}
-> Componentes dinâmicos
- Notação de classes
import React from 'react'
import './styles.css'
export class App extends React.Component {
render() {
const Component = 'div'
return (
<Component className="App">
<h1>Hello World</h1>
<h2>My first dynamic component!</h2>
</Component>
)
}
}
-> Componentes dinâmicos
- Usando createElement
import React from 'react'
import './styles.css'
import h from './createElement'
export default class App extends React.Component {
render() {
const Component = 'div'
const data = { className: 'App' }
const children = [
h('h1', 'Hello World'),
h('h2', 'My first dynamic component!')
]
return h(Component, data, children)
}
}
-> Componentes dinâmicos
- Ferramenta poderosa
const Component = components(field.form.component);
const attrs = (field.form.attrs ? field.form.attrs : {});
return (
<div className={field.form.className}>
<Component
label={field.label}
name={field.key}
value={this.state.record[field.key]}
onChange={this.handleChange}
{...attrs}
/>
</div>
)
-> Componentes dinâmicos
- Em tempo de execução
-> Para levar para casa
- Virtual DOM é sobre dinamismo
- Qualquer "client render" terá "dynamic components"
- Componentes dinâmicos são utilidade e não mágica
-> Abstrações básicas
- Componentes contêineres
- Padrão composite
-> Componentes contêineres
import { Component } from 'react'
export default class BaseComponent extends Component {
}
import BaseComponent from "./BaseComponent";
export default class TableComponent extends BaseComponent {
constructor(props) {
super(props);
this.state = {...};
}
handleChange = e => {...};
handleSubmit = e => {...};
onClickClearFilters = e => {...};
refreshTable = (page = 1) => {...};
onClickPrevious = () => {...};
onClickNext = () => {...};
onClickPage = (e) => {...};
}
import BaseComponent from "./BaseComponent";
export default class FormComponent extends BaseComponent {
constructor(props, schema, scope = 'create') {
super(props);
if (!schema) {
console.error("Schema can't be empty or null");
}
if (!schema.hasOwnProperty('fields')) {
throw new Error("Schema object must be a 'fields' object property.");
}
// ...
}
handleChange = e => {...};
}
-> Aplicando o composite
import React from 'react';
import FormComponent from '~/utils/FormComponent';
import { form } from "../settings";
import { components } from "../../../_config/settings";
class UsersFormPage extends FormComponent {
constructor(props) {
const scope = (props.meta ? props.meta.scope : 'create');
const schema = form(scope);
super(props, scope, schema);
}
// ...
}
class UsersFormPage extends FormComponent {
// ...
render() {
return (
<form className='app-form'>
{
this.state.fields.map(field => {
const Component = this.parseComponent(field.form.component);
const attrs = (field.form.attrs ? field.form.attrs : {});
return (
<div key={field.key} className={this.parseClassName(field)}>
<Component
{...attrs}
value={this.state.record[field.key]} onChange={this.handleChange}
/>
</div>
)
})
}
</form>
);
}
}
-> Renderizando tudo
const Component = this.parseComponent(field.form.component);
const attrs = (field.form.attrs ? field.form.attrs : {});
return (
<div key={field.key} className={this.parseClassName(field)}`}>
<Component
label={field.label}
name={field.key}
value={this.state.record[field.key]}
onChange={this.handleChange}
{...attrs}
/>
</div>
)
export default class FormComponent extends BaseComponent {
// ...
parseClassName (field) {
return `field width-${field.form.width} height-${field.form.height}`
}
}
import AppFormField from "~/components/app/FormWrappers/AppFormField";
import AppFormCheckbox from "~/components/app/FormWrappers/AppFormCheckbox";
import AppFormTextarea from "~/components/app/FormWrappers/AppFormTextarea";
export default class FormComponent extends BaseComponent {
// ...
parseComponent (component) {
const components = {
'input': AppFormField,
'checkbox': AppFormCheckbox,
'textarea': AppFormTextarea
};
return components[component];
}
}
-> Esquemas como funções
import { status } from '~/components/app/AppTable/fields';
import * as defaultActions from '~/components/app/AppTable/actions';
import { COMPONENTS, SCOPES } from "../../_config/settings";
export const fields = () => ([
{
key: 'uuid', // ...
table: {},
form: {}
},
{
key: 'name',
label: 'Nome',
scopes: [SCOPES.DEFAULT, SCOPES.CREATE, SCOPES.UPDATE], //...
},
{
key: 'email' //...
},
{
key: 'observations', //...
},
{
key: 'status', //...
}
]);
export const actions = () => Object.values(defaultActions);
import { fields, actions } from './schema';
import { deleteProperty, filterByScope } from "../../_config/helpers";
export const path = '/auth/users';
export const table = (scope = 'default') => ({
// ...
filters: { name: '', status: '' },
rows: [],
actions: actions().filter(action => filterByScope(action, scope)),
fields: fields()
.filter(field => filterByScope(field, scope))
.map(field => deleteProperty(field, 'form'))
});
export const form = (scope) => ({
// ...
groups: [],
fields: fields()
.filter(field => filterByScope(field, scope))
.map(field => deleteProperty(field, 'table'))
});
-> Esquemas como classes
import Schema from 'src/app/Agnostic/Schema'
import Service from 'src/domains/Example/User/Schema/UserService'
export default class UserSchema extends Schema {
service = Service
construct () {
this.addField('nome')
.fieldTableShow()
.fieldTableWhere()
.fieldFormAutofocus()
.fieldFormWidth(50)
.validationRequired()
this.addField('email')
.fieldTableShow()
.fieldTableWhere()
.fieldIsEmail()
.fieldFormWidth(50)
// ...
}
}
-> Para levar pra casa
- Componentes HTML são apresentação
- Regra de negócio fica no JavaScript
- Quanto mais JS mais manutenível e extensível
hora das palmas
perguntas?
-> Referências
Criando Componentes Dinâmicos com React
By William Correa
Criando Componentes Dinâmicos com React
- 1,411