JavaScript >> Javascript チュートリアル >  >> React

フックを使用するための小さな Next アプリのリファクタリング

React Core チームがフックの概念を立ち上げたとき、ドキュメントを読んでから数分以内に全員が参加しました。クラスを扱うのではなく、すべてを単純な関数として保持する this -binding とライフサイクル メソッドは、私にとって素晴らしいものに思えました。

しかし、フックが出てきたのとほぼ同時に、私の父親の休暇が始まりました.幸運なことに、息子と一緒に家にいるために最大 6 か月の有給休暇を取得できました。それはとても楽しく、たくさんのうんちおむつとたくさんの睡眠不足です.フックはまったくありません。

息子の世話をしているということは、新しい API をいじる暇があまりないということです。また、それらを紹介する「専門的な」プロジェクトもありません。しかし、ここ数日、彼はよく眠れるようになってきました。こんにちはフック!

2 年ほど前に、3 リットルのワインとドメイン名を購入しました。反応します。クリスマス。 React の記事でアドベント カレンダーを作成することにし、数晩でアプリをまとめました。これは、サーバー側のレンダリング React フレームワークである Next.js に基づいており、実にシンプルです。

つまり、フック リファクタリングの完璧な候補です。

この記事では、このアプリ全体をリファクタリングするために行ったプロセスの概要を説明します。大変な作業のように思えますが、正直なところ、それほどの作業ではありませんでした。似たようなことをするきっかけになれば幸いです!

なぜですか?

React Core チームは反復を続けているため、既存のコードをリファクタリングしてフックを使用するべきではありません。彼らがこれを提案する理由は、それが本当に必要ないからです。クラス コンポーネントは (少なくとも当面の間) 存続し、フックの使用によるパフォーマンスの向上は (あったとしても) 非常にわずかです。つまり、明確な価値のないリファクタリングになります。少なくとも、表面的にはそうです。

これらの新しいフックを使用するために古いクラスベースのコンポーネントをリファクタリングすることについての私の主張は単純です:良い習慣です! 現在、実際のプロジェクトに取り組む時間がないため、この小さなリファクタリングは、読んだことを固めるために必要なものです。仕事で時間があれば、同じことを検討することをお勧めします。

なぜだろう?

クラス コンポーネントではフックを使用できないことに注意してください。 HOC と render-props ベースのコンポーネントをカスタム フックにリファクタリングする場合、それらをクラス コンポーネントで使用することはできません。これを回避する方法はありますが、今のところは注意してください。もちろん、すべてのコードをリファクタリングすることもできます😁

コード!

まず、コードを紹介しましょう:

selbekk / 反応クリスマス

コンポジトンの精神に浸る

react.christmas

開発

開発サーバーを実行するには、yarn dev を実行します .

導入

yarn deploy でデプロイ .

自分で作成してください!

このプロジェクトをフォークし、./config.js 内のものを変更します 始めます。その後、さらに React 固有のものを見つけた場合は、それらのテキストなどを ./config.js に移動するプル リクエストを送信してください。 .

コンテンツを書く

すべてのコンテンツは ./content/ にあります 年ごとに分類されたフォルダー。 2018 年の記事を追加する場合は、./content/2018 という名前のフォルダーを作成します。 Markdown ファイルの作成を開始します。

マークダウン ファイルの名前は 01.md にする必要があります 、 02.md など - 24.md までずっと .各記事は、Frontmatter 形式のメタデータで開始する必要があります - 次のようになります:

title: Get started with create-react-app
lead: Creating your first React app usually starts off with a 30 minute crash course with Webpack, Babel and a whole lot
… GitHub で見る

アプリは実際には非常にシンプルです。これには Markdown 形式のコンテンツのフォルダーがあり、API を介して Next.js アプリケーションに公開されます。バックエンドはシンプルな Express サーバーで、フロントエンドも非常にシンプルです。

実際のところ、コードは非常に単純で、リファクタリングするクラス コンポーネントはそれほど多くありませんでした。いくつかありましたが、すべて見ていきます。

react をアップグレードすることを忘れないでください と react-dom

フックを使用するには、フックをサポートする React バージョンを使用する必要があります。 Twitter で大々的に宣伝された後、16.8.0 でついにリリースされました。だから私が最初にしたことは、私のReact depsを更新することでした:

- "react": "^16.4.1",
- "react-dom": "^16.4.1",
+ "react": "^16.8.3",
+ "react-dom": "^16.8.3",

(はい、バージョン範囲で npm update を実行できることはわかっています) ただし、バージョン要件については明示したいと思います)

BackgroundImage コンポーネントのリファクタリング

最初に書き直したコンポーネントは BackgroundImage でした 成分。次のことを行いました:

  • マウントしたら、画面サイズを確認してください。
  • 画面サイズが 1500 ピクセル未満の場合は、適切にスケーリングされたバージョンの画像をリクエストしてください。
  • 画面サイズが 1500 ピクセル以上の場合、何もしない

コードは次のようになります。

class BackgroundImage extends React.Component {
  state = { width: 1500 }
  componentDidMount() {
    this.setState({ width: Math.min(window.innerWidth, 1500) });
  }
  render() {
    const src = `${this.props.src}?width=${this.state.width}`;
    return (
      <Image src={src} />
    );
  }
}

このコンポーネントをカスタム フックに書き直すことは、それほど難しいことではありませんでした。何らかの状態を保持し、その状態をマウントに設定し、その状態に依存する画像をレンダリングしました。

これを書き直す私の最初のアプローチは次のようになりました。

function BackgroundImage(props) {
  const [width, setWidth] = useState(1500);
  useEffect(() => setWidth(Math.min(window.innerWidth, 1500)), []);
  const src = `${props.src}?width=${width}`;
  return <Image src={src} />;
}

useState を使用しています 幅を記憶するフック。デフォルトは 1500 px で、useEffect を使用します。 フックを使用して、取り付けたウィンドウのサイズに設定します。

このコードを見ると、以前は考えもしなかったいくつかの問題が表面化しました。

  • この方法では、常に最大の画像を最初にダウンロードするのではないでしょうか?
  • ウィンドウのサイズが変わったらどうしますか?

まず、最初の問題に取り組みましょう。 useEffect以降 React がその変更を DOM にフラッシュした後に実行されるため、最初のレンダリングでは常に 1500 ピクセルのバージョンが要求されます。それはクールではありません-巨大な画像が必要ない場合は、ユーザーを数バイト節約したいと思います!それでは、これを少し最適化しましょう。

function BackgroundImage(props) {
  const [currentWidth, setCurrentWidth] = useState(
    Math.min(window.innerWidth, maxWidth)
  );
  const src = `${props.src}?width=${currentWidth}`;
  return <Image src={src} />;
}

次に、サイズ変更イベントによってウィンドウ サイズが変更された場合に、新しい画像をダウンロードします。

