programing

이벤트 리스너에서의 잘못된 리액트 후크 동작

linuxpc 2023. 3. 7. 21:08
반응형

이벤트 리스너에서의 잘못된 리액트 후크 동작

리액트 훅을 가지고 놀다가 문제가 생겼어요.이벤트 리스너에 의해 처리되는 버튼을 사용하여 콘솔로그를 실행하려고 하면 잘못된 상태가 표시됩니다.

Code Sandbox: https://codesandbox.io/s/lrxw1wr97m

  1. 'Add card' 버튼을 두 번 클릭합니다.
  2. 첫 번째 카드에서 버튼 1을 클릭하여 콘솔에서 카드가 2개 있는지 확인합니다(올바른 동작).
  3. 첫 번째 카드에서 Button 2(이벤트 리스너에 의해 처리됨)를 클릭하여 콘솔에 카드가1개밖에 없는 것을 확인합니다(잘못된 동작).

왜잘 상태 태태 태? ???
번째 카드에는 '''가 있습니다.Button2를 표시해야 합니다.2카드를 사용할 수 있습니다.은은생 각각?

const { useState, useContext, useRef, useEffect } = React;

const CardsContext = React.createContext();

const CardsProvider = props => {
  const [cards, setCards] = useState([]);

  const addCard = () => {
    const id = cards.length;
    setCards([...cards, { id: id, json: {} }]);
  };

  const handleCardClick = id => console.log(cards);
  const handleButtonClick = id => console.log(cards);

  return (
    <CardsContext.Provider
      value={{ cards, addCard, handleCardClick, handleButtonClick }}
    >
      {props.children}
    </CardsContext.Provider>
  );
};

function App() {
  const { cards, addCard, handleCardClick, handleButtonClick } = useContext(
    CardsContext
  );

  return (
    <div className="App">
      <button onClick={addCard}>Add card</button>
      {cards.map((card, index) => (
        <Card
          key={card.id}
          id={card.id}
          handleCardClick={() => handleCardClick(card.id)}
          handleButtonClick={() => handleButtonClick(card.id)}
        />
      ))}
    </div>
  );
}

function Card(props) {
  const ref = useRef();

  useEffect(() => {
    ref.current.addEventListener("click", props.handleCardClick);
    return () => {
      ref.current.removeEventListener("click", props.handleCardClick);
    };
  }, []);

  return (
    <div className="card">
      Card {props.id}
      <div>
        <button onClick={props.handleButtonClick}>Button1</button>
        <button ref={node => (ref.current = node)}>Button2</button>
      </div>
    </div>
  );
}

ReactDOM.render(
  <CardsProvider>
    <App />
  </CardsProvider>,
  document.getElementById("root")
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id='root'></div>

React 16.7.0-alpha.0과 Chrome 70.0.3538.110을 사용하고 있습니다.

참고로, CardsProvider를 "lass"를 사용하여 다시 쓰면 문제가 해소됩니다.클래스를 사용하는 CodeSandbox: https://codesandbox.io/s/w2nn3mq9vl

은 기능적인 했을 때 볼 수 있는 입니다.useState됩니다.useState상태가 사용됩니다(예: 또는 타이머 기능).

는 에서는 다르게 됩니다.CardsProvider ★★★★★★★★★★★★★★★★★」Card구성 요소들.

handleCardClick ★★★★★★★★★★★★★★★★★」handleButtonClick used the the CardsProvider기능 컴포넌트가 그 범위에 정의되어 있습니다.이 실행될 기능이 이 은 이 이 작동하다를 말합니다.이러한 기능은cards정의된 시점에 취득된 상태.는, 핸들러, 핸들러가 됩니다.CardsProvider이치노

handleCardClick used the the Card 에 한 번 합니다.useEffect한 기능을 이 은 컴포넌트 수명 전체 동안 handleCardClick이치노 handleButtonClick 각 됩니다.Card렌더링, 매번 새로운 기능으로 새로운 상태를 나타냅니다.

가변 상태

하기 위한 은 " " " 입니다.useRefuseState. 될 수 있는 ref는 기본적으로 참조에 의해 전달될 수 있는 가변 객체를 제공하는 레시피입니다.

const ref = useRef(0);

function eventListener() {
  ref.current++;
}

이 시 합니다.이 경우 컴포넌트는 스테이트업데이트 시 .useState참조는 적용되지 않습니다.

한 상태를 할 수 " " " " " " " " " " " " "forceUpdate는 클래스 컴포넌트와 함수컴포넌트 모두에서 안티 패턴으로 간주됩니다(참고용으로만 리스트 되어 있습니다).

const useForceUpdate = () => {
  const [, setState] = useState();
  return () => setState({});
}

const ref = useRef(0);
const forceUpdate = useForceUpdate();

function eventListener() {
  ref.current++;
  forceUpdate();
}

상태 업데이트 프로그램 기능

한 가지 해결책은 동봉된 범위에서 오래된 상태가 아닌 새로운 상태를 수신하는 상태 업데이터 기능을 사용하는 것입니다.

function eventListener() {
  // doesn't matter how often the listener is registered
  setState(freshState => freshState + 1);
}

, 으로 상태가 합니다.console.log회피책은 업데이트를 방지하기 위해 동일한 상태를 반환하는 것입니다.

function eventListener() {
  setState(freshState => {
    console.log(freshState);
    return freshState;
  });
}

useEffect(() => {
  // register eventListener once

  return () => {
    // unregister eventListener once
  };
}, []);

비동기 부작용에는 잘 .async★★★★★★★★★★★★★★★★★★.

수동 이벤트 리스너 재등록

또 하나의 해결책은 이벤트청취자를 매번 재등록하여 콜백이 항상 동봉된 스코프에서 새로운 상태를 얻을 수 있도록 하는 것입니다.

function eventListener() {
  console.log(state);
}

useEffect(() => {
  // register eventListener on each state update

  return () => {
    // unregister eventListener
  };
}, [state]);

내장 이벤트 처리

가 에 되어 있지 않은 document,window컴포넌트의 Resact DOM 한 해야 합니다.에 의해, 「Resact」는.이것에 의해, 다음의 조작이 불필요하게 됩니다.useEffect:

<button onClick={eventListener} />

할 수 .useMemo ★★★★★★★★★★★★★★★★★」useCallback소품으로 전달될 때 불필요한 재접촉을 방지하기 위해:

const eventListener = useCallback(() => {
  console.log(state);
}, [state]);
  • 에서는 초기 판에 수 가변 했습니다.useStateReact 16.7.0-alpha 버전에서는 훅 구현이 가능하지만 최종 React 16.8 구현에서는 사용할 수 없습니다. useState현재 지원되는 것은 불변 스테이트뿐입니다.*

은 내가 '라고 부르는 후크를 것입니다.useStateRef

function useStateRef(initialValue) {
  const [value, setValue] = useState(initialValue);

  const ref = useRef(value);

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return [value, setValue, ref];
}

해서 '어울리다'를 할 수 되었습니다.ref이치

즉, useState에는 다음과 같은 간단한 솔루션이 있습니다.

function Example() {
  const [state, setState] = useState(initialState);

  function update(updates) {
    // this might be stale
    setState({...state, ...updates});
    // but you can pass setState a function instead
    setState(currentState => ({...currentState, ...updates}));
  }

  //...
}

간단한 답변입니다.

myvar가 변경될 때마다 재렌더가 트리거되지 않습니다.

const [myvar, setMyvar] = useState('')
  useEffect(() => {    
    setMyvar('foo')
  }, []);

WILL은 렌더 -> myvar를 []넣습니다.

const [myvar, setMyvar] = useState('')
  useEffect(() => {    
    setMyvar('foo')
  }, [myvar]);

콘솔을 확인하면 다음과 같은 답이 나옵니다.

React Hook useEffect has a missing dependency: 'props.handleCardClick'. Either include it or remove the dependency array. (react-hooks/exhaustive-deps)

더하면 돼요.props.handleCardClick올바르게 동작합니다.

이렇게 하면 콜백의 상태 값이 항상 갱신됩니다.

 // registers an event listener to component parent
 React.useEffect(() => {

    const parentNode = elementRef.current.parentNode

    parentNode.addEventListener('mouseleave', handleAutoClose)

    return () => {
        parentNode.removeEventListener('mouseleave', handleAutoClose)
    }

}, [handleAutoClose])

Moses Gitau의 훌륭한 답변을 바탕으로 Typescript를 개발하고 있다면, Type 오류를 해결하기 위해 후크 함수를 일반 함수로 만듭니다.

function useStateRef<T>(initialValue: T | (() => T)): 
   [T, React.Dispatch<React.SetStateAction<T>>, React.MutableRefObject<T>] {
  const [value, setValue] = React.useState(initialValue);

  const ref = React.useRef(value);

  React.useEffect(() => {
    ref.current = value;
  }, [value]);

  return [value, setValue, ref];
}

@Moses Gitau의 답변부터 시작해서, 저는 가치의 "지연" 버전에 접근할 수 없고, 조금 더 미니멀리즘적인 것을 사용하고 있습니다.

import { useState, useRef } from 'react';

function useStateRef(initialValue) {
    const [, setValueState] = useState(initialValue);

    const ref = useRef(initialValue);

    const setValue = (val) => {
        ref.current = val;
        setValueState(val); // to trigger the refresh
    };

    const getValue = (val) => {
        return ref.current;
    };

    return [getValue , setValue];
}
export default useStateRef;

이게 내가 쓰고 있는 거야

사용 예:

const [getValue , setValue] = useStateRef(0);

const listener = (event) => {
    setValue(getValue() + 1);
};

useEffect(() => {
    window.addEventListener('keyup', listener);

    return () => {
        window.removeEventListener('keyup', listener);
    };
}, []);

Edit : 참조 자체가 아닌 getValue가 표시됩니다.그럴 때는 좀 더 캡슐화해 두는 게 좋을 것 같아요.

index.js을 제기하다button2정상적으로 동작합니다.

useEffect(() => {
    ref.current.addEventListener("click", props.handleCardClick);
    return () => {
        ref.current.removeEventListener("click", props.handleCardClick);
    };
- }, []);
+ });

하면 안 요.[] 인수로서useEffect한번면면면면면면면면면면면면

상세: https://reactjs.org/docs/hooks-effect.html

언급URL : https://stackoverflow.com/questions/53845595/wrong-react-hooks-behaviour-with-event-listener

반응형