Hooks使用useEffect

useEffect 处理副作用的
useEffect(setup,dependencies),setup初始化,是一个函数,dependencies依赖项,是一个数组

useEffect执行机制

第二个参数不传则会在每次渲染的时候执行
第二个参数如果传空数组,会在组件首次渲染的时候执行
第二个参数如果不是空数组, React 会将数组的内容当成依赖项存起来,在下次渲染的时候进行比较,发生改变了就会把 useEffect 的第一个参数回调函数推入任务队列等待执行

React每次挂载到页面执行对应的setup函数,是一个异步的执行setup


挂载:React将一个组件渲染到页面的过程叫挂载,对应的是类组件的挂载阶段onComponentDidMount

useEffect(()=>{console.log('挂载')},[])

更新:当依赖发生变更时,会执行对应的setup函数

useEffect(()=>{console.log('更新')},[count])

//若依赖项是对象或数组,使用useMemo或useCallback缓存,避免引用变化触发重复执行。

const memoizedConfig = useMemo(() => ({ id }), [id]);
useEffect(() => {
  updateConfig(memoizedConfig);
}, [memoizedConfig]); //仅在id变化时触发

卸载:一般用作dom事件绑定,定时器清除等

//在组件卸载或依赖变化前,取消订阅、定时器或请求
useEffect(() => {
  const timer = setInterval(() => {}, 1000);
  return () => clearInterval(timer); // ✅ 清理定时器
}, []);

//使用AbortController取消未完成的请求
useEffect(() => {
  const controller = new AbortController();
  fetch(url, { signal: controller.signal })
    .then(res => res.json())
    .catch(() => { /* 处理取消错误 */ });
  return () => controller.abort(); // ✅ 取消请求
}, [url]);
 避免无限循环
//避免在useEffect中直接修改依赖项触发的状态,除非有终止条件。
useEffect(() => {
  setCount(prev => prev + 1); //  可能导致循环
}, [count]); 


//仅在必要时更新状态。
useEffect(() => {
  if (data && !isLoaded) {
    setIsLoaded(true); // ✅ 条件阻止循环
  }
}, [data, isLoaded]);
异步操作处理 
//将异步逻辑放在useEffect内
useEffect(() => {
  const fetchData = async () => {
    const result = await axios.get(url);
    setData(result.data);
  };
  fetchData();
}, [url]); //  url变化时重新获取
 多个useEffect执行
// 数据获取
useEffect(() => { ... }, [url]);

// 事件监听
useEffect(() => {
  window.addEventListener('resize', handleResize);
  return () => window.removeEventListener('resize', handleResize);
}, []);
性能优化 
//用useCallback/useMemo减少函数或对象重新创建
const handleClick = useCallback(() => { ... }, [dependency]);

useEffect(() => {
  element.addEventListener('click', handleClick);
  return () => element.removeEventListener('click', handleClick);
}, [handleClick]); // 依赖稳定的回调

//使用ref保存可变值:存储无需触发渲染的值
const intervalRef = useRef();
useEffect(() => {
  intervalRef.current = setInterval(() => { ... });
  return () => clearInterval(intervalRef.current);
}, []);

useEffect执行顺序

 useEffect 是在 render 之后执行的就好解决了

题目一
function App() {
  const [count, setCount] = React.useState(0);
  console.log('before useEffect');
  React.useEffect(() => {
    console.log('useEffect', count);
  });
  
  console.log('after useEffect');
  const handleClick = () => {
      setCount(count + 1)
  }

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={handleClick}>
        Click me
      </button>
    </div>
  );
}

点击一次按钮,打印的日志顺序是怎样的呢?

初始化时,依次打印 'before useEffect', 'after useEffect', 'useEffect 0'
点击一次后,依次打印 'before useEffect', 'after useEffect', 'useEffect 1'

题目二
function App() {
  const [count, setCount] = React.useState(0);
  console.log('before useEffect');
  
  React.useEffect(() => {
    console.log('useEffect', count);
    return () => {
      console.log('useEffect 的 return 回调', count);
    }
  }, [count]);
  console.log('after useEffect');
  
  const handleClick = () => {
      setCount(count + 1)
  }

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={handleClick}>
        Click me
      </button>
    </div>
  );
}

初始化时打印:
"before useEffect"
"after useEffect"
"useEffect" 0


点击一次按钮后:
"before useEffect"
"after useEffect"
"useEffect 的 return 回调" 0
"useEffect" 1

题目三
function App() {
  const [visible, setVisible] = React.useState(true);

  React.useEffect(() => {
    console.log('app useEffect', visible);
    return () => {
      console.log('app useEffect return', visible);
    }
  }, [visible]);
  
  console.log('app render');
  
  const handleClick = () => {
      setVisible((visible) => {
        return !visible;
      });
  }
  
  const Child = () => {
    React.useEffect(() => {
      console.log('child useEffect');
      return () => {
        console.log('child useEffect return');
      }
    }, []);
    console.log('child render');
    return (
      <div>I' m child</div>
    )
  }

  return (
    <div>
      <button onClick={handleClick}>
        Click me toggle child1
      </button>
      {visible ? <Child /> : null}
    </div>
  );
}

初始化时:
"app render"
"child render"
"child useEffect"
"app useEffect" true

第一次点击按钮,隐藏 Child:
"app render"
"child useEffect return"
"app useEffect return" true
"app useEffect" false

题目四
function App() {
  const [count, setCount] = React.useState(0);
  
  const getCount = () => {
    console.log('getCount', count);
    return count;
  }

    console.log('before useEffect');
  
  React.useEffect(() => {
    console.log('useEffect', count);
  }, [getCount()]);
  
  console.log('after useEffect');
  
  const handleClick = () => {
      setCount(count + 1)
  }

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={handleClick}>
        Click me
      </button>
    </div>
  );
}

初始化时:
"before useEffect"
"getCount" 0
"after useEffect"
"useEffect" 0
getCount 这个函数的调用竟然是和 before useEffect, after useEffect 它们一样,当成类似同步代码来执行的。

点击按钮后:
"before useEffect"
"getCount" 1
"after useEffect"
"useEffect" 1

即使我依赖的是一个函数,React 其实也会执行这个函数,并把这个函数的返回值给 useEffect 做比较,再来决定后续要不要执行 useEffect 的回调。

useEffect 和 useLayoutEffect 的区别

useLayoutEffect的作用和 useEffect几乎差不多,useEffect更新可能闪烁,useLayoutEffect不会,一般useEffect使用多
1、useEffect 是在render之后执行的,而useLayoutEffect是在render之前执行的。
2、为了用户体验,一般优先使用 useEffect ,除非useEffect内需要改变 Layout 布局,再考虑使用useLayoutEffect , 因为useLayoutEffect会导致render后执行,间接导致用户要慢几秒才能看到页面。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值