Theo dõi lý do tại sao một thành phần React được kết xuất lại


156

Có một cách tiếp cận có hệ thống để gỡ lỗi những gì đang khiến một thành phần kết xuất lại trong React không? Tôi đặt một console.log () đơn giản để xem nó hiển thị bao nhiêu lần, nhưng tôi gặp khó khăn khi tìm hiểu điều gì khiến thành phần hiển thị nhiều lần, tức là (4 lần) trong trường hợp của tôi. Có một công cụ tồn tại hiển thị dòng thời gian và / hoặc tất cả các thành phần cây biểu hiện và thứ tự?


Có lẽ bạn có thể sử dụng shouldComponentUpdateđể vô hiệu hóa cập nhật thành phần tự động và sau đó bắt đầu theo dõi của bạn từ đó. Thông tin chi tiết có thể được tìm thấy ở đây: facebook.github.io/react/docs/optimizing-performance.html
Reza Sadr

Câu trả lời của @jpdelatorre là chính xác. Nói chung, một trong những điểm mạnh của React là bạn có thể dễ dàng theo dõi luồng dữ liệu sao lưu chuỗi bằng cách xem mã. Phần mở rộng React DevTools có thể giúp với điều đó. Ngoài ra, tôi có một danh sách các công cụ hữu ích để trực quan hóa / theo dõi Kết xuất lại thành phần React như một phần của danh mục addons Redux của tôi và một số bài viết về [Giám sát hiệu suất React] (
ome

Câu trả lời:


253

Nếu bạn muốn một đoạn trích ngắn mà không có bất kỳ sự phụ thuộc bên ngoài nào, tôi thấy điều này hữu ích

componentDidUpdate(prevProps, prevState) {
  Object.entries(this.props).forEach(([key, val]) =>
    prevProps[key] !== val && console.log(`Prop '${key}' changed`)
  );
  if (this.state) {
    Object.entries(this.state).forEach(([key, val]) =>
      prevState[key] !== val && console.log(`State '${key}' changed`)
    );
  }
}

Đây là một cái móc nhỏ tôi sử dụng để theo dõi các bản cập nhật cho các thành phần chức năng

function useTraceUpdate(props) {
  const prev = useRef(props);
  useEffect(() => {
    const changedProps = Object.entries(props).reduce((ps, [k, v]) => {
      if (prev.current[k] !== v) {
        ps[k] = [prev.current[k], v];
      }
      return ps;
    }, {});
    if (Object.keys(changedProps).length > 0) {
      console.log('Changed props:', changedProps);
    }
    prev.current = props;
  });
}

// Usage
function MyComponent(props) {
  useTraceUpdate(props);
  return <div>{props.children}</div>;
}

5
@ yarden.refaeli Tôi thấy không có lý do gì để có một khối if. Ngắn gọn và súc tích.
Isaac

Cùng với điều này, nếu bạn tìm thấy một phần trạng thái đang được cập nhật và không rõ ràng ở đâu hoặc tại sao, bạn có thể ghi đè setStatephương thức (trong thành phần lớp) setState(...args) { super.setState(...args) }và sau đó đặt điểm dừng trong trình gỡ lỗi mà bạn sẽ có thể để theo dõi trở lại chức năng thiết lập trạng thái.
redbmk

Làm thế nào chính xác để tôi sử dụng chức năng móc? Chính xác thì tôi phải gọi useTraceUpdateở đâu sau khi tôi xác định nó như bạn đã viết?
damon

Trong một thành phần chức năng, bạn có thể sử dụng nó như thế này function MyComponent(props) { useTraceUpdate(props); }và nó sẽ ghi lại bất cứ khi nào đạo cụ thay đổi
Jacob Rask

1
@DawsonB có lẽ bạn không có bất kỳ trạng thái nào trong thành phần đó, vì vậy this.statekhông được xác định.
Jacob Rask

67

Dưới đây là một số trường hợp thành phần React sẽ kết xuất lại.

  • Phụ huynh thành phần
  • Gọi this.setState()trong thành phần. Điều này sẽ kích hoạt các phương pháp vòng đời phần sau shouldComponentUpdate> componentWillUpdate> render>componentDidUpdate
  • Thay đổi thành phần props. Điều này sẽ kích hoạt componentWillReceiveProps> shouldComponentUpdate> componentWillUpdate> render> componentDidUpdate( connectphương pháp react-reduxkích hoạt này khi có sự thay đổi áp dụng tại các cửa hàng Redux)
  • gọi this.forceUpdatetương tự nhưthis.setState

Bạn có thể giảm thiểu việc đăng ký lại thành phần của mình bằng cách thực hiện kiểm tra bên trong shouldComponentUpdatevà quay lại falsenếu không cần thiết.

Một cách khác là sử dụng React.PureComponent hoặc các thành phần không trạng thái. Các thành phần thuần túy và không trạng thái chỉ hiển thị lại khi có thay đổi đối với đạo cụ của nó.


6
Nitpick: "statless" chỉ có nghĩa là bất kỳ thành phần nào không sử dụng trạng thái, cho dù nó được xác định bằng cú pháp lớp hoặc cú pháp chức năng. Ngoài ra, các thành phần chức năng luôn luôn kết xuất lại. Bạn cần sử dụng shouldComponentUpdatehoặc mở rộng React.PureComponentđể chỉ thực hiện kết xuất lại khi thay đổi.
Markerikson

1
Bạn nói đúng về thành phần không trạng thái / chức năng luôn được kết xuất lại. Sẽ cập nhật câu trả lời của tôi.
jpdelatorre

bạn có thể làm rõ, khi nào và tại sao các thành phần chức năng luôn luôn tái hiện? Tôi sử dụng khá nhiều thành phần chức năng trong ứng dụng của mình.
jasan

Vì vậy, ngay cả khi bạn sử dụng cách chức năng để tạo thành phần của mình, vd const MyComponent = (props) => <h1>Hello {props.name}</h1>;(đó là thành phần không trạng thái). Nó sẽ kết xuất lại bất cứ khi nào thành phần cha mẹ kết xuất lại.
jpdelatorre

2
Đây là câu trả lời tuyệt vời chắc chắn, nhưng nó không trả lời câu hỏi thực sự - Làm thế nào để theo dõi những gì đã kích hoạt kết xuất lại. Câu trả lời của Jacob R có vẻ hứa hẹn trong việc đưa ra câu trả lời cho vấn đề thực sự.
Sanuj

10

Câu trả lời của @ jpdelatorre rất hay trong việc làm nổi bật những lý do chung tại sao một thành phần React có thể kết xuất lại.

Tôi chỉ muốn đi sâu hơn một chút vào một ví dụ: khi đạo cụ thay đổi . Khắc phục sự cố điều gì khiến thành phần React hiển thị lại là một vấn đề phổ biến và theo kinh nghiệm của tôi, rất nhiều lần theo dõi vấn đề này liên quan đến việc xác định đạo cụ nào đang thay đổi .

Phản ứng lại các thành phần kết xuất lại bất cứ khi nào họ nhận được đạo cụ mới. Họ có thể nhận được đạo cụ mới như:

<MyComponent prop1={currentPosition} prop2={myVariable} />

hoặc nếu MyComponentđược kết nối với một cửa hàng redux:

function mapStateToProps (state) {
  return {
    prop3: state.data.get('savedName'),
    prop4: state.data.get('userCount')
  }
}

Bất cứ lúc nào giá trị của prop1, prop2, prop3, hoặc prop4thay đổi MyComponentsẽ tái render. Với 4 đạo cụ, không quá khó để theo dõi đạo cụ nào đang thay đổi bằng cách đặt một console.log(this.props)ở đầu renderkhối đó. Tuy nhiên với các thành phần phức tạp hơn và ngày càng có nhiều đạo cụ thì phương pháp này là không thể thực hiện được.

Dưới đây là một cách tiếp cận hữu ích (sử dụng lodash để thuận tiện) để xác định những thay đổi prop nào đang khiến một thành phần hiển thị lại:

componentWillReceiveProps (nextProps) {
  const changedProps = _.reduce(this.props, function (result, value, key) {
    return _.isEqual(value, nextProps[key])
      ? result
      : result.concat(key)
  }, [])
  console.log('changedProps: ', changedProps)
}

Thêm đoạn mã này vào thành phần của bạn có thể giúp tiết lộ thủ phạm gây ra sự tái xuất đáng ngờ và nhiều lần điều này giúp làm sáng tỏ dữ liệu không cần thiết được đưa vào các thành phần.


3
Bây giờ UNSAFE_componentWillReceiveProps(nextProps)nó được gọi và nó không dùng nữa. "Vòng đời này đã được đặt tên trước đó componentWillReceiveProps. Tên đó sẽ tiếp tục hoạt động cho đến phiên bản 17." Từ tài liệu React .
Emile Bergeron

1
Bạn có thể đạt được điều tương tự với thành phầnDidUpdate, dù sao cũng tốt hơn, vì bạn chỉ muốn tìm hiểu điều gì đã khiến một thành phần thực sự cập nhật.
xem sắc nét hơn

5

Lạ không ai đưa ra câu trả lời đó nhưng tôi thấy nó rất hữu ích, đặc biệt là vì những thay đổi đạo cụ hầu như luôn được lồng sâu.

Móc fanboy:

import deep_diff from "deep-diff";
const withPropsChecker = WrappedComponent => {
  return props => {
    const prevProps = useRef(props);
    useEffect(() => {
      const diff = deep_diff.diff(prevProps.current, props);
      if (diff) {
        console.log(diff);
      }
      prevProps.current = props;
    });
    return <WrappedComponent {...props} />;
  };
};

Những người hâm mộ trường học "cũ":

import deep_diff from "deep-diff";
componentDidUpdate(prevProps, prevState) {
      const diff = deep_diff.diff(prevProps, this.props);
      if (diff) {
        console.log(diff);
      }
}

PS Tôi vẫn thích sử dụng HOC (thành phần bậc cao hơn) vì đôi khi bạn đã phá hủy đạo cụ của mình ở đầu và giải pháp của Jacob không phù hợp lắm

Tuyên bố miễn trừ trách nhiệm: Không liên kết gì với chủ sở hữu gói. Chỉ cần nhấp vào hàng chục lần xung quanh để cố gắng phát hiện sự khác biệt trong các vật thể được lồng sâu là một nỗi đau trong.



2

Sử dụng móc và các thành phần chức năng, không chỉ thay đổi prop có thể gây ra sự tái xuất hiện. Những gì tôi bắt đầu sử dụng là một bản ghi khá thủ công. Tôi đã giúp tôi rất nhiều. Bạn có thể thấy nó hữu ích quá.

Tôi dán phần này vào tập tin của thành phần:

const keys = {};
const checkDep = (map, key, ref, extra) => {
  if (keys[key] === undefined) {
    keys[key] = {key: key};
    return;
  }
  const stored = map.current.get(keys[key]);

  if (stored === undefined) {
    map.current.set(keys[key], ref);
  } else if (ref !== stored) {
    console.log(
      'Ref ' + keys[key].key + ' changed',
      extra ?? '',
      JSON.stringify({stored}).substring(0, 45),
      JSON.stringify({now: ref}).substring(0, 45),
    );
    map.current.set(keys[key], ref);
  }
};

Khi bắt đầu phương thức, tôi giữ một tham chiếu WeakMap:

const refs = useRef(new WeakMap());

Rồi sau mỗi cuộc gọi "đáng ngờ" (đạo cụ, móc) tôi viết:

const example = useExampleHook();
checkDep(refs, 'example ', example);

1

Các câu trả lời ở trên rất hữu ích, chỉ trong trường hợp nếu bất cứ ai đang tìm kiếm một phương pháp cụ thể để phát hiện nguyên nhân của sự đăng ký lại thì tôi thấy thư viện redux-logger này rất hữu ích.

Những gì bạn có thể làm là thêm thư viện và cho phép khác nhau giữa các trạng thái (nó có trong các tài liệu) như:

const logger = createLogger({
    diff: true,
});

Và thêm phần mềm trung gian trong cửa hàng.

Sau đó đặt một console.log()chức năng kết xuất của thành phần bạn muốn kiểm tra.

Sau đó, bạn có thể chạy ứng dụng của mình và kiểm tra nhật ký bảng điều khiển. Bất cứ nơi nào có nhật ký ngay trước khi nó sẽ hiển thị cho bạn sự khác biệt giữa trạng thái (nextProps and this.props)và bạn có thể quyết định xem kết xuất có thực sự cần thiết ở đó khôngnhập mô tả hình ảnh ở đây

Nó sẽ tương tự như hình ảnh trên cùng với phím diff.

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.