ホームページ > 記事 > ウェブフロントエンド > 独自の React フックを構築する方法: ステップバイステップ ガイド
カスタム React フックは、React アプリケーションに特別でユニークな機能を追加できる重要なツールです。
多くの場合、アプリケーションに特定の機能を追加したい場合は、問題を解決するために作成されたサードパーティのライブラリをインストールするだけで済みます。しかし、そのようなライブラリやフックが存在しない場合はどうすればよいでしょうか?
React 開発者として、問題を解決したり、独自の React プロジェクト内に不足している機能を追加したりするために、カスタム フックを作成するプロセスを学ぶことが重要です。
このステップバイステップのガイドでは、私が自分のアプリケーション用に作成した 3 つのフックを分解して、独自のカスタム React フックを作成する方法と、それらがどのような問題を解決するために作成されたのかを説明します。
ユーザーがスニペットの上にマウスを移動し、クリップボード ボタンをクリックするだけで、コードがコンピュータのクリップボードに追加され、好きな場所にコードを貼り付けて使用できるようになります。
copy-gif.gif
ただし、サードパーティのライブラリを使用する代わりに、独自のカスタム React フックを使用してこの機能を再作成したいと思いました。私が作成するすべてのカスタム反応フックと同様に、アプリ全体で再利用できる関数専用の専用フォルダー (通常は utils または lib と呼ばれます) に配置します。
このフックを useCopyToClipboard.js というファイルに置き、同じ名前の関数を作成します。
テキストをユーザーのクリップボードにコピーするにはさまざまな方法があります。私はこれに、copy-to-clipboard と呼ばれるプロセスの信頼性を高めるライブラリを使用することを好みます。
関数をエクスポートします。これをコピーと呼びます。
// utils/useCopyToClipboard.js
import React from "react";
「クリップボードにコピー」からコピーをインポートします;
デフォルト関数 useCopyToClipboard() をエクスポート {}
次に、ユーザーのクリップボードに追加したいテキストをコピーするために使用される関数を作成します。この関数を handleCopy と呼びます。
handleCopy関数の作り方
関数内では、まず、文字列型または数値型のデータのみを受け入れることを確認する必要があります。 if-else ステートメントを設定して、型が文字列または数値であることを確認します。それ以外の場合は、他のタイプをコピーできないことをユーザーに伝えるエラーがコンソールに記録されます。
「反応」から React をインポート;
「クリップボードにコピー」からコピーをインポートします;
デフォルト関数 useCopyToClipboard() をエクスポート {
const [isCopied, setCopied] = React.useState(false);
関数 handleCopy(text) {
if (テキストの種類 === "文字列" || テキストの種類 == "数値") {
// コピー
} else {
// コピーしないでください
console.error(
typeof ${typeof text} をクリップボードにコピーできません。文字列または数値である必要があります。
);
}
}
}
次に、テキストを取得して文字列に変換し、コピー関数に渡します。そこから、フックからアプリケーション内の任意の場所に handleCopy 関数を返します。
通常、handleCopy 関数はボタンの onClick に接続されます。
「反応」から React をインポート;
「クリップボードにコピー」からコピーをインポートします;
デフォルト関数 useCopyToClipboard() をエクスポート {
関数 handleCopy(text) {
if (テキストの種類 === "文字列" || テキストの種類 == "数値") {
copy(text.toString());
} else {
console.error(
typeof ${typeof text} をクリップボードにコピーできません。文字列または数値である必要があります。
);
}
}
return handleCopy;
}
さらに、テキストがコピーされたかどうかを表す何らかの状態が必要です。これを作成するには、フックの先頭で useState を呼び出し、新しい状態変数 isCopied を作成します。ここで、セッターは setCopy と呼ばれます。
最初、この値は false になります。テキストが正常にコピーされた場合は、copy を true に設定します。それ以外の場合は、false に設定します。
最後に、handleCopy とともに配列内のフックから isCopied を返します。
「反応」から React をインポート;
「クリップボードにコピー」からコピーをインポートします;
デフォルト関数 useCopyToClipboard(resetInterval = null) をエクスポート {
const [isCopied, setCopied] = React.useState(false);
関数 handleCopy(text) {
if (テキストの種類 === "文字列" || テキストの種類 == "数値") {
copy(text.toString());
setCopied(true);
} else {
setCopied(false);
console.error(
typeof ${typeof text} をクリップボードにコピーできません。文字列または数値である必要があります。
);
}
}
return [isCopied, handleCopy];
}
useCopyToClipboard
の使用方法
これで、任意のコンポーネント内で useCopyToClipboard を使用できるようになりました。
私の場合、コードスニペットのコードを受け取ったコピーボタンコンポーネントで使用します。
これを機能させるには、ボタンにオンクリックを追加するだけです。そして、ハンドルと呼ばれる関数の戻りでは、テキストとして要求されたコードをコピーします。そして、一度コピーされれば、それは真実になります。コピーが成功したことを示す別のアイコンを表示できます。
「反応」から React をインポート;
import ClipboardIcon from "../svg/ClipboardIcon";
import SuccessIcon from "../svg/SuccessIcon";
import useCopyToClipboard from "../utils/useCopyToClipboard";
関数 CopyButton({ code }) {
const [isCopied, handleCopy] = useCopyToClipboard();
戻る (
handleCopy(code)}>
{コピーされましたか? : }
);
}
リセット間隔を追加する方法
コードには改善できる点が 1 つあります。現在フックを作成しているので、isCopied は常に true になります。つまり、常に成功アイコンが表示されます。
成功-gif.gif
数秒後に状態をリセットしたい場合は、CopyToClipboard を使用するために時間間隔を渡すことができます。その機能を追加しましょう。
フックに戻ると、resetInterval というパラメーターを作成できます。そのデフォルト値は null です。これにより、引数が渡されない場合に状態がリセットされなくなります。
次に useEffect を追加して、テキストがコピーされ、リセット間隔がある場合、setTimeout を使用してその間隔の後に isCopied を false に戻すように指示します。
さらに、フックが使用されているコンポーネントがアンマウントされている場合 (つまり、状態を更新する必要がなくなった場合)、タイムアウトをクリアする必要があります。
「反応」から React をインポート;
「クリップボードにコピー」からコピーをインポートします;
デフォルト関数 useCopyToClipboard(resetInterval = null) をエクスポート {
const [isCopied, setCopied] = React.useState(false);
const handleCopy = React.useCallback((text) => {
if (テキストの種類 === "文字列" || テキストの種類 == "数値") {
copy(text.toString());
setCopied(true);
} else {
setCopied(false);
console.error(
typeof ${typeof text} をクリップボードにコピーできません。文字列または数値である必要があります。
);
}
}, []);
React.useEffect(() => {
タイムアウトにする;
if (isCopied &&resetInterval) {
timeout = setTimeout(() => setCopied(false),resetInterval);
}
return() => {
clearTimeout(タイムアウト);
};
}、[isCopied、resetInterval]);
return [isCopied, handleCopy];
}
最後に、私たちができる最後の改善は、再レンダリングのたびに再作成されないようにするために、handleCopy を useCallback フックでラップすることです。
最終結果
これで、指定された時間間隔の後に状態をリセットできるようにする最後のフックが完成しました。これに 1 つを渡すと、以下のような結果が表示されるはずです。
「反応」から React をインポート;
import ClipboardIcon from "../svg/ClipboardIcon";
import SuccessIcon from "../svg/SuccessIcon";
import useCopyToClipboard from "../utils/useCopyToClipboard";
関数 CopyButton({ code }) {
// isCopied は 3 秒のタイムアウト後にリセットされます
const [isCopied, handleCopy] = useCopyToClipboard(3000);
戻る (
handleCopy(code)}>
{コピーされましたか? : }
);
}
最終結果.gif
Instagram など、無限スクロールがあるアプリでは、ユーザーがページの一番下に到達したら、さらに投稿を取得する必要があります。
Instagram の無限スクロール
無限スクロールの作成など、同様のユースケースのために usePageBottom フックを自分で作成する方法を見てみましょう。
まず、utils フォルダーに別のファイル usePageBottom.js を作成し、同じ名前の関数 (フック) を追加します。
// utils/usePageBottom.js
import React from "react";
デフォルト関数 usePageBottom() をエクスポート {}
次に、ユーザーがページの下部に到達したタイミングを計算する必要があります。これは窓口からの情報で判断できます。これにアクセスするには、フックが呼び出されるコンポーネントがマウントされていることを確認する必要があるため、空の依存関係配列で useEffect フックを使用します。
// utils/usePageBottom.js
import React from "react";
デフォルト関数 usePageBottom() をエクスポート {
React.useEffect(() => {}, []);
}
ウィンドウのinnerHeight値とドキュメントのscrollTop値を足した値がoffsetHeightと等しい場合、ユーザーはページの一番下までスクロールしたことになります。これら 2 つの値が等しい場合、結果は true となり、ユーザーはページの一番下までスクロールしました:
// utils/usePageBottom.js
import React from "react";
デフォルト関数 usePageBottom() をエクスポート {
React.useEffect(() => {
window.innerHeight + document.documentElement.scrollTop ===
document.documentElement.offsetHeight;
}, []);
}
この式の結果を変数 isBottom に保存し、bottom という状態変数を更新します。これは最終的にフックから返されます。
// utils/usePageBottom.js
import React from "react";
デフォルト関数 usePageBottom() をエクスポート {
const [bottom, setBottom] = React.useState(false);
React.useEffect(() => {
const isBottom =
window.innerHeight + document.documentElement.scrollTop ===
document.documentElement.offsetHeight;
setBottom(isButton);
}, []);
一番下に戻る;
}
しかし、このコードはそのままでは機能しません。なぜそうではないのでしょうか?
問題は、ユーザーがスクロールするたびに isBottom を計算する必要があるという事実にあります。その結果、window.addEventListener を使用してスクロール イベントをリッスンする必要があります。ユーザーがスクロールするたびに呼び出される、handleScroll というローカル関数を作成することで、この式を再評価できます。
// utils/usePageBottom.js
import React from "react";
デフォルト関数 usePageBottom() をエクスポート {
const [bottom, setBottom] = React.useState(false);
React.useEffect(() => {
関数 handleScroll() {
const isBottom =
window.innerHeight + document.documentElement.scrollTop
=== document.documentElement.offsetHeight;
setBottom(isButton);
}
window.addEventListener("scroll", handleScroll);
}, []);
一番下に戻る;
}
最後に、状態を更新するイベント リスナーがあるため、ユーザーがページから移動してコンポーネントが削除されるイベントを処理する必要があります。追加したスクロール イベント リスナーを削除する必要があるため、存在しない状態変数を更新しようとしません。
これを行うには、window.removeEventListener とともに useEffect から関数を返し、同じ handleScroll 関数への参照を渡します。これで完了です。
// utils/usePageBottom.js
import React from "react";
デフォルト関数 usePageBottom() をエクスポート {
const [bottom, setBottom] = React.useState(false);
React.useEffect(() => {
関数 handleScroll() {
const isBottom =
window.innerHeight + document.documentElement.scrollTop
=== document.documentElement.offsetHeight;
setBottom(isButton);
}
window.addEventListener("scroll", handleScroll);
return() => {
window.removeEventListener("scroll", handleScroll);
};
}, []);
一番下に戻る;
}
これで、ページの下部に到達したかどうかを知りたい関数でこのコードを呼び出すだけで済みます。
Gatsby サイト内にヘッダーがあり、ページのサイズを小さくするにつれて、表示するリンクの数を減らしたいと考えています。
ヘッダーを表示するためにウィンドウのサイズを変更します
これを行うには、メディア クエリ (CSS) を使用するか、カスタム React フックを使用してページの現在のサイズを取得し、JSX 内のリンクを表示または非表示にすることができます。
以前は、react-use というライブラリのフックを使用していました。サードパーティのライブラリ全体を持ち込む代わりに、ウィンドウの寸法 (幅と高さの両方) を提供する独自のフックを作成することにしました。このフックを useWindowSize と呼びました。
フックの作成方法
まず、ユーティリティ (utils) フォルダーに、フック useWindowSize と同じ名前の新しいファイル .js を作成します。カスタム フックをエクスポートしながら、(フックを使用するために) React をインポートします。
// utils/useWindowSize.js
「反応」から React をインポート;
デフォルト関数 useWindowSize() をエクスポート {}
サーバーレンダリングされた Gatsby サイト内でこれを使用しているため、ウィンドウのサイズを取得する必要があります。ただし、サーバー上にあるため、アクセスできない可能性があります。
サーバー上にいないことを確認するには、ウィンドウのタイプが文字列 undefine と等しくないかどうかを確認します。
この場合、オブジェクト内でブラウザのデフォルトの幅と高さ、たとえば 1200 と 800 に戻すことができます。
// utils/useWindowSize.js
「反応」から React をインポート;
デフォルト関数 useWindowSize() をエクスポート {
if (ウィンドウの種類 !== "未定義") {
return { 幅: 1200、高さ: 800 };
}
}
ウィンドウから幅と高さを取得する方法
そして、クライアント上にいてウィンドウを取得できると仮定すると、useEffect フックを使用してウィンドウと対話することで副作用を実行できます。コンポーネント (このフックが呼び出される) がマウントされたときにのみエフェクト関数が呼び出されるように、空の依存関係配列を組み込みます。
ウィンドウの幅と高さを調べるには、イベント リスナーを追加し、サイズ変更イベントをリッスンします。そして、ブラウザのサイズが変更されるたびに、(useState で作成された) 状態の一部を更新できます。これを windowSize と呼び、それを更新するためのセッターは setWindowSize になります。
// utils/useWindowSize.js
「反応」から React をインポート;
デフォルト関数 useWindowSize() をエクスポート {
if (ウィンドウの種類 !== "未定義") {
return { 幅: 1200、高さ: 800 };
}
const [windowSize, setWindowSize] = React.useState();
React.useEffect(() => {
window.addEventListener("サイズ変更", () => {
setWindowSize({ width: window.innerWidth, height: window.innerHeight });
});
}, []);
}
ウィンドウのサイズが変更されると、コールバックが呼び出され、windowSize 状態が現在のウィンドウの寸法で更新されます。これを取得するには、幅を window.innerWidth に設定し、高さを window.innerHeight に設定します。
SSR サポートを追加する方法
ただし、ここにあるコードは機能しません。これは、フックの重要なルールとして条件付きで呼び出すことができないためです。結果として、 useState フックや useEffect フックが呼び出される前に、それらの上に条件を置くことはできません。
これを修正するために、useState の初期値を条件付きで設定します。 isSSR という変数を作成します。この変数は、同じチェックを実行して、ウィンドウが文字列 unknown と等しくないかどうかを確認します。
そして、最初にサーバー上にあるかどうかを確認して、幅と高さを設定するために 3 項を使用します。デフォルト値を使用する場合はデフォルト値を使用し、そうでない場合は window.innerWidth と window.innerHeight を使用します。
// utils/useWindowSize.js
「反応」から React をインポート;
デフォルト関数 useWindowSize() をエクスポート {
// if (ウィンドウの種類 !== "未定義") {
// return { 幅: 1200、高さ: 800 };
// }
const isSSR = ウィンドウの種類 !== "未定義";
const [windowSize, setWindowSize] = React.useState({
幅:SSRですか? 1200 : window.innerWidth,
身長:SSRですか? 800 : window.innerHeight、
});
React.useEffect(() => {
window.addEventListener("サイズ変更", () => {
setWindowSize({ width: window.innerWidth, height: window.innerHeight });
});
}, []);
}
そして最後に、コンポーネントがいつアンマウントされるかを考える必要があります。何をする必要があるでしょうか?サイズ変更リスナーを削除する必要があります。
サイズ変更イベント リスナーを削除する方法
useEffectand から関数を返すことでこれを行うことができます。 window.removeEventListener.
// utils/useWindowSize.js
「反応」から React をインポート;
デフォルト関数 useWindowSize() をエクスポート {
// if (ウィンドウの種類 !== "未定義") {
// return { 幅: 1200、高さ: 800 };
// }
const isSSR = ウィンドウの種類 !== "未定義";
const [windowSize, setWindowSize] = React.useState({
幅:SSRですか? 1200 : window.innerWidth,
身長:SSRですか? 800 : window.innerHeight、
});
React.useEffect(() => {
window.addEventListener("サイズ変更", () => {
setWindowSize({ width: window.innerWidth, height: window.innerHeight });
});
return () => { window.removeEventListener("resize", () => { setWindowSize({ width: window.innerWidth, height: window.innerHeight }); }); };
}, []);
}
しかし、ここにあるような 2 つの異なる関数ではなく、同じ関数への参照が必要です。これを行うには、changeWindowSize という両方のリスナーへの共有コールバック関数を作成します。
そして最後に、フックの最後で windowSize 状態を返します。以上です。
// utils/useWindowSize.js
「反応」から React をインポート;
デフォルト関数 useWindowSize() をエクスポート {
const isSSR = ウィンドウの種類 !== "未定義";
const [windowSize, setWindowSize] = React.useState({
幅:SSRですか? 1200 : window.innerWidth,
身長:SSRですか? 800 : window.innerHeight、
});
関数changeWindowSize() {
setWindowSize({ width: window.innerWidth, height: window.innerHeight });
}
React.useEffect(() => {
window.addEventListener("resize",changeWindowSize);
return () => { window.removeEventListener("resize", changeWindowSize); };
}, []);
ウィンドウサイズを返す;
}
最終結果
フックを使用するには、必要な場所にフックをインポートして呼び出し、特定の要素を表示または非表示にしたい場所で幅を使用するだけです。
私の場合、これは 500px マークにあります。そこで、上の例にあるように、他のリンクをすべて非表示にして、[今すぐ参加] ボタンのみを表示したいとします。
// コンポーネント/StickyHeader.js
「反応」から React をインポート;
import useWindowSize from "../utils/useWindowSize";
関数 StickyHeader() {
const { width } = useWindowSize();
戻る (
このフックは、Gatsby や Next.js など、サーバーでレンダリングされる任意の React アプリで機能します。
しかし、携帯で見ると、すべてが場違いで壊れていました。
アプリエラーに反応
私は、ユーザーがモバイル デバイスを持っているかどうかを検出するために使用していた、react-device-detect という 1 つのライブラリに問題を追跡しました。もしそうなら、ヘッダーを削除します。
// テンプレート/course.js
import React from "react";
import { isMobile } from "react-device-detect";
関数 Course() {
戻る (
<>
{!isMobile && }
{/* さらにコンポーネント... */}
>
);
}
問題は、このライブラリが、Gatsby がデフォルトで使用するサーバー側レンダリングをサポートしていないことでした。そのため、ユーザーがいつモバイル デバイスを使用しているかを確認する独自のソリューションを作成する必要がありました。そのために、useDeviceDetect という名前のカスタム フックを作成することにしました。
フックの作成方法
このフック用に、utils フォルダーに同じ名前の useDeviceDetect.js という別のファイルを作成しました。フックは React フックを利用する共有可能な JavaScript 関数であるため、useDeviceDetect という関数を作成し、React をインポートしました。
// utils/useDeviceDetect.js
import React from "react";
デフォルト関数 useDeviceDetect() をエクスポート {}
ウィンドウからユーザーエージェントを取得する方法
ユーザーのデバイスに関する情報を取得できるかどうかを確認するには、userAgent プロパティ (ウィンドウの navigator プロパティにあります) を使用します。
API/外部リソースとしてウィンドウ API と対話することは副作用として分類されるため、useEffect フック内でユーザー エージェントへのアクセスを取得する必要があります。
// utils/useDeviceDetect.js
import React from "react";
デフォルト関数 useDeviceDetect() をエクスポート {
React.useEffect(() => {
console.log(ユーザーのデバイス: ${window.navigator.userAgent});
// 'navigator.userAgent'
としても記述できます
}, []);
}
コンポーネントがマウントされたら、typeof ナビゲーターを使用して、クライアント上にいるのかサーバー上にいるのかを判断できます。サーバー上にいる場合は、ウィンドウにアクセスできません。 typeof navigator は、文字列 unknown が存在しないため、それと等しくなります。それ以外の場合、クライアント側にいる場合は、ユーザー エージェント プロパティを取得できます。
userAgent データを取得するための 3 項を使用してこれらすべてを表現できます。
// utils/useDeviceDetect.js
import React from "react";
デフォルト関数 useDeviceDetect() をエクスポート {
React.useEffect(() => {
const userAgent =
ナビゲータの種類 === "未定義" ? "" : navigator.userAgent;
}, []);
}
userAgent がモバイルデバイスかどうかを確認する方法
userAgent は文字列値で、モバイル デバイスを使用している場合は次のデバイス名のいずれかに設定されます:
Android、BlackBerry、iPhone、iPad、iPod、Opera Mini、IEMobile、または WPDesktop。
私たちがしなければならないことは、取得した文字列を取得し、正規表現を指定して .match() メソッドを使用して、それがこれらの文字列のいずれかであるかどうかを確認することだけです。これを mobile というローカル変数に保存します。
useState フックを使用して結果を状態に保存します。初期値として false を与えます。そのために、対応する状態変数 isMobile を作成し、セッターは setMobile になります。
// utils/useDeviceDetect.js
import React from "react";
デフォルト関数 useDeviceDetect() をエクスポート {
const [isMobile, setMobile] = React.useState(false);
React.useEffect(() => {
const userAgent =
window.navigator の種類 === "未定義" ? "" : navigator.userAgent;
const mobile = Boolean(
userAgent.match(
/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i
)
);
setMobile(モバイル);
}, []);
}
モバイル値を取得したら、それを状態に設定します。次に、最後にフックからオブジェクトを返します。これにより、将来このフックにさらに機能を追加することを選択した場合に、さらに値を追加できるようになります。
オブジェクト内に、isMobile をプロパティと値として追加します。
// utils/useDeviceDetect.js
import React from "react";
デフォルト関数 useDeviceDetect() をエクスポート {
const [isMobile, setMobile] = React.useState(false);
React.useEffect(() => {
const userAgent =
window.navigator の種類 === "未定義" ? "" : navigator.userAgent;
const mobile = Boolean(
userAgent.match(
/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i
)
);
setMobile(モバイル);
}, []);
return { isMobile };
}
最終結果
ランディング ページに戻ると、フックを実行して、構造化されたオブジェクトからそのプロパティを取得して、必要な場所で使用することができます。
// テンプレート/course.js
import React from "react";
import useDeviceDetect from "../utils/useDeviceDetect";
関数 Course() {
const { isMobile } = useDeviceDetect();
戻る (
<>
{!isMobile && }
{/* さらにコンポーネント... */}
>
);
}
結論
これらの各例を通して説明しようとしたように、カスタム React フックは、サードパーティのライブラリでは不十分な場合に、独自の問題を解決するツールを提供します。
このガイドが、独自の React フックをいつ、どのように作成するかについてより良いアイデアを提供していただければ幸いです。これらのフックと上記のコードは、ご自身のプロジェクトで、また独自のカスタム React フックのインスピレーションとして自由に使用してください。
以上が独自の React フックを構築する方法: ステップバイステップ ガイドの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。