我们相信:世界是美好的,你是我也是。平行空间的世界里面,不同版本的生活也在继续...

setInterval或者setTimeout之类的异步函数,在react项目里面,都被称之为副作用。意思是这些函数可以超出react的控制范围而存在,需要在合适的时机对之进行清除处理。react对于这种情况,给出的方案是useEffect。但是,useEffect并不是专用于清除副作用的,它可以类比成原来的react生命周期。这样的话,useEffect就可以理解为:在恰当的时机做恰当的代码逻辑。

苏南大叔:react项目,函数组件如何使用生命周期?useEffect与副作用 - 函数式组件使用生命周期
react项目,函数组件如何使用生命周期?useEffect与副作用(图4-1)

大家好,这里是苏南大叔的程序如此灵动博客,这里记录苏南大叔和计算机代码的故事。本文又说的是函数式组件,写类式组件的可以不用看本文了。测试环境:create-react-app@5.0.1react@18.2.0react-dom@18.2.0node@16.14.2,react-router-dom@6.4.3

导入useEffect

useEffectreact的函数式组件里面的概念。如果想要正确使用useEffect,首先要引入:

import { useEffect } from "react";

当然,大多数时候,是要和其它类似函数一起导入。例如:

import { useState, useRef, useEffect } from "react";

useEffect(func,when),共两个参数。第一个参数是个可执行函数逻辑,第二个参数表明监控的时机。

[]监控组件挂载和卸载

这个是最为推崇的写法,组件挂载的时候,执行副作用函数逻辑。在组件卸载的时候,要对其进行清除。

useEffect(() => {
  // componentDidMount,挂载,执行副作用
  return () => {
    // componentWillUnmount,将要卸载,清除副作用
  }
}, []);  // 传空数组,才会引发此监控

这里,第二个参数传递的是[],将要引发对组件挂载和组件卸载的监控。下面的是个例子,逻辑是:组件挂载执行setInterval,会持续输出字符0,卸载的时候,清除setInterval,停止输出字符0

苏南大叔:react项目,函数组件如何使用生命周期?useEffect与副作用 - 挂载和卸载
react项目,函数组件如何使用生命周期?useEffect与副作用(图4-2)

const [count, setCount] = useState(0);
const timerRef = useRef(null);
useEffect(() => {
  // componentDidMount
  console.log("挂载完成");
  if (timerRef.current === null) {
    timerRef.current = setInterval(() => {
      setCount((count) => (count + 1) % 6);
      console.log(count); //总是0
    }, 1000);
  }
  return () => {
    // componentWillUnmount
    console.log("即将卸载,console里面应该不继续输出");
    clearInterval(timerRef.current);
  }
}, []);

这里逻辑的正确理解,需要参考下面的文字:

监控某个或某组状态变量

这里就是useEffect的第二种使用方法,就是当某个或某组【stateprops】变量发生变化的时候,就执行某个逻辑,(不一定是副作用逻辑)。如果是一组变量的话,变某一个或者某几个,都会触发监控逻辑。

在网络上的教程中,似乎没人强调这里的变量是什么样的变量。在苏南大叔的实践中,得出的结论是:这些用于监控的变量最好是状态(或props)变量。

代码范例:

const [count, setCount] = useState(0);
//...
const [odd, checkOdd] = useState("偶数");
useEffect(() => {
  if (count % 2 == 0) {
    checkOdd("偶数");
  }
  else {
    checkOdd("奇数");
  }
}, [count]) // 最好是个状态变量或者`props`值

这个例子结合上个例子进行理解,组件挂载的时候,每隔一秒改变一下count。当状态值[count]发生变化后,这里来检测一下是奇数还是偶数。监控的状态变量多的话,就修改一下第二个数组参数[a,b,c]就可以了。

苏南大叔:react项目,函数组件如何使用生命周期?useEffect与副作用 - 监控变量
react项目,函数组件如何使用生命周期?useEffect与副作用(图4-3)

无条件执行

当第二个参数什么都不传递的时候,也就是说连空数组都不传递的时候,就是无条件执行了。既然都无条件执行了,那么useEffect()还有啥意义?当然,也有说法是挂载和更新的时候会执行,那么,不还是无条件执行么?难道还有其它触发时机?欢迎留言。

下面的代码是个范例:

const isMounted = useRef(null);
useEffect(() => {
  // 函数操作每次都会执行,写不写useEffect,都没有啥区别
  if (isMounted.current) {
    // componentDidUpdate
    console.log("更新")
  } else {
    // componentDidMount
    console.log("挂载")
    isMounted.current = true;
  }
})

这个代码使用ref.current来区分了一些是更新还是挂载。

苏南大叔:react项目,函数组件如何使用生命周期?useEffect与副作用 - 区分更新和挂载
react项目,函数组件如何使用生命周期?useEffect与副作用(图4-4)

对于useEffect(() => {})useEffect(() => {},[])的区别,前者是无条件执行(包括挂载和更新)。后者是挂载和卸载的时候执行,更新的时候不执行。

代码挂载更新卸载
useEffect(() => {A;})挂载执行A更新执行A---------------
useEffect(() => {B;return{C};},[])挂载执行B------------卸载执行B

比如:页面执行完毕后(执行AB),某个按钮执行更新state,然后组件更新,就会执行useEffect(() => {A;}),而不会执行useEffect(() => {B;return{C};},[])中的B。当该组件被卸载的时候,会执行C

完整代码

完整代码中,使用了react-route,通过加载新的空组件的方式,触发目标组件的卸载,进而消除副作用。
react-router的使用方法:

index.js:

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { BrowserRouter as Router } from "react-router-dom";

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  // <React.StrictMode>
  <Router>
    <App />
  </Router>
  // </React.StrictMode>
);

app.js:

import './App.css';
import { Routes, Route, Link} from "react-router-dom";
import Child from './Child';
import Child9 from './Child9';

function App() {
  return (
    <div className="App">
      <nav>
          <Link to="/">函数式组件</Link>
          &nbsp;&nbsp;
          <Link to="/other">空组件</Link>
        </nav>
        <Routes>
          <Route index element={<Child />} />
          <Route path="other" element={<Child9 />} />
        </Routes>
    </div>
  );
}
export default App;

Child.js:

import { useState, useRef, useEffect } from "react";

function Child(props) {
  const isMounted = useRef(null);
  useEffect(() => {
    // 函数操作每次都会执行,写不写useEffect,都没有啥区别
    if (isMounted.current) {
      // componentDidUpdate
      console.log("更新")
    } else {
      // componentDidMount
      console.log("挂载")
      isMounted.current = true;
    }
  })
  const [count, setCount] = useState(0);
  const timerRef = useRef(null);
  useEffect(() => {
    // componentDidMount
    console.log("挂载完成");
    if (timerRef.current === null) {
      timerRef.current = setInterval(() => {
        setCount((count) => (count + 1) % 6);
        console.log(count); //总是0
      }, 1000);
    }
    return () => {
      // componentWillUnmount
      console.log("即将卸载,console里面应该不继续输出");
      clearInterval(timerRef.current);
    }
  }, []);
  const [odd, checkOdd] = useState(0);
  useEffect(() => {
    if (count % 2 == 0) {
      checkOdd("偶数");
    }
    else {
      checkOdd("奇数");
    }
  }, [count]) // 最好是个状态变量或者一个props值
  return (
    <div className="Child">
      {count}{odd}
    </div>
  );
}
export default Child;

Child9.js:

import React, { Component } from 'react'

export default class Child9 extends Component {
  render() {
    return (
      <div>空的组件</div>
    )
  }
}

结束语

react里面的函数式组件,使用生命周期的方式就是useEffect,只不过使用方式比较隐晦而已。更多react经验文章,请点击苏南大叔的博客文章:

如果本文对您有帮助,或者节约了您的时间,欢迎打赏瓶饮料,建立下友谊关系。
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。

 【福利】 腾讯云最新爆款活动!1核2G云服务器首年50元!

 【源码】本文代码片段及相关软件,请点此获取更多信息

 【绝密】秘籍文章入口,仅传授于有缘之人   react