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')
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 = {}
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 useCallbackCara mengoptimalkan nilai konteksCara menggunakan Bereaksi Konteks secara efektifMengelola status aplikasi dalam Bereaksi.Satu trik sederhana untuk mengoptimalkan rendering ulang di Bereaksi