Сравнение Vuex и Redux при разработке приложения

Я уже давно использую Vue и React.

Я изучил Vue + Vuex до React. У меня было ощущение, что Redux будет очень похож на Vuex. Но вскоре я понял, что это не так прямолинейно, как Vuex, и он делает вещи по-другому (хотя оба они вдохновлены архитектурой Flux).

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

Итак, вот статья о сравнении того, как Vuex и Redux делают одни и те же вещи разными способами.

Примечание. Я не сравниваю основные фреймворки, я концентрируюсь только на особенностях Вьюекс а также Редукс .

HTML

Разметка обоих приложений на данный момент одинакова, позже мы будем использовать директивы в функциях Vue и Render в React для их изменения.

<section>
  <h1>TO DO LIST</h1>
  <div class="todoapp">
    <header class="header">
      <input class="new-todo"
        autofocus autocomplete="off"
        placeholder="What's on your Mind?">
    </header>
    <section class="main">
      <ul class="todo-list">
        <li>
          <div class="view">
            <input class="toggle" type="checkbox">
            <label> Placeholder TEXT </label>
            <button class="destroy"></button>
          </div>
        </li>
      </ul>
    </section>
  </div>
</section>

Начиная

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

Вьюекс

Vuex тесно связан с VueJS, поэтому для начала работы с Vuex требуется меньше стандартного кода.

  import Vue from 'vue'
  import App from './App.vue'
  import store from './store'
  import './main.css';
  Vue.config.productionTip = false

  new Vue({
    store,
    render: h => h(App)
  }).$mount('#app')

Редукс

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

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

const store = createStore(
    reducer
    );

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

Магазин и состояние

И Vuex, и Redux имеют один объект хранилища, который поддерживает все переменные состояния приложения. Давайте посмотрим, как создавать переменные состояния в хранилище.

Как это делает Vuex

Состояние Vuex изменчиво, поэтому мы можем напрямую создавать переменные состояния и присваивать им значения.

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    todos: []
  }
});

Как Redux это делает

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

var initialState = [];
const todos = (state = initialState, action) => {
  
};

export default todos;

Redux использует редукторы для создания набора состояний и управления ими.

Использование этих состояний в нашем приложении

Теперь, когда мы смогли создать состояние с одним жестко запрограммированным элементом TODO, давайте посмотрим, как мы можем использовать это в нашем приложении.

Как это делает Vuex

вы должны mapState() вспомогательная функция для сопоставления состояний из нашего хранилища Vuex с нашими компонентами. К этим сопоставленным переменным можно обращаться напрямую, как к переменным нормального состояния, хотя мы не можем напрямую изменять эти переменные.

//app.vue
<script>
import { mapState } from 'vuex';
export default {
  name: 'app',
  computed: {
    ...mapGetters(['completedList']),
    ...mapState({
      todoList: state => state.todos
    })
   }
  }
  </script>

export default new Vuex.Store({
state: {
  todos: []
},
getters: {
  completedList(state) {
    return state.todos.filter(todos => todos.completed === true);
  }
}
})

Если нам нужно выполнить какую-то операцию над нашими переменными состояния и получить вычисленное значение для использования в различных компонентах, мы можем использовать Vuex getters используя это в нашем шаблоне:

<li v-for="(item, index) in todoList" 
  :key="item.id" 
  :class="{ completed: item.completed}"
>

Как Redux это делает

Редукс имеет mapStateToProps() метод, который передается компоненту более высокого порядка connect предоставлено react-redux библиотека. Эти состояния теперь доступны как реквизиты в нашем компоненте.


import { connect } from 'react-redux';

const mapStateToProps = (state) => {
  return { todos: state };
}

export default connect(mapStateToProps)(App);

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

renderList() {
return this.props.todos.map(item => {
  return (
    <li key={item.id}
      className={"todo " + (item.completed ? "completed" : "")}
      onClick={() => this.props.toggleCompletion(item.id)}>
    </li>
  )
})
}

Изменение состояния

Переменная состояния не должна изменяться напрямую. Мы используем специальные методы для их изменения/обновления, чтобы их можно было правильно отслеживать.

Как это делает Vuex

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


    addItem(state, payload) {
      state.todos.push({id:GLOBAL_ID++, title: payload, completed: false});
    },
    togglecompletion(state, id) {
      state.todos.forEach( item => {
        if(item.id === id) 
          item.completed = !item.completed;
      })
    },
    removeItem(state, index) {
      state.todos.splice(index, 1);
    }
  }

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

Как Redux это делает

В Redux методы изменения состояния также написаны в редьюсерах.



const todos = (state = initialState, action) => {
  switch (action.type) {
    case "ADD_ITEM":
      return [
        ...state,
        {
          id: GLOBAL_ID++,
          title: action.title,
          completed: false
        }
      ];
    case "TOGGLE_COMPLETION":
      console.log('action', action);
      return state.map(todo =>
        todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
      );
    case "REMOVE_ITEM":
      return state.filter(todo => todo.id !== action.id);
    default:
      return state;
  }
};

Редукторы поддерживают как состояние, так и методы их модификации. Эти методы вызываются диспетчерские действия. Эти действия также используют полезную нагрузку для отправки данных из нашего приложения в наш магазин Redux. (Помните, в Redux состояния неизменяемы)



let nextTodoId = 0;

export const addItem = title => {
  return {
    type: "ADD_ITEM",
    id: nextTodoId++,
    title
  };
};

export const toggleCompletion = id => {
  return {
    type: "TOGGLE_COMPLETION",
    id
  };
};

export const removeItem = id => {
  return {
    type: "REMOVE_ITEM",
    id
  }
};

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

Изменение состояния из наших компонентов

Как это делает Vuex

Vuex предоставляет вспомогательный метод mapMutations() чтобы получить доступ к нашим мутациям в компонентах.

methods: {
 ...mapMutations([
  'addItem', 
  'togglecompletion',
  'removeItem',
 ])
}

После сопоставления к этим методам можно получить доступ как к обычным методам компонента, используя эти мутации в нашем компоненте:

<button class="destroy" @click.stop="removeTodo(index)"></button>

removeTodo: function(index) {
  this.removeItem(index);
}

Как Redux это делает

Похожий на mapStateToProps() Redux предоставляет нам еще одного помощника, называемого mapDispatchToProps() перешла к нашему УК.

const mapDispatcherstoProps = dispatch => {
  return {
    toggleCompletion: (id) => dispatch(toggleCompletion(id)),
    removeItem: (id) => dispatch(removeItem(id)),
    addItem: (title)=> dispatch(addItem(title)),
    addItemFromWeb: ()=> dispatch(addItemFromWeb())   
  }
}

export default connect(mapStateToProps, mapDispatcherstoProps)(App);

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

<button className="destroy" 
    onClick={() => this.props.removeItem(item.id)}
/>

Теперь наше приложение TO DO LIST полностью функционально, мы можем добавлять элементы, проверять выполненные элементы и удалять элементы.

Выполнение асинхронных вызовов

Расширяя наше приложение TO DO LIST, допустим, мы хотим загрузить список для пользователя, хранящийся на сервере, мы не можем сделать вызов на сервер напрямую из наших мутаций Vuex или действий Redux, поскольку они являются синхронными методами. Для этого нужны особые способы.

Как это делает Vuex

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


actions: {
    addItemFromWeb(context) {
      axios.get('[https://jsonplaceholder.typicode.com/todos/1'](https:
      .then((response) => {
        console.log(response);
        context.commit('addItem', response.data.title)
      })
      .catch((error) => console.log(error));
    }
  }

В приведенном выше примере мы используем axiosбиблиотека для выполнения HTTP-вызова.

Чтобы использовать эти действия в наших компонентах, Vuex предоставляет нам mapActions() вспомогательный метод.

<button @click="addItemFromWeb"> Async Add </button>

methods: {
    ...mapActions([
      'addItemFromWeb'
    ])
}

Как Redux это делает

Redux не предоставляет готовых решений, как Vuex, поэтому нам нужно ПО промежуточного слоя для совершения асинхронных вызовов.

Для этого мы используем промежуточное ПО под названием redux-thunk. Это промежуточное ПО очень простое, оно проверяет, является ли действие функцией. Если это так, то эта функция вызывается с dispatch. Если нет, редукторы вызываются напрямую.

import { applyMiddleware } from 'redux';
import thunk from 'redux-thunk';

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

Сейчас в actions.js мы создаем нашу асинхронную функцию:

export const addItemFromWeb = () => {
    return dispatch => {
        axios.get('[https://jsonplaceholder.typicode.com/todos/1'](https:
        .then((response) =>{
            console.log(response);
            dispatch(addItem(response.data.title));
        })
        .catch((error) => {
            console.log(error);
        })
    }
}

Управление приложениями и масштабирование

По мере роста нашего приложения у нас будет больше состояний для управления, у нас не может быть одногоstore.js файл для Vuex или одиночный reducer.js файл для Redux, нам понадобится
Vuex позволяет нам разделить наш магазин на modules. Каждый модуль может содержать свое состояние, мутации, действия, геттеры и даже вложенные модули.

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a 
store.state.b 

Внутри мутаций и геттеров модуля первым полученным аргументом будет модуль местное государство.

Каждый module можно записать в отдельные файлы, которые можно импортировать в store.js

Как Redux это делает

Мы можем разделить наш корневой редьюсер на несколько редьюсеров, а затем объединить их вместе. Каждый редуктор будет отвечать за управление состояниями внутри них.

import { combineReducers } from 'redux' 
import todos from './todos' 
import counter from './counter'
  
let reducers = combineReducers({
todo: todos,
ctr: counter
})

const store = createStore(reducer);

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

Библиотека Redux предоставляет нам функцию под названием combineReducers объединить все наши редукторы в один редуктор. (Обратите внимание, что мы не можем напрямую получить доступ к состояниям, присутствующим в одном редьюсере, в другом) данные, необходимые для редуктора, должны передаваться компонентом через действия.

Вывод

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

Я надеюсь, что вы нашли это полезным. Если да, то обязательно нажмите на кнопку Like 😉

Живая демонстрация

Вью + Вьюэкс

Реагировать + Редукс

Исходный код демонстрационного приложения

Vuex ВСЕ Гитхаб

Редукс TODO Github

Я полный рабочий день фрилансер. Есть проекты для меня? Напишите мне по электронной почте contact@preetish.in 😃

Эта статья была первоначально опубликована на Середина

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *