ไม่สามารถทำการอัพเดตสถานะการตอบสนองบนส่วนประกอบที่ไม่ได้ต่อเชื่อม


143

ปัญหา

ผมเขียนประยุกต์ใช้ในการตอบสนองและไม่สามารถที่จะหลีกเลี่ยงอันตรายที่พบบ่อยสุดซึ่งจะเรียกหลังจากsetState(...)componentWillUnmount(...)

ฉันดูรหัสของตัวเองอย่างถี่ถ้วนและพยายามใส่มาตราการป้องกันไว้ แต่ปัญหายังคงอยู่และฉันยังคงสังเกตคำเตือนอยู่

ดังนั้นฉันมีคำถามสองข้อ:

  1. ฉันจะทราบได้อย่างไรจากการติดตามสแต็กคอมโพเนนต์เฉพาะและตัวจัดการเหตุการณ์หรือจุดเชื่อมต่อวงจรชีวิตที่รับผิดชอบต่อการละเมิดกฎ
  2. วิธีแก้ไขปัญหาด้วยตัวเองเนื่องจากรหัสของฉันเขียนขึ้นโดยคำนึงถึงข้อผิดพลาดนี้และกำลังพยายามป้องกันอยู่แล้ว แต่ส่วนประกอบพื้นฐานบางส่วนยังคงสร้างคำเตือน

คอนโซลเบราว์เซอร์

Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount
method.
    in TextLayerInternal (created by Context.Consumer)
    in TextLayer (created by PageInternal) index.js:1446
d/console[e]
index.js:1446
warningWithoutStack
react-dom.development.js:520
warnAboutUpdateOnUnmounted
react-dom.development.js:18238
scheduleWork
react-dom.development.js:19684
enqueueSetState
react-dom.development.js:12936
./node_modules/react/cjs/react.development.js/Component.prototype.setState
react.development.js:356
_callee$
TextLayer.js:97
tryCatch
runtime.js:63
invoke
runtime.js:282
defineIteratorMethods/</prototype[method]
runtime.js:116
asyncGeneratorStep
asyncToGenerator.js:3
_throw
asyncToGenerator.js:29

ใส่คำอธิบายภาพที่นี่

รหัส

Book.tsx

import { throttle } from 'lodash';
import * as React from 'react';
import { AutoWidthPdf } from '../shared/AutoWidthPdf';
import BookCommandPanel from '../shared/BookCommandPanel';
import BookTextPath from '../static/pdf/sde.pdf';
import './Book.css';

const DEFAULT_WIDTH = 140;

class Book extends React.Component {
  setDivSizeThrottleable: () => void;
  pdfWrapper: HTMLDivElement | null = null;
  isComponentMounted: boolean = false;
  state = {
    hidden: true,
    pdfWidth: DEFAULT_WIDTH,
  };

  constructor(props: any) {
    super(props);
    this.setDivSizeThrottleable = throttle(
      () => {
        if (this.isComponentMounted) {
          this.setState({
            pdfWidth: this.pdfWrapper!.getBoundingClientRect().width - 5,
          });
        }
      },
      500,
    );
  }

  componentDidMount = () => {
    this.isComponentMounted = true;
    this.setDivSizeThrottleable();
    window.addEventListener("resize", this.setDivSizeThrottleable);
  };

  componentWillUnmount = () => {
    this.isComponentMounted = false;
    window.removeEventListener("resize", this.setDivSizeThrottleable);
  };

  render = () => (
    <div className="Book">
      { this.state.hidden && <div className="Book__LoadNotification centered">Book is being loaded...</div> }

      <div className={this.getPdfContentContainerClassName()}>
        <BookCommandPanel
          bookTextPath={BookTextPath}
          />

        <div className="Book__PdfContent" ref={ref => this.pdfWrapper = ref}>
          <AutoWidthPdf
            file={BookTextPath}
            width={this.state.pdfWidth}
            onLoadSuccess={(_: any) => this.onDocumentComplete()}
            />
        </div>

        <BookCommandPanel
          bookTextPath={BookTextPath}
          />
      </div>
    </div>
  );

  getPdfContentContainerClassName = () => this.state.hidden ? 'hidden' : '';

  onDocumentComplete = () => {
    try {
      this.setState({ hidden: false });
      this.setDivSizeThrottleable();
    } catch (caughtError) {
      console.warn({ caughtError });
    }
  };
}

export default Book;

AutoWidthPdf.tsx

import * as React from 'react';
import { Document, Page, pdfjs } from 'react-pdf';

pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;

interface IProps {
  file: string;
  width: number;
  onLoadSuccess: (pdf: any) => void;
}
export class AutoWidthPdf extends React.Component<IProps> {
  render = () => (
    <Document
      file={this.props.file}
      onLoadSuccess={(_: any) => this.props.onLoadSuccess(_)}
      >
      <Page
        pageNumber={1}
        width={this.props.width}
        />
    </Document>
  );
}

อัปเดต 1: ยกเลิกฟังก์ชั่นคันเร่ง (ยังไม่มีโชค)

const DEFAULT_WIDTH = 140;

class Book extends React.Component {
  setDivSizeThrottleable: ((() => void) & Cancelable) | undefined;
  pdfWrapper: HTMLDivElement | null = null;
  state = {
    hidden: true,
    pdfWidth: DEFAULT_WIDTH,
  };

  componentDidMount = () => {
    this.setDivSizeThrottleable = throttle(
      () => {
        this.setState({
          pdfWidth: this.pdfWrapper!.getBoundingClientRect().width - 5,
        });
      },
      500,
    );

    this.setDivSizeThrottleable();
    window.addEventListener("resize", this.setDivSizeThrottleable);
  };

  componentWillUnmount = () => {
    window.removeEventListener("resize", this.setDivSizeThrottleable!);
    this.setDivSizeThrottleable!.cancel();
    this.setDivSizeThrottleable = undefined;
  };

  render = () => (
    <div className="Book">
      { this.state.hidden && <div className="Book__LoadNotification centered">Book is being loaded...</div> }

      <div className={this.getPdfContentContainerClassName()}>
        <BookCommandPanel
          BookTextPath={BookTextPath}
          />

        <div className="Book__PdfContent" ref={ref => this.pdfWrapper = ref}>
          <AutoWidthPdf
            file={BookTextPath}
            width={this.state.pdfWidth}
            onLoadSuccess={(_: any) => this.onDocumentComplete()}
            />
        </div>

        <BookCommandPanel
          BookTextPath={BookTextPath}
          />
      </div>
    </div>
  );

  getPdfContentContainerClassName = () => this.state.hidden ? 'hidden' : '';

  onDocumentComplete = () => {
    try {
      this.setState({ hidden: false });
      this.setDivSizeThrottleable!();
    } catch (caughtError) {
      console.warn({ caughtError });
    }
  };
}

export default Book;

ปัญหายังคงมีอยู่หรือไม่หากคุณแสดงความคิดเห็นในการเพิ่มและลบผู้ฟัง
ic3b3rg

@ ic3b3rg ปัญหาจะหายไปหากไม่มีรหัสฟังเหตุการณ์
Igor Soloydenko

โอเคคุณลองทำตามคำแนะนำthis.setDivSizeThrottleable.cancel()แทนthis.isComponentMountedยามหรือยัง?
ic3b3rg

1
@ ic3b3rg ยังคงคำเตือนเวลาทำงานเหมือนเดิม
Igor Soloydenko

คำตอบ:


95

นี่คือโซลูชันเฉพาะของReact Hooksสำหรับ

ข้อผิดพลาด

คำเตือน: ไม่สามารถทำการอัพเดตสถานะการตอบสนองในคอมโพเนนต์ที่ไม่ได้ต่อเชื่อม

วิธีการแก้

คุณสามารถประกาศlet isMounted = trueภายในuseEffectซึ่งจะถูกเปลี่ยนแปลงในการเรียกกลับการล้างข้อมูลทันทีที่ถอนการต่อเชื่อมส่วนประกอบ ก่อนอัปเดตสถานะตอนนี้คุณตรวจสอบตัวแปรนี้ตามเงื่อนไข:

useEffect(() => {
  let isMounted = true; // note this flag denote mount status
  someAsyncOperation().then(data => {
    if (isMounted) setState(data);
  })
  return () => { isMounted = false }; // use effect cleanup to set flag false, if unmounted
});

ส่วนขยาย: useAsyncตะขอแบบกำหนดเอง

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

function useAsync(asyncFn, onSuccess) {
  useEffect(() => {
    let isMounted = true;
    asyncFn().then(data => {
      if (isMounted) onSuccess(data);
    });
    return () => { isMounted = false };
  }, [asyncFn, onSuccess]);
}


2
เทคนิคของคุณใช้ได้ผล! ฉันสงสัยว่าเวทมนตร์เบื้องหลังคืออะไร?
Niyongabo

1
เราใช้ประโยชน์จากคุณลักษณะการล้างเอฟเฟกต์ในตัวที่นี่ซึ่งจะทำงานเมื่อการอ้างอิงเปลี่ยนไปและไม่ว่าในกรณีใดก็ตามเมื่อส่วนประกอบเลิกต่อเชื่อม ดังนั้นนี่จึงเป็นสถานที่ที่สมบูรณ์แบบในการสลับการisMountedตั้งค่าสถานะfalseซึ่งสามารถเข้าถึงได้จากขอบเขตการปิดการเรียกกลับเอฟเฟกต์โดยรอบ คุณสามารถคิดว่าฟังก์ชั่นการทำความสะอาดเป็นที่อยู่ในผลสอดคล้องกัน
ford04

1
เข้าท่า! ฉันมีความสุขกับคำตอบของคุณ ฉันเรียนรู้จากมัน
Niyongabo

1
@VictorMolina ไม่นั่นคงเกินความจริง พิจารณาเทคนิคนี้สำหรับส่วนประกอบ a) โดยใช้การดำเนินการแบบอะซิงโครนัสเช่นfetchในuseEffectและ b) ที่ไม่เสถียรกล่าวคืออาจถูกยกเลิกการต่อเชื่อมก่อนที่ผลลัพธ์ async จะส่งคืนและพร้อมที่จะตั้งค่าเป็นสถานะ
ford04

1
stackoverflow.com/a/63213676และmedium.com/better-programming/…น่าสนใจ แต่ในที่สุดคำตอบของคุณก็คือสิ่งที่ช่วยให้ฉันทำงานได้ในที่สุด ขอบคุณ!
Ryan

87

ในการลบ - ไม่สามารถทำการอัพเดตสถานะการตอบสนองบนคำเตือนส่วนประกอบที่ไม่ได้ต่อเชื่อมให้ใช้เมธอด componentDidMount ภายใต้เงื่อนไขและทำให้เงื่อนไขนั้นเป็นเท็จบนเมธอด componentWillUnmount ตัวอย่างเช่น : -

class Home extends Component {
  _isMounted = false;

  constructor(props) {
    super(props);

    this.state = {
      news: [],
    };
  }

  componentDidMount() {
    this._isMounted = true;

    ajaxVar
      .get('https://domain')
      .then(result => {
        if (this._isMounted) {
          this.setState({
            news: result.data.hits,
          });
        }
      });
  }

  componentWillUnmount() {
    this._isMounted = false;
  }

  render() {
    ...
  }
}

3
สิ่งนี้ได้ผล แต่ทำไมถึงต้องทำงานนี้? อะไรทำให้เกิดข้อผิดพลาดนี้? และวิธีแก้ไข: |
Abhinav

มันทำงานได้ดี หยุดการเรียกซ้ำของเมธอด setState เนื่องจากตรวจสอบค่า _isMounted ก่อนการเรียก setState จากนั้นในที่สุดก็รีเซ็ตเป็นเท็จใน componentWillUnmount () ฉันคิดว่านั่นคือวิธีการทำงาน
Abhishek

