비동기 componentDidMount ()를 사용하는 것이 좋습니까?


139

componentDidMount()React Native에서 비동기 함수로 사용 하는 것이 좋습니까? 아니면 피해야합니까?

AsyncStorage구성 요소가 마운트 될 때 정보를 얻을 필요가 있지만 가능하게하는 유일한 방법은 componentDidMount()함수를 비동기 로 만드는 것 입니다.

async componentDidMount() {
    let auth = await this.getAuth();
    if (auth) 
        this.checkAuth(auth);
}

그것에 문제가 있습니까?이 문제에 대한 다른 해결책이 있습니까?


2
"모범 사례"는 의견의 문제입니다. 작동합니까? 예.
Kraylog

2
다음은 ahack
Shubham Khatri

단지 redux-thunk를 사용하면 문제가 해결 될 것입니다
Tilak Maddy

@TilakMaddy 왜 모든 반응 앱이 redux를 사용한다고 가정합니까?
Mirakurun

@ Mirakurun 왜 하루 동안 일반 자바 스크립트 질문을 할 때 jQuery를 사용한다고 가정 했습니까?
Tilak 매디

답변:


162

차이점을 지적하고 문제를 일으킬 수있는 방법을 결정하는 것으로 시작하겠습니다.

다음은 async 및 "sync" componentDidMount()수명주기 방법 의 코드입니다 .

// This is typescript code
componentDidMount(): void { /* do something */ }

async componentDidMount(): Promise<void> {
    /* do something */
    /* You can use "await" here */
}

코드를 보면 다음과 같은 차이점을 알 수 있습니다.

  1. async키워드 : 타이프 라이터, 이것은 단지 코드 마커입니다. 그것은 두 가지 일을합니다 :
    • 반환 유형을 Promise<void>대신 강제로 설정하십시오 void. 반환 유형을 약속이 아닌 것으로 명시 적으로 지정하면 (예 : void) typescript에서 오류가 발생합니다.
    • await메소드 내에서 키워드 를 사용할 수 있습니다.
  2. 반환 형식이 변경됩니다 voidPromise<void>
    • 이제이 작업을 수행 할 수 있습니다.
      async someMethod(): Promise<void> { await componentDidMount(); }
  3. await메소드 내에서 키워드를 사용 하고 일시적으로 실행을 일시 중지 할 수 있습니다 . 이처럼 :

    async componentDidMount(): Promise<void> {
        const users = await axios.get<string>("http://localhost:9001/users");
        const questions = await axios.get<string>("http://localhost:9001/questions");
    
        // Sleep for 10 seconds
        await new Promise(resolve => { setTimeout(resolve, 10000); });
    
        // This line of code will be executed after 10+ seconds
        this.setState({users, questions});
        return Promise.resolve();
    }

이제 어떻게 문제를 일으킬 수 있습니까?

  1. async키워드는 절대적으로 무해하다.
  2. componentDidMount()반환 유형 Promise<void>도 무해 하므로 메소드를 호출 해야하는 상황을 상상할 수 없습니다 .

    키워드가 Promise<void>없는 반환 유형을 가진 메소드를 호출하면 반환 유형이 인 메소드를 호출하는 await것과 차이가 없습니다 void.

  3. componentDidMount()실행 지연 후 수명주기 방법이 없기 때문에 꽤 안전 해 보입니다. 그러나 문제가 있습니다.

    위의 내용 this.setState({users, questions});은 10 초 후에 실행됩니다. 지연 시간의 중간에 또 다른 ...

    this.setState({users: newerUsers, questions: newerQuestions});

    ... 성공적으로 실행되었으며 DOM이 업데이트되었습니다. 결과는 사용자에게 공개되었습니다. 시계는 계속 똑딱 거리고 10 초가 경과했습니다. this.setState(...)그런 다음 지연 이 실행되고 DOM은 다시 이전 사용자와 이전 질문으로 업데이트됩니다. 결과는 사용자에게도 표시됩니다.

=> 방법 async과 함께 사용 하는 것이 안전합니다 (약 100 % 확실하지 않습니다) componentDidMount(). 나는 그것의 큰 팬이고 지금까지 나는 너무 많은 두통을주는 문제가 발생하지 않았습니다.


보류중인 약속 전에 다른 setState가 발생한 문제에 대해 이야기 할 때 비동기 / 대기 구문 설탕이나 고전적인 콜백이 없으면 Promise와 동일하지 않습니까?
Clafou

3
예! 지연은 setState()항상 작은 위험 을가 집니다. 조심해서 진행해야합니다.
Cù Đức Hiếu

문제를 피하는 한 가지 방법 isFetching: true은 구성 요소의 상태 와 같은 것을 사용하는 것 입니다. 나는 이것을 redux에서만 사용했지만 반응 전용 상태 관리에서는 완전히 유효하다고 가정합니다. 그것은 코드의 다른 곳에서 업데이트되는 동일한 상태의 문제를 실제로 해결하지는 않지만 ...
Clafou

1
동의합니다. 실제로 isFetching플래그 솔루션은 특히 백엔드 응답을 기다리는 동안 일부 애니메이션을 프런트 엔드에서 재생하려는 경우 매우 일반적입니다 ( isFetching: true).
Cù Đức Hiếu

3
구성 요소를 마운트 해제 한 후 setState를 수행하면 문제가 발생할 수 있습니다.
Eliezer Steinbock

18

2020 년 4 월 업데이트 : 이 문제는 최신 React 16.13.1에서 수정 된 것으로 보입니다 ( 이 샌드 박스 예 참조) . 이것을 지적 해 준 @abernier에게 감사합니다.


나는 약간의 연구를 해왔고 중요한 한 가지 차이점을 발견했다. React는 비동기 라이프 사이클 방법의 오류를 처리하지 않습니다.

따라서 다음과 같이 작성하면

componentDidMount()
{
    throw new Error('I crashed!');
}

그러면 귀하의 오류가 오류 경계에 오류 를 처리하여 우아한 메시지를 표시 할 수 있습니다.

다음과 같이 코드를 변경하면 :

async componentDidMount()
{
    throw new Error('I crashed!');
}

이것은 이것과 같습니다 :

componentDidMount()
{
    return Promise.reject(new Error('I crashed!'));
}

그때 오류가 자동으로 삼켜 질 것입니다 집니다. 부끄러운 줄 알아

그렇다면 어떻게 오류를 처리합니까? 유일한 방법은 다음과 같이 명시 적으로 잡는 것 같습니다.

async componentDidMount()
{
    try
    {
         await myAsyncFunction();
    }
    catch(error)
    {
        //...
    }
}

또는 이와 같이 :

componentDidMount()
{
    myAsyncFunction()
    .catch(()=>
    {
        //...
    });
}

여전히 오류가 오류 경계에 도달하기를 원한다면 다음 트릭을 생각할 수 있습니다.

  1. 오류를 잡아서 오류 처리기가 구성 요소 상태를 변경하게합니다.
  2. 상태가 오류를 나타내는 경우 render메소드 에서 오류를 처리하십시오.

예:

class BuggyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null };
  }

  buggyAsyncfunction(){ return Promise.reject(new Error('I crashed async!'));}

  async componentDidMount() {
    try
    {
      await this.buggyAsyncfunction();
    }
    catch(error)
    {
        this.setState({error: error});
    }
  }

  render() {
    if(this.state.error)
        throw this.state.error;

    return <h1>I am OK</h1>;
  }
}

이 문제가보고 되었습니까? 여전히 사건이 발생하면이를보고하는 것이 유용 할 수 있습니다 ... thx
abernier

@abernier 나는 그것을 무시한 것으로 생각합니다 ... 아마 그들은 그것을 향상시킬 수 있지만. 나는 이것에 대해 어떤 문제도 제기하지 않았다.
CF

1
적어도 여기에서 테스트 된 React 16.13.1에서는 더 이상 사실이 아닌 것 같습니다 : codesandbox.io/s/bold-ellis-n1cid?file=/src/App.js
abernier

9

귀하의 코드는 훌륭하고 읽을 수 있습니다. 이 Dale Jefferson의 기사 에서 비동기 componentDidMount예제 를 보여주고 실제로 잘 보입니다.

그러나 어떤 사람들은 코드를 읽는 사람이 React가 반환 된 약속으로 무언가를한다고 가정 할 수 있습니다.

따라서이 코드의 해석과 그것이 좋은 습관인지 아닌지는 매우 개인적인 것입니다.

다른 솔루션을 원한다면 promises를 사용할 수 있습니다 . 예를 들면 다음과 같습니다.

componentDidMount() {
    fetch(this.getAuth())
      .then(auth => {
          if (auth) this.checkAuth(auth)
      })
}

3
... 또는 내부 asyncawaits 인라인 함수를 사용하십시오 ...?
Erik Kaplun

또한 옵션 @ErikAllik :)
Tiago Alves

@ErikAllik 예가 있습니까?
Pablo Rincon

1
같은 떨어지게 @PabloRincon (async () => { const data = await fetch('foo'); const result = await submitRequest({data}); console.log(result) })()fetchsubmitRequest반환 약속하는 기능입니다.
Erik Kaplun

이 코드는 getAuth 함수에서 발생한 오류를 삼키기 때문에 확실히 나쁩니다. 그리고 함수가 네트워크와 관련이있는 경우 (예를 들어) 오류가 예상되어야합니다.
CF

6

키워드 componentDidMount없이 사용 async하면 의사는 다음과 같이 말합니다.

componentDidMount ()에서 즉시 setState ()를 호출 할 수 있습니다. 추가 렌더링을 트리거하지만 브라우저가 화면을 업데이트하기 전에 발생합니다.

사용 async componentDidMount하면이 기능을 잃게됩니다. 브라우저가 화면을 업데이트 한 후에 다른 렌더링이 발생합니다. 그러나 imo, 데이터 가져 오기와 같은 비동기 사용을 생각하고 있다면 브라우저가 화면을 두 번 업데이트하는 것을 피할 수 없습니다. 다른 세계에서는 브라우저가 화면을 업데이트하기 전에 componentDidMount를 일시 중지 할 수 없습니다


1
간결하고 문서가 지원하기 때문에이 답변이 마음에 듭니다. 참조하고있는 문서에 대한 링크를 추가 할 수 있습니까?
TheUtherSide

예를 들어 리소스가로드되는 동안로드 상태를 표시 한 다음 완료되면 내용을 표시하는 경우에도 좋습니다.
Hjulle

3

최신 정보:

(내 빌드 : React 16, Webpack 4, Babel 7) :

Babel 7을 사용하면 다음을 발견 할 수 있습니다.

이 패턴을 사용하여 ...

async componentDidMount() {
    try {
        const res = await fetch(config.discover.url);
        const data = await res.json();
        console.log(data);
    } catch(e) {
        console.error(e);
    }
}

다음과 같은 오류가 발생합니다 ...

잡히지 않은 ReferenceError : regeneratorRuntime이 정의되지 않았습니다

이 경우 babel-plugin-transform-runtime 을 설치해야 합니다.

https://babeljs.io/docs/en/babel-plugin-transform-runtime.html

어떤 이유로 위의 패키지 (babel-plugin-transform-runtime)를 설치하지 않으려면 Promise 패턴을 고수하고 싶을 것입니다 ...

componentDidMount() {
    fetch(config.discover.url)
    .then(res => res.json())
    .then(data => {
        console.log(data);
    })
    .catch(err => console.error(err));
}

3

당신이하고있는 일을 아는 한 괜찮습니다. 그러나 async componentDidMount()실행 후 componentWillUnmount구성 요소가 마운트 해제 된 후에도 계속 실행될 수 있으므로 혼동 될 수 있습니다 .

내부에서 동기 및 비동기 작업을 모두 시작할 수도 있습니다 componentDidMount. componentDidMount비동기 인 경우 모든 동기 코드를 첫 번째 앞에 두어야합니다 await. 첫 번째 코드 await가 동기식으로 실행 되기 전에 누군가에게 분명하지 않을 수 있습니다 . 이 경우 아마도 componentDidMount동기를 유지 하지만 sync 및 async 메서드를 호출해야합니다.

당신이 선택하든 async componentDidMount()동기 대 componentDidMount()호출 async방법, 당신은 당신이 아직 구성 요소 마운트 해제를 실행중인 모든 청취자 또는 비동기 방법을 정리해야한다.


2

실제로 React가 레거시 라이프 사이클 메소드 (componentWillMount, componentWillReceiveProps, componentWillUpdate)에서 비동기 렌더링으로 이동함에 따라 ComponentDidMount의 비동기 로딩이 권장되는 설계 패턴 입니다.

이 블로그 게시물은 이것이 왜 안전한지 설명하고 ComponentDidMount에서 비동기 로딩을위한 예제를 제공하는 데 매우 도움이됩니다.

https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html


3
비동기 렌더링은 실제로 수명주기를 명시 적으로 비 동기화하는 것과 아무 관련이 없습니다. 실제로 반 패턴입니다. 권장되는 솔루션은 실제로 수명주기 메소드에서 비동기 메소드를 호출하는 것입니다.
Clayton Ray

1

나는 이런 것을 사용하고 싶다

componentDidMount(){
   const result = makeResquest()
}
async makeRequest(){
   const res = await fetch(url);
   const data = await res.json();
   return data
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.