Скалирање ваше Редук апликације паткама

Како се мери ваша предња апликација? Како се осигуравате да код који пишете буде одржив за 6 месеци?

Редук је 2015. године олујно заузео свет фронт-енд развоја и успоставио се као стандард - чак и изван домета Реацт-а.

У компанији у којој радим, недавно смо завршили са рефакторингом прилично велике базе Реацт кода, додајући редукс уместо рефлукса.

Учинили смо то јер напредак не би био могућ без добро структуриране апликације и доброг сета правила.

База кодова стара је више од две године и рефлукс је био тамо од почетка. Морали смо да променимо код који није додириван више од годину дана и био је прилично запетљан са Реацт компонентама.

На основу рада који смо обавили на пројекту, саставио сам овај репо, објашњавајући наш приступ у организовању нашег редук кода.

Када научите о редуксу и улогама акција и редуктора, започињете са врло једноставним примерима. Већина данас доступних водича не прелази на следећи ниво. Али ако градите нешто са Редук-ом што је компликованије од листе задатака, требат ће вам паметнији начин скалирања базе кодова с временом.

Неко је једном рекао да је именовање ствари један од најтежих послова у рачунарству. Не бих се могао сложити више. Али структурирање директоријума и организовање датотека је близу друге.

Истражимо како смо приступали организацији кодова у прошлости.

Функција вс карактеристика

Постоје два успостављена приступи структурним апликације: Функција-први и функција-први .

Лево доле можете видети структуру директоријума која је прва по функцији. Са десне стране можете видети приступ који је први по карактеру.

Функција прво значи да су ваши директорији највишег нивоа именовани према намени датотека у њима. Дакле, имате: контејнере , компоненте , акције , редукторе итд.

Ово се уопште не мери. Како ваша апликација расте, а ви додајете више функција, додајете датотеке у исте директоријуме. Дакле, на крају ћете морати да се померите унутар једне фасцикле да бисте пронашли своју датотеку.

Проблем је и у спајању директоријума. За један проток кроз апликацију вероватно ће бити потребне датотеке из свих директоријума.

Једна од предности овог приступа је што изолује - у нашем случају - реагује од редукса. Дакле, ако желите да промените државну библиотеку за управљање, знате које фасцикле треба да додирнете. Ако промените библиотеку приказа, своје редук фасцикле можете задржати нетакнутима.

Функција прво значи да су директоријуми највишег нивоа названи према главним карактеристикама апликације: производ , колица , сесија .

Овај приступ се много боље скалира, јер свака нова функција долази са новом фасциклом. Али, немате раздвајања између Реацт компоненти и редука. Дугорочна промена једног од њих врло је лукав посао.

Поред тога, имате датотеке које не припадају ниједној функцији. На крају ћете добити заједничку или дељену фасциклу јер желите да поново користите код за многе функције у апликацији.

Најбоље од два света

Иако није у делокругу овог чланка, желео бих да додирнем ову једину идеју: увек одвојите датотеке Државног управљања од УИ датотека.

Размислите о својој пријави на дужи рок. Замислите шта се дешава са кодном базом када пређете са Реацт-а на другу библиотеку. Или размислите како би ваша база кода користила РеацтНативе паралелно са веб верзијом.

Наш приступ полази од потребе за изоловањем Реацт кода у једну фасциклу - која се назива погледи - и редук кода у посебну фасциклу - која се зове редук.

Ова подела на првом нивоу пружа нам флексибилност да два различита дела апликације организујемо потпуно различито.

Унутар фасцикле погледа, преферирамо приступ функцији у структурирању датотека. Ово се чини врло природним у контексту Реацт-а: странице , распореди , компоненте, побољшачи итд.

Да не бисмо полудели од броја датотека у фасцикли, можда имамо поделу засновану на карактеристикама унутар сваке од ових фасцикли.

Затим, унутар фасцикле редук ...

Унесите ре-патке

Свака карактеристика апликације треба да се преслика на одвојене радње и редукторе, па има смисла одабрати приступ који је први од карактеристика.

Оригинални модуларни приступ паткама је лепо поједностављење редукса и нуди структурирани начин додавања сваке нове функције у вашу апликацију.

Ипак, желели смо мало да истражимо шта се дешава када се апликација скалира. Схватили смо да једна датотека за функцију постаје превише претрпана и тешко је дугорочно је одржавати.

Тако су рођене ре-патке . Решење је било поделити сваку особину у фасциклу за патке .

duck/ ├── actions.js ├── index.js ├── operations.js ├── reducers.js ├── selectors.js ├── tests.js ├── types.js ├── utils.js

Фасцикла за патке МОРА:

  • садрже целокупну логику руковања само ЈЕДНИМ концептом у апликацији, нпр. производ , колица , сесија итд.
  • имају index.jsдатотеку која извози према оригиналним правилима о паткама.
  • држите код сличне намене у истој датотеци, као што су редуктори , селектори и акције
  • садрже тестове повезане са патком.

У овом примеру нисмо користили ниједну апстракцију изграђену на врху редукса. Када градите софтвер, важно је започети са најмање апстракција. На овај начин осигуравате да трошкови апстракција не превладају над предностима.

Ако требате да се убедите да апстракције могу бити лоше, погледајте овај сјајни говор Цхенг Лоу-а.

Погледајмо шта иде у сваку датотеку.

Врсте

Врсте датотека садржи имена радњи које су Диспецерски у пријави. Као добру праксу, покушајте да примените имена на основу особине којој припадају. Ово помаже при отклањању грешака у сложенијим апликацијама.

const QUACK = "app/duck/QUACK"; const SWIM = "app/duck/SWIM"; export default { QUACK, SWIM };

Акције

Ова датотека садржи све функције креатора радњи.

import types from "./types"; const quack = ( ) => ( { type: types.QUACK } ); const swim = ( distance ) => ( { type: types.SWIM, payload: { distance } } ); export default { swim, quack };

Notice how all the actions are represented by functions, even if they are not parametrized. A consistent approach is more than needed in a large codebase.

Operations

To represent chained operations you need a redux middleware to enhance the dispatch function. Some popular examples are: redux-thunk, redux-saga or redux-observable.

In our case, we use redux-thunk. We want to separate the thunks from the action creators, even with the cost of writing extra code. So we define an operation as a wrapper over actions.

If the operation only dispatches a single action — doesn’t actually use redux-thunk — we forward the action creator function. If the operation uses a thunk, it can dispatch many actions and chain them with promises.

import actions from "./actions"; // This is a link to an action defined in actions.js. const simpleQuack = actions.quack; // This is a thunk which dispatches multiple actions from actions.js const complexQuack = ( distance ) => ( dispatch ) => { dispatch( actions.quack( ) ).then( ( ) => { dispatch( actions.swim( distance ) ); dispatch( /* any action */ ); } ); } export default { simpleQuack, complexQuack };

Call them operations, thunks, sagas, epics, it’s your choice. Just find a naming convention and stick with it.

At the end, when we discuss the index, we’ll see that the operations are part of the public interface of the duck. Actions are encapsulated, operations are exposed.

Reducers

If a feature has more facets, you should definitely use multiple reducers to handle different parts of the state shape. Additionally, don’t be afraid to use combineReducers as much as needed. This gives you a lot of flexibility when working with a complex state shape.

import { combineReducers } from "redux"; import types from "./types"; /* State Shape { quacking: bool, distance: number } */ const quackReducer = ( state = false, action ) => { switch( action.type ) { case types.QUACK: return true; /* ... */ default: return state; } } const distanceReducer = ( state = 0, action ) => { switch( action.type ) { case types.SWIM: return state + action.payload.distance; /* ... */ default: return state; } } const reducer = combineReducers( { quacking: quackReducer, distance: distanceReducer } ); export default reducer;

In a large scale application, your state tree will be at least 3 level deep. Reducer functions should be as small as possible and handle only simple data constructs. The combineReducers utility function is all you need to build a flexible and maintainable state shape.

Check out the complete example project and look how combineReducers is used. Once in the reducers.js files and then in the store.js file, where we put together the entire state tree.

Selectors

Together with the operations, the selectors are part of the public interface of a duck. The split between operations and selectors resembles the CQRS pattern.

Selector functions take a slice of the application state and return some data based on that. They never introduce any changes to the application state.

function checkIfDuckIsInRange( duck ) { return duck.distance > 1000; } export default { checkIfDuckIsInRange };

Index

This file specifies what gets exported from the duck folder. It will:

  • export as default the reducer function of the duck.
  • export as named exports the selectors and the operations.
  • export the types if they are needed in other ducks.
import reducer from "./reducers"; export { default as duckSelectors } from "./selectors"; export { default as duckOperations } from "./operations"; export { default as duckTypes } from "./types"; export default reducer;

Tests

A benefit of using Redux and the ducks structure is that you can write your tests next to the code you are testing.

Testing your Redux code is fairly straight-forward:

import expect from "expect.js"; import reducer from "./reducers"; import actions from "./actions"; describe( "duck reducer", function( ) { describe( "quack", function( ) { const quack = actions.quack( ); const initialState = false; const result = reducer( initialState, quack ); it( "should quack", function( ) { expect( result ).to.be( true ) ; } ); } ); } );

Inside this file you can write tests for reducers, operations, selectors, etc.

I could write a whole different article about the benefits of testing your code, there are so many of them. Just do it!

So there it is

The nice part about re-ducks is that you get to use the same pattern for all your redux code.

Подјела заснована на карактеристикама за редукс код је много флексибилнија и скалабилнија како ваша база кодова апликације расте. И подјела за погледе заснована на функцијама дјелује када направите мале компоненте које се дијеле у апликацији.

Овде можете погледати потпуну базу кодова за реакцију-редукс-пример. Само имајте на уму да је репо још увек у активном развоју.

Како структурирате своје редук апликације? Радујем се чути повратне информације о овом приступу који сам представио.

Ако вам је овај чланак био користан, кликните на зелено срце испод и знаћу да мој труд није узалудан.