[Старый стиль Redux без инструментария reduxjs]

В любом реактивном проекте нам нужны два пакета: redux и react-redux, react redux делает соединение react и redux очень простым.

В реактивном проекте внутри папки src мы создадим папку хранилища и создадим store.js, содержимое которого будет следующим:

import {legacy_createStore as createStore} from 'redux';

const counterReducer = (currentState = { counter: 0 }, action) => {
  console.log(action);
  if (action.type === 'increment') {
    return {
      ...currentState,
      counter: currentState.counter + 1
    };
  } else if (action.type === 'decrement') {
    return {
      ...currentState,
      counter: currentState.counter - 1
    };
  }
  return currentState;
}

const store = createStore(counterReducer);
export default store;

Теперь, как мы можем зарегистрировать это хранилище в нашем приложении для реагирования, обернув Provider, который импортирован из react-redux, в компонент App внутри index.js следующим образом.

import React from ‘react’;
import ReactDOM from ‘react-dom/client’;
import ‘./index.css’;
import App from ‘./App’;
import { Provider } from 'react-redux';
import store from './store/index';

const root = ReactDOM.createRoot(document.getElementById(‘root’));
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

Поставщик, обернутый вокруг компонента приложения, может быть обернут в любой другой компонент, но когда мы обернем его в определенный компонент, только этот компонент и все дочерние компоненты этого компонента будут иметь доступ к хранилищу, здесь, в этом случае, компонент приложения и все дочерние элементы, поскольку каждый компонент является дочерним компонентом компонента приложения, поэтому хранилище будет доступно для всех компонентов.

Как использовать этот магазин в любом дочернем компоненте?

допустим, у нас есть компонент Counter.js, который отображает значение счетчика и имеет кнопку, которая увеличивает значение счетчика из хранилища, мы можем использовать хук useSelector из react-redux для извлечения части данных из хранилища, как показано ниже,

import { useSelector } from 'react-redux';
import classes from './Counter.module.css';


const Counter = () => {
  const counterHandler = (type) => {
    // will see how to dispatch an action, below
  };
  const counter = useSelector(state => state.counter);
  return (
    <main className={classes.counter}>
      <h1>Counter App</h1>
      <div className={classes.value}>{counter}</div>
      <button onClick={() => counterHandler('incr')}>Increment Counter</button>
      <button onClick={() => counterHandler('decr')}>Decrement Counter</button>
    </main>
  );
};

export default Counter;

Важно отметить, что useSelector внутренне создает подписку на хранилище redux, поэтому каждый раз, когда хранилище обновляет значение из useSelector, всегда будет обновляться хранилище. Мы можем видеть, что useSelector использует анонимную функцию, которая фильтрует нужные нам данные, и всякий раз, когда компонент отключается, подписка отменяется внутри.

давайте теперь посмотрим, как мы можем отправить действие,

У нас есть специальный хук, который называется useDispatch из react-redux, который дает нам функцию dispatch, которую можно использовать для отправки действия, как показано ниже.

import { useDispatch, useSelector } from 'react-redux';
import classes from './Counter.module.css';


const Counter = () => {
  const dispatch = useDispatch();
  const counter = useSelector(state => state.counter);

  const counterHandler = (type) => {
    if (type === 'incr') {
      dispatch({type: 'increment'});
    }
    if (type === 'decr') {
      dispatch({type: 'decrement'});
    }
  };
  return (
    <main className={classes.counter}>
      <h1>Redux Counter</h1>
      <div className={classes.value}>{counter}</div>
      <button onClick={() => counterHandler('incr')}>Increment </button>
      <button onClick={() => counterHandler('decr')}>Decrement</button>
    </main>
  );
};

export default Counter;

Как мы можем справиться с этим в компонентах на основе классов

Преобразуем Counter.js в компонент на основе классов.

class Counter extends Component {
  
