Sedikit latihan dengan JS Proxy untuk mengoptimalkan penggambaran ulang komponen React saat menggunakan useContext

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.








All Articles