ReactJS
les évolutions de 2018-2019

Les fragments (16.2.0)

Avant les fragments


                    import React from 'react';

                    function FooBar() {
                        return (
                            
foo bar
); }
Trop de div !

Avec les les fragments


                    import React, { Fragment } from 'react';

                    function FooBar() {
                        return (
                            <Fragment>
                                foo
                                bar
                            </Fragment>
                        );
                    }
                
C'est bien, mais c'est verbeux 🤔

Avec les fragments "courts"


                    import React from 'react';
                    
                    function FooBar() {
                        return (
                            <>
                                foo
                                bar
                            </>
                        );
                    }
                

Les refs (16.3.0)

Avant

"string" refs

callback refs

Maintenant


                    class MyComponent extends React.Component {
                        constructor(props) {
                          super(props);
                      
                          this.inputRef = React.createRef();
                        }
                      
                        render() {
                          return <input type="text" ref={this.inputRef} />;
                        }
                      
                        componentDidMount() {
                          this.inputRef.current.focus();
                        }
                    }
                

Passage de ref


                    const FancyButton = React.forwardRef((props, ref) => (
                        <button ref={ref} className="FancyButton">
                            {props.children}
                        </button>
                    ));

                    // You can now get a ref directly to the DOM button:
                    const ref = React.createRef();
                    <FancyButton ref={ref}>Click me!</FancyButton>;
                    

L'API Context (16.3.0)

Le problème


                    const App = () => <Toolbar theme="dark" />
                
                  
                    const Toolbar(props) => (
                      
<ThemedButton theme={props.theme} />
); const ThemedButton = () => ( <Button theme={this.props.theme} /> );

Toolbar prend un thème et le passe aux ThemedButton.
Contraignant si tous les boutons doivent connaître le thème.

Une solution de ReactJS

On crée un contexte :


                    const ThemeContext = React.createContext('light');
                    // light = valeur par défaut
                    // sert pour les Consumer sans Provider
                

Un composant parent définit une valeur aux descendant.


                    class ThemeProvider extends React.Component {
                        state = { theme: 'light' };

                        render() {
                            return (
                                <ThemeContext.Provider value={this.state.theme}>
                                    {this.props.children}
                                </ThemeContext.Provider>
                            );
                        }
                    }
                

Un composant enfant peux accéder aux données.


                    const ThemedButton = () => {
                        return (
                            <ThemeContext.Consumer>
                                {theme => <Button theme={theme} />}
                            </ThemeContext.Consumer>
                        );
                    }
                

Avant d'utiliser les Context

composition de composants


                        <SplitPane
                          left={<Contacts user={user} />}
                          right={<Chat user={user} />} 
                        />
                    
C'est bien… mais c'est chiant (le Consumer doit avoir une fonction de callback un peu bizarre, etc.)

Les Hooks (16.8.0)

C'est quoi les "hooks" ?

Les hooks permettent d'utiliser le "state" et d'autres fonctionnalités de React sans écrire de classe.
On peut aussi écrire ses propres hooks pour partager une logique d'état entre composants.

