Mengelola Status dengan React Hooks - Tanpa Redux dan Context API

Halo semuanya! Nama saya Arthur, saya bekerja di VKontakte sebagai tim web seluler, saya terlibat dalam proyek VKUI - pustaka komponen React, dengan bantuan yang sebagian antarmuka kami dalam aplikasi seluler ditulis. Masalah bekerja dengan negara global masih terbuka untuk kita. Ada beberapa pendekatan terkenal: Redux, MobX, Context API. Saya baru-baru ini menemukan sebuah artikel oleh Manajemen Negara AndrΓ© Gardi dengan React Hooks - No Redux atau Context API , di mana penulis menyarankan untuk menggunakan React Hooks untuk mengontrol keadaan aplikasi.

Hook dengan cepat menembus kehidupan pengembang, menawarkan cara-cara baru untuk memecahkan atau memikirkan kembali tugas dan pendekatan yang berbeda. Mereka mengubah pemahaman kita tentang tidak hanya bagaimana mendeskripsikan komponen, tetapi juga bagaimana bekerja dengan data. Baca terjemahan artikel dan komentar penerjemah di bawah kucing.

gambar

React Hooks Lebih Kuat dari yang Anda kira


Hari ini kita akan mempelajari React Hooks dan mengembangkan hook kustom untuk mengelola status global aplikasi, yang akan lebih sederhana daripada implementasi Redux dan lebih produktif daripada Context API.

Bereaksi Dasar-dasar Hooks


Anda dapat melewati bagian ini jika Anda sudah terbiasa dengan kait.

useState ()


Sebelum munculnya kait, komponen fungsional tidak memiliki kemampuan untuk mengatur keadaan lokal. Situasi telah berubah dengan munculnya useState() .



Panggilan ini mengembalikan array. Elemen pertamanya adalah variabel yang menyediakan akses ke nilai status. Elemen kedua adalah fungsi yang memperbarui keadaan dan menggambar ulang komponen untuk mencerminkan perubahan.

 import React, { useState } from 'react'; function Example() { const [state, setState] = useState({counter:0}); const add1ToCounter = () => { const newCounterValue = state.counter + 1; setState({ counter: newCounterValue}); } return ( <div> <p>You clicked {state.counter} times</p> <button onClick={add1ToCounter}> Click me </button> </div> ); } 

useEffect ()


Komponen kelas merespons efek samping menggunakan metode siklus hidup seperti componentDidMount() . useEffect() memungkinkan Anda melakukan hal yang sama dalam komponen fungsional.

Secara default, efek dipicu setelah setiap redraw. Tetapi Anda dapat memastikan bahwa mereka dieksekusi hanya setelah mengubah nilai variabel tertentu, memberikan mereka parameter opsional kedua dalam bentuk array.

 //     useEffect(() => { console.log('     '); }); //    useEffect(() => { console.log('     valueA'); }, [valueA]); 

Untuk mencapai hasil yang mirip dengan componentDidMount() , kami akan meneruskan array kosong ke parameter kedua. Karena isi dari array kosong selalu tidak berubah, efeknya akan dieksekusi hanya sekali.

 //     useEffect(() => { console.log('    '); }, []); 

Berbagi negara


Kami melihat bahwa keadaan kait berfungsi seperti keadaan komponen kelas. Setiap instance komponen memiliki keadaan internalnya sendiri.

Untuk berbagi status antar komponen, kami akan membuat kait kami sendiri.



Idenya adalah untuk menciptakan berbagai pendengar dan hanya satu negara. Setiap kali komponen berubah status, semua komponen berlangganan memanggil getState() mereka getState() dan diperbarui karena ini.

Kita dapat mencapai ini dengan memanggil useState() di dalam kait kustom kami. Tetapi alih-alih mengembalikan fungsi setState() , kami menambahkannya ke array pendengar dan mengembalikan fungsi yang memperbarui objek keadaan secara internal dan memanggil semua pendengar.

Tunggu sebentar. Bagaimana itu membuat hidup saya lebih mudah?


Ya kamu benar Saya membuat paket NPM yang merangkum semua logika yang dijelaskan.

Anda tidak harus mengimplementasikannya di setiap proyek. Jika Anda tidak lagi ingin menghabiskan waktu membaca dan ingin melihat hasil akhir, cukup tambahkan paket ini ke aplikasi Anda.

 npm install -s use-global-hook 

Untuk memahami cara bekerja dengan paket, pelajari contoh-contoh dalam dokumentasi. Dan sekarang saya mengusulkan untuk fokus pada bagaimana paket itu diatur di dalam.

Versi pertama


 import { useState, useEffect } from 'react'; let listeners = []; let state = { counter: 0 }; const setState = (newState) => { state = { ...state, ...newState }; listeners.forEach((listener) => { listener(state); }); }; const useCustom = () => { const newListener = useState()[1]; useEffect(() => { listeners.push(newListener); }, []); return [state, setState]; }; export default useCustom; 

Gunakan dalam komponen


 import React from 'react'; import useCustom from './customHook'; const Counter = () => { const [globalState, setGlobalState] = useCustom(); const add1Global = () => { const newCounterValue = globalState.counter + 1; setGlobalState({ counter: newCounterValue }); }; return ( <div> <p> counter: {globalState.counter} </p> <button type="button" onClick={add1Global}> +1 to global </button> </div> ); }; export default Counter; 

Versi ini sudah menyediakan status berbagi. Anda dapat menambahkan jumlah counter sewenang-wenang ke aplikasi Anda, dan mereka semua akan memiliki keadaan global yang sama.

Tapi kita bisa berbuat lebih baik


Apa yang kamu inginkan:

  • menghapus pendengar dari array ketika melepas komponen;
  • membuat kait lebih abstrak untuk digunakan dalam proyek lain;
  • mengelola initialState menggunakan parameter;
  • tulis ulang pengait dengan gaya yang lebih fungsional.

Memanggil fungsi sesaat sebelum melepas komponen


Kami telah menemukan bahwa memanggil useEffect(function, []) dengan array kosong berfungsi dengan cara yang sama seperti componentDidMount() . Tetapi jika fungsi yang dilewatkan dalam parameter pertama mengembalikan fungsi lain, maka fungsi kedua akan dipanggil tepat sebelum melepas komponen. Persis seperti componentWillUnmount() .

Jadi, dalam kode fungsi kedua, Anda dapat menulis logika untuk menghapus komponen dari berbagai pendengar.

 const useCustom = () => { const newListener = useState()[1]; useEffect(() => { //     listeners.push(newListener); return () => { //     listeners = listeners.filter(listener => listener !== newListener); }; }, []); return [state, setState]; }; 

Versi kedua


Selain pembaruan ini, kami juga merencanakan:

  • lulus Bereaksi parameter dan singkirkan impor;
  • ekspor bukan customHook, tetapi fungsi yang mengembalikan customHook dengan initalState yang diberikan;
  • membuat objek store yang akan berisi nilai state dan fungsi setState() ;
  • ganti fungsi panah dengan yang biasa di setState() dan useCustom() sehingga Anda dapat mengaitkan store dengan this .

 function setState(newState) { this.state = { ...this.state, ...newState }; this.listeners.forEach((listener) => { listener(this.state); }); } function useCustom(React) { const newListener = React.useState()[1]; React.useEffect(() => { //     this.listeners.push(newListener); return () => { //     this.listeners = this.listeners.filter(listener => listener !== newListener); }; }, []); return [this.state, this.setState]; } const useGlobalHook = (React, initialState) => { const store = { state: initialState, listeners: [] }; store.setState = setState.bind(store); return useCustom.bind(store, React); }; export default useGlobalHook; 

Pisahkan tindakan dari komponen


Jika Anda pernah bekerja dengan perpustakaan manajemen negara yang kompleks, maka Anda tahu bahwa memanipulasi keadaan global dari komponen bukanlah ide yang baik.

Akan lebih benar untuk memisahkan logika bisnis dengan menciptakan tindakan untuk mengubah keadaan. Oleh karena itu, saya ingin versi terbaru dari paket menyediakan akses komponen bukan ke setState() , tetapi ke serangkaian tindakan.

Untuk melakukan ini, kami menyediakan useGlobalHook(React, initialState, actions) kami useGlobalHook(React, initialState, actions) argumen ketiga. Hanya ingin menambahkan beberapa komentar.

  • Tindakan akan memiliki akses ke store . Dengan cara ini, tindakan dapat membaca konten store.state , memperbarui store.setState() memanggil store.setState() dan bahkan memanggil store.actions lain dengan store.actions .
  • Untuk menghindari kekacauan, objek tindakan mungkin berisi sub-objek. Dengan demikian, Anda dapat mentransfer actions.addToCounter(amount ) ke subobjek dengan semua tindakan balasan: actions.counter.add(amount)

Versi final


Cuplikan berikut adalah versi saat ini dari paket NPM use-global-hook .

 function setState(newState) { this.state = { ...this.state, ...newState }; this.listeners.forEach((listener) => { listener(this.state); }); } function useCustom(React) { const newListener = React.useState()[1]; React.useEffect(() => { this.listeners.push(newListener); return () => { this.listeners = this.listeners.filter(listener => listener !== newListener); }; }, []); return [this.state, this.actions]; } function associateActions(store, actions) { const associatedActions = {}; Object.keys(actions).forEach((key) => { if (typeof actions[key] === 'function') { associatedActions[key] = actions[key].bind(null, store); } if (typeof actions[key] === 'object') { associatedActions[key] = associateActions(store, actions[key]); } }); return associatedActions; } const useGlobalHook = (React, initialState, actions) => { const store = { state: initialState, listeners: [] }; store.setState = setState.bind(store); store.actions = associateActions(store, actions); return useCustom.bind(store, React); }; export default useGlobalHook; 

Contoh Penggunaan


Anda tidak lagi harus berurusan dengan useGlobalHook.js . Sekarang Anda dapat fokus pada aplikasi Anda. Berikut ini adalah dua contoh penggunaan paket.

Banyak Penghitung, Satu Nilai


Tambahkan penghitung sebanyak yang Anda inginkan: semuanya akan memiliki nilai global. Setiap kali salah satu penghitung akan meningkatkan negara global, yang lainnya akan digambar ulang. Dalam hal ini, komponen induk tidak perlu digambar ulang.
Contoh hidup .

Permintaan ajax asinkron


Cari repositori GitHub berdasarkan nama pengguna. Kami memproses permintaan ajax secara asinkron menggunakan async / menunggu. Kami memperbarui penghitung kueri dengan setiap pencarian baru.
Contoh hidup .

Yah, itu saja


Kami sekarang memiliki perpustakaan manajemen negara kami sendiri di React Hooks.

Komentar Penerjemah


Sebagian besar solusi yang ada pada dasarnya adalah perpustakaan yang terpisah. Dalam hal ini, pendekatan yang dijelaskan oleh penulis menarik karena hanya menggunakan fitur Bereaksi bawaan. Selain itu, dibandingkan dengan API Konteks yang sama, yang juga keluar dari kotak, pendekatan ini mengurangi jumlah redraw yang tidak perlu dan karenanya menang dalam kinerja.

Source: https://habr.com/ru/post/id454348/


All Articles