8
สำหรับส่วนประกอบเบ็ดให้ใช้สิ่งนี้:const isMountedComponent = useRef(true); useEffect(() => { if (isMountedComponent.current) { ... } return () => { isMountedComponent.current = false; }; });
x-magix

@ x-magix คุณไม่จำเป็นต้องมีการอ้างอิงสำหรับสิ่งนี้คุณสามารถใช้ตัวแปรท้องถิ่นซึ่งฟังก์ชัน return สามารถปิดได้
หมอเดชชัย

@Abhinav เดาที่ดีที่สุดของฉันทำไมงานนี้คือการที่_isMountedไม่ได้รับการจัดการโดยตอบสนอง (เหมือนstate) และดังนั้นจึงไม่ใช่เรื่องที่จะตอบสนองของไปป์ไลน์ ปัญหาคือเมื่อตั้งค่าคอมโพเนนต์ให้ยกเลิกการต่อเชื่อม React จะยกเลิกการเรียกไปยังsetState()(ซึ่งจะทำให้เกิดการ ดังนั้นสถานะจะไม่อัปเดต
Lightfire228

35

หากวิธีแก้ปัญหาข้างต้นไม่ได้ผลให้ลองทำสิ่งนี้และใช้ได้กับฉัน:

componentWillUnmount() {
    // fix Warning: Can't perform a React state update on an unmounted component
    this.setState = (state,callback)=>{
        return;
    };
}

1
ขอบคุณที่ใช้งานได้สำหรับฉัน ใครช่วยอธิบายรหัสชิ้นนี้ให้ฉันได้ไหม
Badri Paudel

@BadriPaudel ส่งคืนค่า null เมื่อองค์ประกอบ Escape มันจะไม่เก็บข้อมูลใด ๆ ในหน่วยความจำอีกต่อไป
May'Habit

ขอบคุณมากสำหรับเรื่องนี้!
Tushar Gupta

คืนอะไร? แค่แปะเหมือนเดิม?
บวก

11

ฉันได้รับคำเตือนนี้อาจเป็นเพราะการโทรsetStateจากเอฟเฟกต์เบ็ด (สิ่งนี้จะกล่าวถึงใน 3 ประเด็นที่ เชื่อมโยง กัน )

อย่างไรก็ตามการอัปเกรดเวอร์ชันตอบสนองได้ลบคำเตือน


5

ลองเปลี่ยนsetDivSizeThrottleableเป็น

this.setDivSizeThrottleable = throttle(
  () => {
    if (this.isComponentMounted) {
      this.setState({
        pdfWidth: this.pdfWrapper!.getBoundingClientRect().width - 5,
      });
    }
  },
  500,
  { leading: false, trailing: true }
);

ฉันไม่ได้ลอง ตอนนี้ฉันเห็นคำเตือนอยู่ตลอดเวลาซึ่งฉันสังเกตเห็นเป็นครั้งคราวในการปรับขนาดหน้าต่างก่อนที่จะทำการเปลี่ยนแปลงนี้ ¯_ (ツ) _ / ¯ขอบคุณที่ลองทำดู
Igor Soloydenko

5

ฉันรู้ว่าคุณไม่ได้ใช้ประวัติ แต่ในกรณีของฉันฉันใช้useHistoryhook จาก React Router DOM ซึ่งยกเลิกการต่อเชื่อมส่วนประกอบก่อนที่สถานะจะยังคงอยู่ในผู้ให้บริการบริบทการตอบสนองของฉัน

เพื่อแก้ไขปัญหานี้ฉันได้ใช้เบ็ดwithRouterรังส่วนประกอบในกรณีของฉันและภายในส่วนประกอบexport default withRouter(Login) const Login = props => { ...; props.history.push("/dashboard"); ...ฉันได้ลบอีกอันหนึ่งออกprops.history.pushจากส่วนประกอบเช่นif(authorization.token) return props.history.push('/dashboard')เนื่องจากสิ่งนี้ทำให้เกิดการวนซ้ำเนื่องจากauthorizationสถานะ

อีกทางเลือกหนึ่งที่จะผลักดันรายการใหม่ลงในประวัติศาสตร์


2

หากคุณกำลังดึงข้อมูลจาก axios และข้อผิดพลาดยังคงเกิดขึ้นให้ห่อ setter ไว้ในเงื่อนไข

let isRendered = useRef(false);
useEffect(() => {
    isRendered = true;
    axios
        .get("/sample/api")
        .then(res => {
            if (isRendered) {
                setState(res.data);
            }
            return null;
        })
        .catch(err => console.log(err));
    return () => {
        isRendered = false;
    };
}, []);

2

มีตะขอที่เรียกได้ว่าเป็นเรื่องธรรมดาuseIsMountedที่ช่วยแก้ปัญหานี้ได้ (สำหรับส่วนประกอบที่ใช้งานได้) ...

import { useRef, useEffect } from 'react';

export function useIsMounted() {
  const isMounted = useRef(false);

  useEffect(() => {
    isMounted.current = true;
    return () => isMounted.current = false;
  }, []);

  return isMounted;
}

จากนั้นในส่วนประกอบการทำงานของคุณ

function Book() {
  const isMounted = useIsMounted();
  ...

  useEffect(() => {
    asyncOperation().then(data => {
      if (isMounted.current) { setState(data); }
    })
  });
  ...
}

1

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

1) การรับอินสแตนซ์ของส่วนประกอบสำหรับคำเตือนนี้เป็นเรื่องยาก ดูเหมือนว่าจะมีการพูดคุยเพื่อปรับปรุงสิ่งนี้ใน React แต่ตอนนี้ยังไม่มีวิธีง่ายๆในการทำ ฉันสงสัยว่าสาเหตุที่ยังไม่ได้สร้างอาจเป็นเพราะส่วนประกอบที่คาดว่าจะเขียนในลักษณะที่ setState หลังจากยกเลิกการต่อเชื่อมไม่สามารถทำได้ไม่ว่าสถานะของส่วนประกอบจะเป็นอย่างไร ปัญหาเท่าที่ทีม React เกี่ยวข้องมักจะอยู่ในรหัสคอมโพเนนต์ไม่ใช่อินสแตนซ์คอมโพเนนต์ซึ่งเป็นสาเหตุที่คุณได้รับชื่อ Component Type

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

2) Lodashes throttled function มีcancelวิธีการ โทรcancelเข้าcomponentWillUnmountและทิ้งไฟล์isComponentMounted. การยกเลิกเป็นการตอบสนองแบบ "สำนวน" มากกว่าการแนะนำคุณสมบัติใหม่


