Menskalakan aplikasi Redux dengan bebek

Untuk mengantisipasi dimulainya kursus, "Pengembang React.js" menyiapkan terjemahan materi yang bermanfaat.





Bagaimana front-end skala aplikasi Anda? Bagaimana membuat kode Anda mendukung enam bulan kemudian?

Pada 2015, Redux menyerbu dunia pengembangan front-end dan memantapkan dirinya sebagai standar di luar React.

Perusahaan tempat saya bekerja baru-baru ini menyelesaikan refactoring basis kode besar pada React, tempat kami menerapkan redux alih-alih refluks .

Kami harus mengambil langkah ini karena bergerak maju tidak mungkin tanpa aplikasi yang terstruktur dengan baik dan seperangkat aturan yang jelas.

Basis kode telah ada selama lebih dari dua tahun dan refluks telah ada sejak awal. Kami harus mengubah kode, sangat terkait dengan komponen Bereaksi, yang tidak ada yang menyentuh selama lebih dari setahun.
Berdasarkan pengalaman dari pekerjaan yang dilakukan, saya membuat repositori ini , yang akan membantu menjelaskan pendekatan kami untuk mengatur kode pada redux.
Ketika Anda belajar lebih banyak tentang redux, tindakan, dan reduksi , Anda mulai dengan contoh sederhana. Banyak tutorial yang tersedia saat ini tidak lebih dari itu. Namun, jika Anda membuat sesuatu yang lebih kompleks pada Redux daripada daftar tugas, Anda akan memerlukan cara yang masuk akal untuk skala basis kode Anda dari waktu ke waktu.

Seseorang pernah berkata bahwa dalam ilmu komputer tidak ada tugas yang lebih sulit daripada memberikan nama hal yang berbeda. Saya tidak bisa tidak setuju. Dalam hal ini, penataan folder dan organisasi file akan berada di posisi kedua.

Mari kita lihat bagaimana kita dulu mendekati organisasi kode.

Fungsi vs Fitur


Ada dua pendekatan yang diterima secara umum untuk mengatur aplikasi: function-first dan feature-first.
Pada tangkapan layar di sebelah kiri, struktur folder disusun menurut prinsip fungsi-pertama, dan di kanan - fitur-pertama.



Fungsi-pertama berarti direktori tingkat atas Anda diberi nama sesuai dengan file di dalamnya. Jadi Anda memiliki: wadah, komponen, aksi, reduksi , dll.

Ini tidak berskala sama sekali. Ketika aplikasi Anda tumbuh dan fungsi baru muncul, Anda akan menambahkan file ke folder yang sama. Akibatnya, Anda perlu menggulir konten dari salah satu folder untuk waktu yang lama untuk menemukan file yang diinginkan.

Masalah lain adalah menggabungkan folder. Salah satu utas aplikasi Anda mungkin akan memerlukan akses ke file dari semua folder.

Salah satu keuntungan dari pendekatan ini adalah dapat mengisolasi, dalam kasus kami, Bereaksi dari Redux. Oleh karena itu, jika Anda ingin mengubah perpustakaan manajemen negara bagian, Anda akan tahu folder mana yang Anda perlukan. Jika Anda perlu mengubah perpustakaan tampilan, Anda dapat meninggalkan folder dengan redux utuh.

Fitur-pertama berarti direktori tingkat atas akan dinamai sesuai dengan fungsi utama aplikasi: produk, kereta, sesi.

Pendekatan ini berskala jauh lebih baik, karena setiap fitur baru terletak di folder baru. Namun, Anda tidak memiliki pemisahan antara komponen Redux dan Bereaksi. Mengubah salah satunya dalam jangka panjang bukanlah tugas yang mudah.

Selain itu, Anda akan memiliki file yang bukan milik fungsi apa pun. Akibatnya, semuanya berujung pada folder bersama atau bersama, karena Anda juga ingin menggunakan kode Anda dalam berbagai fitur aplikasi Anda.

Menggabungkan yang terbaik dari dua dunia


Meskipun ini bukan topik artikel, saya ingin mengatakan bahwa file manajemen negara dari file UI perlu disimpan secara terpisah.

Pikirkan aplikasi Anda dalam jangka panjang. Bayangkan apa yang terjadi pada kode Anda jika Anda beralih dari Bereaksi ke sesuatu yang lain. Atau pikirkan tentang bagaimana basis kode Anda akan menggunakan ReactNative secara paralel dengan versi web.

Inti dari pendekatan kami adalah prinsip isolasi React code dalam folder bernama views , dan kode redux di folder lain yang disebut redux.

Pemisahan di tingkat entri ini memberi kita keleluasaan untuk mengatur bagian-bagian aplikasi secara terpisah.

Di dalam folder viewskami mendukung organisasi file fungsi-pertama. Dalam konteks Bereaksi, ini terlihat alami: halaman, tata letak, komponen, peningkat , dll.

Agar tidak tergila-gila dengan jumlah file dalam folder, Anda dapat menggunakan pendekatan fitur-pertama di dalam folder ini.

Sementara itu, di folder redux ...

Memperkenalkan bebek-ulang


Setiap fungsi aplikasi harus sesuai dengan tindakan dan reduksi terpisah, sehingga masuk akal untuk menerapkan pendekatan fitur-pertama.

Pendekatan modular asli bebek memudahkan untuk bekerja dengan redux dan menawarkan cara terstruktur untuk menambahkan fungsionalitas baru ke aplikasi Anda.

Namun, Anda ingin memahami apa yang terjadi ketika Anda mengatur skala aplikasi. Kami menyadari bahwa cara mengatur satu file per fitur mengacaukan aplikasi dan membuat dukungannya bermasalah.

Jadi bebek re-muncul . Solusinya adalah dengan membagi fungsionalitas menjadi folder bebek.

duck/
β”œβ”€β”€ actions.js
β”œβ”€β”€ index.js
β”œβ”€β”€ operations.js
β”œβ”€β”€ reducers.js
β”œβ”€β”€ selectors.js
β”œβ”€β”€ tests.js
β”œβ”€β”€ types.js
β”œβ”€β”€ utils.js

Folder bebek harus:

  • Berisi semua logika pemrosesan hanya SATU konsep aplikasi Anda, misalnya: produk, kereta, sesi, dll.
  • Berisi file index.js, yang diekspor sesuai dengan aturan bebek.
  • Simpan kode dalam satu file yang melakukan pekerjaan serupa, seperti reduksi, penyeleksi, dan tindakan.
  • Berisi tes yang berhubungan dengan bebek.

Misalnya, dalam contoh ini, kami tidak menggunakan abstraksi yang dibangun di atas redux. Saat membuat perangkat lunak, penting untuk memulai dengan jumlah abstraksi yang paling sedikit. Dengan demikian, Anda akan melihat bahwa nilai abstraksi Anda tidak melebihi manfaatnya.

Jika Anda ingin memastikan abstraksi buruk, tonton video Cheng Lou ini .

Mari kita lihat isi setiap file.

Jenis


File types berisi nama tindakan yang Anda lakukan di aplikasi Anda. Sebagai praktik yang baik, Anda harus mencoba untuk menutupi namespace yang sesuai dengan fungsi yang dimiliki. Pendekatan ini akan membantu ketika men-debug aplikasi yang kompleks.

const QUACK = "app/duck/QUACK";
const SWIM = "app/duck/SWIM";

export default {
    QUACK,
    SWIM
};

Tindakan


File ini berisi semua fungsi pembuat tindakan .

import types from "./types";

const quack = ( ) => ( {
    type: types.QUACK
} );

const swim = ( distance ) => ( {
    type: types.SWIM,
    payload: {
        distance
    }
} );

export default {
    swim,
    quack
};

Perhatikan bahwa semua tindakan diwakili oleh fungsi, bahkan jika itu tidak parameter. Pendekatan yang konsisten adalah prioritas tertinggi untuk basis kode besar.

Operasi


Untuk mewakili rantai operasi ( Operasi ), Anda akan memerlukan redux middleware , untuk meningkatkan fungsi pengiriman . Contoh populer adalah redux-thunk , redux-saga, atau redux-observable .

Dalam kasus kami, redux-thunk digunakan . Kita perlu memisahkan thunks dari pencipta aksi, bahkan dengan biaya menulis kode tambahan. Oleh karena itu, kami akan mendefinisikan operasi sebagai pembungkus tindakan .

Jika operasi hanya mengirim satu tindakan , yaitu, itu tidak benar-benar menggunakan redux-thunk , kami mengirim fungsi pembuat tindakan. Jika operasi menggunakan thunk , ia dapat mengirim banyak tindakan dan menautkannya menggunakan janji .

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
};

Sebut mereka operasi, thunks , sagas, epos, seperti yang Anda inginkan. Identifikasi saja prinsip penamaan dan patuhi mereka.

Pada akhirnya, kita akan berbicara tentang indeks dan melihat bahwa operasi adalah bagian dari antarmuka publik bebek. Tindakan dienkapsulasi, operasi menjadi dapat diakses dari luar.

Reduksi


Jika Anda memiliki fungsi yang lebih beragam, Anda harus menggunakan beberapa reduksi untuk menangani struktur keadaan kompleks. Selain itu, jangan takut untuk menggunakan sebanyak mungkin CombedReducers yang Anda butuhkan. Ini akan memungkinkan bekerja dengan struktur objek negara lebih bebas.

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;

Dalam aplikasi besar, pohon negara akan terdiri dari setidaknya tiga tingkatan. Fungsi peredam harus sekecil mungkin dan hanya menangani konstruksi data sederhana. Fungsi CombReducers adalah semua yang Anda butuhkan untuk membuat struktur keadaan yang fleksibel dan dapat dipelihara.

Lihat contoh proyek lengkap dan lihat bagaimana menggunakan gabunganReducer dengan benar , terutama dalam file reducers.jsdan di store.jsmana kita sedang membangun pohon negara.

Penyeleksi


Seiring dengan operasi pemilih ( pemilih ) adalah bagian dari antarmuka publik bebek. Perbedaan antara operasi dan penyeleksi mirip dengan pola CQRS .

Fungsi pemilih mengambil sepotong status aplikasi dan mengembalikan beberapa data berdasarkan itu. Mereka tidak pernah melakukan perubahan pada status aplikasi.

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

export default {
    checkIfDuckIsInRange
};

Indeks


File ini menunjukkan apa yang akan diekspor dari folder bebek.
Apakah dia:

  • Mengekspor fungsi peredam dari bebek secara default.
  • Ekspor pemilih dan operasi sebagai ekspor terdaftar.
  • Jenis ekspor jika diperlukan di bebek lain.

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;

Tes


Keuntungan menggunakan Redux bersama dengan kerangka kerja bebek adalah Anda dapat menulis tes tepat setelah kode yang ingin Anda uji.

Menguji kode Anda di Redux cukup mudah:

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 ) ;
        } );
    } );
} );

Di dalam file ini Anda dapat menulis tes untuk reduksi , operasi, penyeleksi, dll.
Saya bisa menulis seluruh artikel terpisah tentang manfaat pengujian kode, tetapi mereka sudah cukup, jadi cukup uji kode Anda!

Itu saja


Kabar baiknya tentang re-itik adalah Anda dapat menggunakan template yang sama untuk semua kode redux Anda.

Pendekatan partisi berbasis fitur untuk kode redux Anda membantu aplikasi Anda tetap fleksibel dan dapat diskalakan seiring pertumbuhannya. Pendekatan pemisahan berbasis fungsi akan bekerja dengan baik ketika membangun komponen kecil yang umum untuk bagian aplikasi yang berbeda.

Anda dapat melihat pada basis kode contoh reaksi-redux penuh di sini . Ingatlah bahwa repositori aktif bekerja.

Bagaimana Anda mengatur aplikasi redux Anda? Saya menantikan umpan balik tentang pendekatan yang dijelaskan.

Sampai jumpa di lapangan .

All Articles