useState 和 useEffect 有啥区别?

Posted

技术标签:

【中文标题】useState 和 useEffect 有啥区别?【英文标题】:What’s the difference between useState and useEffect?useState 和 useEffect 有什么区别? 【发布时间】:2019-04-12 15:15:44 【问题描述】:

我已经看到了 react v16 中引入的这两个新概念。

据我了解:

useState 类似于带有钩子的setStateuseEffect 的工作方式类似于生命周期方法。

我的理解正确吗?如果不是,useStateuseEffect 之间的确切区别是什么?

【问题讨论】:

【参考方案1】:

简单地说,useStateuseEffect 都增强了函数式组件,使它们能够做类可以做但函数式组件(没有钩子)不能做的事情:

useState 允许功能组件具有状态,例如类组件中的this.stateuseEffect 允许功能组件在一个 API 中拥有生命周期方法(例如 componentDidMountcomponentDidUpdatecomponentWillUnmount)。

请参阅以下示例以获得进一步说明:

useState

class CounterClass extends React.Component 
  constructor(props) 
    super(props);
    this.state =  count: 1 ;
  
  
  render() 
    return <div>
      <p>Count: this.state.count</p>
      <button onClick=() => this.setState( 
        count: this.state.count + 1
      )>Increase</button>
    </div>;
  


function CounterFunction() 
  const [count, setCount] = React.useState(1);
  return (
    <div>
      <p>Count: count</p>
      <button onClick=() => 
        setCount(count + 1)
      >Increase</button>
    </div>
  );