Motivation

  • Dur de réutiliser la logique d'état entre composant (render props, HOC, redux, mobx, etc.)
  • Les composants complexes sont dur à comprendre (componentDidUpdate de l'enfer ?)
  • Les classes sont difficiles à comprendre (que vaut this ?)

State hook


                    import React, { useState } from 'react';

                    function Example() {
                      // Declare a new state variable, 
                      // which we'll call "count"
                      const [count, setCount] = useState(0);
                    
                      return (
                        

You clicked {count} times

<button onClick={() => setCount(count + 1)}> Click me </button>
); }

Multiple états


                    import React, { useState } from 'react';

                    function ExampleWithManyStates() {
                        // Declare multiple state variables!
                        const [age, setAge] = useState(42);
                        const [fruit, setFruit] = useState('banana');
                        const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
                        // ...
                    }
                

Hook d'effet


                    import React, { useState, useEffect } from 'react';
                    function Example() {
                        const [count, setCount] = useState(0);

                        // Similar to componentDidMount and componentDidUpdate:
                        useEffect(() => {
                            // Update the document title using the browser API
                            document.title = `You clicked ${count} times`;
                        });

                        return (<div>
                            <p>You clicked {count} times</p>
                            <button onClick={() => setCount(count + 1)}>
                                Click me
                            </button>
                        </div>);
                    }
                

Éviter d'exécuter un effet ?

Dans une classe :


                    componentDidUpdate(prevProps, prevState) {
                      if (prevState.count !== this.state.count) {
                        document.title = `You clicked ${this.state.count} times`;
                      }
                    }
                  

Éviter d'exécuter un effet ?

Dans une fonction :


                    useEffect(() => {
                      document.title = `You clicked ${count} times`;
                    }, [count]); // Only re-run the effect if count changes                  
Équivalent à componentDidMount ET componentDidUpdate

Mais où est le componentWillUnmount ? 🤔


                    function FriendStatus({ friend }) {
                        const [isOnline, setIsOnline] = useState(null);

                        function handleStatusChange(status) {
                            setIsOnline(status.isOnline);
                        }
                    
                        useEffect(() => {
                            ChatAPI.subscribe(friend, handleStatusChange);
                    
                            return () => {
                                ChatAPI.unsubscribe(friend, handleStatusChange);
                            };
                        });
                    
                        if (isOnline === null) {
                            return 'Loading...';
                        }
                        return isOnline ? 'Online' : 'Offline';
                    }    
                  

Vous vous rappelez des contextes ?


                    function Example() {
                      const locale = useContext(LocaleContext);
                      const theme = useContext(ThemeContext);
                      // ...
                    }
                

Bieeeeen plus simple que le consumer avec render function

Vous vous rappelez de redux-pâté ?


                    function Todos() {
                        const [todos, dispatch] = useReducer(todosReducer);
                        // ...
                

Un "state" local (et donc maitrisé) avec un reducer qui peut rester simple.


                  const initialState = {
                    isLoading: false,
                    data: null,
                    error: null,
                  };

                  function reducer(state, action) {
                    switch (action.type) {
                      case 'FETCH_DATA': 
                        return { ...state, isLoading: true };
                  
                      case 'DATA_RECEIVED':
                        return {
                            data: action.data,
                            isLoading: false,
                            error: null,
                        };
                  
                      case 'ERROR_RECEIVED':
                        return {
                            data: null,
                            isLoading: false,
                            error: action.error,
                        };
                  
                      default:
                        throw new Error(
                          `Unable to handle action with type ${action.type}`
                        );
                    }
                  }
                
On peut même mettre un reducer et son state dans un contexte tout en haut d'une application…
Mais dans ce cas là, redux est fait pour ça 👹

Toujours plus de hooks

  • useRef : pour les références
  • useMemo : valeur mémoïsée
  • useCallback : fonction de rappel mémoïsée
  • useImperativeHandle : un truc avec les forwardRef
  • useLayoutEffect : préférer useEffect
  • useDebugValue affiche une étiquette dans les devtools

Hook personnalisés


                    import React, { useState, useEffect } from 'react';

                    function FriendStatus({ friend }) {
                      const [isOnline, setIsOnline] = useState(null);
                     
                      function handleStatusChange(status) {
                          setIsOnline(status.isOnline);
                      }
                      
                      useEffect(() => {
                          ChatAPI.subscribe(friend, handleStatusChange);
                          
                          return () => {
                              ChatAPI.unsubscribe(friend, handleStatusChange);
                          };
                      });
                    
                      if (isOnline === null) {
                          return 'Loading...';
                      }
                      return isOnline ? 'Online' : 'Offline';
                    }
                

Hook personnalisés : extraction


                    import React, { useState, useEffect } from 'react';

                    function useFriendStatus(friend) {
                      const [isOnline, setIsOnline] = useState(null);
                     
                      function handleStatusChange(status) {
                          setIsOnline(status.isOnline);
                      }
                      
                      useEffect(() => {
                          ChatAPI.subscribe(friend, handleStatusChange);
                          
                          return () => {
                              ChatAPI.unsubscribe(friend, handleStatusChange);
                          };
                      });
                    
                      return isOnline;
                    }
                

Hook personnalisés : utilisation


                    function FriendStatus({ friend }) {
                      const isOnline = useFriendStatus(friend);
                    
                      if (isOnline === null) {
                        return 'Chargement...';
                      }
                      return isOnline ? 'En ligne' : 'Hors-ligne';
                    }
                

                    function FriendListItem({ friend }) {
                      const isOnline = useFriendStatus(friend);
                    
                      return (
                        <li style={{ color: isOnline ? 'green' : 'black' }}>
                          {friend.name}
                        </li>
                      );
                    }
                

Règles des hooks

  • N’appelez pas de Hooks à l’intérieur de boucles, de code conditionnel ou de fonctions imbriquées
  • N’appelez pas les Hooks depuis des fonctions JavaScript classiques.

Il y a un plugin eslint pour ça 😉

J'ai un aveu à faire :

J'ai tout pompé sur reactjs.org
et sur leur blog

 

J'ai un aveu à faire :

J'ai tout pompé sur reactjs.org
et sur leur blog

Des questions ?