Animațiile încântă utilizatorii. Și ați crede, prin volumul de articole, că React Hooks încântă dezvoltatorii. Dar pentru mine, oboseala începea să se strecoare în părerile mele despre Hooks.

Dar serendipitatea m-a salvat. Am găsit un exemplu care a fost un meci bun pentru React Hooks, mai degrabă decât „noul mod”. După cum probabil ați ghicit prin titlul acestui articol, exemplul respectiv a fost o animație.

Lucram la o aplicație React cu carduri într-o grilă. Când un element a fost eliminat, am vrut să-i anim ieșirea, așa.

Cum se construieste o componenta de animatie reutilizabila folosind React
scopul meu

Din păcate, există nuanțe pentru a face acest lucru să funcționeze. Iar soluția mea m-a condus la o bună utilizare a React Hooks.

Ce vom face?

  • începeți cu un exemplu de aplicație de bază
  • animați în mod incremental dispărând de elemente, subliniind unele provocări
  • odată ce vom realiza animația dorită, vom refactura o componentă de animație reutilizabilă
  • vom folosi această componentă pentru a anima o bară laterală și o navbar
  • și …. (trebuie să citiți / săriți până la capăt)

Pentru cei nerăbdători, iată Repo GitHub pentru codul din acest proiect. Există etichete pentru fiecare pas. (Consultați README pentru linkuri și descrieri pentru fiecare etichetă.)

De bază

Am creat o aplicație simplă, folosind creați-reacționați-aplicație. Are o grilă de cărți simple. Puteți ascunde cărți individuale.

1611267005 125 Cum se construieste o componenta de animatie reutilizabila folosind React
fără animație – articolele dispar rapid

Codul pentru acest lucru este de bază, iar rezultatele sunt neinteresante. Când un utilizator face clic pe ochi buton pictogramă, schimbăm elementul display proprietate.

function Box({ word }) {
  const color = colors[Math.floor(Math.random() * 9)];
  const [visible, setVisible] = useState(true);
  function hideMe() {
    setVisible(false);
  }
  let style = { borderColor: color, backgroundColor: color };
  if (!visible) style.display = "none";
  return (
    <div className="box" style={style}>
      {" "}
      <div className="center">{word}</div>{" "}
      <button className="button bottom-corner" onClick={hideMe}>
        {" "}
        <i className="center far fa-eye fa-lg" />{" "}
      </button>{" "}
    </div>
  );
}

(Da, folosesc cârlige de mai sus, dar aceasta nu este utilizarea interesantă a cârligelor.)

Adăugarea animației

În loc să-mi construiesc propria bibliotecă de animație, am căutat o bibliotecă de animație de genul animate.css. react-animat-css este o bibliotecă frumoasă care oferă un înveliș în jur animate.css.

npm install --save react-animated-css

adăuga animate.css la index.html

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.2/animate.css" />

În Box componentă de mai sus, schimbăm redarea acestuia la

return (
  <Animated animationIn="zoomIn" animationOut="zoomOut" isVisible={visible}>
    <div className="box" style={style}>
      <div className="center">{word}</div>
      <button className="button bottom-corner" onClick={hideMe}>
        <i className="center far fa-eye fa-lg" />
      </button>
    </div>
  </Animated>
);

Nu chiar ceea ce ne dorim

Dar animate.css animă opacity și alte proprietăți CSS; nu puteți face o tranziție CSS pe display proprietate. Deci, un obiect invizibil rămâne și ocupă spațiu în fluxul documentului.

1611267005 483 Cum se construieste o componenta de animatie reutilizabila folosind React

daca tu Google un pic, veți găsi câteva soluții care sugerează utilizarea unui temporizator pentru setare display: none la sfârșitul animației.

Deci putem adăuga asta,

function Box({ word }) {
  const color = colors[Math.floor(Math.random() * 9)];
  const [visible, setVisible] = useState(true);
  const [fading, setFading] = useState(false);

  function hideMe() {
    setFading(true);
    setTimeout(() => setVisible(false), 650);
  }

  let style = { borderColor: color, backgroundColor: color };

  return (
    <Animated
      animationIn="zoomIn"
      animationOut="zoomOut"
      isVisible={!fading}
      style={visible ? null : { display: "none" }}
    >
      <div className="box" style={style}>
        <div className="center">{word}</div>
        <button className="button bottom-corner" onClick={hideMe}>
          <i className="center far fa-eye fa-lg" />
        </button>
      </div>
    </Animated>
  );
}

(Notă: Durata implicită a animației este de 1000 ms. Folosesc 650 ms pentru timeout, pentru a minimiza o bâlbâială / pauză înainte de a seta display proprietate. Aceasta este o chestiune de preferință.)

Și asta ne va da efectul dorit.

1611267005 843 Cum se construieste o componenta de animatie reutilizabila folosind React
Ura!

Crearea unei componente reutilizabile

Ne-am putea opri aici, dar există două probleme (pentru mine):

  1. Nu vreau să copiez / lipesc fișierul Animated bloc, stiluri și funcții pentru a recrea acest efect
  2. Box componenta amestecă diferite tipuri de logică, adică încalcă Separarea preocupărilor. Mai exact, BoxFuncția esențială este de a reda un card cu conținutul acestuia. Dar detaliile animației sunt amestecate.

Componenta clasei

Putem crea o componentă tradițională a clasei React pentru a gestiona starea animației: comutați vizibilitatea și setați expirarea pentru display Proprietate CSS.

class AnimatedVisibility extends Component {
  constructor(props) {
    super(props);
    this.state = { noDisplay: false, visible: this.props.visible };
  }

  componentWillReceiveProps(nextProps, nextContext) {
    if (!nextProps.visible) {
      this.setState({ visible: false });
      setTimeout(() => this.setState({ noDisplay: true }), 650);
    }
  }

  render() {
    return (
      <Animated
        animationIn="zoomIn"
        animationOut="zoomOut"
        isVisible={this.state.visible}
        style={this.state.noDisplay ? { display: "none" } : null}
      >
        {this.props.children}
      </Animated>
    );
  }
}

și apoi folosește-l

function Box({ word }) {
  const color = colors[Math.floor(Math.random() * 9)];
  const [visible, setVisible] = useState(true);

  function hideMe() {
    setVisible(false);
  }

  let style = { borderColor: color, backgroundColor: color };

  return (
    <AnimatedVisibility visible={visible}>
      <div className="box" style={style}>
        <div className="center">{word}</div>
        <button className="button bottom-corner" onClick={hideMe}>
          <i className="center far fa-eye fa-lg" />
        </button>
      </div>
    </AnimatedVisibility>
  );
}

Acest lucru creează o componentă reutilizabilă, dar este puțin complicat. Putem face mai bine.

React Cârlige și utilizați Effect

React Hooks sunt o caracteristică nouă în React 16.8. Acestea oferă o abordare mai simplă a ciclului de viață și a gestionării stării în componentele React.

useEffect cârligul oferă un înlocuitor elegant pentru utilizarea noastră de componentWillReceiveProps. Codul este mai simplu și putem folosi din nou o componentă funcțională.

function AnimatedVisibility({ visible, children }) {
  const [noDisplay, setNoDisplay] = useState(!visible);
  useEffect(() => {
    if (!visible) setTimeout(() => setNoDisplay(true), 650);
    else setNoDisplay(false);
  }, [visible]);

  const style = noDisplay ? { display: "none" } : null;
  return (
    <Animated
      animationIn="zoomIn"
      animationOut="zoomOut"
      isVisible={visible}
      style={style}
    >
      {children}
    </Animated>
  );
}

Există câteva subtilități cu useEffect cârlig. Este în primul rând pentru efecte secundare: schimbarea stării, apelarea funcțiilor asincrone etc. În cazul nostru, setează internul noDisplay boolean pe baza valorii anterioare a visible.

Prin adăugarea visible la matricea de dependențe pentru useEffect, al nostru useEffect hook va fi apelat numai atunci când valoarea lui visible schimbări.

cred useEffect este o soluție mult mai bună decât dezordinea componentelor clasei. ?

Reutilizarea componentei: bare laterale și bare de navigare

Toată lumea iubește barele laterale și barele. Deci, să adăugăm câte unul din fiecare.

function ToggleButton({ label, isOpen, onClick }) {
  const icon = isOpen ? (
    <i className="fas fa-toggle-off fa-lg" />
  ) : (
    <i className="fas fa-toggle-on fa-lg" />
  );
  return (
    <button className="toggle" onClick={onClick}>
      {label} {icon}
    </button>
  );
}

function Navbar({ open }) {
  return (
    <AnimatedVisibility
      visible={open}
      animationIn="slideInDown"
      animationOut="slideOutUp"
      animationInDuration={300}
      animationOutDuration={600}
    >
      <nav className="bar nav">
        <li>Item 1</li>
        <li>Item 2</li>
        <li>Item 3</li>
      </nav>
    </AnimatedVisibility>
  );
}

function Sidebar({ open }) {
  return (
    <AnimatedVisibility
      visible={open}
      animationIn="slideInLeft"
      animationOut="slideOutLeft"
      animationInDuration={500}
      animationOutDuration={600}
      className="on-top"
    >
      <div className="sidebar">
        <ul>
          <li>Item 1</li>
          <li>Item 2</li>
          <li>Item 3</li>
        </ul>
      </div>
    </AnimatedVisibility>
  );
}

function App() {
  const [navIsOpen, setNavOpen] = useState(false);
  const [sidebarIsOpen, setSidebarOpen] = useState(false);

  function toggleNav() {
    setNavOpen(!navIsOpen);
  }

  function toggleSidebar() {
    setSidebarOpen(!sidebarIsOpen);
  }

  return (
    <Fragment>
      <main className="main">
        <header className="bar header">
          <ToggleButton
            label="Sidebar"
            isOpen={sidebarIsOpen}
            onClick={toggleSidebar}
          />
          <ToggleButton label="Navbar" isOpen={navIsOpen} onClick={toggleNav} />
        </header>
        <Navbar open={navIsOpen} />
        <Boxes />
      </main>
      <Sidebar open={sidebarIsOpen} />
    </Fragment>
  );
}
1611267005 945 Cum se construieste o componenta de animatie reutilizabila folosind React
reutilizarea realizată

Dar nu am terminat …

Ne-am putea opri aici. Dar, ca și în cazul comentariilor mele anterioare despre Separarea preocupărilor, Aș prefera să evit să amestec AnimatedVisibility componentă în metoda de redare a Box, Sidebar nici Navbar. (Este, de asemenea, o cantitate mică de duplicare.)

Putem crea un HOC. (De fapt, am scris un articol despre animații și HOC-uri, Cum să construiești microinteracțiuni animate în React.) Dar HOC-urile implică de obicei componente de clasă, din cauza managementului de stat.

Dar cu React Hooks, putem compune doar HOC (abordare funcțională de programare).

function AnimatedVisibility({
  visible,
  children,
  animationOutDuration,
  disappearOffset,
  ...rest
})
// ... same as before
}


function makeAnimated(
  Component,
  animationIn,
  animationOut,
  animationInDuration,
  animationOutDuration,
  disappearOffset
) {
  return function({ open, className, ...props }) {
    return (
      <AnimatedVisibility
        visible={open}
        animationIn={animationIn}
        animationOut={animationOut}
        animationInDuration={animationInDuration}
        animationOutDuration={animationOutDuration}
        disappearOffset={disappearOffset}
        className={className}
      >
        <Component {...props} />
      </AnimatedVisibility>
    );
  };
}

export function makeAnimationSlideLeft(Component) {
  return makeAnimated(Component, "slideInLeft", "slideOutLeft", 400, 500, 200);
}

export function makeAnimationSlideUpDown(Component) {
  return makeAnimated(Component, "slideInDown", "slideOutUp", 400, 500, 200);
}

export default AnimatedVisibility

și apoi utilizați aceste HOC-uri bazate pe funcții în App.js

function Navbar() {
  return (
    <nav className="bar nav">
      <li>Item 1</li>
      <li>Item 2</li>
      <li>Item 3</li>
    </nav>
  );
}

function Sidebar() {
  return (
    <div className="sidebar">
      <ul>
        <li>Item 1</li>
        <li>Item 2</li>
        <li>Item 3</li>
      </ul>
    </div>
  );
}

const AnimatedSidebar = makeAnimationSlideLeft(Sidebar);
const AnimatedNavbar = makeAnimationSlideUpDown(Navbar);

function App() {
  const [navIsOpen, setNavOpen] = useState(false);
  const [sidebarIsOpen, setSidebarOpen] = useState(false);

  function toggleNav() {
    setNavOpen(!navIsOpen);
  }

  function toggleSidebar() {
    setSidebarOpen(!sidebarIsOpen);
  }

  return (
    <Fragment>
      <main className="main">
        <header className="bar header">
          <ToggleButton
            label="Sidebar"
            isOpen={sidebarIsOpen}
            onClick={toggleSidebar}
          />
          <ToggleButton label="Navbar" isOpen={navIsOpen} onClick={toggleNav} />
        </header>
          <AnimatedNavbar open={navIsOpen} />
        <Boxes />
      </main>
      <AnimatedSidebar open={sidebarIsOpen} className="on-top"/>
    </Fragment>
  );
}

Cu riscul de a-mi promova propria muncă, prefer mult codul rezultat curat.

Iată un sandbox al rezultatului final.

Acum ce?

Pentru animații simple, abordarea pe care o descriu funcționează bine. Pentru cazuri mai complexe, aș folosi biblioteci precum reacție-mișcare.

Dar, separate de animații, React Hooks oferă oportunități de a crea cod simplu și lizibil. Cu toate acestea, există o ajustare în gândire. Cârlige ca useEffect nu sunt un înlocuitor direct pentru toate metodele ciclului de viață. Va trebui să studiați și să experimentați.

Vă sugerez să căutați site-uri precum useHooks.com și biblioteci precum reacție-utilizare, o colecție de cârlige pentru o varietate de cazuri de utilizare.