Bagaimana kait berguna?
Sebelum saya memberi tahu Anda apa dan mengapa saya kecewa, saya ingin secara resmi menyatakan bahwa, sebenarnya, saya adalah penggemar hook .
Saya sering mendengar bahwa hook dibuat untuk menggantikan komponen kelas. Sayangnya, posting "Introduction to Hooks" di situs resmi React mengiklankan inovasi ini, terus terang, sangat disayangkan:
Hooks adalah inovasi di React 16.8 yang memungkinkan Anda menggunakan state dan fitur React lainnya tanpa kelas menulis.
Pesan yang saya lihat di sini berbunyi seperti ini: "Kelas tidak keren!". Tidak cukup untuk memotivasi Anda menggunakan pengait. Menurut pendapat saya, kait memungkinkan kita menyelesaikan fungsionalitas lintas sektor dengan lebih elegan daripada pendekatan sebelumnya: mixin , komponen tingkat tinggi, dan alat peraga render .
Fungsi pencatatan log dan otentikasi adalah umum untuk semua komponen, dan hook memungkinkan fungsi yang dapat digunakan kembali tersebut untuk dilampirkan ke komponen.
Apa yang salah dengan komponen kelas?
Ada beberapa keindahan yang tidak dapat dipahami dalam komponen tanpa kewarganegaraan (yaitu komponen tanpa keadaan internal) yang menggunakan props sebagai input dan mengembalikan elemen React. Ini adalah fungsi murni, yaitu fungsi tanpa efek samping.
export const Heading: React.FC<HeadingProps> = ({ level, className, tabIndex, children, ...rest }) => {
const Tag = `h${level}` as Taggable;
return (
<Tag className={cs(className)} {...rest} tabIndex={tabIndex}>
{children}
</Tag>
);
};
Sayangnya, kurangnya efek samping membatasi penggunaan komponen tanpa kewarganegaraan. Bagaimanapun, manipulasi negara sangat penting. Di React, ini berarti efek samping ditambahkan ke kacang kelas yang stateful. Mereka juga disebut komponen wadah. Mereka melakukan efek samping dan meneruskan alat peraga ke fungsi murni - komponen tanpa negara.
Ada beberapa masalah umum terkait peristiwa siklus proses berbasis kelas. Banyak orang tidak senang bahwa mereka harus mengulangi logika
componentDidMountdan metode componentDidUpdate.
async componentDidMount() {
const response = await get(`/users`);
this.setState({ users: response.data });
};
async componentDidUpdate(prevProps) {
if (prevProps.resource !== this.props.resource) {
const response = await get(`/users`);
this.setState({ users: response.data });
}
};
Cepat atau lambat, semua pengembang dihadapkan pada masalah ini.
Kode efek samping ini dapat dijalankan dalam satu komponen menggunakan hook efek.
const UsersContainer: React.FC = () => {
const [ users, setUsers ] = useState([]);
const [ showDetails, setShowDetails ] = useState(false);
const fetchUsers = async () => {
const response = await get('/users');
setUsers(response.data);
};
useEffect( () => {
fetchUsers(users)
}, [ users ]
);
// etc.
Hook
useEffectmembuat hidup lebih mudah, tetapi menghilangkan fungsi murni - komponen tanpa negara - yang kita gunakan sebelumnya. Ini adalah hal pertama yang mengecewakan saya.
Paradigma JavaScript lain yang perlu diketahui
Saya berusia 49 tahun dan saya adalah penggemar React. Setelah mengembangkan aplikasi ember dengan pengamat ini dan kegilaan properti yang dihitung, saya akan selalu memiliki perasaan hangat untuk aliran data searah.
Masalah dengan hook
useEffectdan sejenisnya adalah mereka tidak menggunakannya di tempat lain dalam lanskap JavaScript. Dia tidak biasa dan umumnya aneh. Saya hanya melihat satu cara untuk menjinakkannya - menggunakan kail ini dalam praktik dan menderita. Dan tidak ada contoh penghitung yang akan mendorong saya untuk membuat kode tanpa pamrih sepanjang malam. Saya seorang freelancer dan saya tidak hanya menggunakan React tetapi juga perpustakaan lain, dan saya sudah lelahmelacak semua inovasi ini. Segera setelah saya berpikir bahwa saya perlu menginstal plugin eslint, yang akan membuat saya berada di jalur yang benar, paradigma baru ini mulai membebani saya.
Larik ketergantungan adalah neraka
The useEffect kait dapat mengambil argumen opsional kedua disebut ketergantungan berbagai , yang memungkinkan Anda untuk memanggil kembali efeknya ketika Anda membutuhkannya. Untuk menentukan apakah perubahan telah terjadi , React membandingkan nilai satu sama lain menggunakan metode Object.is . Jika ada elemen yang berubah sejak siklus render terakhir, efeknya akan diterapkan ke nilai baru.
Perbandingan sangat bagus untuk menangani tipe data primitif. Tetapi jika salah satu elemen adalah objek atau larik, masalah bisa muncul. Object.is membandingkan objek dan array dengan referensi dan Anda tidak dapat berbuat apa-apa. Algoritme perbandingan khusus tidak dapat diterapkan.
Memvalidasi objek dengan referensi adalah batu sandungan yang diketahui. Mari kita lihat versi sederhana dari masalah yang baru-baru ini saya alami.
const useFetch = (config: ApiOptions) => {
const [data, setData] = useState(null);
useEffect(() => {
const { url, skip, take } = config;
const resource = `${url}?$skip=${skip}&take=${take}`;
axios({ url: resource }).then(response => setData(response.data));
}, [config]); // <-- will fetch on each render
return data;
};
const App: React.FC = () => {
const data = useFetch({ url: "/users", take: 10, skip: 0 });
return <div>{data.map(d => <div>{d})}</div>;
};
Pada baris 14 ,
useFetchobjek baru akan diteruskan ke fungsi untuk setiap render, kecuali kita membuatnya sehingga objek yang sama digunakan setiap saat. Dalam skenario ini, Anda ingin memeriksa bidang objek, bukan referensi ke sana.
Saya mengerti mengapa React tidak melakukan perbandingan objek yang dalam seperti solusi ini . Oleh karena itu, Anda perlu menggunakan pengait dengan hati-hati, jika tidak masalah serius dengan kinerja aplikasi dapat muncul. Saya terus-menerus bertanya-tanya apa yang dapat dilakukan tentang itu, dan telah menemukan beberapa opsi . Untuk objek yang lebih dinamis, Anda harus mencari solusi lainnya.
Ada plugin eslint untuk memperbaiki kesalahan secara otomatisditemukan selama validasi kode. Ini cocok untuk editor teks apa pun. Sejujurnya, saya kesal dengan semua fitur baru ini yang memerlukan penginstalan plugin eksternal untuk mengujinya.
Adanya plugin seperti use-deep-object-bandingkan dan use-memo-one menunjukkan bahwa memang ada masalah (atau setidaknya kekacauan).
React bergantung pada urutan pemanggilan hook
Hook kustom paling awal adalah beberapa implementasi dari sebuah fungsi
useFetchuntuk membuat permintaan ke API jarak jauh. Kebanyakan dari mereka tidak menyelesaikan masalah pembuatan permintaan API jarak jauh dari pengendali event, karena hook hanya bisa digunakan di awal komponen fungsional.
Tetapi bagaimana jika ada tautan ke situs yang diberi nomor halaman dalam data, dan kami ingin menjalankan kembali efeknya saat pengguna mengeklik tautan tersebut? Berikut kasus penggunaan sederhana
useFetch:
const useFetch = (config: ApiOptions): [User[], boolean] => {
const [data, setData] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const { skip, take } = config;
api({ skip, take }).then(response => {
setData(response);
setLoading(false);
});
}, [config]);
return [data, loading];
};
const App: React.FC = () => {
const [currentPage, setCurrentPage] = useState<ApiOptions>({
take: 10,
skip: 0
});
const [users, loading] = useFetch(currentPage);
if (loading) {
return <div>loading....</div>;
}
return (
<>
{users.map((u: User) => (
<div>{u.name}</div>
))}
<ul>
{[...Array(4).keys()].map((n: number) => (
<li>
<button onClick={() => console.log(' ?')}>{n + 1}</button>
</li>
))}
</ul>
</>
);
};
Pada baris 23, hook
useFetchakan dipanggil sekali pada render pertama. Pada baris 35–38, kami merender tombol penomoran halaman. Tapi bagaimana kita memanggil hook useFetchdari event handler untuk tombol-tombol ini?
Aturan hook dengan jelas menyatakan:
Jangan gunakan hook di dalam loop, kondisional, atau fungsi bersarang; sebaliknya, selalu gunakan hook hanya pada level teratas dari fungsi React.
Hooks dipanggil dalam urutan yang sama setiap kali komponen dirender. Ada beberapa alasan untuk ini, yang dapat Anda pelajari dari pos luar biasa ini.
Anda tidak dapat melakukan ini:
<button onClick={() => useFetch({ skip: n + 1 * 10, take: 10 })}>
{n + 1}
</button>
Memanggil hook
useFetchdari event handler melanggar aturan hook, karena urutan panggilannya berubah dengan setiap render.
Mengembalikan fungsi yang dapat dieksekusi dari hook
Saya akrab dengan dua solusi untuk masalah ini. Mereka mengambil pendekatan yang sama dan saya suka keduanya. Plugin react-async-hook mengembalikan fungsi dari hook
execute:
import { useAsyncCallback } from 'react-async-hook';
const AppButton = ({ onClick, children }) => {
const asyncOnClick = useAsyncCallback(onClick);
return (
<button onClick={asyncOnClick.execute} disabled={asyncOnClick.loading}>
{asyncOnClick.loading ? '...' : children}
</button>
);
};
const CreateTodoButton = () => (
<AppButton
onClick={async () => {
await createTodoAPI('new todo text');
}}
>
Create Todo
</AppButton>
);
Memanggil hook
useAsyncCallbackakan mengembalikan objek dengan properti load, error, dan result yang diharapkan, serta fungsi executeyang bisa dipanggil dari event handler.
React-hooks-async adalah plugin dengan pendekatan serupa. Ini menggunakan fungsi
useAsyncTask.
Berikut contoh lengkap dengan versi yang disederhanakan
useAsyncTask:
const createTask = (func, forceUpdateRef) => {
const task = {
start: async (...args) => {
task.loading = true;
task.result = null;
forceUpdateRef.current(func);
try {
task.result = await func(...args);
} catch (e) {
task.error = e;
}
task.loading = false;
forceUpdateRef.current(func);
},
loading: false,
result: null,
error: undefined
};
return task;
};
export const useAsyncTask = (func) => {
const forceUpdate = useForceUpdate();
const forceUpdateRef = useRef(forceUpdate);
const task = useMemo(() => createTask(func, forceUpdateRef), [func]);
useEffect(() => {
forceUpdateRef.current = f => {
if (f === func) {
forceUpdate({});
}
};
const cleanup = () => {
forceUpdateRef.current = () => null;
};
return cleanup;
}, [func, forceUpdate]);
return useMemo(
() => ({
start: task.start,
loading: task.loading,
error: task.error,
result: task.result
}),
[task.start, task.loading, task.error, task.result]
);
};
Fungsi createTask mengembalikan objek tugas dalam bentuk berikut.
interface Task {
start: (...args: any[]) => Promise<void>;
loading: boolean;
result: null;
error: undefined;
}
Pekerjaan itu memiliki status
, dan , yang kami harapkan. Namun fungsi tersebut juga mengembalikan fungsi startyang bisa dipanggil nanti. Pekerjaan yang dibuat dengan fungsi createTasktersebut tidak mempengaruhi pembaruan. Pembaruan dipicu oleh fungsi forceUpdatedan forceUpdateRefdalam useAsyncTask.
Kami sekarang memiliki fungsi
startyang dapat dipanggil dari event handler atau dari bagian kode lain, tidak harus dari awal komponen fungsional.
Tetapi kami kehilangan kemampuan untuk memanggil pengait pada operasi pertama komponen fungsional. Ada baiknya plugin react-hooks-async berisi sebuah fungsi
useAsyncRun- ini membuat segalanya lebih mudah:
export const useAsyncRun = (
asyncTask: ReturnType<typeof useAsyncTask>,
...args: any[]
) => {
const { start } = asyncTask;
useEffect(() => {
start(...args);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [asyncTask.start, ...args]);
useEffect(() => {
const cleanup = () => {
//
};
return cleanup;
});
};
Fungsi
startini akan dijalankan setiap kali ada argumen yang berubah args. Sekarang kode dengan kait terlihat seperti ini:
const App: React.FC = () => {
const asyncTask = useFetch(initialPage);
useAsyncRun(asyncTask);
const { start, loading, result: users } = asyncTask;
if (loading) {
return <div>loading....</div>;
}
return (
<>
{(users || []).map((u: User) => (
<div>{u.name}</div>
))}
<ul>
{[...Array(4).keys()].map((n: number) => (
<li key={n}>
<button onClick={() => start({ skip: 10 * n, take: 10 })}>
{n + 1}
</button>
</li>
))}
</ul>
</>
);
};
Menurut aturan kait, kami menggunakan kait
useFetchdi awal komponen fungsional. Fungsi tersebut useAsyncRunmemanggil API di awal, dan startkami menggunakan fungsi di handler onClickuntuk tombol pagination.
Sekarang kail
useFetchdapat digunakan untuk tujuan yang dimaksudkan, tetapi, sayangnya, Anda harus mencari cara lain. Kami juga menggunakan closure, yang harus saya akui, membuat saya sedikit takut.
Mengontrol kait dalam program aplikasi
Dalam program aplikasi, semuanya harus berfungsi sebagaimana mestinya. Jika Anda berencana untuk melacak masalah terkait komponen DAN interaksi pengguna dengan komponen tertentu, Anda dapat menggunakan LogRocket .
LogRocket adalah sejenis perekam video aplikasi web yang merekam hampir semua yang terjadi di situs. Plugin LogRocket untuk React memungkinkan Anda menemukan sesi pengguna di mana pengguna mengklik komponen tertentu dari aplikasi Anda. Anda akan memahami bagaimana pengguna berinteraksi dengan komponen dan mengapa beberapa komponen tidak merender apa pun.
LogRocket mencatat semua tindakan dan status dari penyimpanan Redux. Ini adalah seperangkat alat untuk aplikasi Anda yang memungkinkan Anda merekam permintaan / tanggapan dengan header dan isi. Mereka menulis HTML dan CSS di halaman, menyediakan rendering piksel demi piksel bahkan untuk aplikasi satu halaman yang paling kompleks.
LogRocket menawarkan pendekatan modern untuk men-debug aplikasi React - coba gratis .
Kesimpulan
Saya pikir contoh c
useFetchpaling baik menjelaskan mengapa saya frustrasi dengan hook.
Mencapai hasil yang diinginkan tidak semudah yang saya harapkan, tetapi saya masih mengerti mengapa sangat penting menggunakan pengait dalam urutan tertentu. Sayangnya, kemampuan kami sangat terbatas karena fakta bahwa pengait hanya dapat dipanggil di awal komponen fungsional, dan kami harus mencari solusi lebih lanjut. Solusinya
useFetchagak rumit. Selain itu, saat menggunakan pengait, Anda tidak dapat melakukannya tanpa penutup. Penutupan adalah kejutan terus menerus yang meninggalkan banyak luka di jiwa saya.
Penutupan (seperti yang diteruskan ke
useEffectdanuseCallback) dapat mengambil props dan nilai status versi lama. Ini terjadi, misalnya, ketika salah satu variabel yang ditangkap hilang dalam larik masukan karena suatu alasan - kesulitan dapat muncul.
Keadaan usang yang terjadi setelah menjalankan kode di closure adalah salah satu masalah yang dirancang untuk diselesaikan oleh hook linter. Ada banyak pertanyaan di Stack Overflow tentang hook yang sudah usang
useEffectdan sejenisnya. Saya telah membungkus fungsi useCallbackdan memutar array ketergantungan dengan cara ini dan itu untuk menyingkirkan keadaan basi atau masalah pengulangan tak terbatas. Tidak bisa sebaliknya, tapi itu sedikit mengganggu. Ini adalah masalah nyata yang harus Anda selesaikan untuk membuktikan nilai Anda.
Di awal posting ini, saya mengatakan bahwa secara umum saya suka pengait. Tapi tampaknya sangat rumit. Tidak ada yang seperti itu dalam lanskap JavaScript saat ini. Memanggil hook setiap kali komponen fungsional diberikan akan menciptakan masalah yang tidak dimiliki mixin. Kebutuhan linter untuk menggunakan pola ini sangat tidak kredibel, dan closure merupakan masalah.
Saya harap saya salah memahami pendekatan ini. Jika iya, tulis di kolom komentar.
Baca lebih banyak: