Pengembangan perpustakaan perusahaan komponen Bereaksi. Pendekatan lintas platform

Artikel ini menceritakan tentang keberhasilan implementasi sistem desain di perusahaan salah satu pengecer DIY terbesar. Prinsip-prinsip dan pendekatan pengembangan lintas-platform komponen UI menggunakan perpustakaan Asli Bereaksi dan Bereaksi dijelaskan, serta solusi untuk masalah penggunaan kembali kode antara proyek untuk platform yang berbeda.

Pertama, beberapa kata tentang bagaimana semuanya dimulai, dan mengapa ide menerapkan desain sistem muncul. Semuanya dimulai dengan aplikasi Android seluler untuk penjual di toko. Aplikasi ini dibangun di atas kerangka React-Native. Fungsi awal diwakili oleh hanya beberapa modul, seperti mencari produk dalam katalog dan kartu produk, dokumen penjualan. Omong-omong, sekarang ini adalah aplikasi yang cukup kuat yang telah sebagian besar menggantikan fungsi meja informasi di toko.

Selanjutnya, proyek aplikasi web untuk karyawan departemen logistik, serta berbagai konfigurator, diluncurkan.

Pada tahap ini, pemahaman tentang pendekatan umum untuk desain aplikasi ini, serta keberadaan basis kode yang cukup besar, muncul. Dan logis untuk mensistematisasikan yang lain untuk digunakan kembali lebih lanjut.

Untuk mensistematisasikan UI / UX, diputuskan untuk mengembangkan sistem desain. Saya tidak akan merinci apa itu. Di Internet, Anda dapat menemukan banyak artikel tentang topik ini. Misalnya, di HabrΓ©, karya Andrei Sundiev dapat direkomendasikan untuk dibaca .

Mengapa mendesain sistem dan apa kelebihannya? Yang pertama adalah pengalaman umum dan perasaan menggunakan produk. Pengguna mendapatkan antarmuka yang familier terlepas dari aplikasinya: tombol-tombolnya terlihat dan bekerja seperti biasa, menu terbuka di tempat yang tepat dan dengan dinamika yang tepat, bidang input bekerja dengan cara biasa. Keuntungan kedua adalah pengenalan standar tertentu dan pendekatan umum baik dari sisi desain dan dari sisi pengembangan. Setiap fungsionalitas baru dikembangkan sesuai dengan kanon dan pendekatan yang sudah mapan. Dari hari-hari pertama, karyawan baru menerima pekerjaan yang jelas. Berikutnya adalah menggunakan kembali komponen dan menyederhanakan pengembangan. Tidak perlu "menemukan kembali roda" setiap saat. Anda dapat membangun antarmuka dari blok siap pakai dengan hasil akhir yang diharapkan.Nah, keuntungan utama di tempat pertama bagi pelanggan adalah menghemat uang dan waktu.

Jadi apa yang sudah kita lakukan. Bahkan, kami telah membuat tidak hanya perpustakaan komponen, tetapi seluruh kerangka kerja lintas platform. Kerangka kerja ini didasarkan pada skema batch. Kami memiliki 5 paket inti npm. Ini adalah inti untuk menyebarkan aplikasi lintas-platform web dan Android. Paket modul, utilitas dan layanan. Dan satu paket komponen, yang akan dibahas nanti.
Di bawah ini adalah diagram UML dari paket komponen.

gambar

Ini termasuk komponen itu sendiri, beberapa di antaranya independen (elemen), dan beberapa terhubung satu sama lain, serta inti dalam atau "sub-inti".

Mari kita pertimbangkan secara lebih terperinci apa yang termasuk dalam β€œsubnukleus”. Yang pertama adalah lapisan visual dari desain sistem. Semuanya di sini adalah tentang palet warna, tipografi, sistem indentasi, kisi, dll. Blok selanjutnya adalah layanan yang diperlukan agar komponen dapat berfungsi, seperti: ComponentsConfig (konfigurasi komponen), StyleSet (saya akan membahas konsep ini secara lebih rinci nanti) dan Device (metode untuk bekerja dengan perangkat api perangkat). Dan blok ketiga adalah semua jenis pembantu (resolver, generator gaya, dll.).

gambar

Saat mengembangkan perpustakaan, kami menggunakan pendekatan atom untuk desain komponen. Semuanya dimulai dengan penciptaan komponen atau elemen elementer. Mereka adalah "partikel" elementer yang tidak tergantung satu sama lain. Yang utama adalah View, Text, Image, Icon. Berikutnya adalah komponen yang lebih kompleks. Masing-masing dari mereka menggunakan satu atau lebih elemen untuk membangun strukturnya. Misalnya, tombol, bidang input, pilih, dll. Level selanjutnya adalah pola. Mereka adalah kombinasi komponen untuk menyelesaikan masalah UI. Misalnya, formulir otorisasi, header dengan parameter dan pengaturan, atau kartu produk yang dirancang oleh perancang yang dapat digunakan dalam modul yang berbeda. Tingkat penting terakhir dan paling sulit dan pada saat yang sama adalah perilaku yang disebut. Ini adalah modul yang siap digunakan,Menerapkan logika bisnis tertentu dan, mungkin, termasuk serangkaian permintaan back-end yang diperlukan.

gambar

Jadi, mari kita beralih ke implementasi perpustakaan komponen. Seperti yang saya sebutkan sebelumnya, kami memiliki dua platform target - web dan Android (versi asli). Jika di web ini adalah elemen yang terkenal untuk semua pengembang web seperti div, span, img, header, dll., Dalam reaksi-asli ini adalah komponen View, Text, Image, Modal. Dan hal pertama yang kami sepakati adalah nama komponennya. Kami memutuskan untuk menggunakan sistem gaya reaksi-asli, sebagai pertama, beberapa basis komponen sudah diimplementasikan dalam proyek-proyek, dan kedua, nama-nama ini adalah yang paling universal dan dapat dipahami untuk pengembang web dan pengembang yang asli-bereaksi. Misalnya, perhatikan komponen Lihat. Metode komponen render bersyarat untuk web terlihat seperti ini:

render() {
	return(
		<div {...props}>
			{children}
		</div>
	)
}

Itu di bawah tenda, ini tidak lebih dari sebuah div dengan alat peraga dan keturunan yang diperlukan. Dalam reaksi asli, strukturnya sangat mirip, hanya komponen View yang digunakan dan bukan divs:

render() {
	return(
		<View {...props}>
			{children}
		</View>
	)
}

Muncul pertanyaan: bagaimana menggabungkan ini menjadi satu komponen dan pada saat yang sama membagi rendering?

Di sinilah pola Bereaksi yang disebut HOC atau Komponen Orde Tinggi datang untuk menyelamatkan. Jika Anda mencoba menggambar diagram UML dari pola ini, Anda mendapatkan sesuatu seperti berikut:

gambar

Dengan demikian, setiap komponen terdiri dari apa yang disebut delegasi yang menerima alat peraga dari luar dan bertanggung jawab atas logika yang sama untuk kedua platform, dan dua bagian platform di mana metode spesifik untuk setiap platform sudah dienkapsulasi dan render yang paling penting. Misalnya, pertimbangkan kode delegasi tombol:

export default function buttonDelegate(ReactComponent: ComponentType<Props>): ComponentType<Props> {
    return class ButtonDelegate extends PureComponent<Props> {
        
        // Button common methods

        render() {
           const { onPress, onPressIn, onPressOut } = this.props;
            const delegate = {
                buttonContent: this.buttonContent,
                buttonSize: this.buttonSize,
                iconSize: this.iconSize,
                onClick: onPress,
                onMouseUp: onPressIn,
                onMouseDown: onPressOut,
                onPress: this.onPress,
                textColor: this.textColor,
            };
            return (<ReactComponent {...this.props} delegate={delegate} />);
        }
    };
}

Delegasi menerima sebagai argumen bagian platform dari komponen, mengimplementasikan metode yang sama untuk kedua platform dan meneruskannya ke bagian platform. Bagian platform dari komponen itu sendiri adalah sebagai berikut:

class Button extends PureComponent<WebProps, State> {
    
   // Web specific methods

    render() {
        const { delegate: { onPress, buttonContent } } = this.props;
        return (
            <button
                className={this.classes}
                {...buttonProps}
                onClick={onPress}
                style={style}
            >
                {buttonContent(this.spinner, this.iconText)}
            </button>
        );
    }
}

export default buttonDelegate(Button);

Berikut adalah metode render dengan semua fitur platformnya. Fungsi umum dari delegasi datang dalam bentuk objek melalui alat peraga delegasi. Contoh bagian platform dari tombol untuk implementasi asli-reaksi:

class Button extends PureComponent<NativeProps, State> {

    // Native specific methods

    render() {
        const { delegate: { onPress, buttonContent } } = this.props;
        return (
            <View styleSet={this.styles} style={style}>
                <TouchableOpacity
                    {...butonProps}
                    onPress={onPress}
                    style={this.touchableStyles}
                    {...touchableProps}    
                >
                    {buttonContent(this.spinner, this.iconText)}
                </TouchableOpacity>
            </View>
        );
    }
}

export default buttonDelegate(Button);

Dalam hal ini, logikanya serupa, tetapi komponen yang bereaksi asli digunakan. Di kedua daftar, buttonDelegate adalah HOC dengan logika umum.

Dengan pendekatan ini dalam implementasi komponen, muncul pertanyaan tentang pemisahan bagian platform selama perakitan proyek. Penting untuk memastikan bahwa paket web yang digunakan oleh kami dalam proyek-proyek untuk web hanya mengumpulkan bagian-bagian komponen yang dimaksudkan untuk web, sedangkan metro bundler dalam bahasa asli harus "menghubungkan" bagian platformnya, tidak memperhatikan komponen untuk web.

Untuk mengatasi masalah ini, mereka menggunakan fitur bundler metro bawaan, yang memungkinkan Anda menentukan awalan ekstensi file platform. Dalam kasus kami, metro.config.js terlihat seperti ini:

module.exports = {
    resolver: {
        useWatchman: false,
        platforms: ['native'],
    },
};

Jadi, ketika membangun bundel, metro pertama-tama mencari file dengan ekstensi native.js, dan kemudian, jika tidak ada di direktori saat ini, itu mengaitkan file dengan ekstensi .js. Fungsionalitas ini memungkinkan untuk menempatkan bagian platform dari komponen dalam file terpisah: bagian untuk web terletak di file .js, bagian asli-reaksi ditempatkan dalam file dengan ekstensi .native.js.

Omong-omong, webpack memiliki fungsi yang sama menggunakan NormalModuleReplacementPlugin.

Tujuan lain dari pendekatan lintas-platform adalah untuk menyediakan mekanisme tunggal untuk komponen styling. Dalam hal aplikasi web, kami memilih prepasscessor sass, yang akhirnya dikompilasi menjadi css biasa. Itu untuk komponen web, kami menggunakan pengembang className reaksi yang familier.

Komponen asli-reaksi ditata melalui gaya inline dan gaya alat peraga. Itu perlu untuk menggabungkan kedua pendekatan ini, sehingga memungkinkan untuk menggunakan kelas gaya untuk aplikasi Android. Untuk tujuan ini, konsep styleSet diperkenalkan, yang tidak lebih dari serangkaian string - nama kelas:

styleSet: Array<string>

Pada saat yang sama, layanan StyleSet dengan nama yang sama diimplementasikan untuk react-asli, yang memungkinkan mendaftarkan nama kelas:

export default StyleSet.define({
    'lmui-Button': {
        borderRadius: 6,
    },
    'lmui-Button-buttonSize-md': {
        paddingTop: 4,
        paddingBottom: 4,
        paddingLeft: 12,
        paddingRight: 12,
    },
    'lmui-Button-buttonSize-lg': {
        paddingTop: 8,
        paddingBottom: 8,
        paddingLeft: 16,
        paddingRight: 16,
    },
})

Untuk komponen web, styleSet adalah array nama kelas css yang "direkatkan" menggunakan perpustakaan classnames .

Karena proyek ini adalah lintas-platform, jelas bahwa dengan pertumbuhan basis kode, jumlah dependensi eksternal juga meningkat. Selain itu, dependensi berbeda untuk setiap platform. Misalnya, untuk komponen web, perpustakaan seperti style-loader, react-dom, classnames, webpack, dll diperlukan. Untuk komponen yang asli, sejumlah besar perpustakaan asli digunakan, misalnya asli yang bereaksi sendiri. Jika sebuah proyek di mana ia seharusnya menggunakan perpustakaan komponen hanya memiliki satu platform target, maka menginstal semua dependensi pada platform lain tidak rasional. Untuk mengatasi masalah ini, kami menggunakan kait postinstall dari npm itu sendiri, di mana skrip diinstal untuk menginstal dependensi untuk platform yang ditentukan. Ketergantungan itu sendiri terdaftar di bagian yang sesuai dari package.json paket,dan platform target harus ditentukan dalam paket project.json sebagai sebuah array.
Namun, pendekatan ini mengungkapkan kelemahan, yang kemudian berubah menjadi beberapa masalah selama perakitan dalam sistem CI. Akar masalahnya adalah bahwa dengan package-lock.json, skrip yang ditentukan dalam postinstall tidak menginstal semua dependensi terdaftar.

Saya harus mencari solusi lain untuk masalah ini. Solusinya sederhana. Skema dua paket diterapkan di mana semua dependensi platform ditempatkan di bagian dependensi paket platform yang sesuai. Sebagai contoh, dalam kasus web, paket disebut komponen-web, di mana ada satu file package.json tunggal. Ini berisi semua dependensi untuk platform web, serta paket utama dengan komponen komponen. Pendekatan ini memungkinkan kami untuk mempertahankan pemisahan dependensi dan menjaga fungsionalitas package-lock.json.

Sebagai kesimpulan, saya akan memberikan contoh kode JSX menggunakan perpustakaan komponen kami:

<View row>
   <View
      col-xs={12}
      col-md={8}
      col-lg={4}
      col-xl={4}
      middle-xs
      col-md-offset-3
   />
     <Text size=”fs1”>Sample text</Text>
   </View>
</View>

Cuplikan kode ini adalah lintas-platform dan berfungsi sama di aplikasi reaksi untuk web dan aplikasi Android di asli-reaksi. Jika perlu, kode yang sama dapat "ditutup" di bawah iOS.

Dengan demikian, tugas utama yang kami hadapi terpecahkan - penggunaan kembali maksimum dari kedua pendekatan desain dan basis kode antara berbagai proyek.
Tolong tunjukkan dalam komentar mana pertanyaan tentang topik ini yang menarik untuk dipelajari di artikel selanjutnya.

All Articles