Alternatif bawaan redux dengan Bereaksi Konteks dan kait

Dari penerjemah:

Saya menyajikan terjemahan bebas dari artikel tentang bagaimana menerapkan solusi yang efektif untuk menggantikan Redux dengan konteks dan kaitan Bereaksi. Indikasi kesalahan dalam terjemahan atau teks dipersilahkan. Selamat menikmati.



Sejak rilis Context API baru di React 16.3.0, banyak orang bertanya pada diri sendiri apakah API baru cukup baik untuk menganggapnya sebagai pengganti Redux? Saya memikirkan hal yang sama, tetapi saya tidak sepenuhnya mengerti bahkan setelah rilis versi 16.8.0 dengan kait. Saya mencoba menggunakan teknologi populer, jalannya tidak selalu memahami berbagai masalah yang mereka pecahkan, jadi saya terlalu terbiasa dengan Redux.

Dan kebetulan saya mendaftar untuk buletin Kent C. Dodds dan menemukan beberapa email tentang masalah konteks dan manajemen negara. Saya mulai membaca .... dan baca ... dan setelah 5 posting blog, sesuatu diklik.

Untuk memahami semua konsep dasar di balik ini, kami akan membuat tombol, dengan mengklik di mana kami akan menerima lelucon dengan icanhazdadjoke dan menampilkannya. Ini adalah contoh kecil tapi cukup.

Untuk mempersiapkan, mari kita mulai dengan dua tips yang tampaknya acak.

Pertama, izinkan saya memperkenalkan console.count teman console.count :

 console.count('Button') // Button: 1 console.count('Button') // Button: 2 console.count('App') // App: 1 console.count('Button') // Button: 3 

Kami akan menambahkan panggilan console.count ke setiap komponen untuk melihat berapa kali itu diberikan. Cukup keren, ya?

Kedua, ketika komponen Bereaksi dibuat ulang, itu tidak merender kembali konten yang dilewatkan sebagai children - children .

 function Parent({ children }) { const [count, setCount] = React.useState(0) console.count('Parent') return ( <div> <button type="button" onClick={() => { setCount(count => count + 1) }}> Force re-render </button> {children} </div> ) } function Child() { console.count('Child') return <div /> } function App() { return ( <Parent> <Child /> </Parent> ) } 

Setelah beberapa klik pada tombol, Anda akan melihat konten berikut di konsol:

 Parent: 1 Child: 1 Parent: 2 Parent: 3 Parent: 4 

Ingatlah bahwa ini adalah cara yang sering diabaikan untuk meningkatkan kinerja aplikasi Anda.

Sekarang kita siap, mari kita buat kerangka aplikasi kita:

 import React from 'react' function Button() { console.count('Button') return ( <button type="button"> Fetch dad joke </button> ) } function DadJoke() { console.count('DadJoke') return ( <p>Fetched dad joke</p> ) } function App() { console.count('App') return ( <div> <Button /> <DadJoke /> </div> ) } export default App 

Button harus menerima generator tindakan (kira-kira. Pencipta Tindakan. Terjemahan diambil dari dokumentasi Redux dalam bahasa Rusia ) yang akan menerima lelucon. DadJoke harus mendapatkan status, dan App menampilkan kedua komponen menggunakan konteks Penyedia.

Sekarang buat komponen khusus dan DadJokeProvider nama DadJokeProvider , yang di dalamnya akan mengatur status dan membungkus komponen anak dalam Penyedia Konteks. Ingatlah bahwa memperbarui statusnya tidak akan merender ulang seluruh aplikasi karena optimasi anak-anak di atas dalam Bereaksi.

Jadi, buat file dan sebut itu contexts/dad-joke.js :

 import React from 'react' const DadJokeContext = React.createContext() export function DadJokeContextProvider({ children }) { const state = { dadJoke: null } const actions = { fetchDadJoke: () => {}, } return ( <DadJokeContext.Provider value={{ state, actions }}> {children} </DadJokeContext.Provider> ) } 

Kami juga mengekspor 2 kait untuk mendapatkan nilai dari konteksnya.

 export function useDadJokeState() { return React.useContext(DadJokeContext).state } export function useDadJokeActions() { return React.useContext(DadJokeContext).actions } 

Sekarang kita bisa menerapkan ini:

 import React from 'react' import { DadJokeProvider, useDadJokeState, useDadJokeActions, } from './contexts/dad-joke' function Button() { const { fetchDadJoke } = useDadJokeActions() console.count('Button') return ( <button type="button" onClick={fetchDadJoke}> Fetch dad joke </button> ) } function DadJoke() { const { dadJoke } = useDadJokeState() console.count('DadJoke') return ( <p>{dadJoke}</p> ) } function App() { console.count('App') return ( <DadJokeProvider> <Button /> <DadJoke /> </DadJokeProvider> ) } export default App 

Di sini! Berkat API kami membuatnya menggunakan kait. Kami tidak akan lagi membuat perubahan pada file ini selama posting.

Mari kita mulai menambahkan fungsionalitas ke file konteks kita, mulai dengan keadaan DadJokeProvider . Ya, kita bisa menggunakan hook useState , tapi mari kita mengelola negara kita melalui reducer dengan hanya menambahkan fungsionalitas Redux terkenal dan dicintai kepada kita.

 function reducer(state, action) { switch (action.type) { case 'SET_DAD_JOKE': return { ...state, dadJoke: action.payload, } default: return new Error(); } } 

Sekarang kita bisa meneruskan peredam ini ke hook useReducer dan mendapatkan lelucon dengan API:

 export function DadJokeProvider({ children }) { const [state, dispatch] = React.useReducer(reducer, { dadJoke: null }) async function fetchDadJoke() { const response = await fetch('https://icanhazdadjoke.com', { headers: { accept: 'application/json', }, }) const data = await response.json() dispatch({ type: 'SET_DAD_JOKE', payload: data.joke, }) } const actions = { fetchDadJoke, } return ( <DadJokeContext.Provider value={{ state, actions }}> {children} </DadJokeContext.Provider> ) } 

Harus bekerja! Klik pada tombol akan menerima dan menampilkan lelucon!

Mari kita periksa konsol:

 App: 1 Button: 1 DadJoke: 1 Button: 2 DadJoke: 2 Button: 3 DadJoke: 3 

Kedua komponen dirender ulang setiap kali status diperbarui, tetapi hanya satu yang benar-benar menggunakannya. Bayangkan aplikasi nyata di mana ratusan komponen hanya menggunakan tindakan. Apakah lebih baik jika kita bisa memberikan semua render ulang opsional ini?

Dan di sini kita memasuki wilayah kesetaraan relatif, jadi pengingat kecil:

 const obj = {} //       console.log(obj === obj) // true //        //  2   console.log({} === {}) // false 

Komponen yang menggunakan konteks akan diberikan kembali setiap kali nilai konteks ini berubah. Mari kita lihat arti dari Penyedia Konteks kami:

 <DadJokeContext.Provider value={{ state, actions }}> 

Di sini kita membuat objek baru selama setiap renderer, tetapi ini tidak bisa dihindari, karena objek baru akan dibuat setiap kali kita melakukan tindakan ( dispatch ), sehingga tidak mungkin untuk memoize cache ( memoize ) nilai ini.

Dan itu semua tampak seperti akhir dari cerita, kan?

Jika kita melihat fungsi fetchDadJoke , satu-satunya hal yang digunakannya dari lingkup eksternal adalah dispatch , bukan? Secara umum, saya akan memberi tahu Anda sedikit rahasia tentang fungsi yang dibuat di useReducer dan useState . Untuk singkatnya, saya akan menggunakan useState sebagai contoh:

 let prevSetCount function Counter() { const [count, setCount] = React.useState() if (typeof prevSetCount !== 'undefined') { console.log(setCount === prevSetCount) } prevSetCount = setCount return ( <button type="button" onClick={() => { setCount(count => count + 1) }}> Increment </button> ) } 

Klik tombol beberapa kali dan lihat konsol:

 true true true 

Anda akan melihat bahwa setCount fungsi yang sama untuk setiap render. Ini juga berlaku untuk fungsi dispatch kami.

Ini berarti bahwa fungsi fetchDadJoke kami tidak bergantung pada apa pun yang berubah seiring waktu, dan tidak bergantung pada generator tindakan lainnya, oleh karena itu objek tindakan perlu dibuat hanya sekali, pada render pertama:

 const actions = React.useMemo(() => ({ fetchDadJoke, }), []) 

Sekarang kita memiliki objek yang di-cache dengan tindakan, dapatkah kita mengoptimalkan nilai konteks? Sebenarnya, tidak, karena tidak peduli seberapa baik kita mengoptimalkan objek nilai, kita masih perlu membuat yang baru setiap kali karena perubahan keadaan. Namun, bagaimana jika kita memindahkan objek tindakan dari konteks yang ada ke yang baru? Siapa bilang kita hanya punya satu konteks?

 const DadJokeStateContext = React.createContext() const DadJokeActionsContext = React.createContext() 

Kita dapat menggabungkan kedua konteks di DadJokeProvider kami:

  return ( <DadJokeStateContext.Provider value={state}> <DadJokeActionsContext.Provider value={actions}> {children} </DadJokeActionsContext.Provider> </DadJokeStateContext.Provider> ) 

Dan tweak kait kami:

 export function useDadJokeState() { return React.useContext(DadJokeStateContext) } export function useDadJokeActions() { return React.useContext(DadJokeActionsContext) } 

Dan kita selesai! Serius, unduh lelucon sebanyak yang Anda inginkan dan lihat sendiri.

 App: 1 Button: 1 DadJoke: 1 DadJoke: 2 DadJoke: 3 DadJoke: 4 DadJoke: 5 

Jadi, Anda telah menerapkan solusi manajemen negara Anda sendiri yang dioptimalkan! Anda dapat membuat penyedia yang berbeda menggunakan templat dua konteks ini untuk membuat aplikasi Anda, tetapi itu tidak semua, Anda juga dapat merender komponen penyedia yang sama beberapa kali! Apa? Ya, coba DadJokeProvider di beberapa tempat dan lihat bagaimana penerapan manajemen negara Anda dengan mudah!

Lepaskan imajinasi Anda dan tinjau mengapa Anda benar-benar membutuhkan Redux .

Terima kasih kepada Kent C. Dodds untuk artikel tentang templat dua konteks. Saya belum melihatnya di tempat lain dan menurut saya ini mengubah aturan permainan.

Baca posting blog Kent berikut untuk informasi lebih lanjut tentang konsep yang saya bicarakan:

Kapan menggunakan useMemo dan useCallback
Cara mengoptimalkan nilai konteks
Cara menggunakan Bereaksi Konteks secara efektif
Mengelola status aplikasi dalam Bereaksi.
Satu trik sederhana untuk mengoptimalkan rendering ulang di Bereaksi

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


All Articles