Небольшой пример для понимания процедуры редукции

внешний интерфейс JavaScript React.js Redux
Небольшой пример для понимания процедуры редукции

Особенности Редукса

  1. Единое управление состоянием, только одно хранилище в приложении
  2. Дерево состояний управляется на складе
  3. Склад нельзя модифицировать напрямую, модификация может распространять действия только через Dispatch.
  4. Обновить логику состояния до редьюсера

Что может Редукс?

Поскольку разработка одностраничных приложений на JavaScript становится все более сложной, управлять изменяющимся состоянием очень сложно, для решения проблемы с данными в состоянии появился Redux. В React данные передаются в одном направлении в компоненте, а данные передаются в одном направлении от родительского компонента к дочернему компоненту (через реквизиты).Из-за этой функции связь между двумя компонентами, не являющимися родительскими и дочерними (или одноуровневыми компонентами), более проблематичной.

Описание каждого объекта в редуксе

store

Хранилище — это хранилище данных. Хранилище уникально в приложении. Оно инкапсулирует состояние состояния. Когда пользователь хочет получить доступ к состоянию, он может получить объект состояния только через store.getState, а полученный объект — это моментальный снимок Это защищает объект магазина.

action

action описывает действие по обновлению состояния, это объект, атрибут type обязателен, он указывает действие и значение, которое нужно изменить:

{type: UPDATE_TITLE_COLOR, payload: 'green'}

actionCreator

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

dispatch

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

reducer

Редуктор — это ядро ​​обновления состояния, он инкапсулирует логику обновления состояния, редюсер предоставляется внешним миром (инкапсулирует бизнес-логику, передается при создании хранилища), передает старый объект состояния и действие для обновления новое значение для старого объекта состояния.

Процесс использования редукса

  1. Определите тип действия:
const INCREAMENT='INCREAMENT';
  1. Определить состояние проекта по умолчанию, переданное редьюсеру
let initState={...};
function reducer(state=initState,action){
    //...
}
  1. Напишите редьюсер для реализации конкретной логики обновления состояния
function reducer(state=initState,action){
    let newState;
    switch(action.type){
        //...
    }
    return newState;
}
  1. Создайте контейнер, передайте редуктор
let store=createStore(reducer);
  1. Подписка на нужный метод, который будет автоматически обновляться при изменении состояния
store.subcribe(function(){});
  1. Просто вызовите диспетчер, где вам нужно обновить состояние
store.dispatch(/*某个action*/);

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

Использование Redux с React

Ниже приведена небольшая функция для написания списка дел, описанная следующим образом:

  • Позволяет пользователям добавлять элементы списка дел
  • Вы можете посчитать, сколько предметов еще не закончено
  • Пользователи могут проверить задачу, чтобы установить ее как завершенную.
  • Фильтруемые условия просмотра (отображать все, отображать завершенные, отображать неполные)

Структура каталогов для небольших проектов:

项目根结点
┗━ components 存放组件
    ┗━ todo-header.js
    ┗━ todo-list.js
    ┗━ todo-footer.js
    ┗━ index.js
┗━ store 保存redux的相关文件
        ┗━ actions 定义action
            ┗━ action-type 定义动作类型
                ┗━ action-types.js
            ┗━ index.js
        ┗━ reducers 定义reducer
            ┗━ index.js
        ┗━ index.js 默认文件用于导出store
┗━ index.html 模版页面

Мы используем redux в сочетании с реакцией для реализации вышеуказанных 4 функций.

Компоненты разделены на 3 части:

  • TodoHeader используется для отображения количества ожидающих выполнения и добавления дел.
  • TodoList отображает список дел по условию
  • Кнопки функций TodoFooter (показать все, незавершенные, завершенные)

Считать незаконченные дела

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

//定义默认状态
let initState = {
  todos: [
    {
      id: parseInt(Math.random() * 10000000),
      isComplete: false,
      title: '学习redux'
    }, {
      id: parseInt(Math.random() * 10000000),
      isComplete: true,
      title: '学习react'
    }, {
      id: parseInt(Math.random() * 10000000),
      isComplete: false,
      title: '学习node'
    }
  ]
};

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

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

Поскольку на данный момент конкретной функциональной логики нет, мы создаем пустой редьюсер:

function reducer(state=initState,action){
  return state;
}
export default reducer;

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

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

import {createStore} from 'redux';
import reducer from './reducers';

let store = createStore(reducer);//传入reducer
export default store;

index.js в каталоге магазина по умолчанию экспортирует объект магазина, что удобно для внедрения компонентов.

Внедрить объект хранилища в корневой компонент, который является контейнером всех компонентов, поэтому он должен играть роль поставщика хранилища для всех компонентов, поэтому его задача — предоставить хранилище всем дочерним компонентам, для чего требуется react-redux package для предоставления компонента:Provider:

Провайдер тоже является компонентом, у него всего одно свойство:store, передайте созданный объект хранилища:

import {Provider} from 'react-redux';
import store from '../store';
//其它代码略...
ReactDOM.render(<Provider store={store}>
  <div>
    <TodoHeader/>
    <TodoList/>
    <TodoFooter/>
  </div>
</Provider>, document.querySelector('#root'));

это значитProviderВсе компоненты пакета легально доступны в магазине.

Теперь, когда данные предоставлены, нужны подкомпоненты для их получения.Пакет react-redux также предоставляет нам метод для получения данных хранилища:connect.

Метод connect очень замечательный, и его функция очень мощная, он может вводить данные о состоянии в хранилище в свойства компонента (this.props), чтобы дочерние компоненты могли получать данные в хранилище через свойства . Сначала определите компонент заголовка для отображения количества ожидающих выполнения:

import React from 'react';
import ReactDOM from 'react-dom';

class TodoHeader extends React.Component {
//代码略...
}

В следующем примере используется метод connect для ввода данных состояния в компонент TodoHeader:

import {connect} from 'react-redux';
let ConnectedTodoHeader = connect((state) => ({
  ...state
}))(TodoHeader);

Видно, что его написание очень странное. connect — это функция более высокого порядка (функция, возвращающая функцию), а ее финальное возвращаемое значение — компонент. Компонент уровня Provider.

Первый параметр подключения — это функция, а ее возвращаемый объект — объект.Возвращенный объект будет привязан к свойству целевого компонента.Состояние параметра функции — это возвращаемое значение store.getState, которое можно использовать для получения всех состояния. Текущее состояние – 3 задачи из задач

Параметр, переданный функцией более высокого порядка, — это компонент, который нужно внедрить, здесь — TodoHeader, так что в компоненте TodoHeader вы можете передатьthis.props.todosПолучить данные элемента списка дел.

Таким образом можно написать нашу первую статистическую функцию, и код прилагается ниже:

class TodoHeader extends React.Component {
  //取得未完成的todo数量
  getUnfinishedCount() {
    //this.props.todos就是从connect传入的state数据
    return this.props.todos.filter((i) => {
      return i.isComplete === false;
    }).length;
  }
  render() {
    return (<div>
      <div>您有{this.getUnfinishedCount()}件事未完成</div>
    </div>);
  }
}

//导出注入后的组件
export default connect((state) => ({
  ...state//此时的state就是todos:[...]数据
}))(TodoHeader);

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

Добавить в список дел

Далее завершается функция добавления задач, пользователь вводит задачу в текстовое поле, добавляет данные в хранилище и обновляет представление.

В связи с операцией пользователя нам необходимо написать действие (Action).Действию нужен определенный тип действия. Нужный тип действия мы можем создать в action-types.js:

//添加待办事项
export const ADD_TODO = 'ADD_TODO';

Как видите, это очень просто: он определяет тип действия, то есть инструкцию, описывающую действие Action, и экспортирует ее в редюсер для использования.

Затем напишите ActionCreator, функцию, которая только что возвращает объект Action, сгенерированный с помощью инструкции:

import {ADD_TODO} from './action-type/action-types';

let actions = {
  addTodo: function(payload) {
    return {type: ADD_TODO, payload};
  }
};

export default actions;//导出ActionCreators

Вы можете видеть, что введен тип действия, и addTodo возвращает форму, подобную{type:XXX, payload:XXX}объекта действия. Это форма стандартного объекта Action, а второй полезный параметр — это параметр, переданный пользователем.

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

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

import {ADD_TODO} from '../actions/action-type/action-types';
//部分代码略...
function reducer(state = initState, action) {
  let newState;
  switch (action.type) {
    case ADD_TODO:
      newState = {
        todos: [
          ...state.todos,
          action.payload
        ]
      };
      break;
    default:
      newState = state;
      break;
  }
  return newState;
}

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

Обратите внимание, что каждый раз, когда редьюсер возвращает новый объект, не помещайте эти данные напрямую в исходный файл state.todos.push, потому что редьюсер — это чистая функция.

...Он написан в ES6, что означает оператор расширения. Он расширяет данные исходного состояния. Тодос и добавляет новые данные за ним, что эквивалентно операции слияния.

Хорошо, часть этих данных обработки была записана, и дело дошло до работы инъекционных компонентов, и создайте компоненты для обработки TODOLIST:

import React from 'react';
import {connect} from 'react-redux';
class TodoList extends React.Component {
//代码略...
}
export default connect((state) => ({
  ...state
}))(TodoList);

Опять же, вставьте данные состояния в свойства компонента (TodoList) через метод подключения, чтобы компонент мог получить данные состояния через this.props.

Давайте напишем функцию, которая отображает список дел:

