Kami baru-baru ini menerbitkan 
materi tentang metodologi SOLID. Hari ini kami membawa kepada Anda terjemahan dari artikel yang membahas penerapan prinsip-prinsip SOLID dalam pengembangan aplikasi menggunakan perpustakaan Bereaksi populer.

Penulis artikel mengatakan bahwa di sini, demi singkatnya, ia tidak menunjukkan implementasi penuh dari beberapa komponen.
Prinsip Tanggung Jawab Tunggal
Prinsip Tanggung Jawab Tunggal memberi tahu kita bahwa sebuah modul harus memiliki satu dan hanya satu alasan untuk perubahan.
Bayangkan kita sedang mengembangkan aplikasi yang menampilkan daftar pengguna dalam sebuah tabel. Berikut adalah kode untuk komponen 
App :
 class App extends Component {   state = {       users: [{name: 'Jim', surname: 'Smith', age: 33}]   };   componentDidMount() {       this.fetchUsers();   }   async fetchUsers() {       const response = await fetch('http://totallyhardcodedurl.com/users');       const users = await response.json();       this.setState({users});   }   render() {       return (           <div className="App">               <header className="App-header">                 //                     </header>               <table>                   <thead>                       <tr>                           <th>First name</th>                           <th>Last name</th>                           <th>Age</th>                       </tr>                   </thead>                   <tbody>                       {this.state.users.map((user, index) => (                           <tr key={index}>                               <td><input value={user.name} onChange={/* update name in the state */}/></td>                               <td><input value={user.surname} onChange={/* update surname in the state*/}/></td>                               <td><input value={user.age} onChange={/* update age in the state */}/></td>                           </tr>                       ))}                   </tbody>               </table>               <button onClick={() => this.saveUsersOnTheBackend()}>Save</button>           </div>       );   }   saveUsersOnTheBackend(row) {       fetch('http://totallyhardcodedurl.com/users', {           method: "POST",           body: JSON.stringify(this.state.users),       })   } } 
Kami memiliki komponen dalam kondisi penyimpanan daftar pengguna. Kami mengunduh daftar ini melalui HTTP dari server tertentu, daftar ini dapat diedit. Komponen kami melanggar prinsip tanggung jawab tunggal, karena memiliki lebih dari satu alasan untuk perubahan.
Secara khusus, saya dapat melihat empat alasan untuk mengubah komponen. Yaitu, komponen berubah dalam kasus berikut:
- Setiap kali Anda perlu mengubah judul aplikasi.
- Setiap kali Anda perlu menambahkan komponen baru ke aplikasi (footer halaman, misalnya).
- Setiap kali Anda perlu mengubah mekanisme untuk memuat data pengguna, misalnya, alamat server atau protokol.
- Setiap kali Anda perlu mengubah tabel (misalnya, mengubah format kolom atau melakukan beberapa tindakan lain seperti ini).
Bagaimana cara mengatasi masalah ini? Penting, setelah mengidentifikasi alasan untuk mengubah komponen, untuk mencoba menghilangkannya, untuk menyimpulkan dari komponen asli, menciptakan abstraksi yang sesuai (komponen atau fungsi) untuk setiap alasan tersebut.
Kami akan menyelesaikan masalah komponen 
App kami dengan refactoring. Kodenya, setelah memecahnya menjadi beberapa komponen, akan terlihat seperti ini:
 class App extends Component {   render() {       return (           <div className="App">               <Header/>               <UserList/>           </div>       );   } } 
Sekarang, jika Anda perlu mengubah judul, kami mengubah komponen 
Header , dan jika Anda perlu menambahkan komponen baru ke aplikasi, kami mengubah komponen 
App . Di sini kami memecahkan masalah No. 1 (mengubah tajuk aplikasi) dan masalah No. 2 (menambahkan komponen baru ke aplikasi). Ini dilakukan dengan memindahkan logika yang sesuai dari komponen 
App ke komponen baru.
Kami sekarang akan memecahkan masalah No. 3 dan No. 4 dengan membuat kelas 
UserList . Ini kodenya:
 class UserList extends Component {   static propTypes = {       fetchUsers: PropTypes.func.isRequired,       saveUsers: PropTypes.func.isRequired   };   state = {       users: [{name: 'Jim', surname: 'Smith', age: 33}]   };   componentDidMount() {       const users = this.props.fetchUsers();       this.setState({users});   }   render() {       return (           <div>               <UserTable users={this.state.users} onUserChange={(user) => this.updateUser(user)}/>               <button onClick={() => this.saveUsers()}>Save</button>           </div>       );   }   updateUser(user) {      
UserList adalah komponen wadah baru kami. Berkat dia, kami memecahkan masalah No. 3 (mengubah mekanisme pemuatan pengguna) dengan membuat 
saveUser properti 
saveUser dan 
saveUser . Akibatnya, sekarang kita perlu mengubah tautan yang digunakan untuk memuat daftar pengguna, kita beralih ke fungsi yang sesuai dan melakukan perubahan.
Masalah terakhir yang kita miliki di nomor 4 (mengubah tabel yang menampilkan daftar pengguna) telah diselesaikan dengan memasukkan komponen presentasi 
UserTable ke dalam proyek, yang merangkum pembentukan kode HTML dan menata tabel dengan pengguna.
Prinsip keterbukaan-penutupan (O)
Prinsip Terbuka Tertutup menyatakan bahwa entitas program (kelas, modul, fungsi) harus terbuka untuk ekspansi, tetapi tidak untuk modifikasi.
Jika Anda melihat komponen 
UserList dijelaskan di atas, Anda akan melihat bahwa jika Anda perlu menampilkan daftar pengguna dalam format yang berbeda, kami harus memodifikasi metode 
render komponen ini. Ini merupakan pelanggaran prinsip keterbukaan-kedekatan.
Anda dapat menyesuaikan program dengan prinsip ini menggunakan 
komposisi komponen .
Lihatlah kode komponen 
UserList yang telah di-refactored:
 export class UserList extends Component {   static propTypes = {       fetchUsers: PropTypes.func.isRequired,       saveUsers: PropTypes.func.isRequired   };   state = {       users: [{id: 1, name: 'Jim', surname: 'Smith', age: 33}]   };   componentDidMount() {       const users = this.props.fetchUsers();       this.setState({users});   }   render() {       return (           <div>               {this.props.children({                   users: this.state.users,                   saveUsers: this.saveUsers,                   onUserChange: this.onUserChange               })}           </div>       );   }   saveUsers = () => {       this.props.saveUsers(this.state.users);   };   onUserChange = (user) => {        
Komponen 
UserList , sebagai hasil dari modifikasi, ternyata terbuka untuk ekstensi, karena menampilkan komponen anak, yang memfasilitasi perubahan dalam perilakunya. Komponen ini ditutup untuk modifikasi, karena semua perubahan dilakukan dalam komponen yang terpisah. Kami bahkan dapat menggunakan komponen ini secara mandiri.
Sekarang mari kita lihat caranya, menggunakan komponen baru, daftar pengguna ditampilkan.
 export class PopulatedUserList extends Component {   render() {       return (           <div>               <UserList>{                   ({users}) => {                       return <ul>                           {users.map((user, index) => <li key={index}>{user.id}: {user.name} {user.surname}</li>)}                       </ul>                   }               }               </UserList>           </div>       );   } } 
Di sini kami memperluas perilaku komponen 
UserList dengan membuat komponen baru yang tahu cara membuat daftar pengguna. Kami bahkan dapat mengunduh informasi yang lebih terperinci tentang masing-masing pengguna dalam komponen baru ini tanpa menyentuh komponen 
UserList , dan inilah tepatnya tujuan refactoring komponen ini.
Prinsip Substitusi Barbara Lisk (L)
Prinsip penggantian Barbara Liskov (Prinsip Pergantian Liskov) menunjukkan bahwa objek dalam program harus diganti dengan instance subtipe mereka tanpa melanggar operasi program yang benar.
Jika definisi ini menurut Anda terlalu bebas dirumuskan - ini adalah versi yang lebih ketat.
Prinsip substitusi Barbara Liskov: jika sesuatu terlihat seperti bebek dan dukun seperti bebek, tetapi perlu baterai - mungkin abstraksi yang salah dipilihLihatlah contoh berikut:
 class User { constructor(roles) {   this.roles = roles; } getRoles() {   return this.roles; } } class AdminUser extends User {} const ordinaryUser = new User(['moderator']); const adminUser = new AdminUser({role: 'moderator'},{role: 'admin'}); function showUserRoles(user) { const roles = user.getRoles(); roles.forEach((role) => console.log(role)); } showUserRoles(ordinaryUser); showUserRoles(adminUser); 
Kami memiliki kelas 
User yang konstruktornya menerima peran pengguna. Berdasarkan kelas ini, kami membuat kelas 
AdminUser . Setelah itu, kami membuat fungsi 
showUserRoles sederhana yang mengambil objek bertipe 
User sebagai parameter dan menampilkan semua peran yang ditetapkan untuk pengguna di konsol.
Kami memanggil fungsi ini dengan mengirimkan objek 
ordinaryUser dan 
adminUser ke sana, setelah itu kami menemukan kesalahan.
KesalahanApa yang terjadi Objek kelas 
AdminUser mirip dengan objek kelas 
User . Ini pasti "dukun" sebagai 
User , karena memiliki metode yang sama seperti 
User . Masalahnya adalah "baterai". Faktanya adalah bahwa ketika membuat objek 
adminUser , kami melewati beberapa objek untuk itu, bukan array.
Di sini prinsip substitusi dilanggar, karena fungsi 
showUserRoles harus bekerja dengan benar dengan objek kelas 
User dan dengan objek yang dibuat berdasarkan kelas turunan dari kelas ini.
Tidak sulit untuk 
AdminUser masalah ini - cukup serahkan array ke konstruktor 
AdminUser alih-alih objek:
 const ordinaryUser = new User(['moderator']); const adminUser = new AdminUser(['moderator','admin']); 
Prinsip pemisahan antarmuka (I)
Prinsip Segregasi Antarmuka menunjukkan bahwa program tidak boleh bergantung pada apa yang tidak mereka butuhkan.
Prinsip ini sangat relevan dalam bahasa dengan pengetikan statis, di mana dependensi didefinisikan secara eksplisit oleh antarmuka.
Pertimbangkan sebuah contoh:
 class UserTable extends Component {   ...     render() {       const user = {id: 1, name: 'Thomas', surname: 'Foobar', age: 33};       return (           <div>               ...                 <UserRow user={user}/>               ...           </div>       );   }    ... } class UserRow extends Component {   static propTypes = {       user: PropTypes.object.isRequired,   };   render() {       return (           <tr>               <td>Id: {this.props.user.id}</td>               <td>Name: {this.props.user.name}</td>           </tr>       )   } } 
Komponen 
UserTable UserRow komponen 
UserRow , meneruskannya, di properti, objek dengan informasi pengguna lengkap. Jika kita menganalisis kode komponen 
UserRow , ternyata itu tergantung pada objek yang berisi semua informasi tentang pengguna, tetapi ia hanya membutuhkan properti 
id dan 
name .
Jika Anda menulis tes untuk komponen ini dan menggunakan TypeScript atau Flow, Anda harus membuat tiruan untuk objek 
user dengan semua propertinya, jika tidak, kompiler akan membuat kesalahan.
Pada pandangan pertama, ini tampaknya tidak menjadi masalah jika Anda menggunakan JavaScript murni, tetapi jika TypeScript pernah mengendap dalam kode Anda, ini akan tiba-tiba menyebabkan kegagalan pengujian karena kebutuhan untuk menetapkan semua properti dari antarmuka, bahkan jika hanya beberapa dari mereka yang digunakan.
Namun, program yang memenuhi prinsip pemisahan antarmuka lebih mudah dipahami.
 class UserTable extends Component {   ...     render() {       const user = {id: 1, name: 'Thomas', surname: 'Foobar', age: 33};       return (           <div>               ...                 <UserRow id={user.id} name={user.name}/>               ...           </div>       );   }    ... } class UserRow extends Component {   static propTypes = {       id: PropTypes.number.isRequired,       name: PropTypes.string.isRequired,   };   render() {       return (           <tr>               <td>Id: {this.props.id}</td>               <td>Name: {this.props.name}</td>           </tr>       )   } } 
Ingatlah bahwa prinsip ini berlaku tidak hanya untuk tipe properti yang diteruskan ke komponen.
Prinsip Ketergantungan Inversi (D)
Prinsip Pembalikan Ketergantungan memberi tahu kita bahwa objek ketergantungan harus berupa abstraksi, bukan sesuatu yang spesifik.
Perhatikan contoh berikut:
 class App extends Component { ... async fetchUsers() {   const users = await fetch('http: 
Jika kami menganalisis kode ini, menjadi jelas bahwa komponen 
App bergantung pada fungsi 
fetch global. Jika Anda menggambarkan hubungan entitas ini di UML, Anda mendapatkan diagram berikut.
Hubungan antara komponen dan fungsiModul tingkat tinggi seharusnya tidak bergantung pada implementasi konkret tingkat rendah dari sesuatu. Itu harus tergantung pada abstraksi.
Komponen 
App tidak perlu tahu cara mengunduh informasi pengguna. Untuk mengatasi masalah ini, kita perlu membalikkan dependensi antara komponen 
App dan fungsi 
fetch . Di bawah ini adalah diagram UML yang menggambarkan hal ini.
Pembalikan KetergantunganInilah implementasi mekanisme ini.
 class App extends Component {   static propTypes = {       fetchUsers: PropTypes.func.isRequired,       saveUsers: PropTypes.func.isRequired   };   ...     componentDidMount() {       const users = this.props.fetchUsers();       this.setState({users});   }   ... } 
Sekarang kita dapat mengatakan bahwa komponen tersebut tidak terlalu terhubung, karena tidak memiliki informasi tentang protokol yang kita gunakan - HTTP, SOAP, atau lainnya. Komponen tidak peduli sama sekali.
Kepatuhan dengan prinsip inversi dependensi memperluas kemungkinan kami untuk bekerja dengan kode, karena kami dapat dengan mudah mengubah mekanisme pemuatan data, dan komponen 
App tidak akan berubah sama sekali.
Selain itu, ini menyederhanakan pengujian, karena mudah untuk membuat fungsi yang mensimulasikan fungsi memuat data.
Ringkasan
Investasikan waktu dalam menulis kode berkualitas tinggi, Anda akan mendapatkan rasa terima kasih dari kolega Anda dan diri Anda sendiri ketika, di masa depan, Anda harus menghadapi kode ini lagi. Mengintegrasikan prinsip-prinsip SOLID ke dalam pengembangan aplikasi React adalah investasi yang berharga.
Pembaca yang budiman! Apakah Anda menggunakan prinsip SOLID ketika mengembangkan aplikasi Bereaksi?