function BackgroundImage(props) {
  const [currentWidth, setCurrentWidth] = useState(
    Math.min(window.innerWidth, 1500)
  );
  useEffect(() => {
    const handleResize = () => setCurrentWidth(
      Math.min(window.innerWidth, 1500)
    );
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);
  const src = `${props.src}?width=${currentWidth}`;
  return <Image src={src} />;
}

これは問題なく動作しますが、サイズ変更中に大量の画像を要求します。このイベント ハンドラーをデバウンスして、新しい画像を最大で 1 秒に 1 回だけ要求します。

import debounce from 'debounce'; // or write your own

function BackgroundImage(props) {
  const [currentWidth, setCurrentWidth] = useState(
    Math.min(window.innerWidth, 1500)
  );
  useEffect(() => {
    // Only call this handleResize function once every second
    const handleResize = debounce(() => setCurrentWidth(
      Math.min(window.innerWidth, 1500)
    ), 1000);
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);
  const src = `${props.src}?width=${currentWidth}`;
  return <Image src={src} />;
}

今、私たちは料理をしています!しかし今、私たちのコンポーネントには大量のロジックがあるので、それを独自のフックにリファクタリングしましょう:

function useBoundedWidth(maxWidth) {
  const [currentWidth, setCurrentWidth] = useState(
    Math.min(window.innerWidth, maxWidth)
  );
  useEffect(() => {
    const handleResize = debounce(() => {
      const newWidth = Math.min(window.innerWidth, maxWidth);
      if (currentWidth > newWidth) {
        return; // never go smaller
      }
      setCurrentWidth(newWidth);
    }, 1000);
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, [maxWidth]);

  return currentWidth;
}

function BackgroundImage(props) {
  const currentWidth = useBoundedWidth(1500);
  const src = `${props.src}?width=${currentWidth}`;
  return <Image src={src} />;
}

それを見て!再利用可能で簡単にテストできるコンポーネントは素晴らしく、ある時点で虹を見たと思います。美しい!

また、最初に必要だったものよりも小さいイメージをダウンロードしないようにする機会もあったことに注意してください。それはもったいないです。

ページ トラッキング フック

大丈夫!次のコンポーネントに進みます。次にリファクタリングしたかったコンポーネントは、ページ追跡コンポーネントでした。基本的に、ナビゲーション イベントごとに、分析サービスにイベントをプッシュしました。元の実装は次のようになりました。

class PageTracking extends React.Component {    
  componentDidMount() { 
    ReactGA.initialize(
      this.props.trackingId, 
    );  
    ReactGA.pageview(this.props.path);  
  } 
  componentDidUpdate(prevProps) {   
    if (prevProps.path !== this.props.path) {   
      ReactGA.pageview(this.props.path);    
    }   
  } 
  render() {    
    return this.props.children; 
  } 
}   

基本的に、これはアプリケーションをラップするコンポーネントとして機能します。必要に応じて、HOC として実装することもできます。

私は今ではフックの専門家なので、これがカスタム フックの最有力候補のように見えることにすぐに気付きました。それでは、リファクタリングを始めましょう!

マウント時に分析サービスを初期化し、マウント時とパスが変更されるたびにページビューを登録します。

function usePageTracking({ trackingId, path }) {
  useEffect(() => {
    ReactGA.initialize(trackingId);
  }, [trackingId]);

  useEffect(() => {
    ReactGA.pageview(path)
  }, [path]);
}

それでおしまい! useEffect と呼びます 2 回 - 初期化に 1 回、ページ ビューの追跡に 1 回。初期化効果は trackingId の場合にのみ呼び出されます path の場合にのみ呼び出されます。

これを使用するために、「偽の」コンポーネントをレンダリング ツリーに導入する必要はありません。最上位のコンポーネントで呼び出すことができます。

function App(props) {
  usePageTracking({ trackingId: 'abc123', path: props.path });
  return (
    <>
      <SiteHeader />
      <SiteContent />
      <SiteFooter />
    </>
  );
}

これらのカスタム フックがいかに明確であるかが気に入っています。発生させたいことを指定し、それらの効果をいつ再実行するかを指定します。

まとめ

フックを使用するように既存のコードをリファクタリングすることは、やりがいがあり、素晴らしい学習体験になる可能性があります。決してそうする必要はありません。ある 移行を延期したい場合もありますが、コードをフックにリファクタリングする機会があれば、実行してください。 !

私がこの課題にどのように取り組んだかから少し学び、あなた自身のコードベースで同じことをするように促されたことを願っています.ハッピー ハッキング!


  1. あなたが必要とする最後の反応フォームライブラリ

  2. Masonite Framework と Laravel Mix を連携させてみよう

  3. 初心者向け Node Js + はじめての Node Js プログラム