Desenvolvimento

10 jun, 2016

React.js em projetos pequenos

Publicidade

Eu sou um grande fã de tecnologias acessíveis. Acho suspeito sempre que eu encontro um framework ou biblioteca que “não vale a pena para um projeto pequeno”, porque essa é uma mensagem codificada que significa tanto “Ele tem uma ampla gama de recursos que um projeto pequeno não irá aproveitar”, ou “ele troca a simplicidade ou a experiência do usuário para ter desempenho ou outras razões”. A acessibilidade também ajuda projetos open source a sair da obscuridade em direção da onipresença; se o jQuery tivesse requerido uma ferramenta de linha de comando para compilar logo que foi lançado, eu posso praticamente garantir que você nunca teria ouvido falar sobre ele.

Com isso em mente, sou fã de frameworks JavaScript que podem ser utilizados em projetos pequenos, bem como em grandes. Eu fiz isso antes com Angular 1 (Angular 2 não parece valer a pena para um projeto pequeno), mas eu só tive a oportunidade de experimentar React.js na criação de um complemento sarcástico no meu artigo anterior, e eu pensei que deveria compartilhar a experiência.

Definindo o app

Meu último artigo foi sobre quantas maneiras diferentes podemos escrever aplicações web. Para isso, fiz um aplicativo que seleciona aleatoriamente uma linguagem, framework e armazenamento de dados e atribui um acrônimo para essa combinação.

Primeiros passos com o React.js

Eu nunca usei React em JavaScript antes; minha única experiência é com Om e Reagent, assim, enquanto eu pude ter uma vantagem sobre os broad strokes, ainda estou começando do zero com relação a usar com JavaScript.

Mesmo que eu esteja usando Middleman, e ao contrário da última vez, eu escolhi escrever isso em plain-old-js5, em parte porque CoffeeScript parece estar caindo em desuso e eu preciso reativar minhas conexões neurais em JS novamente. Eu também decidi até mesmo não usar o processamento JSX do React; todo DOM em meu aplicativo seria programado manualmente em JavaScript.

Primeiro, eu fui para o Techempower Benchmarks e comecei minha aventura de copiar e colar, produzindo os dados brutos que você pode encontrar neste gist. Então, eu peguei o react e o linkei ao meu index.html. Finalmente, comecei a escrever um componente e montei:

var LanguageSelect = React.createClass({
  render: function(){
    var opts = [];
    for(var ii=0; ii<LANGUAGES; ii++){
      opts[ii] = React.createElement(
        "option",
        {value: LANGUAGES[ii]},
        LANGUAGES[ii]
      );
    }

    return React.createElement(
      "select",
      {
          value: LANGUAGES[0],
      },
      opts
    );
  }
});

ReactDOM.render(
  React.createElement(LanguageSelect),
  document.getElementById("app")
);

Isso foi bom para um começo. Eu aprendi que a criar componentes React envolve o uso de React.createClass para definir classes e, em seguida, renderizá-los usando React.createElement. Essa parte foi fácil.

Refinando as coisas

Em seguida, eu me propus a fazer um elemento Select genérico. Eu peguei minha LanguageSelect e decidi que iria receber a lista de opções como uma propriedade (eu comecei abusando do initializeState antes de aprender um pouco mais e decidir que o state não era necessário).

Eu também decidi que meus componentes viveriam em um App container, onde eu armazenaria todo e qualquer estado que o aplicativo necessitaria (uma lição de trabalhar com bibliotecas clojurescript react). Eu também decidi que a passagem do estado acima usando eventos onChange foi o melhor caminho.

Fiz o container primeiro, e (depois de alguma experimentação) e atualizei meu código de montagem de acordo:

var App = React.createClass({
    getInitialState: function(){
        return {
            datastore: DATASTORES[0],
            language: LANGUAGES[0],
            framework: FRAMEWORKS_BY_LANGUAGE[LANGUAGES[0]]
        }
    },

    setLanguage: function(e){
        this.setState({
            language: e.target.value,
            framework: FRAMEWORKS_BY_LANGUAGE[e.target.value][0]
        })
    },
    setDatastore: function(e){ this.setState({datastore: e.target.value}); },
    setFramework: function(e){ this.setState({framework: e.target.value}); },

    render: function(){
        var frameworkOptions = FRAMEWORKS_BY_LANGUAGE[this.props.language];
        var langSelect = React.createElement(
            Select,
            {
                options: LANGUAGES,
                value: this.state.language,
                onChange: this.setLanguage
            }
        );
        return React.DOM.div({},
            React.createElement(Select, {
                options: DATASTORES,
                value: this.state.datastore,
                onChange: this.setDatastore
            }),
            langSelect,
            React.createElement(Select, {
                value: this.state.framework,
                options: frameworkOptions,
                onChange: this.setFramework
            }),
        );
    }
});

ReactDOM.render(
  React.createElement(App),
  document.getElementById("app")
);

Então, eu escrevi um componente Select para uso geral:

var Select = React.createClass({
  updateValue: function(e){
    this.props.onChange(e);
  },
  render: function(){
    var opts = [];
    for(var ii=0; ii<this.props.options.length; ii++){
      opts[ii] = React.createElement(
        "option",
        {value: this.props.options[ii]},
        this.props.options[ii]
      );
    }
    return React.createElement(
      "select",
      {
          value: this.props.value,
          onChange: this.props.onChange
      },
      opts
    );
  }
});