class TodoList extends React.Component {
  getTodos() {
    return this.props.todos.map((todo, index) => {
      return (<li key={index}>
        <input type="checkbox" checked={todo.isComplete}/> {
          todo.isComplete
            ? <del>{todo.title}</del>
            : <span>{todo.title}</span>
        }
        <button type="button" data-id={todo.id}>删除</button>
      </li>);
    });
  }
  render() {
    return (<div>
      <ul>
        {this.getTodos()}
      </ul>
    </div>);
  }
}

Определите метод getTodos в компоненте для циклического просмотра всех элементов списка дел.Вы можете видеть, что вы можете получать входящие данные от подключения через this.props.todos и вызывать getTodos в рендеринге.

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

Изменить статус задачи

Затем измените состояние элемента списка дел.Когда пользователь отмечает элемент списка дел, он записывается как завершенный, в противном случае он устанавливается как незавершенный.

Все то же самое, создайте новый тип действия:

//更改待办项的完成状态
export const TOGGLE_COMPLETE = 'TOGGLE_COMPLETE';

Создайте actionCreator и введите этот тип действия:

let actions = {
  //更改完成状态,此处payload传id
  toggleComplete: function(payload) {
    return {type: TOGGLE_COMPLETE, payload};
  }
    //其它略...
};

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

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

Также добавьте в редуктор ветку swtich, чтобы оценить TOGGLE_COMPLETE:

function reducer(state = initState, action) {
  let newState;
  switch (action.type) {
    case TOGGLE_COMPLETE:
      newState = {
        //循环每一条待办,把要修改的记录更新
        todos: state.todos.map(item => {
          if (item.id == action.payload) {
            item.isComplete = !item.isComplete;
          }
          return item;
        })
      };
      break;
    //其它代码略...
    default:
      newState = state;
      break;
  }
  return newState;
}

Можно видеть, что на этот раз свойство ISCOMPLETE изменяет запись, поэтому используйте цикл функции MAP, найдите идентификатор iSComplete и измените статус iScomplete.

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

Затем добавьте событие в элемент checkbox компонента, и когда пользователь проверит его, вызовите соответствующий Action toggleComplete для завершения логики:

//引入actionCreators
import actions from '../store/actions';
//其它 代码略...
class TodoList extends React.Component {
  todoChange = (event) => {
    //当onChange事件发生时,调用toggleComplete动作
    this.props.toggleComplete(event.target.value);
  }
  getTodos() {
    return this.props.todos.map((todo, index) => {
      return (<li key={index}>
        <input type="checkbox" value={todo.id} checked={todo.isComplete} onChange={this.todoChange}/> {
          todo.isComplete
            ? <del>{todo.title}</del>
            : <span>{todo.title}</span>
        }
        <button type="button" data-id={todo.id}>删除</button>
      </li>);
    });
  }
  render() {
    //略...
  }
}
export default connect((state) => ({
  ...state
}), actions)(TodoList); //第二个参数传入actionCreators

Функция подключения здесь передает второй параметр, который является объектом actionCreator. По той же причине компоненту необходимо вызвать действие, чтобы отправить действие для реализации определенной логики. Например, вот состояние компонента, которому необходимо обновить элемент списка дел, затем «функция». Он также передается компоненту с помощью редукции.

Таким образом, this.props в компоненте может заставить метод actionCreator вызывать логику:this.props.toggleComplete().

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

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

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

Фильтровать условия просмотра

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

Поэтому мы модифицируем инициализированное состояние, чтобы оно по умолчанию было «просмотреть все»:

//定义默认状态
let initState = {
    //display用于控制待办项列表的显示
  display:'all', 
  todos: [
  //略...
  ]
};

Та же процедура, создайте тип действия:

//更改显示待办项的状态
export const CHANGE_DISPLAY = 'CHANGE_DISPLAY';

Создайте создателя действия:

//部分代码略...
let actions = {
  //更改显示待办项的状态,
  //payload为以下3个值(all,uncompleted,completed)
  changeDisplay: function(payload) {
    return {type: CHANGE_DISPLAY, payload};
  }
};

Добавьте логику CHANGE_DISPLAY в редюсер:

//部分代码略...
function reducer(state = initState, action) {
  let newState;
  switch (action.type) {
    case CHANGE_DISPLAY:
      newState = {
        display: action.payload,
        todos: [...state.todos]
      };
      break;
    default:
      newState = state;
      break;
  }
  return newState;
}

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

class TodoList extends React.Component {
  //按display条件过滤数据
  filterDisplay() {
     return this.props.todos.filter(item => {
      switch (this.props.display) {
        case 'completed':
          return item.isComplete;
        case 'uncompleted':
          return !item.isComplete;
        case 'all':
        default:
          return true;
      }
    });
  }
  getTodos() {
    return this.filterDisplay().map((todo, index) => {
      //略...
    });
  }
  render() {
    //略...
  }
}
export default connect((state) => ({
  ...state
}), actions)(TodoList);

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

На данный момент все функции реализованы.

текущий результат:

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

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