ReactDOM.render(
  <div>
    <CounterClass />
    <CounterFunction />
  </div>
, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>

useEffect

class LifecycleClass extends React.Component 
  componentDidMount() 
    console.log('Mounted');
  
  
  componentWillUnmount() 
    console.log('Will unmount');
  
  
  render() 
    return <div>Lifecycle Class</div>;
  


function LifecycleFunction() 
  React.useEffect(() => 
    console.log('Mounted');
    return () => 
      console.log('Will unmount');
    ;
  , []); // Empty array means to only run once on mount.
  return (
    <div>Lifecycle Function</div>
  );


ReactDOM.render(
  <div>
    <LifecycleClass />
    <LifecycleFunction />
  </div>
, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>

在官方 React 文档中了解更多关于 useState 和 useEffect 的信息。

【讨论】:

【参考方案2】:

对于useState()

首先,我们有不支持state功能组件,换句话说,一个功能组件是一个无状态组件 .

现在,有了 Hooks,我们有了功能组件,但有状态。它是通过使用useState 来实现的。


对于useEffect()

首先,对于无状态功能组件,我们没有组件生命周期钩子。换句话说,当你想使用组件生命周期钩子时,你应该考虑使用类组件

现在,我们可以在不使用类组件的情况下使用组件生命周期钩子。它是通过使用useEffect 来实现的。换句话说,现在每当我们想使用组件生命周期钩子时,我们已经有两种选择,要么使用类组件,要么使用带有useEffect的钩子。


更新

useStateuseEffect 之间的确切区别是什么?

简单来说,useState 让我们的 功能组件 曾经是 无状态 变为 有状态。而useEffect 允许我们的功能组件利用组件生命周期钩子,这些钩子在过去仅支持类组件

【讨论】:

【参考方案3】:

useStateuseEffect 是 React 16.8+ hooks 生态系统的一部分,旨在为功能组件提供以前仅可用于基于类的组件(state / setState 和组件生命周期方法(例如componentDidMountcomponentDidUpdatecomponentWillUnmount)

useState() 直截了当,它允许您在功能组件中拥有状态访问器。

useEffect() 可以组合componentDidMountcomponentDidUpdatecomponentWillUnmount,但很棘手。

您可以从hooks 的官方文档中解读我在此讨论的大部分内容。看到工作中的钩子比从文本中推理更容易。

预渲染生命周期

预渲染生命周期事件相当于componentWillReceivePropsgetDerivedStateFromPropscomponentWillMount可以只是我们在返回JSX(react-node)之前首先在功能组件中做的事情。函数本身相当于基于类的组件的render(…)方法。

我们不需要处理预渲染生命周期事件的钩子。

渲染后生命周期

渲染后生命周期事件,相当于基于类的组件中的componentDidMountcomponentDidUpdatecomponentDidUnmount

我们需要 ****_useEffect(…)_** 处理这些渲染后生命周期事件** 因为我们无法编写逻辑与主组件函数内的这些生命周期事件相关联,因为这些事件应该在组件函数将 JSX(react-node)返回到 react-dom 渲染器之后运行。

这意味着,我们可以用钩子做很多事情。怎么样?

我们知道useEffect(fn, […watchStates]),接受2个参数。

    fn:(必需)useEffect 调用此函数以在每个渲染周期后根据 (2) 参数给出的更改跟踪值作为副作用运行。函数fn,可以返回另一个函数,该函数应该在效果函数再次运行或组件卸载之前作为清理运行 […watchValues ]:(可选)useEffect 跟踪此数组中的值已从上一个渲染周期更改,然后仅调用效果 fn。如果没有给出这个参数,效果将在每个渲染周期运行。

如果我们不一起传递 (2) 参数,fn 中的效果逻辑将在每个渲染周期后被调用。

如果我们传递 (2) 带有值的数组,组件需要监视更改,并在更改时调用 fn,这很不言自明。

最棘手的部分是使用空数组 [] 作为 (2) 参数,我们可以限制 fn 中的副作用逻辑仅在安装阶段执行,因为没有变化效果挂钩会观察在随后的渲染周期之后再次触发fn

import React,  useState, useEffect  from "react";
export default props => 
  console.log("componentWillMount");
  console.log("componentWillReceiveProps", props);
  const [x, setX] = useState(0);
  const [y, setY] = useState(0);
  const [moveCount, setMoveCount] = useState(0);
  const [cross, setCross] = useState(0);
  const mouseMoveHandler = event => 
    setX(event.clientX);
    setY(event.clientY);
  ;
  useEffect(() => 
    console.log("componentDidMount");
    document.addEventListener("mousemove", mouseMoveHandler);
    return () => 
      console.log("componentDidUnmount");
      document.removeEventListener("mousemove", mouseMoveHandler);
    ;
  , []); // empty-array means don't watch for any updates
  useEffect(
    () => 
      // if (componentDidUpdate & (x or y changed))
      setMoveCount(moveCount + 1);
    ,
    [x, y]
  );
  useEffect(() => 
    // if componentDidUpdate
    if (x === y) 
      setCross(x);
    
  );
  return (
    <div>
      <p style= color: props.color >
        Your mouse is at x, y position.
      </p>
      <p>Your mouse has moved moveCount times</p>
      <p>
        X and Y positions were last equal at cross, cross
      </p>
    </div>
  );
;

代码 sn-p 简单易懂。您可以在CodePen 上试用。

需要注意的重要一点是,如果您要在效果内进行状态更改,请确保从观察数组中排除正在更改的状态。

例如,在第二个效果(计算鼠标移动的效果)中,我们只在 x 和 y 更新时触发它,通过传递 [x , y] 作为第二个参数,因为

    观察 x 和 y 的变化以记录鼠标移动在逻辑上是正确的 如果我们不排除 moveCount 被监视,这个 useEffect 将进入无限循环,因为我们将更新我们也在监视更改的相同值

这篇文章也可以在我的Medium 出版物上找到。如果您喜欢这篇文章,或者有任何 cmets 和建议,请拍手或在Medium留下 cmets .

【讨论】:

我喜欢你的回答,因为它的详细解释很好。但是我看到您的 CodePen 示例中有 2 个与 useEffect 相关的 eslint 警告。有没有更好的方法来构建代码以消除这些警告?如果您能提出一种清除这些警告的方法,我会支持这个答案。

以上是关于useState 和 useEffect 有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

关于 React.useEffect() 和 React.useState() 的问题

useState和useEffect

使用 useEffect 与 onAuthStateChanged 检测 Firebase 用户更改 - 有啥区别?

Firebase 和 React Hooks(useState 和 useEffect)

useMemo 与 useEffect + useState

React Hooks useEffect useState userContext userReducer应用项目