  counterHandler(type){
    
  };
  render() {
    return (
      <main className={classes.counter}>
        <h1>Redux Counter</h1>
        <div className={classes.value}>---Counter Data---</div>
        <button onClick={() => this.counterHandler('incr')}>Increment </button>
        <button onClick={() => this.counterHandler('decr')}>Decrement</button>
      </main>
    );
  }
};

Мы не можем использовать хуки внутри компонента, основанного на классе, есть альтернатива хукам, которые мы используем connect из react-redux, чтобы обернуть компонент Counter перед его экспортом, connect принимает два аргумента и возвращает функцию, которая принимает компонент в качестве параметра, к которому мы хотим получить доступ. состояние редукции в качестве параметра, который возвращает объект, ключи которого будут доступны внутри реквизита компонента, который мы используем, а значения будут логикой, которая будет углубляться в состояние редукции.

У нас также есть функция mapDispatchToProps, эта функция эквивалентна хуку useDispatch, эта функция используется для хранения функций dispatch в свойствах, эта функция получает dispatch в качестве параметра по умолчанию, эта функция возвращает объект, ключи которого являются именами свойств, которые будут использоваться в компоненте, который он обернут, а значения являются анонимной функцией, где мы можем вызвать функцию отправки внутри нее, функция подключения подпишется внутренне для сохранения и отмены запишите его, когда компонент размонтирован внутри.

Пример приведен ниже,

import { connect } from 'react-redux';

class Counter extends Component {
  
  counterHandler(type){
    if (type === 'incr') {
       this.props.increment(); // functions from mapDispatchToProps
    } 
    if (type === 'decr') {
       this.props.decrement(); // functions from mapDispatchToProps
    }
  };
  render() {
    return (
      <main className={classes.counter}>
        <h1>Redux Counter</h1>
        <div className={classes.value}>{this.props.counter}</div>
        <button onClick={() => this.counterHandler('incr')}>Increment</button>
        <button onClick={() => this.counterHandler('decr')}>Decrement</button>
      </main>
    );
  }
};

const mapStateToProps = state => {
  return {
    counter: state.counter
  }
}

const mapDispatchToProps = dispatch => {
  return {
    increment: () => dispatch({type: 'increment'}),
    decrement: () => dispatch({type: 'decrement'})
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

Компонент на основе классов не используется, но для разработчика нам нужно знать, как он реализован для компонентов на основе функций и для компонентов на основе классов, я буду использовать функцию на основе, которая очень проста и прямолинейна.

Здесь мы не можем масштабировать это, например, если мы хотим добавить какое-то число на определенное значение, нам нужно создать новое действие? Нет, нам просто нужно отправить пейлоад на действия, из которых подписчик получит значение, на которое мы хотим увеличить счетчик.

Действия с полезной нагрузкой:

Нам просто нужно добавить дополнительный параметр при отправке любого действия, например, как показано ниже:

import { useDispatch, useSelector } from 'react-redux';
import classes from './Counter.module.css';

const Counter = () => {
  const dispatch = useDispatch();
  const counter = useSelector(state => state.counter);

  const counterHandler = (type, amount = 0) => {
    if (type === 'incr') {
      dispatch({type: 'increment'});
    }
    if (type === 'decr') {
      dispatch({type: 'decrement'});
    }
    if (type === 'increase') {
      dispatch({type: 'increase', amount});
    }
  };
  return (
    <main className={classes.counter}>
      <h1>Redux Counter</h1>
      <div className={classes.value}>{counter}</div>
      <button onClick={() => counterHandler('increase', 5)}>Increase by 5</button>
      <button onClick={() => counterHandler('incr')}>Increment </button>
      <button onClick={() => counterHandler('decr')}>Decrement</button>
    </main>
  );
};

export default Counter;

Как вы можете видеть, мы добавили увеличение типа в counterHandler, и с его помощью мы отправляем действие с настраиваемой полезной нагрузкой, мы можем отправить его как 5 или 6 или любое другое значение, нам просто нужно обработать его внутри нашего централизованного хранилища, как показано ниже:

import {legacy_createStore as createStore} from 'redux';

const counterReducer = (currentState = { counter: 0 }, action) => {
  console.log(action);
  if (action.type === 'increment') {
    return {
      ...currentState,
      counter: currentState.counter + 1
    };
  } else if (action.type === 'decrement') {
    return {
      ...currentState,
      counter: currentState.counter - 1
    };
  } else if (action.type === 'increase') {
     return {
       ...currentState,
       counter: currentState.counter + action.amount  
     }
  }
    
  return currentState;
}

const store = createStore(counterReducer);
export default store;

Здесь вы можете видеть, что мы добавили action.amount, когда мы отправляем увеличение типа действия, это будет извлекать данные из объекта действия и использовать эту сумму для увеличения их на значение, которое мы отправили при отправке, что делает его более масштабируемым.

Работа с несколькими состояниями для обновления хранилища избыточности:

До сих пор мы видели работу с одним состоянием, то есть для обновления значения счетчика, но в реальном сценарии у нас есть много состояний, которые нам нужно обработать, скажем, в приведенном выше примере нам нужно скрыть приращение с помощью кнопки 5, мы можем обработать его с помощью useState, но скажем, нам нужно обработать это с помощью избыточности, это будет следующим образом:

мы просто изменим существующий магазин следующим образом

import {legacy_createStore as createStore} from 'redux';

const counterReducer = (currentState = { counter: 0, showCounter: true }, action) => {
  console.log(action);
  if (action.type === 'increment') {
    return {
      counter: currentState.counter + 1,
      showCounter: currentState.showCounter
    };
  } else if (action.type === 'decrement') {
    return {
      counter: currentState.counter - 1,
      showCounter: currentState.showCounter
    };
  } else if (action.type === 'increase') {
     return {
       counter: currentState.counter + action.amount,
       showCounter: currentState.showCounter
     }
  }
  if (action.type === 'toggle'){
    return {
      showCounter: !currentState.showCounter,
      counter: currentState.counter
    }
  }
    
  return currentState;
}

const store = createStore(counterReducer);
export default store;

import { useDispatch, useSelector } from 'react-redux';
import classes from './Counter.module.css';

const Counter = () => {
  const dispatch = useDispatch();
  const counter = useSelector(state => state.counter);
  const showCounter = useSelector(state => state.showCounter);

  const toggleHandler = () => {
    dispatch({type: 'toggle'});
  }

  const counterHandler = (type, amount = 0) => {
    if (type === 'incr') {
      dispatch({type: 'increment'});
    }
    if (type === 'decr') {
      dispatch({type: 'decrement'});
    }
    if (type === 'increase') {
      dispatch({type: 'increase', amount});
    }
  };
  return (
    <main className={classes.counter}>
      <h1>Redux Counter</h1>
      { showCounter && (<div className={classes.value}>{counter}</div>)}
      <button onClick={() => toggleHandler()}>Toggle counter</button>
      <button onClick={() => counterHandler('increase', 5)}>Increase by 5</button>
      <button onClick={() => counterHandler('incr')}>Increment </button>
      <button onClick={() => counterHandler('decr')}>Decrement</button>
    </main>
  );
};

export default Counter;

Как мы видим, в магазине, когда нам нужно обновить только счетчик, мы возвращаем showCounter из состояния, а когда нам нужно обновить showCounter, мы возвращаем значение счетчика из текущего состояния, обратите внимание, убедитесь, что мы не меняем какое-либо другое состояние или не возвращаем существующее состояние, что приведет к нежелательному состоянию приложения.

Примечание: мы никогда не должны мутировать или изменять существующее состояние.

[Новый инструментарий Redux]

Reduxjs toolkit был разработан той же командой, что и Redux.

Для установки нам нужно использовать npm i @reduxjs/toolkitнам не нужны reduxjs, если мы устанавливаем инструментарий reduxjs, поскольку он встроен, инструментарий redux упрощает некоторые аспекты работы с redux, давайте начнем с редьюсеров и действий.

import { createSlice } from '@reduxjs/toolkit';
import { createStore } from 'redux';

const initialState = { counter: 0 };

const counterSlice = createSlice({
   name: 'counter',
   initialState,
   reducers: {
     increment(state){
       state.counter++;
     },
     decrement(state){
       state.counter--;
     },
     increase(state, action){
        state.counter = state.counter + action.amount;
     },
     toggleCounter(state){
        state.showCounter = !state.showCounter;
     }
   }
})
const store = configureStore({
  reducer: counterSlice.reducer
});

export const counterActions = counterSlice.actions;

export default store;

Используя createSlice, мы создаем срез данных нашего глобального хранилища, и если у нас есть разные части состояний, такие как состояние аутентификации или состояние счетчика изменений или любые другие, мы могли бы создавать разные слайсы хранилища в разных файлах, чтобы упростить сопровождение кода, каждый слайс должен иметь имя, initialState, reducers, который получает объект, содержащий все функции, которые нам нужно отправить, которые ранее мы обрабатывали, используя action.type внутри, если условия проверки, следовательно, из-за этого самого проверки if, которую мы выполняли, теперь удалены, мы можем видеть, что функции внутри редюсеров получают текущее состояние в качестве параметра, и хорошая вещь здесь, в наборе инструментов reduxjs, заключается в том, что мы можем напрямую манипулировать состояниями, чего не было в случае без инструментария reduxjs, потому что набор инструментов reduxjs внутренне следит за тем, чтобы состояние не изменялось напрямую, он заботится о нем внутри.

Мы можем видеть в функции increase, что мы также получаем полезную нагрузку действия в качестве параметра, дело в том, что функции редуктора всегда получают текущее состояние и полезную нагрузку действия, просто мы не принимаем его внутри редукторов increment, decrement и toggleCounter. объединение нескольких редьюсеров в один редуктор за сценой.

Теперь для отправки действий createSlice автоматически создает уникальные идентификаторы действий для наших различных редюсеров. Чтобы получить доступ к этим идентификаторам действий, мы используем counterSlice.actionsв приведенном выше случае,который является объектом, который имеет ключи, совпадающие с именами функций, объявленных внутри объекта редукторов createSlice, как и в приведенном выше случае, ключами будут инкремент, декремент, toggleCounter и увеличение, то есть counterSlice.actions.increment() или counterSlice.actions.decrement() или counterSlice.actions.increase() или counterSlice.actions.toggleCounter(),но когда мы получаем доступ к таким функциям, как counterSlice.actions.increment(), это не будет обращаться или вызывать функцию редуктора внутри метода createSlice, вместо этого будут создаваться активные объекты и возвращаться такие объекты, как { type: ‘some pre generated key’}, следовательно, они известны как Action Creators, и эти активные объекты всегда имеют свойство type, которое имеет уникальный ключ для каждого действия, поэтому нам не нужно беспокоиться о больше типов действий, он обрабатывается для нас за сценой, поэтому мы можем использовать эти предварительно сгенерированные активные объекты для отправки определенных действий, которые будут запускать конкретный редьюсер в зависимости от того, какое действие мы будем использовать. Нам нужно экспортировать такие действия, чтобы мы могли использовать эти предварительно сгенерированные типы действий для целей диспетчеризации.

Давайте посмотрим, как мы можем использовать такие ключи ниже,

import { useDispatch, useSelector } from 'react-redux';
import classes from './Counter.module.css';
import { counterActions } from '../store/store';

const Counter = () => {
  const dispatch = useDispatch();
  const counter = useSelector(state => state.counter);
  const showCounter = useSelector(state => state.showCounter);

  const toggleHandler = () => {
    dispatch(counterActions.toggleCounter());
  }

  const counterHandler = (type) => {
    if (type === 'incr') {
      dispatch(counterActions.increment());
    }
    if (type === 'decr') {
      dispatch(counterActions.decrement());
    }
    if (type === 'increase') {
      dispatch(counterActions.increase(5)); // { type: SOME_PREGENERATED KEY, payload: 5 }
    }
  };
  return (
    <main className={classes.counter}>
      <h1>Redux Counter</h1>
      { showCounter && (<div className={classes.value}>{counter}</div>)}
      <button onClick={() => toggleHandler()}>Toggle counter</button>
      <button onClick={() => counterHandler('increase', 5)}>Increase by 5</button>
      <button onClick={() => counterHandler('incr')}>Increment </button>
      <button onClick={() => counterHandler('decr')}>Decrement</button>
    </main>
  );
};

export default Counter;

Просто в случае действия с каким-либо аргументом нам нужно передать какое-то значение, это может быть что угодно, например, объект, строка или число, для него будет установлено значение action.payload по умолчанию, для него нельзя установить пользовательский ключ, такой как action.amount, поэтому нам нужно изменить наш редуктор увеличения в магазине на код ниже,

// rest of the above code from above example

const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment(state) {
      state.counter++;
    },
    decrement(state) {
      state.counter--;
    },
    increase(state, action) {
      state.counter = state.counter + action.payload; // payload instead of amount
    },
    toggleCounter(state) {
      state.showCounter = !state.showCounter;
    }
  }
});