TextLayerInternalปัญหาคือผมไม่ได้ควบคุมโดยตรง ดังนั้นฉันจึงไม่รู้ว่า "ใครเป็นคนsetState()โทรมา" ฉันจะลองทำcancelตามคำแนะนำของคุณและดูว่าจะเป็นอย่างไร
Igor Soloydenko

น่าเสียดายที่ฉันยังคงเห็นคำเตือน โปรดตรวจสอบรหัสในส่วนอัปเดต 1 เพื่อยืนยันว่าฉันทำสิ่งที่ถูกต้อง
Igor Soloydenko

1

ฉันมีปัญหาที่คล้ายกันขอบคุณ @ ford04 ช่วยฉันออก

อย่างไรก็ตามเกิดข้อผิดพลาดอื่น

NB. ฉันใช้ตะขอ ReactJS

ndex.js:1 Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.

สาเหตุของข้อผิดพลาดคืออะไร?

import {useHistory} from 'react-router-dom'

const History = useHistory()
if (true) {
  history.push('/new-route');
}
return (
  <>
    <render component />
  </>
)

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

ฉันพบวิธีแก้ปัญหาอะไร

import {Redirect} from 'react-router-dom'

if (true) {
  return <redirect to="/new-route" />
}
return (
  <>
    <render component />
  </>
)

1

