Javascript 事件侦听器的错误行为

Javascript 事件侦听器的错误行为,javascript,reactjs,react-hooks,Javascript,Reactjs,React Hooks,我在胡闹,遇到了一个问题。 当我试图使用事件侦听器处理的按钮来控制台记录日志时,它显示了错误的状态 代码沙盒: 点击“添加卡”按钮2次 在第一张卡中,单击按钮1,在控制台中看到有两张卡处于状态(正确的行为) 在第一张卡中,单击按钮2(由事件侦听器处理),并在控制台中看到只有一张卡处于状态(错误行为) 为什么它显示了错误的状态 在第一张卡中,按钮2应在控制台中显示2卡。有什么想法吗 const{useState,useContext,useRef,useffect}=React; const C

我在胡闹,遇到了一个问题。 当我试图使用事件侦听器处理的按钮来控制台记录日志时,它显示了错误的状态

代码沙盒:

  • 点击“添加卡”按钮2次
  • 在第一张卡中,单击按钮1,在控制台中看到有两张卡处于状态(正确的行为)
  • 在第一张卡中,单击按钮2(由事件侦听器处理),并在控制台中看到只有一张卡处于状态(错误行为)
  • 为什么它显示了错误的状态
    在第一张卡中,
    按钮2
    应在控制台中显示
    2
    卡。有什么想法吗

    const{useState,useContext,useRef,useffect}=React;
    const CardsContext=React.createContext();
    const CardsProvider=道具=>{
    const[cards,setCards]=useState([]);
    const addCard=()=>{
    const id=cards.length;
    setCards([…cards,{id:id,json:{}]);
    };
    const handleCardClick=id=>console.log(卡片);
    const handleButtonClick=id=>console.log(卡片);
    返回(
    {props.children}
    );
    };
    函数App(){
    const{cards,addCard,handleCardClick,handleButtonClick}=useContext(
    CardsContext
    );
    返回(
    添加卡
    {cards.map((卡片,索引)=>(
    handleCardClick(card.id)}
    handleButtonClick={()=>handleButtonClick(card.id)}
    />
    ))}
    );
    }
    功能卡(道具){
    const ref=useRef();
    useffect(()=>{
    ref.current.ADDEVENTLISTER(“点击”,道具手柄点击);
    return()=>{
    ref.current.removeEventListener(“单击”,props.handleCardClick);
    };
    }, []);
    返回(
    卡片{props.id}
    按钮1
    (ref.current=node)}>按钮2
    );
    }
    ReactDOM.render(
    ,
    document.getElementById(“根”)
    );
    
    
    
    这是使用
    useState
    hook的功能组件的常见问题。同样的问题也适用于使用
    useState
    state的任何回调函数,例如

    事件处理程序在
    CardsProvider
    Card
    组件中的处理方式不同

    handleCardClick
    handleButtonClick
    用于
    CardsProvider
    的功能组件在其范围内定义。每次运行时都会有新函数,它们指的是在定义卡片时获得的
    状态。每次呈现
    CardsProvider
    组件时,都会重新注册事件处理程序

    handleCardClick
    卡中使用的功能组件作为道具接收,并在组件安装时使用
    useffect
    注册一次。在整个组件寿命期间,它是相同的函数,指的是在第一次定义
    handleCardClick
    函数时新出现的陈旧状态
    handleButtonClick
    作为道具接收,并在每个
    渲染上重新注册,它每次都是一个新函数,表示新状态

    可变态 解决此问题的常用方法是使用
    useRef
    而不是
    useState
    。ref基本上是一个配方,它提供了一个可通过引用传递的可变对象:

    const ref = useRef(0);
    
    function eventListener() {
      ref.current++;
    }
    
    如果组件应该在状态更新时重新呈现,就像在
    useState
    中预期的那样,则参考不适用

    可以将状态更新和可变状态分开保存,但类和函数组件中的
    forceUpdate
    都被视为反模式(列出仅供参考):

    状态更新程序函数 一种解决方案是使用状态更新程序函数,该函数从封闭范围接收新鲜状态,而不是陈旧状态:

    function eventListener() {
      // doesn't matter how often the listener is registered
      setState(freshState => freshState + 1);
    }
    
    function eventListener() {
      console.log(state);
    }
    
    useEffect(() => {
      // register eventListener on each state update
    
      return () => {
        // unregister eventListener
      };
    }, [state]);
    
    如果同步副作用(如
    console.log
    )需要一个状态,解决方法是返回相同的状态以防止更新

    function eventListener() {
      setState(freshState => {
        console.log(freshState);
        return freshState;
      });
    }
    
    useEffect(() => {
      // register eventListener once
    
      return () => {
        // unregister eventListener once
      };
    }, []);
    
    对于异步副作用,尤其是
    async
    函数,这种方法效果不佳

    手动事件侦听器重新注册 另一种解决方案是每次重新注册事件侦听器,这样回调总是从封闭范围中获取新状态:

    function eventListener() {
      // doesn't matter how often the listener is registered
      setState(freshState => freshState + 1);
    }
    
    function eventListener() {
      console.log(state);
    }
    
    useEffect(() => {
      // register eventListener on each state update
    
      return () => {
        // unregister eventListener
      };
    }, [state]);
    
    内置事件处理 除非事件侦听器注册在
    文档
    窗口
    或其他事件目标不在当前组件的范围内,否则必须尽可能使用React自己的DOM事件处理,这样就不需要
    useEffect

    <button onClick={eventListener} />
    

    之前版本的答案建议使用可变状态,该状态适用于React 16.7.0-alpha版本中的初始
    useState
    hook实现,但在最终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有一个简单的解决方案:

    函数示例(){
    常量[状态,设置状态]=使用状态(初始状态);
    功能更新(更新){
    //这可能是陈腐的
    setState({…state,…updates});
    //但您可以将函数传递给setState
    setState(currentState=>({…currentState,…updates}));
    }
    //...
    }
    
    检查控制台,您会得到答案:

    React Hook useffect缺少依赖项:“props.handleCardClick”。包括它或删除依赖项数组。(反应钩/详尽的DEP)

    只需添加
    道具。HandleCard单击
    到依赖项数组,它就会正常工作。

    简短回答

    不会触发重新启动

    触发渲染->将myvar放入[]


    更改
    index.js
    文件中的以下行后,
    按钮2
    起作用
    const [myvar, setMyvar] = useState('')
      useEffect(() => {    
        setMyvar('foo')
      }, []);
    
    const [myvar, setMyvar] = useState('')
      useEffect(() => {    
        setMyvar('foo')
      }, [myvar]);
    
    useEffect(() => {
        ref.current.addEventListener("click", props.handleCardClick);
        return () => {
            ref.current.removeEventListener("click", 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])