Javascript Can';t在未安装的组件上执行React状态更新 问题

Javascript Can';t在未安装的组件上执行React状态更新 问题,javascript,reactjs,typescript,lodash,setstate,Javascript,Reactjs,Typescript,Lodash,Setstate,我正在React中编写一个应用程序,无法避免一个非常常见的陷阱,即在组件将卸载(…)之后调用设置状态(…) 我非常仔细地查看了我的代码,并试图将一些保护条款放在适当的位置,但问题仍然存在,我仍在遵守警告 因此,我有两个问题: 我如何从堆栈跟踪中找出哪个特定组件和事件处理程序或生命周期钩子对规则冲突负责 好吧,如何解决问题本身,因为我的代码是在考虑这个陷阱的情况下编写的,并且已经在尝试防止它,但是一些底层组件仍然在生成警告 浏览器控制台 警告:无法对未安装的组件执行React状态更新。 这是一个n

我正在React中编写一个应用程序,无法避免一个非常常见的陷阱,即在
组件将卸载(…)
之后调用
设置状态(…)

我非常仔细地查看了我的代码,并试图将一些保护条款放在适当的位置,但问题仍然存在,我仍在遵守警告

因此,我有两个问题:

  • 我如何从堆栈跟踪中找出哪个特定组件和事件处理程序或生命周期钩子对规则冲突负责
  • 好吧,如何解决问题本身,因为我的代码是在考虑这个陷阱的情况下编写的,并且已经在尝试防止它,但是一些底层组件仍然在生成警告
  • 浏览器控制台
    警告:无法对未安装的组件执行React状态更新。
    这是一个no-op,但它表示应用程序中存在内存泄漏。
    要修复此问题,请取消componentWillUnmount中的所有订阅和异步任务
    方法。
    在TextLayerInternal中(由Context.Consumer创建)
    在TextLayer中(由PageInternal创建)index.js:1446
    d/控制台[e]
    index.js:1446
    不顾一切地警告
    react dom.development.js:520
    WARNABOUTUPDATEOUNMOUNTED
    react dom.development.js:18238
    日程安排
    react dom.development.js:19684
    排队状态
    react dom.development.js:12936
    ./node_modules/react/cjs/react.development.js/Component.prototype.setState
    react.development.js:356
    _被叫$
    TextLayer.js:97
    tryCatch
    runtime.js:63
    援引
    runtime.js:282
    定义操作方法/无效;
    pdfwraper:htmldevelment | null=null;
    isComponentMounted:boolean=false;
    状态={
    隐藏:是的,
    pdfWidth:默认宽度,
    };
    构造器(道具:任何){
    超级(道具);
    this.setDivSizeThrottleable=节流阀(
    () => {
    if(this.isComponentMounted){
    这是我的国家({
    pdfWidth:this.pdfWrapper!.getBoundingClientRect().width-5,
    });
    }
    },
    500,
    );
    }
    componentDidMount=()=>{
    this.isComponentMounted=true;
    this.setDivSizeThrottleable();
    window.addEventListener(“resize”,this.setDivSizeThrottleable);
    };
    组件将卸载=()=>{
    this.isComponentMounted=false;
    removeEventListener(“resize”,this.setDivSizeThrottleable);
    };
    渲染=()=>(
    {this.state.hidden&&Book正在加载…}
    this.pdfWrapper=ref}>
    this.onDocumentComplete()}
    />
    );
    getPdfContentContainerClassName=()=>this.state.hidden?“隐藏的“:”;
    onDocumentComplete=()=>{
    试一试{
    this.setState({hidden:false});
    this.setDivSizeThrottleable();
    }锁扣(卡紧器错误){
    console.warn({caughtError});
    }
    };
    }
    导出默认账簿;
    
    AutoWidthPdf.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;
    
    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>
      );
    }
    
    import*as React from'React';
    从'react pdf'导入{文档,页面,pdfjs};
    pdfjs.GlobalWorkerOptions.workerSrc=`//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;
    接口IProps{
    文件:字符串;
    宽度:数字;
    onLoadSuccess:(pdf:any)=>void;
    }
    导出类AutoWidthPdf扩展React.Component{
    渲染=()=>(
    this.props.onLoadSuccess(33;)}
    >
    );
    }
    

    更新1:取消可调节功能(仍然没有运气)
    const DEFAULT\u WIDTH=140;
    类书扩展了React.Component{
    setDivSizeThrottleable:((()=>void)&可取消)|未定义;
    pdfwraper:htmldevelment | null=null;
    状态={
    隐藏:是的,
    pdfWidth:默认宽度,
    };
    componentDidMount=()=>{
    this.setDivSizeThrottleable=节流阀(
    () => {
    这是我的国家({
    pdfWidth:this.pdfWrapper!.getBoundingClientRect().width-5,
    });
    },
    500,
    );
    this.setDivSizeThrottleable();
    window.addEventListener(“resize”,this.setDivSizeThrottleable);
    };
    组件将卸载=()=>{
    removeEventListener(“resize”,this.setDivSizeThrottleable!);
    此.setDivSizeThrottleable!.cancel();
    this.setDivSizeThrottleable=未定义;
    };
    渲染=()=>(
    {this.state.hidden&&Book正在加载…}
    this.pdfWrapper=ref}>
    this.onDocumentComplete()}
    />
    );
    getPdfContentContainerClassName=()=>this.state.hidden?“隐藏的“:”;
    onDocumentComplete=()=>{
    试一试{
    this.setState({hidden:false});
    这个.setDivSizeThrottleable!();
    }锁扣(卡紧器错误){
    console.warn({caughtError});
    }
    };
    }
    导出默认账簿;
    
    编辑:我刚刚意识到警告引用了一个名为
    textlayernal
    的组件。这可能就是你的错误所在。其余部分仍然相关,但可能无法解决您的问题

    1) 获取此警告的组件实例非常困难。在React中似乎有一些讨论来改进这一点,但目前还没有简单的方法。我猜想,它尚未构建的原因可能是,组件的编写方式要求无论组件的状态如何,卸载后的setState都是不可能的。就React团队而言,问题总是在组件代码中,而不是组件实例中,这就是为什么您会得到组件类型名称

    这个答案可能不令人满意,但我想我可以解决你的问题


    2) 限制的函数有一个
    cancel
    方法。调用
    组件中的
    cancel
    将卸载
    并放弃
    isComponentMounted
    。取消比引入新属性更“惯用”一些。

    尝试将
    setDivSizeThrottleable
    更改为

    this.setDivSizeThrottleable = throttle(
      () => {
        if (this.isComponentMounted) {
          this.setState({
            pdfWidth: this.pdfWrapper!.getBoundingClientRect().width - 5,
          });
        }
      },
      500,
      { leading: false, trailing: true }
    );
    
    要删除-无法对未安装的组件警告执行反应状态更新,请使用c语言下的componentDidMount方法
    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;
    
    this.setDivSizeThrottleable = throttle(
      () => {
        if (this.isComponentMounted) {
          this.setState({
            pdfWidth: this.pdfWrapper!.getBoundingClientRect().width - 5,
          });
        }
      },
      500,
      { leading: false, trailing: true }
    );
    
    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() {
        ...
      }
    }
    
    useEffect(() => {
      let isMounted = true;               // note mutable flag
      someAsyncOperation().then(data => {
        if (isMounted) setState(data);    // add conditional check
      })
      return () => { isMounted = false }; // use cleanup to toggle value, if unmounted
    }, []);                               // adjust dependencies to your needs
    
    componentWillUnmount() {
        // fix Warning: Can't perform a React state update on an unmounted component
        this.setState = (state,callback)=>{
            return;
        };
    }
    
    {!this.props.user.token &&
            <div>
                <Route path="/register/:type" exact component={MyComp} />                                             
            </div>
    }
    
    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 />
      </>
    )
    
    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> ... ;
    } ;
    
    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;
        };
    }, []);
    
    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); }
        })
      });
      ...
    }
    
    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)
        },[])
    
    
    export const useAsync = (
            asyncFn, 
            onSuccess = false, 
            onError = false, 
            onFinally = false, 
            abortFn = false
        ) => {
    
        useEffect(() => {
            let isMounted = true;
            const run = async () => {
                try{
                    let data = await asyncFn()
                    if (isMounted && onSuccess) onSuccess(data)
                } catch(error) {
                    if (isMounted && onError) onSuccess(error)
                } finally {
                    if (isMounted && onFinally) onFinally()
                }
            }
            run()
            return () => {
                if(abortFn) abortFn()
                isMounted = false
            };
        }, [asyncFn, onSuccess])
    }
    
    import React, { useState } from "react";
    import { useAsyncEffect } from "use-async-effect2";
    import cpFetch from "cp-fetch"; //cancellable c-promise fetch wrapper
    
    export default function TestComponent(props) {
      const [text, setText] = useState("");
    
      useAsyncEffect(
        function* () {
          setText("fetching...");
          const response = yield cpFetch(props.url);
          const json = yield response.json();
          setText(`Success: ${JSON.stringify(json)}`);
        },
        [props.url]
      );
    
      return <div>{text}</div>;
    }
    
    import React from "react";
    import { ReactComponent, timeout } from "c-promise2";
    import cpFetch from "cp-fetch";
    
    @ReactComponent
    class TestComponent extends React.Component {
      state = {
        text: "fetching..."
      };
    
      @timeout(5000)
      *componentDidMount() {
        const response = yield cpFetch(this.props.url);
        this.setState({ text: `json: ${yield response.text()}` });
      }
    
      render() {
        return <div>{this.state.text}</div>;
      }
    }
    
    const handleClick = async (item: NavheadersType, index: number) => {
        const newNavHeaders = [...navheaders];
        if (item.url) {
          await router.push(item.url);   =>>>> line causing error (causing route to happen)
          // router.push(item.url);  =>>> coreect line
          newNavHeaders.forEach((item) => (item.active = false));
          newNavHeaders[index].active = true;
          setnavheaders([...newNavHeaders]);
        }
      };
    
    export const SubscriptionsView: React.FC = () => {
      const [data, setData] = useState<Subscription[]>();
      const isMounted = React.useRef(true);
    
      React.useEffect(() => {
        if (isMounted.current) {
          // fetch data
          // setData (fetch result)
    
          return () => {
            isMounted.current = false;
          };
        }
      }
    });
    
    useEffect(() => { return () => {}; }, []);
    
    someAsyncOperation().then(data => {
        if (isMounted) setState(data);    // add conditional check
      })
    
    useEffect(() => 
      (async () => {
        const bar = await fooAsync();
        setSomeState(bar);
      })(),
      []
    );
    
    import { cancelable } from "cancelable-promise";
    
    ...
    
    useEffect(
      () => {
        const cancelablePromise = cancelable(async () => {
          const bar = await fooAsync();
          setSomeState(bar);
        })
        return () => cancelablePromise.cancel();
      },
      []
    );