Muito melhor! Isso é um pouco mais genérico.

Uma coisa a saber sobre React é que tudo o que você renderizar em seu aplicativo é um componente, do texto ao formulário, toda a coisa em si. É apenas uma árvore de componentes, da mesma forma que o DOM é uma árvore de Nodes. Então, precisava de um componente para exibir as iniciais da pilha, e de outro para exibir os links que eu tinha cuidadosamente recolhido:

var VOWELS = "aeiouyAEIOUY";

var isVowel = function(s){
    return VOWELS.indexOf(s) > -1;
};

var InitialDisplay = React.createClass({
    render: function(){
        var d = this.props.datastore[0],
            l = this.props.language[0],
            f = this.props.framework[0],
            comps = [this.props.datastore, this.props.language, this.props.framework],
            initials = [d, l, f];
            spans = [];

        if(isVowel(d)){
            comps = [this.props.language, this.props.datastore, this.props.framework];
            initials = [l, d, f];
        }else if(isVowel(f)){
            comps = [this.props.datastore, this.props.framework, this.props.language];
            initials = [d, f, l];
        }

        for(var ii=0; ii<initials.length; ii++){
            spans[ii] = React.createElement("span", {key: ii}, initials[ii]);
        }

        return React.DOM.div({},
            React.DOM.h2({},
                "Try the ",
                React.DOM.span({className: "initials"}, spans),
                " stack"
            ),
            React.DOM.section({className: "featuring"},
                "Featuring: ",
                React.createElement(Links, {components: comps}),
                React.DOM.p({}, "Get on the trolley and start writing web applications like it's " + (new Date().getYear() + 1900).toString() + " already!")
            )
        );
    }
});

var Links = React.createClass({
    render: function(){
        var links = [];
        for(var ii=0; ii<this.props.components.length; ii++){
            links[ii] = React.DOM.li({},
                React.DOM.a({href: URLS[this.props.components[ii]]},
                    this.props.components[ii]));
        }
        return React.DOM.ul({className: "links"}, links);
    }
});

Aqui, a classe InitialDisplay é responsável por exibir as iniciais para o armazenamento de dados selecionado, linguagem e framework, que são passados como adereços. Ele também tem um pouco de lógica para tentar colocar uma vogal no meio do acrônimo, se possível. Ele, por sua vez, renderiza o componente Link, que apenas exibe cada framework/linguagem/armazenamento de dados, como um elemento a com sua url como a href.

Finalmente, eu só tive que instalar algumas máquinas de randomização, e atualizar o App para exibir todas as coisas que eu acabei de escrever:

var App = React.createClass({
    getInitialState: function(){
        return generateRandomState();
    },
    setLanguage: function(e){
        this.setState({
            language: e.target.value,
            framework: pickRandom(FRAMEWORKS_BY_LANGUAGE[e.target.value])
        })
    },
    setDatastore: function(e){ this.setState({datastore: e.target.value}); },
    setFramework: function(e){ this.setState({framework: e.target.value}); },
    randomize: function(){
        this.setState(generateRandomState());
    },
    render: function(){
        var frameworkOptions = FRAMEWORKS_BY_LANGUAGE[this.state.language];
        var langSelect = React.createElement(
            Select,
            {
                options: LANGUAGES,
                value: this.state.language,
                onChange: this.setLanguage
            }
        );
        return React.DOM.div({},
            React.createElement(InitialDisplay, {
                datastore: this.state.datastore,
                language: this.state.language,
                framework: this.state.framework
            }),
            React.DOM.hr(),
            React.DOM.div({className: "selects"},
                React.createElement(Select, {
                    options: DATASTORES,
                    value: this.state.datastore,
                    onChange: this.setDatastore
                }),
                langSelect,
                React.createElement(Select, {
                    value: this.state.framework,
                    options: frameworkOptions,
                    onChange: this.setFramework
                }),
                " or, ",
                React.DOM.a({onClick: this.randomize, href: "#"},
                    "generate a new random stack"
                )
            )
        );
    }
});

É isso! A coisa toda. Mais uma vez, aqui está o aplicativo inteiro no gist.

Impressões

Primeiro, escrever DOM em JavaScript não é uma coisa que escala bem, e IMO jsx só ajuda nisso um pouco. Quando toda sua marcação de layout está espalhada em torno dos métodos de suas classes de componentes, você tem uma distribuição estranha de código de exibição espalhada (o que as melhores práticas geralmente exigem que deve ser o mais próximo possível). Em Clojurescript, eu uso kioo para lidar com isso, mas não é uma opção para React.

Isso é apenas uma impressão, para os fins deste pequeno exemplo de React está bom. Além disso e o gnarliness geral do JavaScript (especialmente JS5), realmente não foi tão ruim. Se existisse uma sintaxe de compreensão de loop, acho que a maioria do código teria sido chamada pelo react.DOM e React.createElement.

No geral, eu aprovo o React.js para ser usado em um projeto pequeno como este. Eu poderia também tentar o coffee-react para isso.

Epílogo

Aqui está o Select, se eu decidisse usar o coffee-react:

Select = React.createClass
  render: ->
    <select value={@props.value} onChange={@props.onChange}>
      {<option key={v} value={v}>{v}<option> for v in @props.options}
    </select>

Por que iria me machucar assim?!

***

Adam Bard faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: https://adambard.com/blog/react-in-the-small