Sử dụng ReactEffect hook không chạy khi kết xuất ban đầu


95

Theo tài liệu:

componentDidUpdate()được gọi ngay sau khi cập nhật xảy ra. Phương thức này không được gọi cho kết xuất ban đầu.

Chúng ta có thể sử dụng useEffect()hook mới để mô phỏng componentDidUpdate(), nhưng có vẻ như useEffect()nó đang được chạy sau mỗi lần kết xuất, ngay cả lần đầu tiên. Làm cách nào để làm cho nó không chạy trên kết xuất ban đầu?

Như bạn có thể thấy trong ví dụ bên dưới, componentDidUpdateFunctionđược in trong lần hiển thị ban đầu nhưng componentDidUpdateClasskhông được in trong lần hiển thị đầu tiên.

function ComponentDidUpdateFunction() {
  const [count, setCount] = React.useState(0);
  React.useEffect(() => {
    console.log("componentDidUpdateFunction");
  });

  return (
    <div>
      <p>componentDidUpdateFunction: {count} times</p>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        Click Me
      </button>
    </div>
  );
}

class ComponentDidUpdateClass extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }

  componentDidUpdate() {
    console.log("componentDidUpdateClass");
  }

  render() {
    return (
      <div>
        <p>componentDidUpdateClass: {this.state.count} times</p>
        <button
          onClick={() => {
            this.setState({ count: this.state.count + 1 });
          }}
        >
          Click Me
        </button>
      </div>
    );
  }
}

ReactDOM.render(
  <div>
    <ComponentDidUpdateFunction />
    <ComponentDidUpdateClass />
  </div>,
  document.querySelector("#app")
);
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>


1
Tôi có thể hỏi trường hợp sử dụng là gì khi làm điều gì đó dựa trên số lần hiển thị chứ không phải biến trạng thái rõ ràng như thế countnào không?
Aprillion

Câu trả lời:


111

Chúng ta có thể sử dụng useRefhook để lưu trữ bất kỳ giá trị có thể thay đổi nào mà chúng ta thích , vì vậy chúng ta có thể sử dụng nó để theo dõi nếu đó là lần đầu tiên useEffecthàm được chạy.

Nếu chúng ta muốn hiệu ứng chạy trong cùng một pha componentDidUpdate, chúng ta có thể sử dụng useLayoutEffectthay thế.

Thí dụ

const { useState, useRef, useLayoutEffect } = React;

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

  const firstUpdate = useRef(true);
  useLayoutEffect(() => {
    if (firstUpdate.current) {
      firstUpdate.current = false;
      return;
    }

    console.log("componentDidUpdateFunction");
  });

  return (
    <div>
      <p>componentDidUpdateFunction: {count} times</p>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        Click Me
      </button>
    </div>
  );
}

ReactDOM.render(
  <ComponentDidUpdateFunction />,
  document.getElementById("app")
);
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>


5
Tôi đã cố gắng thay thế useRefbằng useState, nhưng việc sử dụng setter đã kích hoạt kết xuất lại, điều này không xảy ra khi gán cho firstUpdate.currentvì vậy tôi đoán đây là cách tốt nhất :)
Aprillion

2
Ai đó có thể giải thích tại sao sử dụng hiệu ứng bố cục nếu chúng tôi không thay đổi hoặc đo lường DOM?
ZenVentzi

5
@ZenVentzi Nó không cần thiết trong ví dụ này, nhưng câu hỏi là làm thế nào để bắt chước componentDidUpdatevới hook, vì vậy đó là lý do tại sao tôi sử dụng nó.
Tholle

Tôi đã tạo một móc tùy chỉnh ở đây dựa trên câu trả lời này. Cảm ơn vì đã thực hiện!
Patrick Roberts vào

58

Bạn có thể biến nó thành móc tùy chỉnh , như sau:

import React, { useEffect, useRef } from 'react';

const useDidMountEffect = (func, deps) => {
    const didMount = useRef(false);

    useEffect(() => {
        if (didMount.current) func();
        else didMount.current = true;
    }, deps);
}

export default useDidMountEffect;

Ví dụ sử dụng:

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

import useDidMountEffect from '../path/to/useDidMountEffect';

const MyComponent = (props) => {    
    const [state, setState] = useState({
        key: false
    });    

    useEffect(() => {
        // you know what is this, don't you?
    }, []);

    useDidMountEffect(() => {
        // react please run me if 'key' changes, but not on initial render
    }, [state.key]);    

    return (
        <div>
             ...
        </div>
    );
}
// ...

2
Cách tiếp cận này đưa ra các cảnh báo nói rằng danh sách phụ thuộc không phải là một mảng theo nghĩa đen.
theprogrammer,

1
Tôi sử dụng hook này trong các dự án của mình và tôi không thấy bất kỳ cảnh báo nào, bạn có thể cung cấp thêm thông tin không?
Mehdi Dehghani

1
@vsync Bạn đang nghĩ về một trường hợp khác mà bạn muốn chạy một hiệu ứng một lần khi kết xuất ban đầu và không bao giờ lặp lại
Lập trình Guy

2
@vsync Trong phần ghi chú của reactjs.org/docs/… có ghi cụ thể "Nếu bạn muốn chạy một hiệu ứng và làm sạch nó chỉ một lần (khi gắn kết và ngắt kết nối), bạn có thể chuyển một mảng trống ([]) làm đối số thứ hai. " Điều này phù hợp với hành vi quan sát được đối với tôi.
Chàng trai lập trình

5

Tôi đã tạo một useFirstRenderhook đơn giản để xử lý các trường hợp như lấy nét đầu vào biểu mẫu:

import { useRef, useEffect } from 'react';

export function useFirstRender() {
  const firstRender = useRef(true);

  useEffect(() => {
    firstRender.current = false;
  }, []);

  return firstRender.current;
}

Nó bắt đầu như true, sau đó chuyển sang falsetrong useEffect, chỉ chạy một lần và không bao giờ nữa.

Trong thành phần của bạn, hãy sử dụng nó:

const firstRender = useFirstRender();
const phoneNumberRef = useRef(null);

useEffect(() => {
  if (firstRender || errors.phoneNumber) {
    phoneNumberRef.current.focus();
  }
}, [firstRender, errors.phoneNumber]);

Đối với trường hợp của bạn, bạn chỉ cần sử dụng if (!firstRender) { ....


3

@ravi, của bạn không gọi hàm bỏ gắn kết được truyền vào. Đây là một phiên bản hoàn thiện hơn một chút:

/**
 * Identical to React.useEffect, except that it never runs on mount. This is
 * the equivalent of the componentDidUpdate lifecycle function.
 *
 * @param {function:function} effect - A useEffect effect.
 * @param {array} [dependencies] - useEffect dependency list.
 */
export const useEffectExceptOnMount = (effect, dependencies) => {
  const mounted = React.useRef(false);
  React.useEffect(() => {
    if (mounted.current) {
      const unmount = effect();
      return () => unmount && unmount();
    } else {
      mounted.current = true;
    }
  }, dependencies);

  // Reset on unmount for the next mount.
  React.useEffect(() => {
    return () => mounted.current = false;
  }, []);
};


Xin chào @Whatabrain, làm cách nào để sử dụng móc tùy chỉnh này khi chuyển danh sách không giảm tải? Không phải là sản phẩm trống giống như componentDidmount mà giống nhưuseEffect(() => {...});
KevDing

1
@KevDing Nên đơn giản như bỏ qua dependenciestham số khi bạn gọi nó.
Whatabrain

0

@MehdiDehghani, giải pháp của bạn hoạt động hoàn toàn tốt, một điều bạn phải làm là ngắt kết nối, đặt lại didMount.currentgiá trị thành false. Khi nào cố gắng sử dụng móc tùy chỉnh này ở một nơi khác, bạn không nhận được giá trị bộ nhớ cache.

import React, { useEffect, useRef } from 'react';

const useDidMountEffect = (func, deps) => {
    const didMount = useRef(false);

    useEffect(() => {
        let unmount;
        if (didMount.current) unmount = func();
        else didMount.current = true;

        return () => {
            didMount.current = false;
            unmount && unmount();
        }
    }, deps);
}

export default useDidMountEffect;

1
Tôi không chắc điều này là cần thiết, vì nếu thành phần kết thúc việc tháo kết nối, bởi vì nếu nó thực hiện remount, didMount sẽ được khởi tạo lại thành false.
Cameron Yick
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.