简体   繁体   English

功能组件中useEffect()中的useState()

[英]useState() within useEffect() in a functional component

I tried to change a class based component into a functional component and run into troubles when I tried to read state properties inside a handler function attached with useEffect() 我尝试将基于类的组件更改为功能组件,并在尝试读取附加有useEffect()的处理函数中的状态属性时遇到麻烦

Inside the class based component I'll attach the handler method inside componentDidMount and this handler has access to the state. 该类基于组件里面,我会附上里面的处理方法componentDidMount这个处理程序可以访问的状态。

The rendered output does show the correct values in both types of components! 渲染的输出在两种类型的组件中均显示正确的值!

But when I need the current value of the state property inside my handler to calculate the new state values I run into troubles reading the state properties inside functional components. 但是,当我需要处理程序内部的state属性的当前值来计算新的状态值时,我会遇到读取功能组件内部的state属性的麻烦。

I created following example to demonstrate the problem: https://codesandbox.io/s/peaceful-wozniak-l6w2c (open console and scroll) 我创建了以下示例来演示该问题: https : //codesandbox.io/s/peaceful-wozniak-l6w2c (打开控制台并滚动)

If you click on the components both work fine: 如果您单击两个组件都可以正常工作:

  • console outputs the current value of the state property counter 控制台输出状态属性counter的当前值
  • incremenet the state property counter 扩大国家财产counter

If you scroll only the class based component works: 如果滚动仅基于类的组件有效:

  • console outputs the current value of the state property position 控制台输出状态属性position的当前值
  • update the state property position 更新状态属性position

But the console output from the functional components only returns me the initial state property value. 但是功能组件的控制台输出仅向我返回初始状态属性值。

class based component 基于类的组件

import React, { Component } from "react";

export default class CCTest extends Component {
  constructor(props) {
    super(props);
    this.state = { position: 0, counter: 0 };
    this.handleScroll = this.handleScroll.bind(this);
    this.handleClick = this.handleClick.bind(this);
  }

  handleScroll(e) {
    console.log(
      "class.handleScroll()",
      this.state.position
    );
    this.setState({ position: document.body.getBoundingClientRect().top });
  }

  handleClick() {
    console.log("[class.handleClick]", this.state.counter);
    this.setState(prevState => ({ counter: prevState.counter + 1 }));
  }

  componentDidMount() {
    window.addEventListener("scroll", this.handleScroll);
  }

  render() {
    return (
      <div
        style={{
          backgroundColor: "orange",
          padding: "20px",
          cursor: "pointer"
        }}
        onClick={this.handleClick}
      >
        <strong>class</strong>
        <p>
          position: {this.state.position}, counter: {this.state.counter}
        </p>
      </div>
    );
  }
}

functional component 功能组件

import React from "react";

export default prop => {
  const [position, setPosition] = React.useState(0);
  const [counter, setCounter] = React.useState(0);

  React.useEffect(() => {
    window.addEventListener("scroll", handleScroll);
    return () => {
      window.removeEventListener("scroll", handleScroll);
    };
    // eslint-disable-next-line
  }, []);

  const handleScroll = () => {
    console.log(
      "function.handleScroll()",
      position
    );
    setPosition(document.body.getBoundingClientRect().top);
  };

  const handleClick = () => {
    console.log("[function.handleClick]", counter);
    setCounter(counter + 1);
  };

  return (
    <div
      style={{ backgroundColor: "green", padding: "20px", cursor: "pointer" }}
      onClick={handleClick}
    >
      <strong>function</strong>
      <p>
        position: {position}, counter: {counter}
      </p>
    </div>
  );
};

Every little hint helps <3 每个小提示都会帮助<3

Every time your component renders, a new handleScroll function is created, which has access through the component closure to the current state. 每次渲染组件时,都会创建一个新的handleScroll函数,该函数可以通过组件闭包访问当前状态。 But this closure doesn't get updated on every render; 但是这种关闭不会在每个渲染器上都得到更新。 instead, a new handleScroll function is created, which sees the new values. 而是创建一个新的handleScroll函数,该函数可以查看新值。

The problem is that when you do: 问题是您这样做时:

window.addEventListener("scroll", handleScroll);

You're binding the current version of the function to the event, and it will always read the values at that time, instead of new ones. 您将函数的当前版本绑定到该事件,并且该事件将始终在该时间读取值,而不是新值。 Normally, useEffect would be run again and a new instance of the function would be used, but you're preventing it with the empty array as the second parameter. 通常,useEffect将再次运行,并且将使用该函数的新实例,但是您要使用空数组作为第二个参数来阻止它。

There is a linter warning just for these cases, but you have disabled it: 仅在这些情况下会有棉绒警告,但您已将其禁用:

// eslint-disable-next-line

If you remove this empty array, you can remove the linter disabling rule, and it will work properly. 如果删除此空数组,则可以删除禁用linter规则,该规则将正常运行。

Other options 其他选择

When having the function change every time is not an option (for example, when using non-react libraries, or for performance reasons), there are other alternatives: 如果无法每次都更改功能(例如,使用非反应库或出于性能原因),则还有其他选择:

  • You can use a ref instead of state. 您可以使用ref代替state。 Refs get mutated instead of a new one being created on every render. 引用会发生突变,而不是在每个渲染上都创建一个新引用。 This allows previous versions of the function to read the current value, and modify it, even if you don't update the function. 即使您不更新功能,这也允许该功能的先前版本读取当前值并进行修改。

  • You can use useReducer . 您可以使用useReducer The dispatch function never changes, so if your useEffect depends on it, it won't be re-run. 调度功能永远不会改变,因此,如果您的useEffect依赖它,它将不会重新运行。 The reducer can access both the previous value and the action, so it can calc the new state even if it depends on the previous one. Reducer可以访问先前的值和操作,因此即使依赖于先前的值,它也可以计算新状态。

Thanks to the help of @Anxo I ended up ditching useState and utilize useRecuder instead. 多亏了@Anxo的帮助,我最终放弃了useStateuseRecuder

This is my functional component for the specific example in the initial question: 这是第一个问题中特定示例的功能组件

import React from 'react';

const reducer = (state, action) => {
  switch (action.type) {
    case 'UPDATE_POSITION':
      return {
        ...state,
        position: document.body.getBoundingClientRect().top
      };
    case 'INCREMENT_COUNTER':
      return {
        ...state,
        counter: state.counter + 1
      };
    default:
      throw new Error('chalupa batman');
  }
};

const initialState = {
  position: 0,
  counter: 0
};

const FCTest = props => {
  const [state, dispatch] = React.useReducer(reducer, initialState);

  React.useEffect(() => {
    window.addEventListener('scroll', () => {
      dispatch({ type: 'UPDATE_POSITION' });
    });
    return () => {
      window.removeEventListener(
        'scroll',
        dispatch({ type: 'UPDATE_POSITION' })
      );
    };
    // eslint-disable-next-line
  }, []);

  return (
    <div
      style={{ backgroundColor: 'green', padding: '20px', cursor: 'pointer' }}
      onClick={() => dispatch({ type: 'INCREMENT_COUNTER' })}
    >
      <strong>function</strong>
      <p>
        position: {state.position}, counter: {state.counter}
      </p>
    </div>
  );
};

export default FCTest;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 使用 useState 重新渲染功能性 React 组件并确保执行 useEffect 钩子 - Rerender a functional React component using useState and ensure useEffect hook executed 使用 Axios 和 useState/useEffect 无限重新渲染 React 功能组件? - Infinite Re-rendering of React Functional Component using Axios and useState/useEffect? 在功能组件内的功能组件中调用 useEffect 会导致此消息:Rendered more hooks than the previous render - Calling useEffect in a functional component within a functional component causes this message: Rendered more hooks than during the previous render useEffect 和 useState 不重新渲染 React 组件 - useEffect with useState not rerendering React component 以前的 state 在 useState 功能组件中 - Previous state in a useState functional component 调用 state 更新 function 在 state 更新 ZC1C425268E68384 组件4F 中使用功能状态钩子1 - Calling a state update function within a state update function - useState hook in React Functional component 为什么 useEffect 中的 useState 在 React 中不起作用? - Why is useState within useEffect not working in React? 根据条件渲染依赖于 useState 的功能组件 - Render functional component that is reliant on useState, on condition React useState boolean 问题(功能组件) - React useState boolean issue (functional component) react挂钩中的useState转换为功能组件 - useState in react hooks converting to functional component
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM