Aplicações Client-Side Híbridas com React.JS e React Native

Caio Almeida

JSday @ Recife, 16 de Dezembro de 2017

 

 

@caiosba

http://ca.ios.ba

  • Bacharel e Mestre em Ciência da Computação (UFBA)
  • Engenheiro de Software do Meedan (São Francisco/Califórnia/EUA)
  • Colaborador em projetos JavaScript, Ruby On Rails, PHP

O que é React.JS?

  • Desenvolvido pelo Facebook
  • Biblioteca para a camada de visualização (e não um framework)
  • React.js sozinho não irá criar uma aplicação web

REACT.JS NÃO POSSUI...

  • Controllers
  • Directives
  • Templates
  • Global Event Listeners
  • Models
  • (...)

APENAS

COMPONENTES

REGRA #1:

TUDO EM REACT.JS É UM COMPONENTE

ShoppingCartComponent

ShoppingCartComponent

CartListComponent

ShoppingCartComponent

CartListComponent

CartItemComponent

Separação de Concerns

x

Separação de Componentes

Componentes podem ser:

  • Compostos
  • Reutilizados
  • Mantidos
  • Testados
  • Caso eles sejam auto-contidos
import React, { Component, PropTypes } from 'react';

class Embedly extends Component {
  render() {
    return (
      <div className="embed">
        <a id="embedly" href={this.props.url}></a>
      </div>
    );
  }
}

export default Embedly;

Embedly.js

JSX

  • Linguagem de marcação parecida com HTML
  • Descrição declarativa da interface
  • Combina a facilidade dos templates com o poder do JavaScript
  • Pré-processador traduz JSX para JavaScript plano

REGRA #2:

REACT.JS

REDESENHA TUDO

EM QUALQUER ATUALIZAÇÃO

PARECE CUSTOSO? Mas é rápido!

VIRTUAL DOM

  • Cria uma descrição leve da interface do componente
  • Calcula as diferenças entre esta versão e a anterior
  • Computa o conjunto mínimo de alterações a serem aplicadas ao DOM
  • Executa em lote todas as alterações

Regra #3:

Única fonte de dados

props

Imutáveis

state

Mutável

Relay: GraphQL Queries (leitura) e

            Mutations (escrita (create, update, delete))

Ciclo

de Vida

import React, { Component, PropTypes } from 'react';

class Example extends Component {
  componentWillMount() {
    window.alert('Your component is loading');
  }
  
  componentWillReceiveProps: function(nextProps) {
    this.setState({
      increasing: nextProps.count > this.props.count
    });
  }

  componentDidMount() {
    window.alert('Your component is ready');
  }

  render() {
    return (<p>Mounted</p>)
  }
}

export default Example;

React Native

  • Uma biblioteca que converte JSX para:
    • iOS Cocoa
    • Android UI
  • Aplicações com performance similar a aplicação nativa
  • Extensível
  • É possível reaproveitar componentes
  • Código portável para Android e iOS

React Native

  • Performance
  • Sua equipe já utiliza React.JS e possui componentes bem auto-contidos
  • Aplicações mais complexas

Ionic et al.

  • Aplicações mais simples
  • Prazo x Curva de Aprendizado
  • Mesma versão da aplicação para web e para dispositivos móveis?
class Tags extends Component {
  render() {
    return (
      <ul className="tags-list">
        {props.tags.map(function(tag) {
          return (
            <Tag tag={tag}>
          );
        })}
      </ul>
    );
  }
}

Componente React

React.js

DOM

React Native

Android

iOS

React Native Modules

  • Alguns implementados por padrão
  • Outros implementados pela comunidade
  • Documentação para quem quiser criar seus próprios

React.JS + React Native = Keefer

https://github.com/meedan/generator-keefer

Um código, diferentes plataformas

  • Yeoman Generator
  • Gera um esqueleto de aplicação React / React Native
  • Recebe uma URL como parâmetro
  • Tema via SASS
  • Documentação
  • Abstração de plataforma: apenas o componente pai varia entre plataformas
  • Docker
  • GraphQL / Relay
  • Testes automatizados
$ git clone 'https://github.com
  /meedan/generator-keefer.git'
$ cd generator-keefer
$ cp config.yml.example config.yml
$ npm install -g yo
$ npm link
$ yo keefer

Plataforma: Web

URL via parâmetro

$ PLATFORM=web npm run build

Plataforma: Extensão para Navegador

URL via link clicado ou endereço da aba atual

$ PLATFORM=chrome npm run build

Plataforma: Móvel

URL via menu de compartilhamento

