反応にはいくつかのフックがあります

青灯夜游
リリース: 2021-11-25 15:01:27
オリジナル
3888 人が閲覧しました

react には、useState、useEffect、useContext、useReducer、useCallback、useMemo、useRef、useImperativeHandle、useLayoutEffect の合計 9 つのフックがあります。

反応にはいくつかのフックがあります

このチュートリアルの動作環境: Windows7 システム、react17.0.1 バージョン、Dell G3 コンピューター。

React Hooks (全 9 個)

React の世界にはコンテナコンポーネントと UI コンポーネントがあり、React Hooks が登場する前は UI コンポーネントに関数を使用することができました。コンポーネントは UI を表示するために使用されますが、コンテナ コンポーネントの場合、関数コンポーネントは無力であり、データの取得、データの処理、レンダリングのための UI コンポーネントへのパラメータの受け渡しはクラス コンポーネントに依存しています。私の意見では、React Hooks の使用には、以前のクラス コンポーネントと比較して次の利点があります。

  • コードが読みやすくなり、同じ関数のコード ロジックが異なるライフサイクル関数に分割される開発者はメンテナンスやイテレーションで不利になりやすいですが、React Hooks を通じて関数コードを集約して、読み取りやメンテナンスを容易にすることができます。

  • コンポーネント ツリー レベルが浅くなります。元のコードでは、コンポーネントのステータスを再利用したり、機能を強化したりするために、HOC/レンダー プロパティやその他のメソッドをよく使用しますが、これによりコンポーネント ツリーのレイヤーとレンダリングの数が間違いなく増加します。React Hooks では、これらの機能を強力にカスタマイズしたフックを実装することができます

React はバージョン v16.8 で React Hooks の新機能をリリースしましたが、コミュニティには React Hooks に基づいて複雑なアプリケーションを構築する方法に関するベスト プラクティスがまだありません (少なくとも私はまだ) いいえ)、コミュニティでこれに関する多数の記事を読んだことに基づいて、React Hooks のほとんどの機能を理解し、理解し、巧みに使用するのに役立つ 10 のケースを使用します。

1. useState はコンポーネントの状態を保存します

クラス コンポーネントでは、this.state を使用してコンポーネントの状態を保存し、変更をトリガーします。レンダリングします。たとえば、次の単純なカウンター コンポーネントは、クラス コンポーネントがどのように動作するかをよく説明しています:

import React from "react";
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
      name: "alife"
    };
  }
  render() {
    const { count } = this.state;
    return (
      <p>
        Count: {count}
        <button onClick={() => this.setState({ count: count + 1 })}>+</button>
        <button onClick={() => this.setState({ count: count - 1 })}>-</button>
      </p>
    );
  }
}
ログイン後にコピー

単純なカウンター コンポーネントが完成しました。関数コンポーネントでは、このような黒魔術はないため、React は useState を使用してコンポーネントの状態の保存にご協力ください。

import React, { useState } from "react";
function App() {
  const [obj, setObject] = useState({
    count: 0,
    name: "alife"
  });
  return (
    <p className="App">
      Count: {obj.count}
      <button onClick={() => setObject({ ...obj, count: obj.count + 1 })}>+</button>
      <button onClick={() => setObject({ ...obj, count: obj.count - 1 })}>-</button>
    </p>
  );
}
ログイン後にコピー

useState パラメータを渡すことで、デフォルト状態と状態変更関数を含む配列を返します。新しい状態を関数に渡して、元の状態値を変更します。 useState は状態の処理に役立つものではないことに注意してください。setState の非カバレッジ更新状態と比較して、useState のオーバーレイ更新状態では、開発者自身がロジックを処理する必要があります。 (コードは上記の通り)

useStateの後に関数コンポーネントも独自の状態を持つことができるようですが、これだけでは十分ではありません。

2. UseEffect による副作用の処理

Function コンポーネントは状態を保存できますが、非同期リクエストの場合、副作用の操作は依然として無力であるため、React は開発者が関数の副作用に対処できるように useEffect を提供します。新しい API を紹介する前に、まずクラス コンポーネントがどのように行われるかを見てみましょう:

import React, { Component } from "react";
class App extends Component {
  state = {
    count: 1
  };
  componentDidMount() {
    const { count } = this.state;
    document.title = "componentDidMount" + count;
    this.timer = setInterval(() => {
      this.setState(({ count }) => ({
        count: count + 1
      }));
    }, 1000);
  }
  componentDidUpdate() {
    const { count } = this.state;
    document.title = "componentDidMount" + count;
  }
  componentWillUnmount() {
    document.title = "componentWillUnmount";
    clearInterval(this.timer);
  }
  render() {
    const { count } = this.state;
    return (
      <p>
        Count:{count}
        <button onClick={() => clearInterval(this.timer)}>clear</button>
      </p>
    );
  }
}
ログイン後にコピー

この例では、コンポーネントはコンポーネントのステータスを 1 秒ごとに更新し、更新がトリガーされるたびに、更新が行われます。 document.title の関数がトリガーされ (副作用)、コンポーネントがアンロードされるときに document.title が変更されます (クリアと同様)

#例からわかるように、一部の反復関数の開発者は、コンポーネントDidMountとcomponentDidUpdateですが、useEffectを使用する場合はまったく同じではありません。

import React, { useState, useEffect } from "react";
let timer = null;
function App() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = "componentDidMount" + count;
  },[count]);

  useEffect(() => {
    timer = setInterval(() => {
      setCount(prevCount => prevCount + 1);
    }, 1000);
    // 一定注意下这个顺序:
    // 告诉react在下次重新渲染组件之后,同时是下次执行上面setInterval之前调用
    return () => {
      document.title = "componentWillUnmount";
      clearInterval(timer);
    };
  }, []);
  return (
    <p>
      Count: {count}
      <button onClick={() => clearInterval(timer)}>clear</button>
    </p>
  );
}
ログイン後にコピー

useEffect を使用して上記の例を書き直しました。

useEffect の最初のパラメーターは、非同期リクエスト、外部パラメーターの変更、その他の動作などの副作用を実行するために使用できる関数を受け取ります。 2 番目のパラメータ 依存関係と呼ばれる配列であり、配列内の値が変更されると、useEffect の最初のパラメータの関数が実行されます。戻り値 (ある場合) は、コンポーネントが破棄されるか関数が呼び出される前に に呼び出されます。

    1. たとえば、最初の useEffect では、count 値が変更されると document.title 値が変更されると理解されています;
  • 2. 2 番目の useEffect では、空の配列 []。この場合、コンポーネントが初期化または破棄されたときにのみトリガーされ、componentDidMount とcomponentWillUnmount を置き換えるために使用されます。使用には注意が必要です。
    1. 別の状況もあります。 , つまり、2 番目のパラメーターは渡されません。つまり、useEffect は最初の関数パラメーターのみを受け取ります。これは、パラメーターの変更を監視しないことを意味します。 DOM がレンダリングされるたびに、useEffect 内の関数が実行されます。
この強力なフックに基づいて、componentDidUpdate などの他のライフサイクル関数をシミュレートしてカプセル化できます。コードは非常に単純です。

function useUpdate(fn) {
    // useRef 创建一个引用
    const mounting = useRef(true);
    useEffect(() => {
      if (mounting.current) {
        mounting.current = false;
      } else {
        fn();
      }
    });
}
ログイン後にコピー

これで次のようになります。 useState は状態を管理し、useEffect は副作用と非同期ロジックを処理します。これら 2 つのトリックを学習するだけで、ほとんどのクラス コンポーネントの使用シナリオを処理できます。

3、useContext 减少组件层级

上面介绍了 useState、useEffect 这两个最基本的 API,接下来介绍的 useContext 是 React 帮你封装好的,用来处理多层级传递数据的方式,在以前组件树种,跨层级祖先组件想要给孙子组件传递数据的时候,除了一层层 props 往下透传之外,我们还可以使用 React Context API 来帮我们做这件事,举个简单的例子:

const { Provider, Consumer } = React.createContext(null);
function Bar() {
  return <Consumer>{color => <p>{color}</p>}</Consumer>;
}
function Foo() {
  return <Bar />;
}
function App() {
  return (
    <Provider value={"grey"}>
      <Foo />
    </Provider>
  );
}
ログイン後にコピー

通过 React createContext 的语法,在 APP 组件中可以跨过 Foo 组件给 Bar 传递数据。而在 React Hooks 中,我们可以使用 useContext 进行改造。

const colorContext = React.createContext("gray");
function Bar() {
  const color = useContext(colorContext);
  return <p>{color}</p>;
}
function Foo() {
  return <Bar />;
}
function App() {
  return (
    <colorContext.Provider value={"red"}>
      <Foo />
    </colorContext.Provider>
  );
}
ログイン後にコピー

传递给 useContext 的是 context 而不是 consumer,返回值即是想要透传的数据了。用法很简单,使用 useContext 可以解决 Consumer 多状态嵌套的问题。

function HeaderBar() {
  return (
    <CurrentUser.Consumer>
      {user =>
        <Notifications.Consumer>
          {notifications =>
            <header>
              Welcome back, {user.name}!
              You have {notifications.length} notifications.
            </header>
          }
      }
    </CurrentUser.Consumer>
  );
}
ログイン後にコピー

而使用 useContext 则变得十分简洁,可读性更强且不会增加组件树深度。

function HeaderBar() {
  const user = useContext(CurrentUser);
  const notifications = useContext(Notifications);
  return (
    <header>
      Welcome back, {user.name}!
      You have {notifications.length} notifications.
    </header>
  );
}
ログイン後にコピー

4、useReducer

useReducer 这个 Hooks 在使用上几乎跟 Redux/React-Redux 一模一样,唯一缺少的就是无法使用 redux 提供的中间件。我们将上述的计时器组件改写为 useReducer,

import React, { useReducer } from "react";
const initialState = {
  count: 0
};
function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + action.payload };
    case "decrement":
      return { count: state.count - action.payload };
    default:
      throw new Error();
  }
}
function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: "increment", payload: 5 })}>
        +
      </button>
      <button onClick={() => dispatch({ type: "decrement", payload: 5 })}>
        -
      </button>
    </>
  );
}
ログイン後にコピー

用法跟 Redux 基本上是一致的,用法也很简单,算是提供一个 mini 的 Redux 版本。

5、useCallback 记忆函数

在类组件中,我们经常犯下面这样的错误:

class App {
    render() {
        return <p>
            <SomeComponent style={{ fontSize: 14 }} doSomething={ () => { console.log(&#39;do something&#39;); }}  />
        </p>;
    }
}
ログイン後にコピー

这样写有什么坏处呢?一旦 App 组件的 props 或者状态改变了就会触发重渲染,即使跟 SomeComponent 组件不相关,由于每次 render 都会产生新的 style 和 doSomething(因为重新render前后, style 和 doSomething分别指向了不同的引用),所以会导致 SomeComponent 重新渲染,倘若 SomeComponent 是一个大型的组件树,这样的 Virtual Dom 的比较显然是很浪费的,解决的办法也很简单,将参数抽离成变量。

const fontSizeStyle = { fontSize: 14 };
class App {
    doSomething = () => {
        console.log(&#39;do something&#39;);
    }
    render() {
        return <p>
            <SomeComponent style={fontSizeStyle} doSomething={ this.doSomething }  />
        </p>;
    }
}
ログイン後にコピー

在类组件中,我们还可以通过 this 这个对象来存储函数,而在函数组件中没办法进行挂载了。所以函数组件在每次渲染的时候如果有传递函数的话都会重渲染子组件。

function App() {
  const handleClick = () => {
    console.log(&#39;Click happened&#39;);
  }
  return <SomeComponent onClick={handleClick}>Click Me</SomeComponent>;
}
ログイン後にコピー

这里多说一句,一版把函数式组件理解为class组件render函数的语法糖,所以每次重新渲染的时候,函数式组件内部所有的代码都会重新执行一遍。所以上述代码中每次render,handleClick都会是一个新的引用,所以也就是说传递给SomeComponent组件的props.onClick一直在变(因为每次都是一个新的引用),所以才会说这种情况下,函数组件在每次渲染的时候如果有传递函数的话都会重渲染子组件。

而有了 useCallback 就不一样了,你可以通过 useCallback 获得一个记忆后的函数。

function App() {
  const memoizedHandleClick = useCallback(() => {
    console.log(&#39;Click happened&#39;)
  }, []); // 空数组代表无论什么情况下该函数都不会发生改变
  return <SomeComponent onClick={memoizedHandleClick}>Click Me</SomeComponent>;
}
ログイン後にコピー

老规矩,第二个参数传入一个数组,数组中的每一项一旦值或者引用发生改变,useCallback 就会重新返回一个新的记忆函数提供给后面进行渲染。

这样只要子组件继承了 PureComponent 或者使用 React.memo 就可以有效避免不必要的 VDOM 渲染。

6、useMemo 记忆组件

useCallback 的功能完全可以由 useMemo 所取代,如果你想通过使用 useMemo 返回一个记忆函数也是完全可以的。

useCallback(fn, inputs) is equivalent to useMemo(() => fn, inputs).
ログイン後にコピー

所以前面使用 useCallback 的例子可以使用 useMemo 进行改写:

function App() {
  const memoizedHandleClick = useMemo(() => () => {
    console.log(&#39;Click happened&#39;)
  }, []); // 空数组代表无论什么情况下该函数都不会发生改变
  return <SomeComponent onClick={memoizedHandleClick}>Click Me</SomeComponent>;
}
ログイン後にコピー

唯一的区别是:**useCallback 不会执行第一个参数函数,而是将它返回给你,而 useMemo 会执行第一个函数并且将函数执行结果返回给你。**所以在前面的例子中,可以返回 handleClick 来达到存储函数的目的。

所以 useCallback 常用记忆事件函数,生成记忆后的事件函数并传递给子组件使用。而 useMemo 更适合经过函数计算得到一个确定的值,比如记忆组件。

function Parent({ a, b }) {
  // Only re-rendered if `a` changes:
  const child1 = useMemo(() => <Child1 a={a} />, [a]);
  // Only re-rendered if `b` changes:
  const child2 = useMemo(() => <Child2 b={b} />, [b]);
  return (
    <>
      {child1}
      {child2}
    </>
  )
}
ログイン後にコピー

当 a/b 改变时,child1/child2 才会重新渲染。从例子可以看出来,只有在第二个参数数组的值发生变化时,才会触发子组件的更新。

7、useRef 保存引用值

useRef 跟 createRef 类似,都可以用来生成对 DOM 对象的引用,看个简单的例子:

import React, { useState, useRef } from "react";
function App() {
  let [name, setName] = useState("Nate");
  let nameRef = useRef();
  const submitButton = () => {
    setName(nameRef.current.value);
  };
  return (
    <p className="App">
      <p>{name}</p>

      <p>
        <input ref={nameRef} type="text" />
        <button type="button" onClick={submitButton}>
          Submit
        </button>
      </p>
    </p>
  );
}
ログイン後にコピー

useRef 返回的值传递给组件或者 DOM 的 ref 属性,就可以通过 ref.current 值访问组件或真实的 DOM 节点,重点是组件也是可以访问到的,从而可以对 DOM 进行一些操作,比如监听事件等等。

当然 useRef 远比你想象中的功能更加强大,useRef 的功能有点像类属性,或者说您想要在组件中记录一些值,并且这些值在稍后可以更改。

利用 useRef 就可以绕过 Capture Value 的特性。可以认为 ref 在所有 Render 过程中保持着唯一引用,因此所有对 ref 的赋值或取值,拿到的都只有一个最终状态,而不会在每个 Render 间存在隔离。

React Hooks 中存在 Capture Value 的特性:

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

  useEffect(() => {
    setTimeout(() => {
      alert("count: " + count);
    }, 3000);
  }, [count]);

  return (
    <p>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>增加 count</button>
      <button onClick={() => setCount(count - 1)}>减少 count</button>
    </p>
  );
}
ログイン後にコピー

先点击增加button,后点击减少button,3秒后先alert 1,后alert 0,而不是alert两次0。这就是所谓的 capture value 的特性。而在类组件中 3 秒后输出的就是修改后的值,因为这时候** message 是挂载在 this 变量上,它保留的是一个引用值**,对 this 属性的访问都会获取到最新的值。讲到这里你应该就明白了,useRef 创建一个引用,就可以有效规避 React Hooks 中 Capture Value 特性。

function App() {
  const count = useRef(0);

  const showCount = () => {
    alert("count: " + count.current);
  };

  const handleClick = number => {
    count.current = count.current + number;
    setTimeout(showCount, 3000);
  };

  return (
    <p>
      <p>You clicked {count.current} times</p>
      <button onClick={() => handleClick(1)}>增加 count</button>
      <button onClick={() => handleClick(-1)}>减少 count</button>
    </p>
  );
}
ログイン後にコピー

只要将赋值与取值的对象变成 useRef,而不是 useState,就可以躲过 capture value 特性,在 3 秒后得到最新的值。

8、useImperativeHandle 透传 Ref

通过 useImperativeHandle 用于让父组件获取子组件内的索引

import React, { useRef, useEffect, useImperativeHandle, forwardRef } from "react";
function ChildInputComponent(props, ref) {
  const inputRef = useRef(null);
  useImperativeHandle(ref, () => inputRef.current);
  return <input type="text" name="child input" ref={inputRef} />;
}
const ChildInput = forwardRef(ChildInputComponent);
function App() {
  const inputRef = useRef(null);
  useEffect(() => {
    inputRef.current.focus();
  }, []);
  return (
    <p>
      <ChildInput ref={inputRef} />
    </p>
  );
}
ログイン後にコピー

通过这种方式,App 组件可以获得子组件的 input 的 DOM 节点。

9、useLayoutEffect 同步执行副作用

大部分情况下,使用 useEffect 就可以帮我们处理组件的副作用,但是如果想要同步调用一些副作用,比如对 DOM 的操作,就需要使用 useLayoutEffect,useLayoutEffect 中的副作用会在 DOM 更新之后同步执行。

function App() {
  const [width, setWidth] = useState(0);
  useLayoutEffect(() => {
    const title = document.querySelector("#title");
    const titleWidth = title.getBoundingClientRect().width;
    console.log("useLayoutEffect");
    if (width !== titleWidth) {
      setWidth(titleWidth);
    }
  });
  useEffect(() => {
    console.log("useEffect");
  });
  return (
    <p>
      <h1 id="title">hello</h1>
      <h2>{width}</h2>
    </p>
  );
}
ログイン後にコピー

在上面的例子中,useLayoutEffect 会在 render,DOM 更新之后同步触发函数,会优于 useEffect 异步触发函数。

(1) useEffect和useLayoutEffect有什么区别?

简单来说就是调用时机不同,useLayoutEffect和原来componentDidMount&componentDidUpdate一致,在react完成DOM更新后马上同步调用的代码,会阻塞页面渲染。而useEffect是会在整个页面渲染完才会调用的代码。

官方建议优先使用useEffect

However, we recommend starting with useEffect first and only trying useLayoutEffect if that causes a problem.

在实际使用时如果想避免页面抖动(在useEffect里修改DOM很有可能出现)的话,可以把需要操作DOM的代码放在useLayoutEffect里。关于使用useEffect导致页面抖动。

不过useLayoutEffect在服务端渲染时会出现一个warning,要消除的话得用useEffect代替或者推迟渲染时机。

推荐学习:《react视频教程

以上が反応にはいくつかのフックがありますの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート