Masalah yang kami pecahkan
Konteks dalam react dapat mengandung banyak nilai dan konsumen yang berbeda dari konteks hanya dapat menggunakan sebagian dari nilai-nilai tersebut. Namun, jika ada nilai yang berubah dari konteksnya, semua konsumen (khususnya, semua komponen yang menggunakan useContext
) akan dirender , meskipun mereka tidak bergantung pada bagian data yang diubah. Masalahnya cukup dibahas dan memiliki banyak solusi berbeda. Inilah beberapa di antaranya. Saya membuat contoh ini untuk mendemonstrasikan masalahnya. Buka saja konsol dan tekan tombolnya.
tujuan
Solusi kami harus mengubah basis kode yang ada seminimal mungkin. Saya ingin membuat hook kustom saya sendiri useSmartContext
dengan tanda tangan yang sama seperti milik useContext
, tetapi yang hanya akan merender ulang komponen ketika bagian yang digunakan dari konteks berubah.
Ide
Cari tahu apa yang digunakan oleh komponen dengan membungkus nilai kembalian useSmartContext
dalam Proxy.
Penerapan
Langkah 1.
Kami membuat pengait kami sendiri.
const useSmartContext(context) {
const usedFieldsRef = useRef(new Set());
const proxyRef = useRef(
new Proxy(
{},
{
get(target, prop) {
usedPropsRef.current.add(prop);
return context._currentValue[prop];
}
}
)
);
return proxyRef.current;
}
Kami telah membuat daftar di mana kami akan menyimpan bidang konteks yang digunakan. Kami membuat proxy dengan get
jebakan di mana kami mengisi daftar ini. Target
tidak masalah bagi kami, jadi saya memberikan objek kosong sebagai argumen pertama {}
.
Langkah 2.
Anda perlu mendapatkan nilai konteks saat diperbarui dan membandingkan nilai bidang dari daftar usedPropsRef
dengan nilai sebelumnya. Jika sesuatu telah berubah, maka memicu rendering ulang. useContext
Kita tidak dapat menggunakannya di dalam hook kita, jika tidak hook kita juga akan mulai menyebabkan rendering ulang untuk semua perubahan. Di sini tarian dengan rebana dimulai. Saya awalnya berharap untuk berlangganan perubahan konteks dengan context.Consumer
. Yaitu seperti ini:
React.createElement(context.Consumer, {}, (newContextVakue) => {/* handle */})
. . - , , , .
React
, useContext
. , , , . - . _currentValue
. , undefined
. ! Proxy , . Object.defineProperty
.
let val = context._currentValue;
let notEmptyVal = context._currentValue;
Object.defineProperty(context, "_currentValue", {
get() {
return val;
},
set(newVal) {
if (newVal) {
// !
}
val = newVal;
}
});
! : useSmartContext
Object.defineProperty
. useSmartContext
createContext
.
export const createListenableContext = () => {
const context = createContext();
const listeners = [];
let val = context._currentValue;
let notEmptyVal = context._currentValue;
Object.defineProperty(context, "_currentValue", {
get() {
return val;
},
set(newVal) {
if (newVal) {
listeners.forEach((cb) => cb(notEmptyVal, newVal));
notEmptyVal = newVal;
}
val = newVal;
}
});
context.addListener = (cb) => {
listeners.push(cb);
return () => listeners.splice(listeners.indexOf(cb), 1);
};
return context;
};
, . ,
const useSmartContext = (context) => {
const usedFieldsRef = useRef(new Set());
useEffect(() => {
const clear = context.addListener((prevValue, newValue) => {
let isChanged = false;
usedFieldsRef.current.forEach((usedProp) => {
if (!prevValue || newValue[usedProp] !== prevValue[usedProp]) {
isChanged = true;
}
});
if (isChanged) {
//
}
});
return clear;
}, [context]);
const proxyRef = useRef(
new Proxy(
{},
{
get(target, prop) {
usedFieldsRef.current.add(prop);
return context._currentValue[prop];
}
}
)
);
return proxyRef.current;
};
3.
. useState
, . , . - ?
// ...
const [, rerender] = useState();
const renderTriggerRef = useRef(true);
// ...
if (isChanged) {
renderTriggerRef.current = !renderTriggerRef.current;
rerender(renderTriggerRef.current);
}
, . . useContext
->useSmartContext
createContext
->createListenableContext
.
, !
,
Monkey patch
, . .
Saat menulis artikel ini, saya menemukan pustaka lain yang memecahkan masalah yang sama dengan mengoptimalkan gambar ulang saat menggunakan konteks. Solusi dari perpustakaan ini, menurut saya, adalah yang paling benar yang pernah saya lihat. Sumbernya jauh lebih mudah dibaca dan mereka memberi saya beberapa ide tentang bagaimana membuat contoh produksi kita siap tanpa mengubah cara penggunaan. Jika saya mendapat tanggapan positif dari Anda, maka saya akan menulis tentang implementasi baru.
Terima kasih atas perhatiannya.