programing

반응 후크 사용효과의 종속성 누락: 'displatch'

prostudy 2022. 3. 14. 21:46
반응형

반응 후크 사용효과의 종속성 누락: 'displatch'

reaction js로 작업하는 것은 이번이 처음인데, 이 뷰를 떠날 때 경고를 제거하려고 하는 이유는 다른 뷰에 표시하기 싫지만 오류가 없을 경우 성공 경고를 계속 유지하여 다른 뷰로 리디렉션할 때 표시하기를 원하기 때문이다.

하지만 나는 구글 크롬에 이 마모된 것을 얻는다.Line 97:6: React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array react-hooks/exhaustive-deps

만약 내가 디스패치를 포함한다면 나는 무한루프를 얻을 수 있다.

const [state, dispatch] = useUserStore();
useEffect(() => {
    let token = params.params.token;
    checktoken(token, dispatch);
  }, [params.params.token]);

  useEffect(() => {
    return () => {
      if (state.alert.msg === "Error") {
        dispatch({
          type: REMOVE_ALERT
        });
      }
    };
  }, [state.alert.msg]);

//response from the api
if (!token_valide || token_valide_message === "done") {
      return <Redirect to="/login" />;
    }

useUserStore 입니다.

  const globalReducers = useCombinedReducers({
    alert: useReducer(alertReducer, alertInitState),
    auth: useReducer(authReducer, authInitState),
    register: useReducer(registerReducer, registerInitState),
    token: useReducer(passeditReducer, tokenvalidationInitState)
  });
  return (
    <appStore.Provider value={globalReducers}>{children}</appStore.Provider>
  );
};

export const useUserStore = () => useContext(appStore);

업데이트 09/11/2020

이 솔루션은 더 이상 필요 없음eslint-plugin-react-hooks@4.1.0그 이상

지금useMemo그리고useCallback참조 유형을 종속성으로 안전하게 수신할 수 있다.#19590

function MyComponent() {
  const foo = ['a', 'b', 'c']; // <== This array is reconstructed each render
  const normalizedFoo = useMemo(() => foo.map(expensiveMapper), [foo]);
  return <OtherComponent foo={normalizedFoo} />
}

콜백을 안전하게 안정화하는 방법(정상화)의 또 다른 예가 여기에 있다.

const Parent = () => {
    const [message, setMessage] = useState('Greetings!')

    return (
        <h3>
            { message }
        </h3>
        <Child setter={setMessage} />
    )
}

const Child = ({
    setter
}) => {
    const stableSetter = useCallback(args => {
        console.log('Only firing on mount!')
        return setter(args)
    }, [setter])

    useEffect(() => {
        stableSetter('Greetings from child\'s mount cycle')
    }, [stableSetter]) //now shut up eslint

    const [count, setCount] = useState(0)

    const add = () => setCount(c => c + 1)

    return (
        <button onClick={add}>
            Rerender {count}
        </button>
    )
}

이제 다음에서 입증된 것과 같이 안정적인 서명이 있는 참조 유형useState또는useDispatch트리거 없이 효과 내부에서 안전하게 사용 가능exhaustive-deps와도props

바보-앤드라스-9v1yp 편집

---

구답

dispatch풍습에서 유래하다hook따라서 안정적인 서명이 없으므로 렌더별로 변경된다(참조 평등).처리기를 내부로 감싸서 종속성의 이전 계층 추가useCallback갈고리를 걸다

   const [foo, dispatch] = myCustomHook()
  
   const stableDispatch = useCallback(dispatch, []) //assuming that it doesn't need to change

   useEffect(() =>{
        stableDispatch(foo)
   },[stableDispatch])

useCallback그리고useMemo동기성을 보장하기 위해 종속성 검사 계층을 추가하기 위한 주요 목적을 가진 도우미 후크.보통 당신은 함께 일하고 싶어한다.useCallback에 대한 안정적인 서명을 보장하기 위해prop어떻게 바뀔지 알고 반응하지 않는지 말이야

A function(참조 유형)을 통해 전달됨props예를 들면

const Component = ({ setParentState }) =>{
    useEffect(() => setParentState('mounted'), [])
}

uppon 마운팅이 상위(일반적이지 않음)에서 일부 상태를 설정해야 하는 하위 구성 요소가 있다고 가정해 봅시다. 위의 코드는 다음에서 보고되지 않은 종속성에 대한 경고를 생성함useEffect, 그러니까 선언하자.setParentState대응으로 확인할 종속성

const Component = ({ setParentState }) =>{
    useEffect(() => setParentState('mounted'), [setParentState])
}

이제 이 효과는 각 렌더에 대해 실행되며, 마운팅뿐만 아니라 각 업데이트에도 적용된다.때문에 이런 일이 일어난다.setParentStatea이다function매번 그 함수가 재현되는 것Component부름을 받다당신도 아시잖아요.setParentState시간외 근무 수당은 바뀌지 않을 테니 리액션이라고 말해도 안전해원래 도우미를 내부로 감싸서useCallback정확히 그렇게 하고 있다(다른 종속성 검사 계층에 추가).

const Component = ({ setParentState }) =>{
   const stableSetter = useCallback(() => setParentState(), [])

   useEffect(() => setParentState('mounted'), [stableSetter])
}

여기 있다.지금React을 알고 있다stableSetter라이프사이클 내부의 시그니처를 변경하지 않기 때문에 너무 불편하게 실행할 필요가 없다.

곁눈질로useCallback이렇게 쓰이기도 한다.useMemo을 선택하기 , 값비싼 수 있다

두 가지 useCallback이다

  • 참조 평등에 의존하는 하위 구성요소를 최적화하여 불필요한 렌더링을 방지하십시오.글꼴

  • 값비싼 계산을 메모화한다.

근본적으로는 문제를 해결할 수 있다고 생각하지만, 그것은 useCombinedReducers를 변경한다는 것을 의미하며, 나는 useCombinedReducers가 호출할 때마다 발송을 위해 새로운 참조를 반환해서는 안 된다고 생각하기 때문에 repo를 요청하고 당김 요청을 만들었다.

function memoize(fn) {
  let lastResult,
    //initial last arguments is not going to be the same
    //  as anything you will pass to the function the first time
    lastArguments = [{}];
  return (...currentArgs) => {
    //returning memoized function
    //check if currently passed arguments are the same as
    //  arguments passed last time
    const sameArgs =
      currentArgs.length === lastArguments.length &&
      lastArguments.reduce(
        (result, lastArg, index) =>
          result && Object.is(lastArg, currentArgs[index]),
        true,
      );
    if (sameArgs) {
      //current arguments are same as last so just
      //  return the last result and don't execute function
      return lastResult;
    }
    //current arguments are not the same as last time
    //  or function called for the first time, execute the
    //  function and set last result
    lastResult = fn.apply(null, currentArgs);
    //set last args to current args
    lastArguments = currentArgs;
    //return result
    return lastResult;
  };
}

const createDispatch = memoize((...dispatchers) => action =>
  dispatchers.forEach(fn => fn(action)),
);
const createState = memoize(combinedReducers =>
  Object.keys(combinedReducers).reduce(
    (acc, key) => ({ ...acc, [key]: combinedReducers[key][0] }),
    {},
  ),
);
const useCombinedReducers = combinedReducers => {
  // Global State
  const state = createState(combinedReducers);

  const dispatchers = Object.values(combinedReducers).map(
    ([, dispatch]) => dispatch,
  );

  // Global Dispatch Function
  const dispatch = createDispatch(...dispatchers);

  return [state, dispatch];
};

export default useCombinedReducers;

다음은 작업 예시:

const reduceA = (state, { type }) =>
  type === 'a' ? { count: state.count + 1 } : state;
const reduceC = (state, { type }) =>
  type === 'c' ? { count: state.count + 1 } : state;
const state = { count: 1 };
function App() {
  const [a, b] = React.useReducer(reduceA, state);
  const [c, d] = React.useReducer(reduceC, state);
  //memoize what is passed to useCombineReducers
  const obj = React.useMemo(
    () => ({ a: [a, b], c: [c, d] }),
    [a, b, c, d]
  );
  //does not do anything with reduced state
  const [, reRender] = React.useState();
  const [s, dispatch] = useCombinedReducers(obj);
  const rendered = React.useRef(0);
  const [sc, setSc] = React.useState(0);
  const [dc, setDc] = React.useState(0);
  rendered.current++;//display how many times this is rendered
  React.useEffect(() => {//how many times state changed
    setSc(x => x + 1);
  }, [s]);
  React.useEffect(() => {//how many times dispatch changed
    setDc(x => x + 1);
  }, [dispatch]);
  return (
    <div>
      <div>rendered {rendered.current} times</div>
      <div>state changed {sc} times</div>
      <div>dispatch changed {dc} times</div>
      <button type="button" onClick={() => reRender({})}>
        re render
      </button>
      <button
        type="button"
        onClick={() => dispatch({ type: 'a' })}
      >
        change a
      </button>
      <button
        type="button"
        onClick={() => dispatch({ type: 'c' })}
      >
        change c
      </button>
      <pre>{JSON.stringify(s, undefined, 2)}</pre>
    </div>
  );
}

function memoize(fn) {
  let lastResult,
    //initial last arguments is not going to be the same
    //  as anything you will pass to the function the first time
    lastArguments = [{}];
  return (...currentArgs) => {
    //returning memoized function
    //check if currently passed arguments are the same as
    //  arguments passed last time
    const sameArgs =
      currentArgs.length === lastArguments.length &&
      lastArguments.reduce(
        (result, lastArg, index) =>
          result && Object.is(lastArg, currentArgs[index]),
        true
      );
    if (sameArgs) {
      //current arguments are same as last so just
      //  return the last result and don't execute function
      return lastResult;
    }
    //current arguments are not the same as last time
    //  or function called for the first time, execute the
    //  function and set last result
    lastResult = fn.apply(null, currentArgs);
    //set last args to current args
    lastArguments = currentArgs;
    //return result
    return lastResult;
  };
}

const createDispatch = memoize((...dispatchers) => action =>
  dispatchers.forEach(fn => fn(action))
);
const createState = memoize(combinedReducers =>
  Object.keys(combinedReducers).reduce(
    (acc, key) => ({
      ...acc,
      [key]: combinedReducers[key][0],
    }),
    {}
  )
);
const useCombinedReducers = combinedReducers => {
  // Global State
  const state = createState(combinedReducers);

  const dispatchers = Object.values(combinedReducers).map(
    ([, dispatch]) => dispatch
  );

  // Global Dispatch Function
  const dispatch = createDispatch(...dispatchers);

  return [state, dispatch];
};

ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

최소 문제 사례, 2021년

문법: 은 이 있고 당신은 이 코드를 가지고 있고 그것은 무한의 루프를 만들어낸다. 왜하하면 되기 때문이다.array은 어에 있다.setArray매 순간마다useEffect그것은 그것이 계속 설정될 것이라는 것을 의미한다.

const MyComponent = ({ removeValue }) => {
  const [array, setArray] = useState([1, 2, 3, 4, 5]);

  // useEffect to remove `removeValue` from array
  useEffect(() => {
    const newArray = array.filter((value) => value !== removeValue);
    setArray(newArray);
  }, [array, removeValue]);

  return <div>{array.join(" ")}</div>;
};

다음과 같은 여러 솔루션이 있다.

솔루션 #1: 이전 상태 사용

setState또한 콜백을 받을 수 있는데, 이것은 당신에게 논쟁으로서 현재 상태를 제공한다.이것은 때때로 문제를 해결하는 데 사용될 수 있다.이제 다음을 포함하지 않아도 된다.array종속성 배열에서:

const MyComponent = ({ removeValue }) => {
  const [array, setArray] = useState([1, 2, 3, 4, 5]);

  // useEffect to remove `removeValue` from array
  useEffect(() => {
    setArray((previousArray) => {
      const newArray = previousArray.filter((value) => value !== removeValue);
      return newArray;
    });
  }, [removeValue]);

  return <div>{array.join(" ")}</div>;
};

솔루션 #2: 사용 조건부로 실행

가끔 당신은 그 일을 할 수 있는 조건을 찾을 수 있다.useEffect예를 들어, 우리는 단지 우리의array어레이에 다음이 포함된 경우removeValue그러므로 우리는 일찍 돌아올 수 있다:

const MyComponent = ({ removeValue }) => {
  const [array, setArray] = useState([1, 2, 3, 4, 5]);

  // useEffect to remove `removeValue` from array
  useEffect(() => {
    const containsValue = array.includes(removeValue);
    if (!containsValue) return;

    const newArray = array.filter((value) => value !== removeValue);
    setArray(newArray);
  }, [array, removeValue]);

  return <div>{array.join(" ")}</div>;
};

솔루션 #3: 사용useCompare

때로는 제약으로 인해 위의 해결책이 불가능할 때도 있으므로 여기에 좀 더 정교한 방법이 있다.이것은 더 복잡한 문제를 위한 것이지만, 나는 아직 그 문제를 보지 못했다.useEffect그것은 내가 아래에 제공할 두 가지 추가 기능을 필요로 한다.

기능을 설명하기 위해 아래 코드에 대해 코멘트를 했다.

const MyComponent = ({ removeValue }) => {
  const [array, setArray] = useState([1, 2, 3, 4, 5]);

  // useCompare will return either `true` or `false` depending
  // on if the value has changed. In this example, `useEffect` will
  // rerun every time the array length changes.
  // This can be applied to any other logic such as if removeValue
  // changes, depending on when you want to run the `useEffect`
  const arrayLengthChanged = useCompare(array.length);
  useEffect(() => {
    if (!arrayLengthChanged) return;
    const newArray = array.filter((value) => value !== removeValue);
    setArray(newArray);
  }, [array, arrayLengthChanged, removeValue]);

  return <div>{array.join(" ")}</div>;
};

솔루션 #4: 오류 비활성화(권장하지 않음)

마지막 '해결'은 문제를 회피하고 있다.경우에 따라서는 이 정도면 충분하고 완벽하게 작동하겠지만, 만약 이 경우 디버깅 문제가 발생할 수 있다.useEffect나중에 변경하거나 올바르게 사용하지 않을 경우 변경하십시오.

이 예에서, 당신이 당신이 결코 당신이 그것을 실행할 필요가 없을 것이라는 것을 알고 있다고 말하시오.useEffect어레이가 변경되면 종속 어레이에서 해당 어레이를 제거하고 다음 오류를 무시하십시오.

const MyComponent = ({ removeValue }) => {
  const [array, setArray] = useState([1, 2, 3, 4, 5]);

  // useEffect to remove `removeValue` from array
  useEffect(() => {
    const newArray = array.filter((value) => value !== removeValue);
    setArray(newArray);

    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [removeValue]);

  return <div>{array.join(" ")}</div>;
};

참조URL: https://stackoverflow.com/questions/58624200/react-hook-useeffect-has-a-missing-dependency-dispatch

반응형