$ PLATFORM=android npm run build

===

  • State
  • Props
  • Relay: GraphQL Queries
  • Relay: Mutations

!==

  • Styles
  • Requisições Web
  • Sessão (cookies)
  • Input
  • Local Storage
  • DOM
  • Internacionalização

React.JS x React Native

1/7: Estilos

React Native Style

  • Implementação própria de estilo
  • Apenas dois tipos de layout: flexbox ou absoluto
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});
<View style={styles.container}>
</View>

1/7: Estilos

https://github.com/caiosba/react-native-css

description {
  margin-bottom: 20px;
  font-size: 18px;
  text-align: center;
  color: #656656;
}

container {
  padding: 30px;
  margin-top: 65px;
  align-items: center;
  display: block;
}
// style.js
module.exports = require('react-native').StyleSheet.create(
  {
    "description": {
      "marginBottom":20,
      "fontSize":18,
      "textAlign":"center",
      "color":"#656656"
    },
    "container":{
      "padding":30,
      "marginTop":65,
      "alignItems":"center"
    }
  }
);

1/7: Estilos

2/7: Requisições Web

superagent

fetch

superagent.get(url).withCredentials().end(
  (err, response) => {...}
);
fetch(url, { headers: { credentials: 'include' } }).then(
  response => { ... }
);

3/7: Sessão (cookies)

1) Autenticação via API (fetch) e armazenamento de token


2) Autenticação via React Native Cookie Manager (https://github.com/joeferraro/react-native-cookies)



import CookieManager from 'react-native-cookies';

CookieManager.setFromResponse('http://example.com', 
'user_session=abcdefg; path=/; secure; HttpOnly')
.then((res) => {
  callback(res);
});

3/7: Sessão (cookies)

3) Autenticação via WebView + postMessage

 

 

  onMessage(event) {
    const { data } = event.nativeEvent;
    if (data) {
      this.setState({ cookie: data });
    }
  }

  render() {
    return (
      <WebView
        source={{ uri: this.state.url }}
        onMessage={this.onMessage.bind(this)}
        injectedJavaScript="window.postMessage(document.cookie)"
      />
    );
  }

https://www.npmjs.com/package/react-native-share-menu

Módulo React Native que adiciona

sua aplicação ao menu "compartilhar" do aparelho e recebe URLs a partir de outros aplicativos

4/7: User Input

5/7: Local Storage

// Web
window.localStorage.setItem(key, value);

// Browser Extension
chrome.storage.sync.set({ key: value})

// Mobile
await AsyncStorage.setItem(key, value);

Implementar uma única função que abstraia para as diferentes plataformas

6/7: Componentes DOM

<!-- Web / Browser Extension -->
<div>
  <button onClick={this.send.bind(this)}>Send</button>
</div>

<!-- React Native -->
<View>
  <Button onPress={this.send.bind(this)} title="Send" />
</View>

https://github.com/necolas/react-native-web

Permite escrever os componentes em React Native e eles serão convertidos para React DOM

6/7: Componentes DOM

// .babelrc
{
  "plugins": [
    "react-native-web/babel"
  ],
  "presets": [
    "react-native"
  ]
}
// webpack
module.exports = {
  resolve: {
    extensions: ['.web.js', '.js', 
                 '.web.jsx', '.jsx'],
    alias: {
      'react-native': 'react-native-web',
    }
  }
};

7/7: Internacionalização

<!-- Hybrid -->

<IntlProvider locale={l} messages={msgs} textComponent={Text}>
  <FormattedMessage defaultMessage="Original text" id="example" />
</IntlProvider>

<!-- Output: Web and Browser Extension -->

<span>Translated text</span>

<!-- Output: React Native -->

<Text>Translated text</Text>

https://github.com/yahoo/react-intl

Basta definir a propriedade textComponent do componente IntlProvider

7/7: Internacionalização

import { Platform } from 'react-native';

let locale = 'en';

// Mobile
if (this.context.platform === 'mobile') {

  // Android
  if (Platform.OS === 'android') {
    locale = NativeModules.I18nManager.localeIdentifier;
  }

  // iOS
  else {
    locale = NativeModules.SettingsManager.settings.AppleLocale;
  }
}

// Web and Browser Extension
else {
  locale = navigator.languages || navigator.language || navigator.userLanguage;
}

Identificando o idioma atual:

Caso de Uso: Check Mark

https://github.com/meedan/check-mark

 

Obrigado!

htt//ca.ios.ba

slides.com/caiosba/jsdayrecife2017

Made with Slides.com