Управление состояниями в React.Js

На этой неделе я начал играть с React Hooks, чтобы понять основные концепции его работы, плюсы и минусы.

Я разработал простое мини-приложение с табло для футбольного клуба «Ливерпуль» против «Манчестер Юнайтед». Отличная идея, правда?
Вот некоторые основные моменты того, как это происходило.

useState

import {useState} from 'react'
import './App.css';
import ManchesterUnited from './assets/images/manchester-united.png';
import LiverpoolFC from './assets/images/liverpool-fc.png';

function App() {
  const initialScoreBoard = { home: 0, away: 1 };
  const [scores, setScores] = useState(initialScoreBoard);

  const incrementScore = (team) => {
    team === 'home'
      ? setScores({ home: scores.home++, ...scores })
      : setScores({ away: scores.away++, ...scores });
  }

  const decrementScore = (team) => {
    if (team === 'home') {
      if (scores.home === 0) return;
      setScores({ home: scores.home--, ...scores })
    }

    if (team === 'away') {
      if (scores.away === 0) return;
      setScores({ away: scores.away--, ...scores });
    }
   };


  return (
    <div className="App">
      <div className="score-board">
        <img
          className=""
          width="180"
          height="240"
          src={LiverpoolFC}
          alt="Liverpool FC"
        />
        <h1>{scores.home}</h1>
        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
        <h1>{scores.away}</h1>
        <img
          className=""
          width="240"
          height="240"
          src={ManchesterUnited}
          alt="Liverpool FC"
        />
      </div>
      <div>
        <button onClick={() => incrementScore('home')}>Goal!!!</button>
        <button onClick={() => decrementScore('home')}>Reverse Goal!!!</button>
        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
        <button onClick={() => incrementScore('away')}>Goal!!!</button>
        <button onClick={() => decrementScore('away')}> Reverse Goal!!!</button>
      </div>
    </div>
  );
}

export default App;
Войти в полноэкранный режим Выйти из полноэкранного режима

useReducer

import { useReducer } from 'react';
import './App.css';
import ManchesterUnited from './assets/images/manchester-united.png';
import LiverpoolFC from './assets/images/liverpool-fc.png';

function App() {
  const INITIAL_STATE = {
    scores: { home: 0, away: 1 },
  };

  const SCORE_ACTION_TYPES = {
    SET_SCORES: 'SET_SCORES',
  };

  const scoreBoardReducer = (state, action) => {
    const { type, payload } = action;

    switch (type) {
      case SCORE_ACTION_TYPES.SET_SCORES:
        return { scores: payload, ...state };
      default:
        throw new Error(`Invalid action ${type}`);
    }
  };

  const setScores = (scores) => {
    dispatch({ type: SCORE_ACTION_TYPES.SET_SCORES, payload: scores });
  };

  const [{ scores }, dispatch] = useReducer(scoreBoardReducer, INITIAL_STATE);

  const incrementScore = (team) => {
    team === 'home'
      ? setScores({ home: scores.home++, ...scores })
      : setScores({ away: scores.away++, ...scores });
  };

  const decrementScore = (team) => {
    if (team === 'home') {
      if (scores.home === 0) return;
      setScores({ home: scores.home--, ...scores });
    }

    if (team === 'away') {
      if (scores.away === 0) return;
      setScores({ away: scores.away--, ...scores });
    }
  };

  return (
    <div className="App">
      <h1>Score Board</h1>
      <div className="score-board">
        <img
          className=""
          width="180"
          height="240"
          src={LiverpoolFC}
          alt="Liverpool FC"
        />
        <h1>{scores.home}</h1>
        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
        <h1>{scores.away}</h1>
        <img
          className=""
          width="240"
          height="240"
          src={ManchesterUnited}
          alt="Liverpool FC"
        />
      </div>
      <div>
        <button onClick={() => incrementScore('home')}>Goal!!!</button>
        <button onClick={() => decrementScore('home')}>Reverse Goal!!!</button>
        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
        <button onClick={() => incrementScore('away')}>Goal!!!</button>
        <button onClick={() => decrementScore('away')}> Reverse Goal!!!</button>
      </div>
    </div>
  );
}

export default App;
Войти в полноэкранный режим Выход из полноэкранного режима

Контекстный API + useState

import { useState, createContext } from 'react';

const initialScoreBoard = { home: 0, away: 1 };

/**
 * Create Context with default state
 */
export const ScoreContext = createContext({
  score: initialScoreBoard,
  incrementScore: () => null,
  decrementScore: () => null,
});

/**
 * Implement useState for state mgt
 * Expose useState to Context Provider for Accessibility
 * return Context Provider
 */
export const ScoreProvider = ({ children }) => {
  const [scores, setScores] = useState(initialScoreBoard);

  const incrementScore = (team) => {
    team === 'home'
      ? setScores({ home: scores.home++, ...scores })
      : setScores({ away: scores.away++, ...scores });
  };

  const decrementScore = (team) => {
    if (team === 'home') {
      if (scores.home === 0) return;
      setScores({ home: scores.home--, ...scores });
    }

    if (team === 'away') {
      if (scores.away === 0) return;
      setScores({ away: scores.away--, ...scores });
    }
  };

  const value = { scores, incrementScore, decrementScore };

  return (
    <ScoreContext.Provider value={value}>{children}</ScoreContext.Provider>
  );
};
Войти в полноэкранный режим Выход из полноэкранного режима
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { ScoreProvider } from './context/scores.context';
import './index.css';

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <ScoreProvider>
      <App />
    </ScoreProvider>
  </React.StrictMode>
);
Ввести полноэкранный режим Выход из полноэкранного режима
import { useContext } from 'react';
import './App.css';
import ManchesterUnited from './assets/images/manchester-united.png';
import LiverpoolFC from './assets/images/liverpool-fc.png';
import { ScoreContext } from './context/scores.context';

function App() {
  const { scores, incrementScore, decrementScore } = useContext(ScoreContext);

  return (
    <div className="App">
      <h1>Score Board</h1>
      <div className="score-board">
        <img
          className=""
          width="180"
          height="240"
          src={LiverpoolFC}
          alt="Liverpool FC"
        />
        <h1>{scores.home}</h1>
        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
        <h1>{scores.away}</h1>
        <img
          className=""
          width="240"
          height="240"
          src={ManchesterUnited}
          alt="Liverpool FC"
        />
      </div>
      <div>
        <button onClick={() => incrementScore('home')}>Goal!!!</button>
        <button onClick={() => decrementScore('home')}>Reverse Goal!!!</button>
        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
        <button onClick={() => incrementScore('away')}>Goal!!!</button>
        <button onClick={() => decrementScore('away')}> Reverse Goal!!!</button>
      </div>
    </div>
  );
}

export default App;
Войти в полноэкранный режим Выход из полноэкранного режима

Контекстный API + useReducer

import { useReducer, createContext } from 'react';

const initialScoreBoard = { home: 0, away: 1 };

/**
 * Create Context with default state
 */
export const ScoreContext = createContext({
  scores: initialScoreBoard,
  incrementScore: () => null,
  decrementScore: () => null,
});

/**
 * Implement useState for state mgt
 * Expose useState to Context Provider for Accessibility
 * return Context Provider
 */
export const ScoreProvider = ({ children }) => {
  const INITIAL_STATE = {
    scores: { home: 0, away: 1 },
  };

  const SCORE_ACTION_TYPES = {
    SET_SCORES: 'SET_SCORES',
  };

  const scoreBoardReducer = (state, action) => {
    const { type, payload } = action;

    switch (type) {
      case SCORE_ACTION_TYPES.SET_SCORES:
        return { scores: payload, ...state };
      default:
        throw new Error(`Invalid action ${type}`);
    }
  };

  const setScores = (scores) => {
    dispatch({ type: SCORE_ACTION_TYPES.SET_SCORES, payload: scores });
  };

  const [{ scores }, dispatch] = useReducer(scoreBoardReducer, INITIAL_STATE);


  const incrementScore = (team) => {
    team === 'home'
      ? setScores({ home: scores.home++, ...scores })
      : setScores({ away: scores.away++, ...scores });
  };

  const decrementScore = (team) => {
    if (team === 'home') {
      if (scores.home === 0) return;
      setScores({ home: scores.home--, ...scores });
    }

    if (team === 'away') {
      if (scores.away === 0) return;
      setScores({ away: scores.away--, ...scores });
    }
  };

  const value = { scores, incrementScore, decrementScore };

  return (
    <ScoreContext.Provider value={value}>{children}</ScoreContext.Provider>
  );
};
Войти в полноэкранный режим Выход из полноэкранного режима

Мой вывод из этого упражнения заключается в том, что контекстный API дает абстракцию высокого уровня и делает состояние доступным для родительского компонента и его дочерних компонентов, используя принцип DRY, по сравнению с useState и useReducer, реализованными с той же логикой в нескольких компонентах, которым нужно состояние.

useState и useReducer можно использовать независимо от контекстного API, но они прекрасно вписываются в контекстный API по мере развития управления состоянием.
Это действительно излишество реализовывать useReducer, когда все, что вам нужно изменить, это единственное значение состояния, а также учитывая его многословный котел. useReducer пригодится, когда действие изменяет несколько значений состояния, например, в системе корзины, когда вы добавляете товар в корзину — количество товаров, количество товаров в корзине, общая стоимость товаров в корзине, и, возможно, больше, в зависимости от сложности.

Ссылка: https://react-ts-dguw1i.stackblitz.io/

Спасибо, что прочитали. Что вы думаете по этому поводу?

PS: Скоро я добавлю в этот пост redux и его дополнения. Следите за этим местом

Оцените статью
devanswers.ru
Добавить комментарий