• hooks的故事(2):闭包陷阱


    hooks 的故事(1):闭包陷阱

    经典的场景:

    function App(){
        const [count, setCount] = useState(1);
        useEffect(()=>{
            setInterval(()=>{
                console.log(count)
            }, 1000)
        }, [])
    }
    
    
    

    不管你如何setCount,输出的count始终是1!

    经典的闭包场景

    for ( var i=0; i<5; i++ ) {
        setTimeout(()=>{
            console.log(i)
        }, 0)
    }//5,5,5,5,5
    
    // 正确的版本
    
    for ( var i=0; i<5; i++ ) {
       (function(i){
             setTimeout(()=>{
                console.log(i)
            }, 0)
       })(i)
    }// 0,1,2,3,4
    
    

    这是一道经典的js题,输出是5个5,而非 0,1,2,3,4

    原因是因为settimeout被放入任务队列,拿出执行时取到的i就是5

    function App(){
        const [count, setCount] = useState(1);
        useEffect(()=>{
            setInterval(()=>{
                console.log(count)
            }, 1000)
        },
           [])
    }
    
    graph LR a[初次渲染]-->b[执行App]-->c[usestate设置count为初始1]-->d[useeffect 设置定时器每隔一秒打印count]
    graph LR a[state改变]-->b[试图更新]-->c[按照fiber链表执行hooks]-->d[useEffect deps 不变]

    deps不变,就不会重新执行。而一个匿名函数引用了一开始的count(count==1).

    这也就是典型的闭包陷阱。变量因为在匿名回调函数引用,形成了一个闭包一直被保存

    解决办法

    function App(){
        const [count, setCount] = useState(1);
        useEffect(()=>{
            setInterval(()=>{
                console.log(count)
            }, 1000)
        },
           [count])
    }
    

    根据我们上面的推测,最直观的解决就是把状态直接放到deps数组。

    回到我们关于闭包的讨论。闭包本质上就是执行现场的保存。所以需要保持函数执行的正确性。关键在回调函数执行的时机

    function App() {
      return <Demo1 />
    }
    
    function Demo1(){
      const [num1, setNum1] = useState(1)
      const [num2, setNum2] = useState(10)
    
      const text = useMemo(()=>{
        return `num1: ${num1} | num2:${num2}`
      }, [num2])
    
      function handClick(){
        setNum1(2)
        setNum2(20)
      }
    
      return (
        <div>
          {text}
          <div><button onClick={handClick}>click!</button></div>
        </div>
      )
    }
    
    

    比如上面这个例子,我们并没有在useMemo的deps中写入num1,但执行之后你会发现点击按钮之后两个量都会变!因为回调函数在正确的时机被rerun

    为什么useRef每次都可以拿到新鲜的值

    一句话,useRef返回的是同一个对象,指向同一片内存

        /* 将这些相关的变量写在函数外 以模拟react hooks对应的对象 */
    	let isC = false
    	let isInit = true; // 模拟组件第一次加载
    	let ref = {
    		current: null
    	}
    
    	function useEffect(cb){
    		// 这里用来模拟 useEffect 依赖为 [] 的时候只执行一次。
     		if (isC) return
    		isC = true	
    		cb()	
    	}
    
    	function useRef(value){
    		// 组件是第一次加载的话设置值 否则直接返回对象
    		if ( isInit ) {
    			ref.current = value
    			isInit = false
    		}
    		return ref
    	}
    
    	function App(){
    		let ref_ = useRef(1)
    		ref_.current++
    		useEffect(()=>{
    			setInterval(()=>{
    				console.log(ref.current) // 3
    			}, 2000)
    		})
    	}
    
    		// 连续执行两次 第一次组件加载 第二次组件更新
    	App()
    	App()
    
    

    所以,提出一个合理的设想。只要我们能保证每次组件更新的时候,useState 返回的是同一个对象的话?我们也能绕开闭包陷阱这个情景吗? 试一下吧。

    function App() {
      // return <Demo1 />
      return <Demo2 />
    }
    
    function Demo2(){
      const [obj, setObj] = useState({name: 'chechengyi'})
    
      useEffect(()=>{
        setInterval(()=>{
          console.log(obj)
        }, 2000)
      }, [])
      
      function handClick(){
        setObj((prevState)=> {
          var nowObj = Object.assign(prevState, {
            name: 'baobao',
            age: 24
          })
          console.log(nowObj == prevState)
          return nowObj
        })
      }
      return (
        <div>
          <div>
            <span>name: {obj.name} | age: {obj.age}</span>
            <div><button onClick={handClick}>click!</button></div>
          </div>
        </div>
      )
    }
    
    

    Object.assign 返回的就是传入的第一个对象。总儿言之,就是在设置的时候返回了同一个对象。

  • 相关阅读:
    对pg_latch.c 的来源探索
    对PostgreSQL的执行计划的初步学习
    21个css和Ajax表格
    23种设计模式有趣诠释
    Spket IDE, Ext开发人员的紫色匕首~
    Sql Server 2008 Reporting Services系列(一)
    C#积累(二)——ASP.NET的Session会加锁
    在TSQL语句中访问远程数据库(openrowset/opendatasource/openquery)
    ASP.NET视图的保存与加载解析(一)——视图的保存
    C#积累(一)——扩展方法就近原则和匿名类型的成员探讨
  • 原文地址:https://www.cnblogs.com/geck/p/13615881.html
Copyright © 2020-2023  润新知