reactReact

#ES6

@jbcazaux

http://codox.fr/formation-reactjs-es6

my name is
coffee break

Objectifs

goals

Versions


                    "react": "16.8.0-alpha.1"
                

Contenu

  • ES6
    • Quelques nouveautés
    • Rappels sur la programmation fonctionnelle
  • ReactJS
    • Principes
    • Composants
    • Etats
    • Redux
    • Tests
    • Router
    • Optimisations

EcmaScript 6

var, let, const


                let foo = 'bar'
                const MAX = 10
                const ttc = addTva(15)
                

let vs. const ? 0 var, 44 let, 997 const

var, let, const


                if (true) {
                    var i = 1
                } else {
                    var j = 2
                }
                console.log(i, j)
                

                const user = {}
                user.name = 'john' // (1)
                user = {name: 'lea'} // (2)
                

                const arr = ['a']
                arr.push('b') // (3)
                arr = ['a', 'b'] // (4)
                

Arrow functions


                function addOldSchool(a, b) {
                    return a + b
                }
            

                let add = (a, b) => {
                    return a + b
                }
                

                add = (a, b) => a + b
                

Arrow functions


                const squares = [0, 1, 2, 3, 4].map(x => x * x)
                console.log(squares)
                

Attention !

Pas totalement supporté par les navigateurs

  • Shim (Array.map(), Array.reduce(), ...)
  • Transpiler (=>, let, const, ...)

Spread operator


                const odd = [1, 3, 5, 7, 9]
                const even = [2, 4, 6, 8]
                const all = [0]

                // arr.push([element1[, ...[, elementN]]])
                all.push(odd)
                all.push(even)

                console.log(all) // ?
                

                const odd = [1, 3, 5, 7, 9]
                const even = [2, 4, 6, 8]
                const all = [0]

                // arr.push([element1[, ...[, elementN]]])
                for (let i = 0; i < odd.length; i++) {
                    all.push(odd[i])
                }
                for (let i = 0; i < even.length; i++) {
                    all.push(even[i])
                }
                console.log(all) // ?
                

                    const odd = [1, 3, 5, 7, 9]
                    const even = [2, 4, 6, 8]
                    const all = [0]

                    // arr.push([element1[, ...[, elementN]]])
                    all.push(...odd)
                    all.push(...even)

                    console.log(all) // ?
                

Spread operator


                let user = {
                    firstname: 'john',
                    nickname: 'ninja',
                    lastname: 'doe',
                    age: 21
                }

                user = {...user, firstname: 'toto'}

                console.log(user) // ?
            

Destructuring


                    const foo = {a: 1, b: 2, c: 'bar'}
                    let {a} = foo
                    console.log(a) // ?
                

Destructuring


            const user = {
                firstname: 'john',
                nickname: 'ninja',
                lastname: 'doe',
                age: 21
            }

            function getUserName(user) {
                return `${user.firstname} ${user.lastname}`)
            }
            console.log(getUserName(user))
            

            function getUserName({firstname, lastname}) {
                return `${firstname} ${lastname}`)
            }
            

            const getUserName = ({firstname, lastname}) =>
                `${user.firstname} ${user.lastname}`
            

Destructuring - nested


            const user = {
                firstname: 'john',
                lastname: 'doe',
                address: {
                    street: 'avenue des champs élysées',
                    num: '12'
                }
            }

            function getUserAddress(user) {
             return `${user.address.num} ${user.address.street}`
            }
            

            const getUserAddress = ({address: {num, street}}) =>
                `${num} ${street}`
            

Destructuring - alias


            const user = {
                firstname: 'john',
                lastname: 'doe',
                address: {
                    street: 'avenue des champs élysées',
                    num: '12'
                }
            }

            let getUserAddress = ({address: {num: foo, street: bar}}) =>
                `${foo} ${bar}`
            

Destructuring - arrays


            const maths = [x => x + 1, x => x * x, x => x * x * x]
            const [plus1, square] = maths
            plus1(41) // 42
            square(12) // 144
            