ขึ้นอยู่กับว่าคุณเปิดหน้าเว็บอย่างไรคุณอาจไม่ได้ทำให้เกิดการติดตั้ง เช่นการใช้การ<Link/>ย้อนกลับไปยังเพจที่เมาท์อยู่แล้วใน DOM เสมือนดังนั้นต้องการข้อมูลจากวงจรชีวิตของ componentDidMount จึงถูกดักจับ


คุณกำลังบอกว่าcomponentDidMount()สามารถโทรได้สองครั้งโดยไม่มีการcomponentWillUnmount()โทรระหว่างกลาง? ฉันไม่คิดว่าจะเป็นไปได้
Alexis Wilke

1
ไม่ฉันกำลังบอกว่าไม่ได้เรียกสองครั้งซึ่งเป็นสาเหตุที่หน้าไม่ประมวลผลโค้ดภายในcomponentDidMount()เมื่อใช้ไฟล์<Link/>. ฉันใช้ Redux สำหรับปัญหาเหล่านี้และเก็บข้อมูลของหน้าเว็บไว้ในที่เก็บ Reducer เพื่อที่ฉันจะได้ไม่ต้องโหลดหน้านี้ซ้ำอีกต่อไป
coder9833idls

0

ฉันมีปัญหาที่คล้ายกันและแก้ไขได้:

ฉันทำให้ผู้ใช้ล็อกอินโดยอัตโนมัติโดยส่งการดำเนินการกับ redux (วางโทเค็นการตรวจสอบสิทธิ์ในสถานะ redux)

จากนั้นฉันก็พยายามแสดงข้อความที่มี this.setState ({succ_message: "... ") ในส่วนประกอบของฉัน

คอมโพเนนต์ว่างเปล่าโดยมีข้อผิดพลาดเดียวกันบนคอนโซล: "unmounted component" .. "memory leak" เป็นต้น

หลังจากที่ฉันอ่านคำตอบของ Walter ในหัวข้อนี้

ฉันสังเกตเห็นว่าในตารางเส้นทางของแอปพลิเคชันของฉันเส้นทางของส่วนประกอบของฉันไม่ถูกต้องหากผู้ใช้เข้าสู่ระบบ:

{!this.props.user.token &&
        <div>
            <Route path="/register/:type" exact component={MyComp} />                                             
        </div>
}

ฉันทำให้เส้นทางสามารถมองเห็นได้ว่ามีโทเค็นอยู่หรือไม่


0

จากคำตอบ @ ford04 นี่คือวิธีการห่อหุ้มเดียวกัน:

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

export function useEffectAsync( effectAsyncFun : ( isMounted: () => boolean ) => unknown, deps?: DependencyList ) {
    useEffect( () => {
        let isMounted = true;
        const _unused = effectAsyncFun( () => isMounted );
        return () => { isMounted = false; };
    }, deps );
} 

การใช้งาน:

const MyComponent : FC<{}> = (props) => {
    const [ asyncProp , setAsyncProp ] = useState( '' ) ;
    useEffectAsync( async ( isMounted ) =>
    {
        const someAsyncProp = await ... ;
        if ( isMounted() )
             setAsyncProp( someAsyncProp ) ;
    });
    return <div> ... ;
} ;

0

แรงบันดาลใจจากคำตอบที่ยอมรับโดย @ ford04 ฉันมีแนวทางที่ดีกว่าในการจัดการกับมันแทนที่จะใช้useEffectภายในuseAsyncสร้างฟังก์ชันใหม่ที่ส่งคืนการโทรกลับสำหรับcomponentWillUnmount:

function asyncRequest(asyncRequest, onSuccess, onError, onComplete) {
  let isMounted=true
  asyncRequest().then((data => isMounted ? onSuccess(data):null)).catch(onError).finally(onComplete)
  return () => {isMounted=false}
}

...

useEffect(()=>{
        return asyncRequest(()=>someAsyncTask(arg), response=> {
            setSomeState(response)
        },onError, onComplete)
    },[])


ฉันจะไม่แนะนำให้ใช้isMountedตัวแปรภายในแต่ทำให้เป็นสถานะแทน (ผ่านuseStatehook)
Igor Soloydenko

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