【问题标题】:Avoid using callback in setState when referencing the previous state避免在引用前一个状态时在 setState 中使用回调
【发布时间】:2020-11-26 16:09:59
【问题描述】:

我仍然收到this error,但我不明白我的代码怎么可能:

export default class GenericTable extends React.PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      otherStuff: '',
      ...props, // rows are received on props
      sort: {
        col: 0,
        asc: true,
      },
    };
  }

  onSortChange = i => {
    const tempRows = this.state.rows;
    const tempSort = this.state.sort; // this is the line where it says about that error

    const newRows = [];
    if (tempSort.asc) {
      newRows = tempRows.sort(function(a, b) {
        if (a.cells[i] < b.cells[i]) {
          return -1;
        }
        if (a.cells[i] > b.cells[i]) {
          return 1;
        }
        return 0;
      });
    } else {
      newRows = tempRows.sort(function(a, b) {
        if (a.cells[i] > b.cells[i]) {
          return -1;
        }
        if (a.cells[i] < b.cells[i]) {
          return 1;
        }
        return 0;
      });
    }
    tempSort.col = [i];
    tempSort.asc = !tempSort.asc;
    this.setState({ rows: newRows, sort: tempSort });
  };
...
}

所以,这是声明tempSort 的地方。应该如何更改才能正常工作?

我没有看到与 eslint 页面上描述的相似之处。

【问题讨论】:

  • 所以你的 tempSort 设置为旧状态?
  • 您的意思是复制对象而不是就地改变它吗? const tempSort = {...this.state.sort}?
  • @Aprillion,我想这和 Rosen Dimov 说的一样,但不起作用
  • 这个问题看起来非常适合阅读我们一直在讨论的状态突变。请务必检查它,因为它超出了您的问题范围(并且没有必要重新解释那里已经很好解释的东西)。 ;)

标签: javascript reactjs ecmascript-6 eslint setstate


【解决方案1】:

@Leo Messi,你不是应该准备周六对阵那不勒斯的比赛吗? :)

一边开玩笑……

您需要使用this form of setState,它接受使用先前状态调用的函数,以便函数内部的逻辑在相同状态下运行。最后它应该返回新计算的状态。

import React from 'react';

export default class MyComponent extends React.PureComponent {
    constructor(props) {
        super(props);

        this.state = {
            otherStuff: '',
            ...props, // rows are received on props
            sort: {
                col: 0,
                asc: true
            }
        };
    }

    getNewState(prevState, i) {
        const tempSort = prevState.sort;
        const tempRows = this.state.rows;
        let newRows = [];

        if (tempSort.asc) {
            newRows = tempRows.sort(function(a, b) {
                if (a.cells[i] < b.cells[i]) {
                    return -1;
                }
                if (a.cells[i] > b.cells[i]) {
                    return 1;
                }
                return 0;
            });
        } else {
            newRows = tempRows.sort(function(a, b) {
                if (a.cells[i] > b.cells[i]) {
                    return -1;
                }
                if (a.cells[i] < b.cells[i]) {
                    return 1;
                }
                return 0;
            });
        }
        tempSort.col = [i];
        tempSort.asc = !tempSort.asc;
        return { rows: newRows, sort: tempSort };
    }

    onSortChange = i => {
        this.setState(prevState => {
            return this.getNewState(prevState, i);
        });
    };

    render() {
        return <p>test</p>;
    }
}

在我的 VS Code 上进行了测试,并将相关规则添加到我的 .eslintrc.js。但是,请记住,将您的状态计算逻辑放在带有 prevState 参数的版本中并不会阻止您引入其他问题(正如我在之前的答案版本中提到的那样,不是创建新对象,而是改变现有的那些,第一个是预期的。我删除了答案的那部分,因为它实际上并没有解决那个特定的问题,但我建议你在编辑历史记录中再次检查它。

【讨论】:

  • 所以基本上你说用const tempSort = {...this.state.sort}; 替换const tempSort = this.state.sort; - 如果是这样,它在我的情况下不起作用。它仍然返回相同的错误。我还用const tempRows = [...this.state.rows]; 替换了const tempRows = this.state.rows;,但错误说与tempSort 一致
  • 好的,稍后我会尝试在代码沙箱中重现它。或者您已经有一个可以显示问题的沙盒?
  • 很遗憾我没有,但我可以将整个文件粘贴到这里:pastebin.com/idWCQkLN
  • 我更新了我的答案,它不再给我 VS Code 中的 ESLint 错误,但我还没有测试代码在运行时的行为,但我猜它应该是等效的。删除了关于状态突变和创建新对象的解释,因为这实际上并不能解决 ESLint 的问题,但我建议你检查它们并牢记它们(它们可以在答案的编辑历史中看到)。
  • @Aprillion - 我解释了为什么我从我的答案中删除了关于变异状态的 cmets。这是一个不同的主题,会使答案非常冗长。
【解决方案2】:

React Main Concepts - 5. State and Lifecycle 列出规则 关注:

正确使用状态

关于setState(),你应该知道三件事。

不要直接修改状态

例如,这不会重新渲染组件:

// Wrong
this.state.comment = 'Hello';

改为使用setState():

// Correct
this.setState({comment: 'Hello'});

您可以分配this.state 的唯一位置是构造函数。

状态更新可能是异步的

React 可以将多个 setState() 调用批处理到一个更新中 性能。

因为this.propsthis.state可能会异步更新, 你不应该依赖它们的值来计算下一个状态。

例如,这段代码可能无法更新计数器:

// Wrong
this.setState({
  counter: this.state.counter + this.props.increment,
});

要修复它,请使用接受函数的setState() 的第二种形式 而不是一个对象。该函数将接收先前的状态 作为第一个参数,更新时的道具是 作为第二个参数应用:

// Correct
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));

我们在上面使用了arrow function,但它也适用于常规 功能:

// Correct
this.setState(function(state, props) {
  return {
    counter: state.counter + props.increment
  };
});

合并状态更新

当你调用setState() 时,React 会将你提供的对象合并到 当前状态。

例如,您的状态可能包含几个独立变量:

constructor(props) {
  super(props);
  this.state = {
    posts: [],
    comments: []
  };
}

然后您可以使用单独的setState() 独立更新它们 调用:

componentDidMount() {
  fetchPosts().then(response => {
    this.setState({
      posts: response.posts
    });
  });

  fetchComments().then(response => {
    this.setState({
      comments: response.comments
    });
  });
}

合并很浅,所以this.setState({comments})离开 this.state.posts 完好无损,但完全替换 this.state.comments.

使用您提供的代码 sn-p 您打破了其中两个规则。让我们 首先处理导致 JSLint 警告的那个:

防止在 this.setState 中使用 this.state (react/no-access-state-in-setstate)

您收到此警告的原因是:

状态更新可能是异步的......因为this.propsthis.state 可能会异步更新,不要依赖 它们的值用于计算下一个状态。

newRowstempSort 都基于之前的状态。这 JSLint 警告说你应该使用的回调变体 setState().

onSortChange = i => {
  this.setState(({ rows: tempRows, sort: tempSort }) => {
    // sort
    return { rows: newRows, sort: tempSort };
  });
};

处理完警告后,让我们看看另一个问题 sn-p。 sort() 对元素进行就地排序,这意味着 原作修改。同样tempSort.col = [i]tempSort.asc = !tempSort.asc 也都修改当前状态。 分配一个 新变量的对象不会创建副本。这两个变量都会 简单地引用同一个对象。

const object_1 = { a: 1 };
const object_2 = object_1;

object_2.a = 2;

console.log("object_1", object_1);
console.log("object_2", object_2);

确保在改变对象或使用之前复制对象 就地方法。

 const tempRows = [...this.state.rows];
 const tempSort = {...this.state.sort};

在将数组/对象分配给之前,两者都创建了一个浅表副本 一个变量。

如上所述,解决方案可能如下所示:

/* Comparator factories. sort[true] is ascending, sort[false] is descending.
 * This factory accepts a function that is called for both a and b,
 * comparing their return values.
 *
 *     items.sort(sort[true](item => item.a))
 *
 * Sorts an array of items based on their "a" property in ascending order.
 */
const sort = {
   true: (fn) => (a, b) => (a = fn(a), b = fn(b), -(a < b) || +(a > b)),
  // flip a, b arguments for descending order
  false: (fn) => (a, b) => sort[true](fn)(b, a),
};

class GenericTable extends React.Component {
  constructor(props) {
    super(props);
    this.state = { ordered: props.data };
  }
    
  handler(i) {
    // use a callback because the new state depends on the old state
    this.setState(({ordered, i: iPrevious, asc}) => {
      asc = i != iPrevious || !asc;
      return { 
        i, asc,
        // copy `ordered` before using the in-place `sort` method
        ordered: [...ordered].sort(sort[asc](row => row[i]))
      };
    });
  }

  render() {
    return (
      <table>
        <thead>
          <tr>{this.props.headers.map((header, i) => (
            <th key={i} onClick={() => this.handler(i)}>{header}</th>
          ))}</tr>
        </thead>
        <tbody>
          {this.state.ordered.map((row, i) => (
            <tr key={i}>
              {row.map((cell, i) => <td key={i}>{cell}</td>)}
            </tr>
          ))}
        </tbody>
      </table>
    );
  }
}

ReactDOM.render(
  <GenericTable
    headers={["a", "b", "c"]}
    data={[[1, 5, 10], [6, 11, 2], [12, 3, 7]]}
  />,
  document.querySelector("#root")
);
th{cursor: pointer}th,td{border:1px solid black}
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<div id="root"></div>

【讨论】:

    猜你喜欢
    • 2019-09-25
    • 2020-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-16
    • 2019-08-04
    • 2017-06-22
    相关资源
    最近更新 更多