//rest of below code from above example

Работа с несколькими слайсами:

Допустим, у нас есть форма входа в систему, когда мы вошли в систему, мы хотим показать некоторые изменения в пользовательском интерфейсе, например, в заголовке видна кнопка выхода из системы, а вместо формы входа она покажет нам профиль пользователя. Поэтому нам нужно создать срез авторизации, который будет обрабатывать состояние аутентификации входа, т.

import { configureStore, createSlice } from '@reduxjs/toolkit';

const initialCounterState = { counter: 0, showCounter: true };
const counterSlice = createSlice({
  name: 'counter',
  initialState: initialCounterState,
  reducers: {
    increment(state) {
      state.counter++;
    },
    decrement(state) {
      state.counter--;
    },
    increase(state, action) {
      state.counter = state.counter + action.payload;
    },
    toggleCounter(state) {
      state.showCounter = !state.showCounter;
    }
  }
});

const initialAuthState = { isAuth: false };

const authSlice = createSlice({
  name: 'auth',
  initialState: initialAuthState,
  reducers: {
    login(state) {
      state.isAuth = true;
    },
    logout(state) {
      state.isAuth = false;
    }
  }
})


const store = configureStore({
  reducer: {
    counter: counterSlice.reducer, 
    auth: authSlice.reducer
  }
});

