Pengait khusus. Bagian 1





Selamat siang teman!



Saya persembahkan kepada Anda sepuluh pengait khusus teratas .



Daftar Isi







useMemoCompare



Hook ini mirip dengan useMemo, tetapi alih-alih berupa larik dependensi, ia meneruskan fungsi yang membandingkan nilai sebelumnya dan baru. Sebuah fungsi dapat membandingkan properti bertingkat, memanggil metode pada objek, atau melakukan hal lain untuk tujuan perbandingan. Jika fungsi mengembalikan nilai true, hook mengembalikan referensi ke objek lama. Perlu dicatat bahwa hook ini, tidak seperti useMemo, tidak menyiratkan tidak adanya penghitungan kompleks yang berulang. Dia harus melewati nilai yang dihitung untuk perbandingan. Ini bisa berguna saat Anda ingin berbagi pustaka dengan pengembang lain dan tidak ingin memaksa mereka mengingat objek sebelum mengirimkan. Jika sebuah objek dibuat di badan komponen (dalam kasus di mana itu bergantung pada props) maka itu akan menjadi baru setiap kali dirender. Jika objeknya adalah ketergantungan useEffect maka efeknya akan dipicu pada setiap render,yang dapat menyebabkan masalah, hingga putaran tanpa akhir. Hook ini memungkinkan Anda untuk menghindari perkembangan peristiwa ini dengan menggunakan referensi objek lama, bukan yang baru jika fungsi mengenali objek sebagai objek yang sama.



import React, { useState, useEffect, useRef } from "react";

// 
function MyComponent({ obj }) {
  const [state, setState] = useState();

  //   ,   "id"  
  const objFinal = useMemoCompare(obj, (prev, next) => {
    return prev && prev.id === next.id;
  });

  //       objFinal
  //    obj ,   ,  obj  
  //     ,        
  //   ,       ,     
  //   ->      ->    ->  ..
  useEffect(() => {
    //       
    return objFinal.someMethod().then((value) => setState(value));
  }, [objFinal]);

  //     [obj.id]   ?
  useEffect(() => {
    // eslint-plugin-hooks  ,  obj     
    //     eslint-disable-next-line    
    //           
    return obj.someMethod().then((value) => setState(value));
  }, [obj.id]);
}

// 
function useMemoCompare(next, compare) {
  // ref    
  const prevRef = useRef();
  const prev = prevRef.current;

  //       
  //    
  const isEqual = compare(prev, next);

  //    ,  prevRef
  //       
  // ,    true,    
  useEffect(() => {
    if (!isEqual) {
      prevRef.current = next;
    }
  });

  //   ,   
  return isEqual ? prev : next;
}


useAsync



Merupakan praktik yang baik untuk menampilkan status permintaan asinkron. Contohnya

adalah mengambil data dari API dan menampilkan indikator pemuatan sebelum menampilkan hasilnya. Contoh lain adalah menonaktifkan tombol saat formulir sedang dikirimkan dan kemudian menampilkan hasilnya. Daripada mencemari komponen dengan banyak panggilan useState untuk melacak status fungsi asinkron, kita dapat menggunakan hook ini, yang mengambil fungsi asinkron dan mengembalikan nilai "nilai", "kesalahan" dan "status" yang diperlukan untuk memperbarui antarmuka pengguna. Nilai yang memungkinkan untuk properti "status" adalah "idle", "pending", "success", dan "error". Hook kami memungkinkan Anda untuk menjalankan suatu fungsi dengan segera atau terlambat menggunakan fungsi eksekusi.



import React, { useState, useEffect, useCallback } from 'react'

// 
function App() {
  const {execute, status, value, error } = useAsync(myFunction, false)

  return (
    <div>
      {status === 'idle' && <div>     </div>}
      {status === 'success' && <div>{value}</div>}
      {status === 'error' && <div>{error}</div>}
      <button onClick={execute} disabled={status === 'pending'}>
        {status !== 'pending' ? ' ' : '...'}
      </button>
    </div>
  )
}

//     
//    50% 
const myFunction = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const random = Math.random() * 10
      random <=5
        ? resolve(' ')
        : reject(' ')
    }, 2000)
  })
}

// 
const useAsync = (asyncFunction, immediate = true) => {
  const [status, setStatus] = useState('idle')
  const [value, setValue] = useState(null)
  const [error, setError] = useState(null)

  //  "execute"  asyncFunction 
  //     pending, value  error
  // useCallback   useEffect   
  // useEffect     asyncFunction
  const execute = useCallback(() => {
    setStatus('pending')
    setValue(null)
    setError(null)

    return asyncFunction()
      .then(response => {
        setValue(response)
        setStatus('success')
      })
      .catch(error => {
        setError(error)
        setStatus('error')
      })
  }, [asyncFunction])

  //  execute   
  //   , execute    
  // ,    
  useEffect(() => {
    if (immediate) {
      execute()
    }
  }, [execute, immediate])

  return { execute, status, value, error }
}


useRequireAuth



Tujuan dari hook ini adalah untuk mengarahkan pengguna ke halaman login saat logout dari akun. Hook kami adalah komposisi hook "useAuth" dan "useRouter". Tentu saja, kita dapat mengimplementasikan fungsionalitas yang diperlukan dalam hook "useAuth", tetapi kemudian kita harus menyertakannya dengan skema perutean. Dengan komposisi, kita bisa menjaga useAuth dan useRouter tetap sederhana dengan mengimplementasikan pengalihan dengan hook khusus.



import Dashboard from "./Dahsboard.js";
import Loading from "./Loading.js";
import { useRequireAuth } from "./use-require-auth.js";

function DashboardPage(props) {
  const auth = useRequireAuth();

  //   auth  null (   )
  //  false (    )
  //   
  if (!auth) {
    return <Loading />;
  }

  return <Dashboard auth={auth} />;
}

//  (use-require-auth.js)
import { useEffect } from "react";
import { useAuth } from "./use-auth.js";
import { useRouter } from "./use-router.js";

function useRequireAuth(redirectUrl = "./signup") {
  const auth = useAuth();
  const router = useRouter();

  //   auth.user  false,
  // ,   ,  
  useEffect(() => {
    if (auth.user === false) {
      router.push(redirectUrl);
    }
  }, [auth, router]);

  return auth;
}


useRouter



Jika Anda menggunakan React Router dalam pekerjaan Anda, Anda mungkin memperhatikan bahwa beberapa hook yang berguna baru-baru ini muncul, seperti "useParams", "useLocation", "useHistory" dan "useRouterMatch". Mari kita coba membungkusnya dalam satu hook yang mengembalikan data dan metode yang kita butuhkan. Kami akan menunjukkan kepada Anda cara menggabungkan beberapa kait dan mengembalikan satu objek yang berisi statusnya. Untuk pustaka seperti React Router, masuk akal untuk memberikan pilihan hook yang diinginkan. Ini menghindari rendering yang tidak perlu. Tapi terkadang kita membutuhkan semua atau sebagian besar pengait bernama.



import { useMemo } from "react";
import {
  useParams,
  useLocation,
  useHistory,
  useRouterMatch,
} from "react-router-dom";
import queryString from "query-string";

// 
function MyComponent() {
  //   
  const router = useRouter();

  //     (?postId=123)    (/:postId)
  console.log(router.query.postId);

  //    
  console.log(router.pathname);

  //     router.push()
  return <button onClick={(e) => router.push("./about")}>About</button>;
}

// 
export function useRouter() {
  const params = useParams();
  const location = useLocation();
  const history = useHistory();
  const match = useRouterMatch();

  //    
  //    ,        
  return useMemo(() => {
    return {
      //    push(), replace()  pathname   
      push: history.push,
      replace: history.replace,
      pathname: location.pathname,
      //          "query"
      //  ,    
      // : /:topic?sort=popular -> { topic: 'react', sort: 'popular' }
      query: {
        ...queryString.parse(location.search), //    
        ...params,
      },
      //   "match", "location"  "history"
      //     React Router
      match,
      location,
      history,
    };
  }, [params, match, location, history]);
}


useAuth



Biasanya memiliki beberapa komponen yang dirender bergantung pada apakah pengguna login ke akun. Beberapa komponen ini memanggil metode otentikasi seperti signin, signout, sendPasswordResetEmail, dll. Hook "useAuth" sangat cocok untuk ini, yang memastikan bahwa komponen menerima status otentikasi dan menggambar ulang komponen saat ada perubahan. Alih-alih membuat instance useAuth untuk setiap pengguna, hook kita memanggil useContext untuk mendapatkan data dari komponen induk. Keajaiban sesungguhnya terjadi di komponen "ProvidAuth", di mana semua metode autentikasi (dalam contoh kita menggunakan Firebase) dibungkus dalam hook "useProvideAuth". Konteks tersebut kemudian digunakan untuk meneruskan objek otentikasi saat ini ke komponen anak yang memanggil useAuth.Ini akan lebih masuk akal setelah membaca contoh. Alasan lain saya menyukai hook ini adalah karena ia mengabstraksi penyedia otentikasi sebenarnya (Firebase), yang membuatnya lebih mudah untuk membuat perubahan.



//   App
import React from "react";
import { ProvideAuth } from "./use-auth.js";

function App(props) {
  return (
    <ProvideAuth>
      {/*
           ,     
          Next.js,    : /pages/_app.js
      */}
    </ProvideAuth>
  );
}

//  ,    
import React from "react";
import { useAuth } from "./use-auth.js";

function NavBar(props) {
  //   auth      
  const auth = useAuth();

  return (
    <NavbarContainer>
      <Logo />
      <Menu>
        <Link to="/about">About</Link>
        <Link to="/contact">Contact</Link>
        {auth.user ? (
          <Fragment>
            <Link to="/account">Account ({auth.user.email})</Link>
            <Button onClick={() => auth.signout()}>Signout</Button>
          </Fragment>
        ) : (
          <Link to="/signin">Signin</Link>
        )}
      </Menu>
    </NavbarContainer>
  );
}

//  (use-auth.js)
import React, { useState, useEffect, useContext, createContext } from "react";
import * as firebase from "firebase/app";
import "firebase/auth";

//    Firebase
firebase.initializeApp({
  apiKey: "",
  authDomain: "",
  projectId: "",
  appID: "",
});

const authContext = createContext();

//  Provider,      "auth"
//     ,  useAuth
export const useAuth = () => {
  return useContext(authContext);
};

//        "auth"
//      
export const useAuth = () => {
  return useContext(authContext);
};

//  ,   "auth"    
function useProviderAuth() {
  const [user, setUser] = useState(null);

  //    Firebase,   
  //  
  const signin = (email, password) => {
    return firebase
      .auth()
      .signInWithEmailAndPassword(email, password)
      .then((response) => {
        setUser(response.user);
        return response.user;
      });
  };

  const signup = (email, password) => {
    return firebase
      .auth()
      .createUserWithEmailAndPassword(email, password)
      .then((response) => {
        setUser(response.user);
        return response.user;
      });
  };

  const signout = () => {
    return firebase
      .auth()
      .signOut()
      .then(() => {
        setUser(false);
      });
  };

  const sendPasswordResetEmail = (email) => {
    return firebase
      .auth()
      .sendPasswordResetEmail(email)
      .then(() => true);
  };

  const confirmPasswordReset = (code, password) => {
    return firebase
      .auth()
      .confirmPasswordReset(code, password)
      .then(() => true);
  };

  //    
  //       
  //   ,  
  //      "auth"
  useEffect(() => {
    const unsubscribe = firebase.auth().onAuthStateChange((user) => {
      if (user) {
        setUser(user);
      } else {
        setUser(false);
      }
    });

    //   
    return () => unsubscribe();
  }, []);

  //   "user"   
  return {
    user,
    signin,
    signup,
    signout,
    sendPasswordResetEmail,
    confirmPasswordReset,
  };
}


useEventListener



Jika Anda harus berurusan dengan banyak event handler yang mendaftar dengan useEffect, Anda mungkin ingin memisahkan mereka ke dalam hook terpisah. Dalam contoh di bawah ini, kami membuat hook useEventListener yang memeriksa dukungan addEventListener, menambahkan penangan, dan menghapusnya saat keluar.

import { useState, useRef, useEffect, useCallback } from "react";

// 
function App() {
  //     
  const [coords, setCoords] = useState({ x: 0, y: 0 });

  //     useCallback,
  //     
  const handler = useCallback(
    ({ clientX, clientY }) => {
      //  
      setCoords({ x: clientX, y: clientY });
    },
    [setCoords]
  );

  //      
  useEventListener("mousemove", handler);

  return <h1> : ({(coords.x, coords.y)})</h1>;
}

// 
function useEventListener(eventName, handler, element = window) {
  //  ,  
  const saveHandler = useRef();

  //  ref.current   
  //          
  //      
  //      
  useEffect(() => {
    saveHandler.current = handler;
  }, [handler]);

  useEffect(
    () => {
      //   addEventListener
      const isSupported = element && element.addEventListener;
      if (!isSupported) return;

      //   ,   ,   ref
      const eventListener = (event) => saveHandler.current(event);

      //   
      element.addEventListener(eventName, eventListener);

      //     
      return () => {
        element.removeEventListener(eventName, eventListener);
      };
    },
    [eventName, element] //     
  );
}


useWhyDidYouUpdate



Hook ini memungkinkan Anda untuk menentukan perubahan props mana yang menyebabkan rendering ulang. Jika fungsinya "kompleks" dan Anda yakin itu bersih, mis. mengembalikan hasil yang sama untuk props yang sama, Anda dapat menggunakan komponen urutan yang lebih tinggi "React.memo" seperti yang kita lakukan pada contoh di bawah ini. Jika setelah itu render yang tidak perlu belum berhenti, Anda dapat menggunakan useWhyDidYouUpdate, yang menampilkan props yang berubah ke konsol selama rendering, yang menunjukkan nilai sebelumnya dan saat ini.



import { useState, useEffect, useRef } from "react";

// ,  <Counter>     
//      React.memo,   
//   useWhyDidYouUpdate   
const Counter = React.memo((props) => {
  useWhyDidYouUpdate("Counter", props);
  return <div style={props.style}>{props.count}</div>;
});

function App() {
  const [count, setCount] = useState(0);
  const [userId, setUserId] = useState(0);

  //  ,  ,    <Counter>
  //    ,       userId
  //   "switch user". ,   
  //       
  //    ,      
  //    
  const counterStyle = {
    fontSize: "3rem",
    color: "red",
  };
}

return (
  <div>
    <div className="counter">
      <Counter count={count} style={counterStyle} />
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
    <div className="user">
      <img src={`http://i.pravatar.cc/80?img=${userId}`} />
      <button onClick={() => setUserId(userId + 1)}>Switch User</button>
    </div>
  </div>
);

// 
function useWhyDidYouUpdate(name, props) {
  //    "ref"   
  //        
  const prevProps = useRef();

  useEffect(() => {
    if (prevProps.current) {
      //      
      const allKeys = Object.keys({ ...prevProps.current, ...props });
      //       
      const changesObj = {};
      //  
      allKeys.forEach((key) => {
        //     
        if (prevProps.current[key] !== props[key]) {
          //    changesObj
          changesObj[key] = {
            from: prevProps.current[key],
            to: props[key],
          };
        }
      });

      //   changesObj - ,    
      if (object.keys(changesObj).length) {
        console.log("why-did-you-update", name, changesObj);
      }
    }

    // ,  prevProps      
    prevProps.current = props;
  });
}


useDarkMode



Pengait ini mengimplementasikan logika untuk mengganti skema warna situs (terang dan gelap). Ini menggunakan penyimpanan lokal untuk menyimpan skema yang dipilih pengguna, mode default yang disetel di browser menggunakan kueri media "prefers-color-scheme". Untuk mengaktifkan mode gelap, gunakan kelas "mode gelap" dari elemen "tubuh". Hook juga mendemonstrasikan kekuatan komposisi. Status sinkronisasi dengan localStorage diimplementasikan menggunakan hook "useLocalStorage", dan menentukan skema pilihan pengguna menggunakan hook "useMedia", yang dirancang untuk tujuan berbeda. Namun, menyusun hook ini menghasilkan hook yang lebih kuat hanya dengan beberapa baris kode. Ini hampir sama dengan kekuatan "komposisi" dari hook dalam hubungannya dengan status komponen.



function App() {
  const [darkMode, setDarkMode] = useDarkMode();

  return (
    <div>
      <div className="navbar">
        <Toggle darkMode={darkMode} setDarkMode={setDarkMode} />
      </div>
      <Content />
    </div>
  );
}

// 
function useDarkMode() {
  //   "useLocalStorage"   
  const [enabledState, setEnableState] = useLocalStorage("dark-mode-enabled");

  //      
  //   "usePrefersDarkMode"   "useMedia"
  const prefersDarkMode = usePrefersDarkMode();

  //  enabledState ,  , ,  prefersDarkMode
  const enabled =
    typeof enabledState !== "undefined" ? enabledState : prefersDarkMode;

  //   / 
  useEffect(
    () => {
      const className = "dark-mode";
      const element = window.document.body;
      if (enabled) {
        element.classList.add(className);
      } else {
        element.classList.remove(className);
      }
    },
    [enabled] //      enabled
  );

  //     
  return [enabled, setEnableState];
}

//   "useMedia"    
//      ,    ,
//       -   
//        
function usePrefersDarkMode() {
  return useMedia(["(prefers-color-scheme: dark)"], [true], false);
}


useMedia



Hook ini merangkum logika untuk mendefinisikan kueri media. Dalam contoh di bawah ini, kami merender jumlah kolom yang berbeda bergantung pada kueri media berdasarkan lebar layar saat ini, lalu memposisikan gambar di atas kolom sehingga meratakan perbedaan ketinggian kolom (kami tidak ingin satu kolom lebih tinggi dari yang lain) ... Anda dapat membuat hook yang secara langsung menentukan lebar layar, namun hook kami memungkinkan Anda untuk menggabungkan kueri media yang ditentukan dalam JS dan stylesheet.



import { useState, useEffect } from "react";

function App() {
  const columnCount = useMedia(
    // -
    ["(min-width: 1500px)", "(min-width: 1000px)", "(min-width: 600px)"],
    //     
    [5, 4, 3],
    //    
    2
  );

  //      (  0)
  let columnHeight = new Array(columnCount).fill(0);

  //   ,   
  let columns = new Array(columnCount).fill().map(() => []);

  data.forEach((item) => {
    //     
    const shortColumntIndex = columnHeight.indexOf(Math.min(...columnHeight));
    //  
    columns[shortColumntIndex].push(item);
    //  
    columnHeight[shortColumntIndex] += item.height;
  });

  //    
  return (
    <div className="App">
      <div className="columns is-mobile">
        {columns.map((column) => (
          <div className="column">
            {column.map((item) => (
              <div
                className="image-container"
                style={{
                  //     aspect ratio
                  paddingTop: (item.height / item.width) * 100 + "%",
                }}
              >
                <img src={item.image} alt="" />
              </div>
            ))}
          </div>
        ))}
      </div>
    </div>
  );
}

// 
function useMedia(queries, values, defaultValue) {
  //   -
  const mediaQueryList = queries.map((q) => window.matchMedia(q));

  //      
  const getValue = () => {
    //     
    const index = mediaQueryList.findIndex((mql) => mql.matches);
    //       
    return typeof values[index] !== "undefined"
      ? values[index]
      : defaultValue;
  };

  //      
  const [value, setValue] = useState(getValue);

  useEffect(
    () => {
      //   
      //  :  getValue   useEffect,  
      //       
      //        
      const handler = () => setValue(getValue);
      //     -
      mediaQueryList.forEach((mql) => mql.addEventListener(handler));
      //    
      return () =>
        mediaQueryList.forEach((mql) => mql.removeEventListener(handler));
    },
    [] //          
  );

  return value;
}


useLocalStorage



Hook ini dirancang untuk menyinkronkan status dengan penyimpanan lokal untuk mempertahankan status di seluruh halaman yang dimuat ulang. Menggunakan hook ini mirip dengan useState, kecuali bahwa kita meneruskan kunci penyimpanan lokal sebagai default saat pemuatan halaman alih-alih menentukan nilai awal.



import { useState } from "react";

// 
function App() {
  //  useState,      ,    
  const [name, setName] = useLocalStorage("name", "Igor");

  return (
    <div>
      <input
        type="text"
        placeholder="Enter your name"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
    </div>
  );
}

// 
function useLocalStorage(key, initialValue) {
  //    
  //    useState   
  const [storedValue, setStoredValue] = useState(() => {
    try {
      //       
      const item = window.localStorage.getItem(key);
      //      initialValue
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      //   ,    
      console.error(error);
      return initialValue;
    }
  });

  //     useState,
  //       
  const setValue = (value) => {
    try {
      //    
      const valueToStore =
        value instanceof Function ? value(storedValue) : value;
      //  
      setStoredValue(valueToStore);
      //     
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      //            
      console.error(error);
    }
  };

  return [storedValue, setValue];
}


Sekian untuk hari ini. Semoga Anda menemukan sesuatu yang bermanfaat. Terima kasih atas perhatian Anda.



All Articles