ติดตามสาเหตุที่องค์ประกอบ React แสดงผลซ้ำ


156

มีวิธีการที่เป็นระบบในการดีบักสิ่งที่ทำให้ส่วนประกอบแสดงผลซ้ำใน React หรือไม่ ฉันใส่ console.log แบบง่าย () เพื่อดูจำนวนครั้งที่มันแสดงผล แต่ฉันมีปัญหาในการหาสิ่งที่ทำให้องค์ประกอบในการแสดงหลายครั้งเช่น (4 ครั้ง) ในกรณีของฉัน มีเครื่องมือที่มีอยู่ซึ่งแสดงไทม์ไลน์และ / หรือส่วนประกอบของต้นไม้ทั้งหมดที่แสดงและการสั่งซื้อหรือไม่?


บางทีคุณอาจใช้shouldComponentUpdateเพื่อปิดใช้งานการอัพเดทองค์ประกอบอัตโนมัติแล้วเริ่มติดตามจากที่นั่น ข้อมูลเพิ่มเติมสามารถดูได้ที่นี่: facebook.github.io/react/docs/optimizing-performance.html
Reza Sadr

@jpdelatorre คำตอบถูกต้อง โดยทั่วไปหนึ่งในจุดแข็งของ React ก็คือคุณสามารถติดตามกระแสข้อมูลสำรองได้โดยดูที่รหัส React DevTools ขยายความช่วยเหลือสามารถมีที่ นอกจากนี้ผมมีรายชื่อของเครื่องมือที่มีประโยชน์สำหรับการแสดงผลการติดตาม / การตอบสนององค์ประกอบใหม่แสดงผลเป็นส่วนหนึ่งของฉันแคตตาล็อก addons Reduxและจำนวนบทความใน [ตอบสนองการตรวจสอบประสิทธิภาพการทำงาน] (HTT
markerikson

คำตอบ:


253

หากคุณต้องการตัวอย่างสั้น ๆ โดยไม่ต้องพึ่งพาภายนอกฉันพบว่ามีประโยชน์

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`)
    );
  }
}

นี่คือเบ็ดเล็ก ๆ ที่ฉันใช้ติดตามการอัพเดทองค์ประกอบของฟังก์ชั่น

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 ฉันไม่เห็นเหตุผลที่จะมีบล็อก if สั้นและกระชับ
Isaac

นอกจากนี้หากคุณพบชิ้นส่วนของสถานะกำลังอัปเดตและไม่ชัดเจนว่าที่ไหนหรือทำไมคุณสามารถแทนที่setStateวิธี (ในองค์ประกอบของคลาส) ด้วยsetState(...args) { super.setState(...args) }แล้วตั้งจุดพักในดีบักเกอร์ซึ่งคุณจะสามารถ เพื่อติดตามกลับไปที่ฟังก์ชั่นการตั้งค่าสถานะ
redbmk

ฉันจะใช้ฟังก์ชั่น hook ได้อย่างไร? ฉันควรจะโทรไปที่ไหนuseTraceUpdateหลังจากที่ฉันได้กำหนดไว้อย่างที่คุณเขียน?
damon

ในองค์ประกอบของฟังก์ชั่นคุณสามารถใช้มันเช่นนี้function MyComponent(props) { useTraceUpdate(props); }และมันจะเข้าสู่ระบบทุกครั้งที่เปลี่ยนอุปกรณ์
Jacob Rask

1
@DawsonB คุณอาจไม่มีสถานะใด ๆ ในคอมโพเนนต์นั้นดังนั้นจึงthis.stateไม่ได้กำหนดไว้
Jacob Rask

67

นี่คือบางกรณีที่องค์ประกอบการตอบสนองจะแสดงผลอีกครั้ง

  • แสดงส่วนประกอบของพาเรนต์
  • โทรthis.setState()ภายในส่วนประกอบ นี้จะทำให้วิธีการดังต่อไปนี้วงจรส่วนประกอบshouldComponentUpdate> componentWillUpdate> render>componentDidUpdate
  • propsการเปลี่ยนแปลงในส่วนประกอบ จะเรียกนี้componentWillReceiveProps> shouldComponentUpdate> componentWillUpdate> render> componentDidUpdate( connectวิธีการของreact-reduxทริกเกอร์นี้เมื่อมีการเปลี่ยนแปลงบังคับในร้าน Redux)
  • การโทรthis.forceUpdateซึ่งคล้ายกับthis.setState

คุณสามารถย่อการแสดงผลส่วนประกอบของคุณให้น้อยที่สุดได้โดยใช้เช็คภายในshouldComponentUpdateและย้อนกลับfalseหากไม่จำเป็น

อีกวิธีหนึ่งคือการใช้React.PureComponent หรือส่วนประกอบไร้สัญชาติ ส่วนประกอบที่บริสุทธิ์และไร้สัญชาติจะแสดงผลอีกครั้งเมื่อมีการเปลี่ยนแปลงอุปกรณ์ประกอบฉากเท่านั้น


6
Nitpick: "stateless" หมายถึงองค์ประกอบใด ๆ ที่ไม่ได้ใช้สถานะไม่ว่าจะถูกกำหนดด้วยไวยากรณ์คลาสหรือไวยากรณ์การทำงาน นอกจากนี้องค์ประกอบที่ใช้งานได้จะแสดงผลซ้ำ คุณต้องใช้shouldComponentUpdateหรือขยายReact.PureComponentเพื่อบังคับใช้การเรนเดอร์ใหม่เมื่อมีการเปลี่ยนแปลงเท่านั้น
markerikson

1
คุณพูดถูกเกี่ยวกับองค์ประกอบที่ไร้รัฐ / หน้าที่ จะอัปเดตคำตอบของฉัน
jpdelatorre

คุณสามารถชี้แจงได้อย่างไรว่าเมื่อใดและเพราะเหตุใดส่วนประกอบที่ใช้งานได้จะแสดงผลซ้ำ ฉันใช้ส่วนประกอบการทำงานเล็กน้อยในแอปของฉัน
jasan

ดังนั้นแม้ว่าคุณจะใช้วิธีการทำงานในการสร้างองค์ประกอบของคุณเช่นconst MyComponent = (props) => <h1>Hello {props.name}</h1>;(นั่นคือองค์ประกอบไร้สัญชาติ) มันจะแสดงผลอีกครั้งเมื่อใดก็ตามที่องค์ประกอบหลักแสดงผลอีกครั้ง
jpdelatorre

2
นี่เป็นคำตอบที่ดีอย่างแน่นอน แต่ไม่ได้ตอบคำถามจริง - วิธีการติดตามสิ่งที่เรียกให้แสดงผลอีกครั้ง คำตอบของ Jacob R ดูเหมือนจะให้คำตอบสำหรับปัญหาจริง
Sanuj

10

คำตอบของ @ jpdelatorre นั้นยอดเยี่ยมในการเน้นเหตุผลทั่วไปว่าทำไมส่วนประกอบของ React จึงสามารถสร้างการแสดงผลซ้ำได้

ฉันแค่อยากจะดำดิ่งลึกลงไปในตัวอย่างหนึ่ง: เมื่ออุปกรณ์เปลี่ยนไป การแก้ไขปัญหาสิ่งที่ก่อให้เกิดการตอบสนององค์ประกอบอีกครั้งทำให้เป็นปัญหาที่พบบ่อยและในประสบการณ์ของฉันมากครั้งที่ติดตามการลงปัญหานี้เกี่ยวข้องกับการกำหนดอุปกรณ์ประกอบฉากที่มีการเปลี่ยนแปลง

ตอบสนองต่อองค์ประกอบแสดงผลใหม่ทุกครั้งที่ได้รับอุปกรณ์ประกอบฉากใหม่ พวกเขาสามารถรับอุปกรณ์ใหม่เช่น:

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

หรือถ้าMyComponentเชื่อมต่อกับที่เก็บ redux:

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

เมื่อใดก็ตามที่ค่าของprop1, prop2, prop3หรือprop4การเปลี่ยนแปลงที่MyComponentจะ re-ทำให้ ด้วยอุปกรณ์ประกอบฉาก 4 ชุดจึงไม่ยากเกินไปที่จะติดตามว่าอุปกรณ์ประกอบฉากใดเปลี่ยนแปลงไปโดยการวางconsole.log(this.props)จุดเริ่มต้นของrenderบล็อก อย่างไรก็ตามด้วยองค์ประกอบที่ซับซ้อนมากขึ้นและอุปกรณ์ประกอบฉากมากขึ้นวิธีนี้ไม่สามารถป้องกันได้

ต่อไปนี้เป็นวิธีที่มีประโยชน์ (ใช้lodashเพื่อความสะดวก) เพื่อพิจารณาว่าการเปลี่ยนแปลง prop ใดที่ทำให้องค์ประกอบแสดงผลอีกครั้ง:

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

การเพิ่มตัวอย่างนี้ไปยังองค์ประกอบของคุณสามารถช่วยเปิดเผยผู้ร้ายที่ก่อให้เกิดปัญหาอีกครั้งและหลายครั้งสิ่งนี้ช่วยให้ข้อมูลที่ไม่จำเป็นถูกส่งไปยังส่วนประกอบต่างๆ


3
ตอนนี้เรียกว่าUNSAFE_componentWillReceiveProps(nextProps)และเลิกใช้แล้ว "ก่อนหน้านี้วงจรชีวิตนี้มีชื่อว่าชื่อcomponentWillReceivePropsนั้นจะทำงานต่อไปจนถึงรุ่น 17" จากปฏิกิริยาเอกสาร
Emile Bergeron

1
คุณสามารถทำสิ่งเดียวกันกับ componentDidUpdate ซึ่งดีกว่า arguably เพราะคุณเพียงต้องการที่จะหาสิ่งที่ทำให้องค์ประกอบในการปรับปรุงจริง
ดูภาพที่คมชัดยิ่งขึ้น

5

ไม่มีใครแปลกที่ให้คำตอบนั้น แต่ฉันคิดว่ามันมีประโยชน์มากโดยเฉพาะอย่างยิ่งเมื่อการเปลี่ยนแปลงอุปกรณ์ประกอบฉากเกือบจะซ้อนกันอยู่เสมอ

ตะขอแฟนบอย:

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} />;
  };
};

"เก่า" - fanboys โรงเรียน:

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

ป.ล. ฉันยังคงชอบใช้ HOC (ส่วนประกอบลำดับสูงกว่า) เพราะบางครั้งคุณได้ทำลายอุปกรณ์ประกอบฉากของคุณที่ด้านบนและทางออกของยาโคบไม่ค่อยดี

คำเตือน: ไม่มีการติดต่อใด ๆ กับเจ้าของแพคเกจ เพียงคลิกสิบครั้งเพื่อลองเล็งเห็นถึงความแตกต่างในวัตถุที่ซ้อนกันอย่างลึกล้ำนั่นคือความเจ็บปวดในตัว


4

ตอนนี้มีตะขอสำหรับสิ่งนี้ให้บริการในเวลา 23.00 น.:

https://www.npmjs.com/package/use-trace-update

(การเปิดเผยข้อมูลฉันเผยแพร่) การปรับปรุง: พัฒนาตามรหัสของ Jacob Rask


14
นี่เป็นรหัสเดียวกับที่ยาโคบโพสต์ไว้ ได้ให้เครดิตเขาที่นั่น
Christian Ivicevic

2

การใช้ hooks และส่วนประกอบที่ใช้งานได้ไม่ใช่เพียงแค่การเปลี่ยนเสาเท่านั้นที่สามารถทำให้เกิดการแสดงผลซ้ำได้ สิ่งที่ฉันเริ่มใช้คือบันทึกที่ค่อนข้างทำเอง ฉันช่วยฉันมาก คุณอาจพบว่ามีประโยชน์เช่นกัน

ฉันวางส่วนนี้ในไฟล์ขององค์ประกอบ:

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);
  }
};

ที่จุดเริ่มต้นของวิธีฉันเก็บการอ้างอิง WeakMap:

const refs = useRef(new WeakMap());

จากนั้นหลังจากการโทรที่ "น่าสงสัย" (อุปกรณ์ประกอบฉาก, ตะขอ) ฉันเขียน:

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

1

คำตอบข้างต้นมีประโยชน์มากในกรณีที่ใครก็ตามที่กำลังมองหาวิธี specfic เพื่อตรวจหาสาเหตุของการแสดงผลจากนั้นฉันก็พบห้องสมุดนี้ redux-loggerมีประโยชน์มาก

สิ่งที่คุณสามารถทำได้คือเพิ่มห้องสมุดและเปิดใช้งานความแตกต่างระหว่างสถานะ (มันอยู่ในเอกสาร) เช่น:

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

และเพิ่มมิดเดิลแวร์ในร้าน

จากนั้นใส่console.log()ฟังก์ชั่นการเรนเดอร์ของส่วนประกอบที่คุณต้องการทดสอบ

จากนั้นคุณสามารถเรียกใช้แอปของคุณและตรวจสอบบันทึกของคอนโซลได้ทุกที่ที่มีบันทึกก่อนที่จะแสดงความแตกต่างระหว่างสถานะ(nextProps and this.props)และคุณสามารถตัดสินใจได้ว่าจำเป็นต้องใช้เรนเดอร์หรือไม่ป้อนคำอธิบายรูปภาพที่นี่

มันจะคล้ายกับภาพด้านบนพร้อมกับปุ่ม diff

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.