export const counterActions = counterSlice.actions;
export const authActions = authSlice.actions;

export default store;

Здесь мы видим, что мы создали новый authSlice с initialState и редюсерами, как указано, мы можем изменить внутри configureStore, что мы не используем другой configureStore, вместо этого мы добавляем authSlice в тот же configureStore, configureStore нужно использовать только один раз для любого количества фрагментов и каждый фрагмент должен быть сопоставлен с объектом с определенным значащим именем ключа, внутренне configureStore сопоставляет все редукторы и создает один большой редуктор, эти имена ключей будут использоваться для доступа к данным хранилища, например, для доступа к данным счетчика из хранилища, нам понадобится этот ключ (здесь, ключ «счетчик») для доступа к данным счетчика из хранилища, и то же самое в случае, если нам нужно получить доступ к данным аутентификации из хранилища, нам понадобится ключ аутентификации.

Мы должны отметить, что компонент, из которого мы извлекаем эти данные, немного меняется, если мы используем более одного слайса, поскольку мы установили редьюсер для объекта, содержащего счетчик и авторизацию в качестве ключей внутри configureStore, поэтому при извлечении данных мы используем хук useSelector, предоставляемый библиотекой react-redux, которая получает состояние в качестве параметра, и мы используем его для получения прямого значения счетчика из этого состояния, например state.counter , но вместо этого нам нужно углубиться внутрь объекта, который мы установили, поэтому мы будем использовать state.counter.counter, чтобы получить значение из хранилища для доступа к данным счетчика.

Следовательно, для приведенного выше сценария для входа в систему и обработки пользовательского интерфейса у нас есть следующий код:

import Counter from './components/Counter';
import Header from './components/Header';
import Auth from './components/Auth';
import UserProfile from './components/UserProfile';
import { useSelector } from 'react-redux';

function App() {
  const isAuthenticated = useSelector(state => state.auth.isAuth);
  return (
    <>
      <Header isAuth={isAuthenticated}/>
      { isAuthenticated ? <UserProfile/>:<Auth />}
      <Counter />
    </>
  );
}

export default App;

Сначала мы установим isAuthenticated в компоненте приложения, чтобы мы могли передать это в заголовке, чтобы показать кнопку навигации и выхода, и мы можем переключаться между профилем пользователя и формой входа, которая находится в компоненте аутентификации.

import { useDispatch } from 'react-redux';
import { authActions } from '../store/store';
import classes from './Header.module.css';

const Header = ({isAuth}) => {
  const dispatch = useDispatch();
  const logoutHandler = () => {
    dispatch(authActions.logout());
  }
  return (
    <header className={classes.header}>
      <h1>Redux Auth</h1>
      { isAuth &&
          (
            <nav>
              <ul>
                <li>
                  <a href='/'>My Products</a>
                </li>
                <li>
                  <a href='/'>My Sales</a>
                </li>
                <li>
                  <button onClick={logoutHandler}>Logout</button>
                </li>
              </ul>
            </nav>
          )
      }
    </header>
  );
};

export default Header;

Здесь мы видим, что мы использовали isAuth, переданный из компонента приложения, для отображения и скрытия навигации, и мы добавили logoutHandler, который отправляет действие выхода из системы.

import { useRef } from 'react';
import { useDispatch } from 'react-redux';
import { authActions } from '../store/store';
import classes from './Auth.module.css';

const Auth = () => {
  const emailInputRef = useRef();
  const passwordInputRef = useRef();
  const dispatch = useDispatch();
  const submitHandler = () => {
    if (emailInputRef.current.value && passwordInputRef.current?.value) {
      dispatch(authActions.login());
      emailInputRef.current.value = '';
      passwordInputRef.current.value = '';
    }
  }
  return (
    <main className={classes.auth}>
      <section>
        <form onSubmit={e => e.preventDefault()}>
          <div className={classes.control}>
            <label htmlFor='email'>Email</label>
            <input ref={emailInputRef} type='email' id='email' />
          </div>
          <div className={classes.control}>
            <label htmlFor='password'>Password</label>
            <input ref={passwordInputRef} type='password' id='password' />
          </div>
          <button onClick={submitHandler}>Login</button>
        </form>
      </section>
    </main>
  );
};

export default Auth;

В компоненте Auth мы использовали refs для получения значений из входных данных и выполнили некоторые базовые проверки, а при нажатии на вход мы отправляем действие входа.

Примечание. Этот вышеприведенный компонент входа в систему не идеален, это всего лишь пример, в реальных сценариях у нас может быть много вещей, связанных с функцией входа в систему.

Здесь мы видим, что при наличии нескольких срезов файл хранилища index.js станет очень длинным, и мы не сможем эффективно с ним справиться, чтобы решить эту проблему. Мы можем разделить файлы на срезы, где мы можем управлять различными срезами в своем конкретном файле, чтобы он стал простым и удобным в сопровождении.

Мы можем разделить файлы, такие как authSlice.js, ниже файла, код которого приведен в примере ниже.

import { createSlice } from "@reduxjs/toolkit";

const initialAuthState = { isAuth: false };

const authSlice = createSlice({
  name: 'auth',
  initialState: initialAuthState,
  reducers: {
    login(state) {
      state.isAuth = true;
    },
    logout(state) {
      state.isAuth = false;
    }
  }
});

export const authActions = authSlice.actions;

export default authSlice.reducer;

Обработка асинхронного кода:

Есть несколько правил, которым следует следовать для редукторов, редукторы должны быть чистыми функциями, синхронными и свободными от побочных эффектов, это означает, что редукторы должны принимать старое состояние и выводить новое состояние, и для одних и тех же входных данных или одинаковых значений он всегда должен возвращать один и тот же результат.

Итак, как мы можем написать наш асинхронный код?

Есть два способа написания асинхронного кода:

  1. Используйте useEffect или перехватчики перед отправкой действий после выполнения асинхронного кода, чтобы редюсеры не имели асинхронного кода, который обрабатывается на стороне реагирования, очень дублирует код и не может поддерживать код.
  2. Используйте создателей пользовательских действий, а не сгенерированных, без изменения редюсеров, поскольку это должно быть без побочных эффектов.

Использование первого метода приводит к большому количеству дублирований кода, так как данные, которые нам нужно сохранить, имеют некоторую логику, например, удаление товара из корзины требует проверки, существует ли уже элемент в массиве, и если существует, то количество больше 1 или нет, если больше 1, то только уменьшить количество, а если равно 1, то нам нужно удалить запись.

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

Or,

Мы можем сделать это, в нашем компоненте приложения мы можем использовать useEffect для вызова API для хранения данных корзины, до этого мы можем использовать useSelector для подписки на последнее состояние корзины, может всякий раз, когда корзина изменяется, будет срабатывать useEffect, поскольку мы поместим корзину в массив, и данные корзины будут сохранены, но это тоже имеет некоторую проблему, когда пользователь не добавил какой-либо продукт, корзина будет храниться как пустая, перезаписывая любые существующие сохраненные данные в пустые.

Второй метод заключается в использовании создателей действий. Мы можем исправить наши собственные создатели действий, чтобы создать так называемые преобразователи. Преобразователи — это функции, которые откладывают действие до тех пор, пока что-то не будет сделано или не будет завершено. Этот преобразователь не возвращает объект действия немедленно (объект действия: {type:’some pre generated action name’}), но возвращает другую функцию, которая в конечном итоге возвращает объект действия, так что мы можем запустить некоторый код перед отправкой какого-либо действия.

Как создать настраиваемый генератор действий?

В коде, где мы используем createSlice, где мы экспортируем действия, мы можем создать пользовательское действие следующим образом:

// above create slice code

export const sendCartData = () => {
    return (dispatch) => {
        // dispatch()
        // dispatch action for loading start screen
        const sendRequest = async () => {
            const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
            // do whatever you want 
            // dispatch and add it into redux
        }
        // dispatch action for loading end screen
        try {
            sendRequest();
        } catch(error) {
            // dispatch action for error screen
        }
    }
}

export const cartActions = cartSlice.actions;
export default cartSlice.reducer;

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

Как использовать отправку этих пользовательских действий? Так же, как мы делали для действий по умолчанию, как показано ниже.

import classes from './CartButton.module.css';
import {useDispatch, useSelector} from 'react-redux';
import { cartActions, sendCartData } from '../../store/cartSlice';

const CartButton = (props) => {
  const totalQty = useSelector(state => state.cart.totalQty);
  const cart = useSelector(state => state.cart);
  const dispatch = useDispatch();
  const fetchUserData = () => {
    dispatch(sendCartData(cart));
  }
  const toggleCartUI = () => {
    dispatch(cartActions.toggleCart());
  }
  return (
    <button className={classes.button} onClick={toggleCartUI}>
      <span>My Cart</span>
      <span className={classes.badge} onClick={fetchUserData}>Fetch Cart Data</span>
    </button>
  );
};

export default CartButton;

Чтобы отправить почтовый запрос, мы можем отправить параметры внутри функции выборки.

Я настоятельно рекомендую вам, ребята, использовать redux-devtools здесь и изучить его.

TLDR;

Следите за другими блогами по программированию, ссылками здесь, инстаграмом здесь.