programing

수정하려면 useEffect 정리 함수의 모든 구독 및 비동기 작업을 취소합니다.

javaba 2023. 2. 11. 17:34
반응형

수정하려면 useEffect 정리 함수의 모든 구독 및 비동기 작업을 취소합니다.

이 코드가 있습니다.

import ReactDOM from "react-dom";
import React, { useState, useEffect } from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";

function ParamsExample() {
  return (
    <Router>
      <div>
        <h2>Accounts</h2>
        <Link to="/">Netflix</Link>
        <Route path="/" component={Miliko} />
      </div>
    </Router>
  );
}

const Miliko = ({ match }) => {
  const [data, setData] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);

  useEffect(() => {
    (async function() {
      setIsError(false);
      setIsLoading(true);
      try {
        const Res = await fetch("https://foo0022.firebaseio.com/New.json");
        const ResObj = await Res.json();
        const ResArr = await Object.values(ResObj).flat();
        setData(ResArr);
      } catch (error) {
        setIsError(true);
      }
      setIsLoading(false);
    })();
    console.log(data);
  }, [match]);
  return <div>{`${isLoading}${isError}`}</div>;
};

function App() {
  return (
    <div className="App">
      <ParamsExample />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

3개의 링크를 작성했습니다.Milikocomponent. 단, 링크를 빠르게 클릭하면 다음 오류가 나타납니다.

수정하려면 useEffect 정리 함수의 모든 구독 및 비동기 작업을 취소합니다.

비동기 호출이 끝나기 전에 분리한 것이 문제의 원인인 것 같습니다.

const useAsync = () => {
  const [data, setData] = useState(null)
  const mountedRef = useRef(true)

  const execute = useCallback(() => {
    setLoading(true)
    return asyncFunc()
      .then(res => {
        if (!mountedRef.current) return null
        setData(res)
        return res
      })
  }, [])

  useEffect(() => {
    return () => { 
      mountedRef.current = false
    }
  }, [])
}

mountedRef컴포넌트가 아직 마운트되어 있는지 여부를 나타내는 데 사용됩니다.또한 이 경우 비동기 호출을 계속하여 컴포넌트 상태를 업데이트하고 그렇지 않으면 건너뜁니다.

이것이 메모리 누수(액세스 클리닝 메모리)의 문제가 되지 않는 주된 이유입니다.

데모

https://codepen.io/windmaomao/pen/jOLaOxO, 를 사용하여 가져오기useAsync https://codepen.io/windmaomao/pen/GRvOgoa, 수동 가져오기useAsync

갱신하다

위의 답변은 팀 내에서 사용하는 다음과 같은 컴포넌트로 이어집니다.

/**
 * A hook to fetch async data.
 * @class useAsync
 * @borrows useAsyncObject
 * @param {object} _                props
 * @param {async} _.asyncFunc         Promise like async function
 * @param {bool} _.immediate=false    Invoke the function immediately
 * @param {object} _.funcParams       Function initial parameters
 * @param {object} _.initialData      Initial data
 * @returns {useAsyncObject}        Async object
 * @example
 *   const { execute, loading, data, error } = useAync({
 *    asyncFunc: async () => { return 'data' },
 *    immediate: false,
 *    funcParams: { data: '1' },
 *    initialData: 'Hello'
 *  })
 */
const useAsync = (props = initialProps) => {
  const {
    asyncFunc, immediate, funcParams, initialData
  } = {
    ...initialProps,
    ...props
  }
  const [loading, setLoading] = useState(immediate)
  const [data, setData] = useState(initialData)
  const [error, setError] = useState(null)
  const mountedRef = useRef(true)

  const execute = useCallback(params => {
    setLoading(true)
    return asyncFunc({ ...funcParams, ...params })
      .then(res => {
        if (!mountedRef.current) return null
        setData(res)
        setError(null)
        setLoading(false)
        return res
      })
      .catch(err => {
        if (!mountedRef.current) return null
        setError(err)
        setLoading(false)
        throw err
      })
  }, [asyncFunc, funcParams])

  useEffect(() => {
    if (immediate) {
      execute(funcParams)
    }
    return () => {
      mountedRef.current = false
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  return {
    execute,
    loading,
    data,
    error
  }
}

업데이트 2022

이 어프로치는, 이 토픽이 에 기재되어 있는 「https://www.amazon.com/Designing-React-Hooks-Right-Way/dp/1803235950」에서 채용되고 있습니다.useRef그리고.custom hooks챕터 및 더 많은 예가 여기에 나와 있습니다.

useEffect는 컴포넌트가 마운트 해제되어 있는 동안에도 데이터 가져오기 절차와의 통신을 유지하려고 합니다.이는 안티패턴으로, 메모리 누전에 노출되므로 useEffect 구독을 취소하면 앱이 최적화됩니다.

아래의 간단한 구현 예에서는 플래그(isSubscribed)를 사용하여 구독을 취소할 시기를 결정합니다.효과가 끝나면, 청소하라고 전화할 거야.

export const useUserData = () => {
  const initialState = {
    user: {},
    error: null
  }
  const [state, setState] = useState(initialState);

  useEffect(() => {
    // clean up controller
    let isSubscribed = true;

    // Try to communicate with sever API
    fetch(SERVER_URI)
      .then(response => response.json())
      .then(data => isSubscribed ? setState(prevState => ({
        ...prevState, user: data
      })) : null)
      .catch(error => {
        if (isSubscribed) {
          setState(prevState => ({
            ...prevState,
            error
          }));
        }
      })

    // cancel subscription to useEffect
    return () => (isSubscribed = false)
  }, []);

  return state
}

이 블로그 juliangaramendy에서 더 많이 읽을 수 있습니다.

@windmaomao가 응답하지 않으면 구독을 취소하는 방법을 찾기 위해 몇 시간을 더 소비할 수 있습니다.

즉, 각각 2개의 훅을 사용했습니다.useCallback기능을 메모하다useEffect데이터를 가져옵니다.

  const fetchSpecificItem = useCallback(async ({ itemId }) => {
    try {
        ... fetch data

      /* 
       Before you setState ensure the component is mounted
       otherwise, return null and don't allow to unmounted component.
      */

      if (!mountedRef.current) return null;

      /*
        if the component is mounted feel free to setState
      */
    } catch (error) {
      ... handle errors
    }
  }, [mountedRef]) // add variable as dependency

나는 사용했다useEffect데이터를 가져옵니다.

단순히 함수 내부에서 훅을 호출할 수 없기 때문에 함수 내부 효과를 호출할 수 없습니다.

   useEffect(() => {
    fetchSpecificItem(input);
    return () => {
      mountedRef.current = false;   // clean up function
    };
  }, [input, fetchSpecificItem]);   // add function as dependency

여러분, 덕분에 후크 사용법을 배울 수 있었습니다.

fetchData는 약속을 반환하는 비동기 함수입니다.하지만 당신은 그것을 해결하지 않고 호출했다.구성 요소 마운트 해제 시 정리를 수행해야 하는 경우 정리 코드가 있는 이펙트 내에 함수를 반환합니다.다음을 시도해 보십시오.

const Miliko = () => {
  const [data, setData] = useState({ hits: [] });
  const [url, setUrl] = useState('http://hn.algolia.com/api/v1/search?query=redux');
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);

  useEffect(() => {
    (async function() {
      setIsError(false);
      setIsLoading(true);
      try {
        const result = await axios(url);
        setData(result.data);
      } catch (error) {
        setIsError(true);
      }
      setIsLoading(false);
    })();

    return function() {
      /**
       * Add cleanup code here
       */
    };
  }, [url]);

  return [{ data, isLoading, isError }, setUrl];
};

공식 문서와 함께 설정 가능한 파라미터가 명확하게 설명되어 있는 문서를 읽는 것이 좋습니다.

@Niyongabo 솔루션에 따라 수정한 방법은 다음과 같습니다.

  const mountedRef = useRef(true);

  const fetchSpecificItem = useCallback(async () => {
    try {
      const ref = await db
        .collection('redeems')
        .where('rewardItem.id', '==', reward.id)
        .get();
      const data = ref.docs.map(doc => ({ id: doc.id, ...doc.data() }));
      if (!mountedRef.current) return null;
      setRedeems(data);
      setIsFetching(false);
    } catch (error) {
      console.log(error);
    }
  }, [mountedRef]);

  useEffect(() => {
    fetchSpecificItem();
    return () => {
      mountedRef.current = false;
    };
  }, [fetchSpecificItem]);

가변 ref 객체를 생성하여 true로 설정하고 정리 중에 값을 전환하여 컴포넌트가 아웃 해제되었는지 확인합니다.

const mountedRef = useRef(true)

useEffect(() => {
  // CALL YOUR API OR ASYNC FUNCTION HERE
  return () => { mountedRef.current = false }
}, [])

const [get All Jobs, setget]Alljobs] = useState();

useEffect(() => {
    let mounted = true;
    axios.get('apiUrl')
        .then(function (response) {
            const jobData = response.data;
            if (mounted) {
                setgetAlljobs(jobData)
            }
        })
        .catch(function (error) {
            console.log(error.message)
        })
    return () => mounted = false;

}, [])

true->에 마운트된 변수를 설정한 후 true이면 맨 아래에 function->을 마운트하고 반환하여 마운트 해제합니다.

내 경우는 이 질문들이 원하는 것과 상당히 달랐다.그래도 같은 에러가 나요.

는 '목록 '목록'을 썼는데, '목록'은 '목록'을 사용해서 예요..map는 '아예' 사용해야 ..shift (의 첫 합니다). (어느쪽인가 하면)

1개만 가 있기 에 ->첫은 삭제 1개의 아이템을 에 2개의 아이템이 있습니다.key={index}, 는 from (from)입니다)..map), 이후 첫 번째 항목인 두 번째 항목은 이전 항목과 동일한 구성요소라고 가정했다.

항목를 리액트합니다두가 "" (모두 노드)를 사용했을 , "" " " " " 를 사용합니다.useEffect()인덱스가 0이고 키가 0인 이전 노드의 키가 두 번째 컴포넌트와 같기 때문에 컴포넌트가 이미 마운트 해제되었다는 React through 오류입니다.

사용되었습니다.useEffect가정했습니다 노드는 더 현장에 하지 않아가 발생하였습니다.이 노드는 이미 현장에 존재하지 않게 되어 있습니다.

는 이것을 것을 고쳤습니다.keyvalue(인덱스가 몇 한 stringprop value(인덱스 값)입니다.

모든 액션을 checkUnmount 내에서 콜백으로 랩할 수 있습니다.

const useUnmounted = () => {
  const mountedRef = useRef(true);

  useEffect(
    () => () => {
      mountedRef.current = false;
    },
    [],
  );

  const checkUnmount = useCallback(
    (cb = () => {}) => {
      try {
        if (!mountedRef.current) throw new Error('Component is unmounted');
        cb();
      } catch (error) {
        console.log({ error });
      }
    },
    [mountedRef.current],
  );

  return [checkUnmount, mountedRef.current];
};

import React, { useCallback, useEffect, useRef, useState } from "react";
import { userLoginSuccessAction } from "../../../redux/user-redux/actionCreator";
import { IUser } from "../../../models/user";
import { Navigate } from "react-router";
import XTextField from "../../../x-lib/x-components/x-form-controls/XTextField";
import { useDispatch } from "react-redux";
interface Props {
  onViewChange?: (n: number) => void;
  userInit?: (user: IUser) => void;
}

interface State {
  email: string;
  password: string;
  hasError?: boolean;
  errorMessage?: string;
}

const initialValue = {
  email: "eve.holt@reqres.in",
  password: "cityslicka",
  errorMessage: "",
};
const LoginView: React.FC<Props> = (props) => {
  const { onViewChange } = props;
  const [state, setState] = useState(initialValue);
  const mountedRef = useRef(true);
  const dispatch = useDispatch();
  const handleEmailChange = useCallback(
    (val: string) => {
      setState((state) => ({
        ...state,
        email: val,
      }));
    },
    [state.email]
  );

  const handlePasswordChange = useCallback(
    (val: string) => {
      setState((state) => ({
        ...state,
        password: val,
      }));
    },
    [state.password]
  );

  const  onUserClick = useCallback( async () =>  {
    // HTTP Call
    const data = {email: state.email , password: state.password}
    try{
      await dispatch(userLoginSuccessAction(data));
      <Navigate to = '/' />
      setState( (state)=>({
        ...state,
        email: "",
        password: ""
      })) 
    }
    catch(err){
      setState( (state)=>({
        ...state,
        errorMessage: err as string
      }))
    }
  },[mountedRef] )
  
  useEffect(()=>{
    onUserClick();
    return ()=> {
      mountedRef.current = false;
    };
  },[onUserClick]); 
  
  const Error = (): JSX.Element => {
    return (
      <div
        className="alert alert-danger"
        role="alert"
        style={{ width: "516px", margin: "20px auto 0 auto" }}
      >
        {state.errorMessage}
      </div>
    );
  };

  return (
    <div>
      <div>
        email: "eve.holt@reqres.in"
        <span style={{ paddingRight: "20px" }}></span> password: "cityslicka"{" "}
      </div>
      {state.errorMessage && <Error />}
      <form className="form-inline">
        <div className="form-group">
          <XTextField
            label="email"
            placeholder="E-Posta"
            value={state.email}
            onChange={handleEmailChange}
          />
        </div>
        <div className="form-group my-sm-3">
          <XTextField
            type="password"
            label="password"
            placeholder="Şifre"
            value={state.password}
            onChange={handlePasswordChange}
          />
        </div>
        <button type="button" className="btn btn-primary" onClick = {onUserClick} >
          Giriş Et
        </button>
        <a
          href="#"
          onClick={(e) => {
            e.preventDefault();
            onViewChange && onViewChange(3);
          }}
        >
          Şifremi Unuttum!
        </a>
      </form>

      <p>
        Hələdə üye deyilsiniz? <br />
        pulsuz registir olmak üçün
        <b>
          <u>
            <a
              style={{ fontSize: "18px" }}
              href="#"
              onClick={(e) => {
                e.preventDefault();
                onViewChange && onViewChange(2);
              }}
            >
              kilik edin.
            </a>
          </u>
        </b>
      </p>
    </div>
  );
};

export default LoginView;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

이 문제로 나는 까다로운 방법을 사용했다.

먼저 이런 상태를 도입합니다.

const [routing,setRouting] = useState(false)

그리고 작업이 끝나면 true로 변경하고 useEffect를 이렇게 변경했습니다.

useEffect(()=>{
if(routing)
    navigation.navigate('AnotherPage')

),[routing]}

언급URL : https://stackoverflow.com/questions/56450975/to-fix-cancel-all-subscriptions-and-asynchronous-tasks-in-a-useeffect-cleanup-f

반응형