упростить редукс с помощью общего действия и редуктора - PullRequest
0 голосов
/ 27 мая 2018

В проекте React-Redux люди обычно создают несколько действий и уменьшителей для каждого подключенного компонента.Однако это создает много кода для простых обновлений данных.

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

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

// Say we're in user.js, User page

// state
var initialState = {};

// generic action --> we only need to write ONE DISPATCHER
function setState(obj){
    Store.dispatch({ type: 'SET_USER', data: obj });
}

// generic reducer --> we only need to write ONE ACTION REDUCER
function userReducer = function(state = initialState, action){
    switch (action.type) {
        case 'SET_USER': return { ...state, ...action.data };
        default: return state;
    }
};

// define component
var User = React.createClass({
    render: function(){
        // Here's the magic...
        // We can just call the generic setState() to update any data.
        // No need to create separate dispatchers and reducers, 
        // thus greatly simplifying and fasten app development.
        return [
            <div onClick={() => setState({ someField: 1 })}/>,
            <div onClick={() => setState({ someOtherField: 2, randomField: 3 })}/>,
            <div onClick={() => setState({ orJustAnything: [1,2,3] })}/>
        ]
    }
});

// register component for data update
function mapStateToProps(state){
    return { ...state.user };
}

export default connect(mapStateToProps)(User);

Редактировать

Итак, типичная архитектура Redux предлагает создавать:

  1. Централизованные файлы со всеми действиями
  2. Централизованные файлы со всеми редукторами

Вопрос в том, почему двухэтапный процесс ?Вот еще одно архитектурное предложение:

Создать 1 набор файлов , содержащий все setXField(), которые обрабатывают все изменения данных .А другие компоненты просто используют их для запуска изменений.Легко.Пример:

/** UserAPI.js
  * Containing all methods for User.
  * Other components can just call them.
  */

// state
var initialState = {};

// generic action
function setState(obj){
    Store.dispatch({ type: 'SET_USER', data: obj });
}

// generic reducer 
function userReducer = function(state = initialState, action){
    switch (action.type) {
        case 'SET_USER': return { ...state, ...action.data };
        default: return state;
    }
};


// API that we export
let UserAPI = {};

// set user name
UserAPI.setName = function(name){
    $.post('/user/name', { name }, function({ ajaxSuccess }){
        if (ajaxSuccess) setState({ name });
    });
};

// set user picture URL
UserAPI.setPicture = function(url){
    $.post('/user/picture', { url }, function({ ajaxSuccess }){
        if (ajaxSuccess) setState({ url });
    });
};

// logout, clear user
UserAPI.logout = function(){
    $.post('/logout', {}, function(){
        setState(initialState);
    });
};

// Etc, you got the idea...
// Moreover, you can add a bunch of other User related methods, 
// like some helper methods unrelated to Redux, or Ajax getters. 
// Now you have everything related to User available in a single file! 
// It becomes much easier to read through and understand.

// Finally, you can export a single UserAPI object, so other 
// components only need to import it once. 
export default UserAPI

Пожалуйста, прочитайте комментарии в разделе кода выше.

Теперь вместо множества действий / диспетчеров / редукторов.У вас есть файл 1, содержащий все необходимое для концепции пользователя . Почему это плохая практика ?IMO, это делает жизнь программиста намного проще , а другие программисты могут просто прочитать файл сверху вниз, чтобы понять бизнес-логику , им не нужно переключаться назад и впередмежду файлами действий / редукторов.Черт, даже redux-thunk не нужен!И вы даже можете проверить функции по одной.Так что тестируемость не теряется.

Ответы [ 7 ]

0 голосов
/ 08 июня 2018

Я начал писать пакет , чтобы сделать его более простым и универсальным.Также для улучшения производительности.Он все еще находится на ранних стадиях (охват 38%).Вот небольшой фрагмент (если вы можете использовать новые функции ES6), однако есть и другие варианты.

import { create_store } from 'redux';
import { create_reducer, redup } from 'redux-decorator';

class State {
    @redup("Todos", "AddTodo", [])
    addTodo(state, action) {
        return [...state, { id: 2 }];
    }
    @redup("Todos", "RemoveTodo", [])
    removeTodo(state, action) {
        console.log("running remove todo");
        const copy = [...state];
        copy.splice(action.index, 1);
        return copy;
    }
}
const store = createStore(create_reducer(new State()));

Вы также можете даже вкладывать свое состояние:

class Note{
        @redup("Notes","AddNote",[])
        addNote(state,action){
            //Code to add a note
        }
    }
    class State{
        aConstant = 1
        @redup("Todos","AddTodo",[])
        addTodo(state,action){
            //Code to add a todo
        }
        note = new Note();
    }
    // create store...
    //Adds a note
    store.dispatch({
        type:'AddNote'
    })
    //Log notes
    console.log(store.getState().note.Notes)

Множество документации доступно наНПМ.Как всегда, вы можете внести свой вклад!

0 голосов
/ 08 июня 2018

Ключевое решение, которое необходимо принять при разработке программ React / Redux, - куда поместить бизнес-логику (она должна куда-то идти!).

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

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

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

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

Краткое примечание: как уже упоминалось в https://stackoverflow.com/a/50646935, объектдолжен быть возвращен с setState.Это связано с тем, что перед вызовом store.dispatch может потребоваться некоторая асинхронная обработка.

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

import ActionType from "../actionsEnum.jsx";

const reducer = (state = {
    // Initial state ...
}, action) => {
    var actionsAllowed = Object.keys(ActionType).map(key => {
        return ActionType[key];
    });
    if (actionsAllowed.includes(action.type) && action.type !== ActionType.NOP) {
        return makeNewState(state, action.state);
    } else {
        return state;
    }
}

const makeNewState = (oldState, partialState) => {
    var newState = Object.assign({}, oldState);
    const values = Object.values(partialState);
    Object.keys(partialState).forEach((key, ind) => {
        newState[key] = values[ind];
    });
    return newState;
};

export default reducer;

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

0 голосов
/ 06 июня 2018

Ладно, я просто напишу свой собственный ответ:

  • при использовании приставки задайте себе два вопроса:

    1. Нужен ли мне доступ кданные по нескольким компонентам?
    2. Эти компоненты находятся в другом дереве узлов?Я имею в виду, что это не дочерний компонент.

      Если ваш ответ положительный, то используйте для этих данных избыточность, поскольку вы можете легко передавать эти данные своим компонентам через connect() API, который в терминах делает их containers.

  • Иногда, если вам необходимо передать данные в родительский компонент, вам необходимо пересмотреть, где находится ваше состояние.Существует вещь, называемая Поднятие состояния вверх.

  • Если ваши данные имеют значение только для вашего компонента, то вы должны использовать только setState, чтобы ограничить объем,Пример:

class MyComponent extends Component {
   constructor() {
       super()
       this.state={ name: 'anonymous' }
   }

   render() {
       const { name } = this.state
       return (<div>
           My name is { name }.
           <button onClick={()=>this.setState({ name: 'John Doe' })}>show name</button>
       </div>)
   }
}
  • Также не забывайте поддерживать однонаправленный поток данных.Не просто подключите компонент к хранилищу притока, если, во-первых, данные уже доступны его родительскому компоненту, например:
<ChildComponent yourdata={yourdata} />
  • Если вам нужно изменить состояние родителяот потомка просто передайте контекст функции логике вашего дочернего компонента.Пример:

В родительском компоненте

updateName(name) {
    this.setState({ name })
}

render() {
    return(<div><ChildComponent onChange={::this.updateName} /></div>)
}

В дочернем компоненте

<button onClick={()=>this.props.onChange('John Doe')}

Вот хорошая статья об этом.

0 голосов
/ 05 июня 2018

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

1) Что если я вызываю другой API, который имеет тот же ключ но идет к другому объекту?

2) Как я могу позаботиться о том, чтобы данные были потоком из сокета?Нужно ли повторять объект, чтобы получить тип (так как тип будет в заголовке и ответ в полезной нагрузке) или попросить мой внутренний ресурс отправить его с определенной схемой.

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

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

0 голосов
/ 04 июня 2018

Прежде всего, некоторая терминология :

  • action : сообщение, которое мы хотим отправить в все редукторы.Это может быть что угодно.Обычно это простой объект Javascript, такой как const someAction = {type: 'SOME_ACTION', payload: [1, 2, 3]}
  • тип действия : константа, используемая создателями действия для создания действия и редукторами, чтобы понять, какое действие они только что получили.Вы используете их, чтобы не вводить 'SOME_ACTION' как в создателях действий, так и в редукторах.Вы определяете тип действия, например const SOME_ACTION = 'SOME_ACTION', чтобы вы могли import его в создателях действий и в редукторах.
  • создатель действий : функция, которая создает действие и отправляет егоредукторы.
  • редуктор : функция, которая получает все действий, отправленных в хранилище, и отвечает за обновление состояния для этого редуктора store (у вас может быть несколько магазинов, если ваше приложение сложное).

Теперь к вопросу.

Я думаю, что общее действиесоздатель - не лучшая идея.

Ваше приложение может нуждаться в использовании следующих создателей действий:

fetchData()
fetchUser(id)
fetchCity(lat, lon)

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

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

Я начинаю с создания модуля для всех моих типов действий и создателей действий.Если мое приложение растет, я могу разделить создателей действий на разные модули (например, actions/user.js, actions/cities.js), но я думаю, что наличие отдельных модулей для типов действий немного излишне.


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

Редуктор получает все действия, отправленные создателями действий.Затем, посмотрев на action.type, он создает новое состояние магазина.Так как вам все равно приходится иметь дело со всеми поступающими действиями, мне приятно иметь всю логику в одном месте.Это, конечно, становится трудным, если ваше приложение растет (например, switch/case для обработки 20 различных действий не очень удобен в обслуживании).

Вы можете начать с одного редуктора, перейти к нескольким редукторам и объединить их.в корневом редукторе с функцией combineReducer.

0 голосов
/ 02 июня 2018

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

0 голосов
/ 01 июня 2018

Во-первых, вместо того, чтобы вызывать store.dispatch в вашем создателе действий, он должен вместо этого возвращать объект (действие), что упрощает тестирование, а включает рендеринг сервера .

const setState = (obj) => ({
  type: 'SET_USER', 
  data: obj
})

onClick={() => this.props.setState(...)}

// bind the action creator to the dispatcher
connect(mapStateToProps, { setState })(User)

Youтакже следует использовать класс ES6 вместо React.createClass.

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

const setSomeField = value => ({
  type: 'SET_SOME_FIELD',
  value,
});
...
case 'SET_SOME_FIELD': 
  return { ...state, someField: action.value };

Преимущества этогоПодойдите к вашему универсальному

1.Более высокая возможность повторного использования

Если someField установлен в нескольких местах, лучше набрать setSomeField(someValue), чем setState({ someField: someValue })}.

2.Более высокая тестируемость

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

С помощью универсального setState вы также можете проверить на setState({ someField: someValue })}, но нет прямой гарантии, что весь ваш код будет вызывать его правильно.
Например.кто-то в вашей команде может сделать опечатку и вместо этого позвонить setState({ someFeild: someValue })}.

Заключение

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

РЕДАКТИРОВАТЬ

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...