useEffect

275 阅读2分钟

监听普通变量

  • 点击按钮修改变量n
  • useEffect是监听不到的
  • 因为修改变量n没有触发重新渲染
import { useEffect, useState } from 'react'

let n = 0

function App() {
  useEffect(() => console.log(n), [n])

  const tap = () => {
    n++
  }

  return <button onClick={tap}>xxx{n}</button>

}

export default App

我们在修改变量n时,触发重新渲染,此时可以监听到变量n

import { useEffect, useState } from 'react'

let n = 0

function App() {
  const [_, render] = useState({})
  
  useEffect(() => console.log(n), [n])

  const tap = () => {
    n++
    render({})
  }

  return <button onClick={tap}>xxx{n}</button>

}

export default App

手写

v1.0

注意:

useEffect(callback)

😅组件每次加载时, callback 就会执行一次, 

😅但实际情况是, 组件每次加载 callback 会执行两次, 

😅这就涉及到 V2.0的功能: 监听数据变化前, 多出来的一次执行是为了存值

let pre: any[] = []

function useEffect_(cb: Function, arr: any[]) {
  // 没有依赖, useEffect执行, cb就执行
  if (!arr) {
    cb()
    return
  }
  // 如果有依赖, 依赖变化就执行
  if (hasChanged(pre, arr)) {
    cb()
    pre = arr
  }
}

function hasChanged(pre: any[], arr: any[]) {
  for (let i = 0; i <= arr.length - 1; i++) {
    if (arr[i] !== pre[i]) return true
  }
  return false
}

function App() {
  const [n, setN] = useState(0)

  useEffect_(() => {
    console.log(n, '---------');
  }, [n])

  const tap = () => {
    setN(n + 1)
  }

  return <button onClick={tap}>xxx{n}</button>
}

export default App

v2.0

  • 新增攻能: 监听数据变化前
  • 新增了该功能后, 组件每次加载时, 回调函数都会执行两次
// 依赖是否发生变化
function hasChanged(pre: any[], arr: any[]) {
  for (let i = 0; i <= arr.length - 1; i++) {
    if (arr[i] !== pre[i]) return true
  }
  return false
}

let pre: any[] = []
let before: Function = null

function useEffect_(after: Function, arr: any[]) {

  const run = () => {
    if (!before) before = after()
    before()
    before = after()
    pre = arr
  }

  // 没有依赖, useEffect执行, cb就执行
  if (!arr) {
    run()
    return
  }
  // 如果有依赖, 依赖变化就执行
  if (hasChanged(pre, arr)) {
    run()
  }
}


function App() {
  const [n, setN] = useState(0)

  useEffect_(() => {
    console.log(n, '---------after');
    return () => console.log(n, '-------before');
  }, [n])

  const tap = () => {
    setN(n + 1)
  }

  return <button onClick={tap}>xxx{n}</button>
}

export default App

v2.0的bug: 使用了两次useEffect就会出问题

function App() {
  const [n, setN] = useState(0)

  useEffect_(() => {
    console.log(n, '---------after');
  }, [n])

  useEffect_(() => {
    console.log(n, '---------after');
  }, [n])

  const tap = () => {
    setN(n + 1)
  }

  return <button onClick={tap}>xxx{n}</button>
}

useEffect中的闭包

1. 打印变化前的值

  const [n, setN] = useState(0)

  useEffect(() => {
    console.log(n, '------ after');
    return () => console.log(n, '-------- before'); // 打印变化前的值
  })

useEffect原理

let before: Function = null

function useEffect(after: Function) {
  if (!before) before = after()
  before()
  before = after()
}

解释

let before: Function = null
// 第一次执行
{
    const n = 0
    const after = () => {
        console.log(n)
        return () => console.log(n)
    }
    before = after() // 打印0
    before() // 打印0
    before = after() // 打印0
}
// 第二次执行
{
    const n = 1
    const after = () => {
        console.log(n)
        return () => console.log(n)
    }
    before() // 打印: 0, 这是第一次执行after返回的函数
    before = after() // 打印: 1
}
// 第三次执行
{
    const n = 2
    const after = () => {
        console.log(n)
        return () => console.log(n)
    }
    before()
    before = after()
}

2. 获取不到最新值

useEffect中的异步获取不到最新值


export default function App() {
  const [count, setCount] = useState(1);

  useEffect(() => {
    const timer = setInterval(() => {
      console.log(count); // 这里永远都是 0
      setCount(count + 1);
    }, 1000);

    return () => clearInterval(timer);
  }, []);

  return (
    <div className="App">
      <h2>{count}</h2>
    </div>
  );
}

原因

// App 第一次执行时的内容
{
    const count = 1
    setInterval(() => {
        console.log(count); // 这里永远都是 0
        setCount(count + 1);
    }, 1000);
}
// App 第二次执行时的内容
{
    const count = 2
}