Shorthand Notation


                const foo = 42
                const bar = 1

                const longer = {
                    foo: foo,
                    bar: bar
                }
            

                const shorter = {
                    foo,
                    bar
                }
            

Promesses VS. async - await


            axios.get('/users/42/items')
                .then(resp => resp.data)
                .then(items => items.map(item => item.id))
                .then(ids => {/* use the ids */})
                .catch(error => console.log(error.toString()))
            

            async function fetchItems {
                try {
                    const response = await axios.get('/users/42/items')
                    const items = response.data
                    const itemIds = items.map(item => item.id))
                    /* use the ids */
                } catch(error){
                    console.log(error.toString())
                }
            }
            

Promesses VS. async - await


            axios.get('/users/42/items')
                .then(({data : items})) => items.map(item => item.id))
                .then(ids => {/* use the ids */})
                .catch(error => console.log(error.toString()))
            

            async function fetchItems {
              try {
                const {data: items} = await axios.get('/users/42/items')
                const itemIds = items.map(item => item.id))
                /* use the ids */
              } catch(error){
                console.log(error.toString())
              }
            }
            

Rappels - currying


                let add = function(x) {
                    return function(y) {
                        return x + y
                    }
                }
                add(2)(3) // ???
            

                const add2 = add(2)
                add2(3) // = ?
            

                let add = x => y => x + y // WTF ?!
            

                let add = x =>
                    y => (x + y)
                //easy !
            

Rappels - this


                function f() {
                    this.foo // what is this ?
                }
            

react

Tout est composant

components

Hello world

hello.js
            import React from "react"

            class Hello extends React.Component {
                render() {
                    return (
                        
Hello {this.props.name} !
) } } export default Hello
index.js
            import React from "react"
            import ReactDOM from "react-dom"
            import Hello from "./Hello"

            ReactDOM.render(
                <Hello name="world"/>,
                document.getElementById('root')
            )
            

JSX


                render: function() {
                    return (
                      <li className="contact">
                          <h2 className="contact-name">{this.props.name}</h2>
                      </li>
                    )
                }

                render: function() {
                    return (
                      React.createElement('li', {className: 'contact'},
                        React.createElement('h2',
                            {className: 'contact-name'}, this.props.name)
                      )
                    )
                }
                

TP-01

Hello World

  1. Etendre la classe React.Component
  2. Implémenter la méthode render()

Type checking

On peut ne rien faire, utiliser Typescript , Flow ou les PropTypes


            import PropTypes from 'prop-types'
            class Hello extends React.Component {
                static propTypes = {
                    name: PropTypes.string.isRequired,
                    truc: PropTypes.bool
                }
                render() {
                    return 

Hello {this.props.name}!

} } Hello.propTypes = { name: PropTypes.string.isRequired, truc: PropTypes.bool }

Au choix: array, bool, func, number, object, string, symbol, node, arrayOf(), shape()... https://reactjs.org/docs/typechecking-with-proptypes.html

Composant et Cycle de vie

Les incontournables :

  • render()
  • componentDidMount()
  • componentWillUnmount()

Les autres :

  • componentDidUpdate(nextProps, nextState)
  • static getDerivedStateFromProps(nextProps, prevState): State
  • getDefaultProps()
  • shouldComponentUpdate(nextProps, nextState)

Composant et Cycle de vie

lifecycle

Variable de classe


            class Count extends React.Component {
                count = 0

                constructor(props) {
                    super(props)
                    setInterval(() => this.count = this.count + 1, 1000)
                }

                render() {
                    return 
Count: {this.count}
; } }
Don't try this at home !

State


            {
                count: 0,
                date: new Date(),
                wishList: [],
                friends: [],
                // ...
            }
            

State

Définir l'état par défaut


            //dans le contructeur
            this.state = {counter: 0}
            

Définir un nouvel état


            this.setState({counter: 3})
            this.setState(prevState => ({counter: prevState.counter +1}))
            

Récupérer l'état courant


            this.state
            

State


            class Count extends React.Component {

                constructor(props) {
                    super(props)
                    this.state = {count: 0}

                    setInterval(() => this.setState(
                        prevState => ({count : prevState.count + 1 })),
                    1000)
                }

                render() {
                    return <div>
                        Count: {this.state.count}
                    </div>
                }
            }
            

Props vs State

Props

  • Les données passées par le parent

State

  • Les données modifiables par les événements de la UI
  • Le plus possible de composants sans état
  • Des données calculées(à faire dans render)
  • Des composants

Functional component


                import React from "react"
                const Hello = (props) => 

Hello {props.name}!


            class ButtonApp extends React.Component {
                render() {
                    return <div>
                        <div>{this.props.title}</div>
                        <button>{this.props.label}</button>
                    </div>
                }
            }
            

            const ButtonApp = (props) =>
                <div>
                    <div>{props.title}</div>
                    <button>{props.label}</button>
                </div>
            

            const ButtonApp = ({title, label}) =>
                <div>
                    <div>{title}</div>
                    <button>{label}</button>
                </div>
            

Itérer sur un tableau


            render() {
                return <ul>
                {
                    this.state.items.map(item =>
                        <li key={item.id}>{item.label}</li>)
                }
                </ul>
            }
            

TP-02

Liste de courses

  1. Afficher le titre passé par le parent
  2. Mettre un état par défaut
  3. Faire afficher la liste des courses dans des balises <li></li>
  4. Afficher un message de log pour chaque moment du cycle de vie
  5. Créer un composant distinct pour les éléments <li> de la liste
  6. Bonus: Utiliser une classe qui encapsule le composant "liste" pour gérer l'appel au serveur, et transmettre les data
  7. en tant que props

Bonus TP-02


            class ShoppingList extends React.Component {
                // constructor...
                // componentDidMount() {...}
                render() {
                   return <ShoppingListInternal title={this.props.title}
                            items={this.state.items}/>
                }
            }
            const ShoppingListInternal = ({title, items}) =>
            <div>
                <h2>{title}</h2>
                <ul>{items.map(item =>
                    <ShoppingItem key={item.id} item={item}/>)}
                </ul>
            </div>
            

Passer des fonctions dans Props


            class ShoppingList extends React.Component {
                // constructor...
                // componentDidMount() {...}
                deleteItem() {
                    const newItems = this.state.items.slice(1)
                    this.setState(items: newItems)
                }
                render() {
                    return (<ShoppingListInternal
                            title={this.props.title}
                            items={this.state.items}
                            del={this.deleteItem.bind(this)}/>)
                    }
                }
            }
            

Recevoir des fonctions dans Props


            const ShoppingListInternal = ({title, items, del}) => (
                <div>
                    <h2>{title}</h2>
                    <ul>
                        {
                          items.map(item => (<li onClick={del}>
                            {item.label}: {item.price}€ </li>))
                        }
                    </ul>
                </div>)
            

Intercepter les événements


            class ClickableApp extends React.Component {
                render() {
                 return <div onclick={this.handleClick}>Click me !</div>
                }

                handleClick(e) {
                    console.log('click !', e)
                }
            }
            

            class ClickableApp extends React.Component {
                render() {
                    return <div
                        onclick={(e) => console.log('click !', e)}>
                        Click me !
                    </div>
                }
            }
            

Avec du style

inline style vs css

Pourquoi pas du inline ?


            render() {
                const mystyle = {backgroundColor: '#F0ABCD'}
                return <div style={mystyle}/>
            }
            

Sinon une CSS globale, ou une CSS par composant (Webpack powered)


                import './MyComponent.css'
                

React-bootstrap

Adaptation des composants bootstrap à React


                <Button bsStyle="primary"
                        onClick={this.handleClick}>Go!</Button>
            

La documentation

Material-UI

React components that implement Google's Material Design


                <Button onClick={this.handleClick}>Go!</Button>
            

La documentation

https://material-ui-next.com/api/button/

Styled-Components

Visual primitives for the component age.


            const Title = styled.h1`
                font-size: 1.5em;
                text-align: center;
                color: palevioletred;
            `
            <Title>Superbe titre !</Title>
            

La documentation

https://www.styled-components.com/docs/api

Trucs et astuces

(Workarounds)

Les fragments


            render() {
                return (
                <React.Fragment>
                    <div>Pourquoi </div>
                    <span>pas ?</span>
                </React.Fragment>
                )
            }
            

            render() {
                return (
                <>
                    <div>Pourquoi </div>
                    <span>pas ?</span>
                </>
                )
            }
            

Caractères spéciaux


                 Jet d{String.fromCharCode(39)}eau
            

JSX if (else)

if


                {this.state.user && 
{this.state.user.login}
}

not


                {this.state.user || 
No User
}

if-else


                {this.state.user
                    ? 
{this.state.user.login}
:
No User
}

C'est pas mieux en angular :)

Commentaires


            render() {
                return <div>
                    {/* Sans commentaire ;) */}
                </div>
            }
            

Children


            ReactDOM.render(
               <ButtonApp title="Mon Application">Press Me! </ButtonApp>,
               document.getElementById('root')
            )
            

            export default class ButtonApp extends React.Component {
                render() {
                    return <div>
                        <div>{this.props.title}</div>
                        <button>{this.props.children}</button>
                    </div>
                }
            }
            

Quizz


            class App extends React.Component {
              constructor(props) {
                super(props)
                this.name = 'MyComponent'
                this.handleClick2 = this.handleClick1.bind(this)
              }
              handleClick1() { alert(this.name);}
              handleClick3 = () => alert(this.name)
              render() {
               return ( <div>
                 <button onClick={this.handleClick1()}>click 1</button>
                 <button onClick={this.handleClick1}>click 2</button>
                 <button onClick={this.handleClick2}>click 3</button>
                 <button onClick={this.handleClick3}>click 4</button>
               </div> )
               }
            }
            

https://github.com/sudheerj/reactjs-interview-questions

TP-03

Liste des étudiants

  1. Afficher un champ texte "input" qui permet de filtrer les éléments d'un tableau
  2. Afficher le nom de l'étudiant sur lequel on a cliqué, ou un message si aucun n'est sélectionné
  3. Bonus : Faire une pull request d'une appli jolie ET simple ;)
  4. Bonus ES6: Calculer de façon élégante la note max d'un élève (reduce)

Rappels

  • Un composant a des propriétés et des états
  • Un composant stateless est plus maintenable et testable
  • On ne peut passer des propriétés qu'aux enfants

Flux & Redux

Flux

  • Store: Stocker la donnée + Logique
  • Action: Modifier le modèle
  • Dispatcher: Répartir les actions dans les stores
  • View: Les composants UI
flux

Le flux ne va que dans un seul sens ! (2-way binding)

Bad Practice vs. Good Practice

best practices
  • On ne met pas à jour directement les autres composants
  • On passe par le store (redux), les stores (flux), les services (Subject RxJs), ...

Redux

Actions


                {type: 'INCREMENT', inc: 3}
                {type: 'ADD_TODO', text: 'Acheter du pain'}
                {type: 'SET_USER', user: new User(1, 'toto')}
            

Action Creator


            export const incrementor = (inc) => {
                return {type: 'INCREMENT', inc}
            }
            

            export const incrementor =
                (inc) => ({type: 'INCREMENT', inc})
            

Redux


                const userId = window.current_account.id
            

«Pourquoi utiliser redux alors qu'on a les variables globales ?»

#TROLL

Redux

  • Un store unique (pratique pour l'isomorphisme)
  • L'état du store est read-only
  • On émet des actions pour modifier l'état
  • Les actions sont interprétées par des reducers
  • Les reducers sont des fonctions pures
  • Les reducers prennent en entrée l'état précédent et une action
  • Ils retournent le nouvel état dans un objet immutable
redux

Reducer


            export const display = (state = 0, action) => {
                switch (action.type) {
                    case 'INCREMENT':
                        return state + action.inc
                    case 'DECREMENT':
                        return state - action.inc
                    default:
                        return state
                }
            }
            
Le state est en read-only ! redux

Store

Deux méthodes importantes dans le store: dispatch et getState.

  • Le store garde l'état de l'application. On y accède via getState().
  • setState() !! On modifie l'état en 'dispatchant' une action: dispatch(inc(3))

Et aussi...

  • On peut être prévenu des changements du store via subscribe(listener)
  • On se désenregistre avec la méthode retournée par subscribe
  • Mais on ne s'en sert jamais ;)
redux

React & Redux

Composition de reducers

Approche naïve


            const initialState = {user: '', counter: 0}
            export const myGlobalReducer =
                (state = initialState, action) => {
                    switch (action.type) {
                        case 'SET_USER':
                           return {...state, user: action.user}
                        case 'INCREMENT':
                           return {...state, counter: state.counter + 1}
                        case 'DECREMENT':
                           return {...state, counter: state.counter - 1}
                        default:
                           return state
                }
            }
            
Ca peut faire lourd sur une grosse appli...!

Composition de reducers


            export const user =
                (state = User.NULL, action) => {
                    switch (action.type) {
                        case 'SET_USER':
                            return action.user
                        default:
                            return state
                    }
                }
            export const counter =
                (state = 0, action) => {
                    switch (action.type) {
                        case 'INCREMENT':
                            return state + 1
                        case 'DECREMENT':
                            return state - 1
                        default:
                            return state
                    }
            }
            

Composition de reducers

Effet Waouh


            const init = {user: 'nobody', counter: 0}
            export const myGlobalReducer = (state = init, action) =>
                ({
                    user: user(state.user, action),
                    counter: counter(state.counter, action)
                })
            

Et avec un peu de magie...


            import { combineReducers } from 'redux'
            export const myGlobalReducer = combineReducers({
                user,
                counter
            })
            

Récapitulatif

  • Actions
  • Reducers
  • Store
  • Composants React
  • Il reste à lier les composants au store !
redux

Présentation et Conteneur

  • Les composants stateless sont simples à maintenir
  • Séparation des responsabilités
conteneur-presentation

Présentation et Conteneur

On découpe nos composants (connectés au store) en 2, présentation & conteneur

Presentational Components Container Components
Purpose How things look (markup, styles) How things work (data fetching, state updates)
Aware of Redux No Yes
To read data Read data from props Subscribe to Redux state
To change data Invoke callbacks from props Dispatch Redux actions
Are written By hand Usually generated by React Redux

Connect

Le composant de présentation est comme les composants simples rencontrés jusque là (sans appels ajax).

Le composant conteneur va être généré automatiquement, mais:

  • il faut décrire comment créer les props à partir des données du store: mapStateToProps(state) {...}
  • il faut implémenter les callbacks qui seront injectés dans les props: mapDispatchToProps(dispatch) {...}

            import {connect} from 'react-redux'

            export const MyComponent =
            connect(mapStateToProps, mapDispatchToProps)(MyPresentationalComponent)
            

Composant de présentation:


                export const SelectCodePostal_ =
                    ({cps, currentCp, onSelectCp}) => (
                <select onChange={onSelectCp} value={currentCp}>
                {
                    cps.map(cp =>
                        <option key={cp} value={cp}>{cp}</option> )
                }
                </select>
                )
                

Conteneur


            const mapStateToProps = (state) => {
                const cpsOfAgence = state.codesPostaux.map(cp => cp.id)
                return {cps: cpsOfAgence, currentCp: state.codePostal.id}
            }

            const mapDispatchToProps = (dispatch) => {
                return {
                    onSelectCp: (event) => {
                     const cpId = parseInt(event.target.value)
                     dispatch({type: 'SET_CODE_POSTALID', cpId: cpId})
                    }
                }
            }
            export const SelectCodePostal =
            connect(mapStateToProps, mapDispatchToProps)(SelectCodePostal_)
            

Passer le store

Tous les conteneurs de l'application doivent avoir accès au store.


                import React from 'react'
                import ReactDOM from 'react-dom'
                import {Provider} from 'react-redux'
                import {createStore} from 'redux'
                import {reducer} from './reducers/index'
                import {App} from './components/app'

                const store = createStore(reducer)

                ReactDOM.render(
                    <Provider store={store}>
                        <App/>
                    </Provider>,
                    document.getElementById('app')
                )
                

TP-04

Liste de courses - bis

  1. Installer le plugin redux
  2. Comme le TP-02 mais en utilisant le state du store plutôt que le state du composant, et en initialisant la liste sans chargement asynchrone (liste en dur).
  3. Créer un action creator setItems qui renvoie une ItemAction
  4. Créer un reducer items qui prend une ItemAction en entrée
  5. Créer un reducer global à l'aide de combineReducer
  6. Créer le contener et le presentational component
  7. Bonus: Créer un bouton qui permet d'ajouter des items au store.

Actions asynchrones

Généralités

  • Redux ne permet que de dispatcher des objets
  • redux-thunk ou redux-saga permettent de lancer une fonction, et donc des actions asynchrones ou des actions avec conditions. Ce sont des middlewares.
  • En redux-thunk, c'est plus élégant que les actions retournent des promesses

redux-thunk

Configuration


                import {reducer} from './reducers/index'
                import {createStore, applyMiddleware} from 'redux'
                import thunk from 'redux-thunk'

                const store = createStore(
                reducer,
                applyMiddleware(thunk)
                )

                

ThunkActionCreator


            function incrementCreator(inc) {
                return {
                    type: 'INCREMENT_COUNTER',
                    inc: inc
                }
            }

            function incrementAsyncCreator(inc) {
                return dispatch => setTimeout(
                        () => dispatch(incrementCreator(inc)),
                        1000)
            }
            

En 2 mots

Une ThunkAction est une fonction qui reçoit en entrée les méthodes dispatch() et getState(), et retourne le résultat de dispatch().

Exemple complet


            const getItems =
                (userId) => axios.get('users/' + userId + '/items')
                    .then(resp => resp.data)
                    .catch(error => console.log(error.toString()))
            

            export const fetchItems = () =>
                (dispatch, getState) => getItems(getState().userId)
                .then(items => dispatch({type: 'SET_ITEMS', items: items}))
            .catch((error) => console.log(error))
            

TP-05

Liste de courses - ter

  1. Configurer le Middleware "thunk" dans le index.js
  2. Comme le TP-04 mais en faisant un appel asynchrone (http) pour charger la liste d'items
  3. Bonus: Faire une action qui avant d'ajouter l'item au store, applique la TVA sur le prix lors d'un ajout d'item par le formulaire

Bravo !

C'était la partie la plus compliquée

On sait créer des composants et leurs passer des propriétés

  • Depuis leur composant parent
  • Depuis l'état 'local' (LocalState)
  • Depuis l'état du store (State)
  • Depuis un composant grand(n)-parent par le context... ou pas !

Context

Context API

  • On peut passer des objets à ses composants enfants (n'importe où dans la hiérarchie).
  • Beaucoup de librairies sont basées la dessus. Ce n'était pas une API officiellement supportée avant react 16.3.0.
  • react-redux fonctionne grâce à ça (le store est passé à tous les enfants).

Utilisation

Il faut Créer le Context, puis wrapper l'application avec le <Context.Provider>


                const Context = React.createContext({color: 'green', user: null})
                export default Context
            

                render() {
                    return <Context.Provider
                                value={{color: 'purple', user: new User(1, 'admin')}}>
                        <App/>
                    </Context.Provider>
                }
            

Utilisation

Et enfin récupérer le context là où il est nécessaire.


            import React from 'react'
            import MyContext from './Context'

            class Small extends React.Component {

              static contextType = MyContext

              render() {
                const ctx = this.context
                return <div
                        style={{backgroundColor: ctx.color,
                            height: '125px',
                            width: '33%'}}
                        >
                   Small, user = {ctx.user.id}-{ctx.user.login}
                </div>
              }
            }
            

TP-06

Contexte

Le but est de passer une couleur et un User d'un composant à son composant 'petit-fils'

  1. Déclarer un context autour du grand-parent (Large), avec une couleur et un User.
  2. Afficher les informations passées dans le context dans le composant petit-fils (Small)

Tests

Jest & Enzyme

L'idée est de tester les composants de présentation, avec leurs états et leurs propriétés.


            import React from "react"
            import {shallow} from "enzyme"

            describe('MyComponent', () => {
                it('should work !', () => {
                    const component = shallow(<MyComponent/>)
                })
            })
            

Jest API

Enzyme API

Présence de sous-composants


            describe('Hello', () => {
                it('renders Hello and its sub components', () => {
                    const component = shallow(<Hello name="world"/>)
                    expect(component.find(SubComponent1).exists())
                        .toBeTruthy()
                    expect(component.find(SubComponent2).exists())
                        .toBeTruthy()
                })
            })
            

Texte affiché


            expect(component.text()).toContain('mon texte attendu')
            

Vérifier qu'une méthode a été appelée


            const mockOnChange = jest.fn()
            expect(mockOnChange).toBeCalledWith(myParam)
            

Simulation


            component.find('button')
                .at(1)
                .simulate('click')
            component.find('input')
                .simulate('change', {target: {value: 'ma valeur'}})
            

Appeler une méthode


            component.instance().maMethode()
            

Récupérer l'état


                expect(component.state('name')).toEqual('mon nom')
            

Mocking avancé - fonction


            import * as Maths from './maths'

            describe('Maths', () => {
              it('should add', () => {
                Maths.add = jest.fn().mockImplementationOnce(() => 21)

                expect(Maths.add(1,2)).toEqual(21)
              }
            })
            

Mocking avancé - module


                import * as Maths from './maths'
                import { calculette } from './calculette'

                jest.mock('./maths')
                describe('calculette', () => {
                    it('should use maths module to performs add42', () => {
                        calculette.add42(11)

                        expect(Maths.add).toHaveBeenCalledWith(42, 11)
                    }
                })
            

Lancer les tests

  • yarn test en ligne de commande
  • runner jest dans Webstorm
  • --coverage pour la couverture de tests

TP-07

Les tests

Le but est de tester complètement le TP-03

  1. Créer un fichier .test.js par composant
  2. StudentDetails: Vérifier le texte pour les 2 cas (Student.NULL ou non)
  3. Filter: Vérifier que le callback est appelé lors d'un événement change
  4. StudenstTable: Vérifier le nombre de lignes du tableau suivant le nombre de Student passé. Vérifier également le callback.
  5. StudentsApp: Vérifier que le composant et ses sous composants sont affichés, puis vérifier l'impact des méthodes handleSelectStudent() et handleFilterChange() sur le state. Enfin valider que filteredStudents() renvoie les bons éléments pour un filtre donné.
  6. Bonus: Essayer les snapshots jest

Router

react-router

Il existe plusieurs systèmes de gestion de l'historique de navigation :

  • BrowserRouter : /monsite/page1/partie2
  • HashRouter: /monsite/#/page1/partie2
  • MemoryRouter : /

Pour le BrowserRouter il faut modifier le serveur pour rediriger toutes les requêtes vers index.html, sauf les css, js, et appels aux webservices.

react-router

Définir le Router au plus haut niveau dans l'application.


            import {BrowserRouter as Router,
                Route} from 'react-router-dom'

            ReactDOM.render(
              <Router>
                  <Route exact path="/" component={Home} />
                  <Route path="/about" render={() => 

About...

} /> <Route path="/admin" component={Admin} /> </Router>, document.getElementById('root') )

On peut imbriquer les routes !


            const Admin = () => (
            <div>
              <h2>Administration</h2>
              <Route path={'/admin/users'} component={UsersAdmin}/>
              <Route path={'/admin/articles'} component={ArticlesAdmin}/>
            </div>
            )
            

react-router

On peut générer des liens automatiquement (voir la doc pour les propriétés)


            <Link to="/admin/users">Administration des utilisateurs</Link>
            

react-router

Urls dynamiques


            <Route path={'/admin/users/:userId'} component={UserAdmin}/>
            

            const UserAdmin = ({match}) => (
            <div>
                Gérer l'utilisateur {match.params.userId}
            </div>
            )
            

match

L'objet match contient les informations qui ont permis a un <Route path> de matcher. On retrouve:

  • params: liste de clé/valeur de l'url (ex: /user/:id)
  • url: url matchée
  • isExact: vrai si l'url a exactement matché
  • path: pattern du match

            const Admin = ({match}) => (
                <Link to={`${match.url}/users`}>
                    Users
                </Link>
                <Link to={`${match.url}/articles`}>
                    Articles
                </Link>
            )
            

TP-08

React-router

L'objectif est d'avoir une application avec 3 pages, dont l'une va chercher un identifiant dans l'url.

  1. Générer les liens vers les pages About et Users
  2. Ecrire les routes vers les pages About et Users
  3. Générer les liens vers les détails des 2 utilisateurs: /users/elsa et /users/anna
  4. Ecrire la route vers le composant UserDetail
  5. Générer le lien vers l'image des utilisateurs (elsa.jpeg et anna.jpeg)

Optimisations

On ne redessine que le nécessaire

shouldComponentUpdate & Virtual-DOM update

Reconciliation

Le Virtual DOM est... une réprésentation des composants en mémoire. Quand les propriétés d'un composant changent, le nouveau DOM est comparé à l'ancien (celui du Virtual DOM). Si il y a une différence, le 'vrai' DOM est modifié. Si on sait que le composant n'a pas besoin d'être redessiné, on peut surcharger shouldComponentUpdate


            shouldComponentUpdate(nextProps, nextState) {
                return true
            }
            

Faut-il étendre React.PureComponent (shallow equality) ou ré-implémenter shouldComponentUpdate() ? Dans tous les cas le mieux est de ne pas avoir des données mutables: Object.assign, spread properties, Immutable.js, ...

Memoization

Permet d'éviter le recalcul d'une propriété dérivée de l'état du store redux. Par exemple le tri d'une liste, le caclcul du prix total d'un panier, ...

Memoization


            const shopItemsSelector = state => state.shop.items
            const taxPercentSelector = state => state.shop.taxPercent

            const subtotalSelector = createSelector(
            shopItemsSelector,
            items => items.reduce((acc, item) => acc + item.value, 0)
            )

            const taxSelector = createSelector(
            subtotalSelector,
            taxPercentSelector,
            (subtotal, taxPercent) => subtotal * (taxPercent / 100)
            )

            export const totalSelector = createSelector(
            subtotalSelector,
            taxSelector,
            (subtotal, tax) => ({ total: subtotal + tax })
            )
            

TP-09

Optimisation

Jouer avec la méthode shouldComponentUpdate

  1. Implémenter la méthode shouldComponentUpdate
  2. Installer le plugin React, pour voir si le composant est redessiné
  3. Bonus: Reprendre le TP-03 pour limiter les renders inutiles

Let's do it !

help ? jbcazaux@gmail.com

http://codox.fr/formation-reactjs-es6