javascript——为什么我不能直接修改组件的状态呢?

ojsjcaue  于 2021-09-23  发布在  Java
关注(0)|答案(7)|浏览(266)

我知道react教程和文档明确地警告说,状态不应该被直接改变,一切都应该经历 setState .
我想知道为什么我不能直接改变状态,然后(在同一个函数中)调用 this.setState({}) 只是为了触发 render .
e、 g:下面的代码似乎工作得很好:

const React = require('react');

const App = React.createClass({
    getInitialState: function() {
        return {
            some: {
                rather: {
                    deeply: {
                        embedded: {
                            stuff: 1
                        }}}}};
    },
    updateCounter: function () {
        this.state.some.rather.deeply.embedded.stuff++;
        this.setState({}); // just to trigger the render ...
    },
    render: function() {
        return (
                <div>
                Counter value: {this.state.some.rather.deeply.embedded.stuff}
                <br></br>
                <button onClick={this.updateCounter}>inc</button>
                </div>
        );
    }
});

export default App;

我完全支持以下约定,但我想进一步了解reactjs实际上是如何工作的,以及上面的代码会出现什么问题或是次优。
下面的注解 this.setState 文档基本上确定了两个问题:
如果你直接改变状态,然后调用 this.setState 这可能会取代(覆盖?)你所做的变异。在上面的代码中,我看不出这是如何发生的。
那个 setState 可能变异 this.state 以异步/延迟的方式有效地访问 this.state 刚打过电话 this.setState 您不能保证访问最终的变异状态。我明白了,如果 this.setState 是更新函数的最后一次调用。

vulvrdjw

vulvrdjw1#

医生为他做了些什么 setState 可以这样说:
永不变异 this.state 直接地 setState() 之后可能会替换你所做的突变。对待 this.state 好像它是不变的。 setState() 不会立即变异 this.state 但会创建一个挂起的状态转换。访问 this.state 调用此方法后,可能会返回现有值。
无法保证对的调用的同步操作 setState 为了提高性能,可以对调用进行批处理。 setState() 将始终触发重新渲染,除非在中实现了条件渲染逻辑 shouldComponentUpdate() . 如果正在使用可变对象,并且逻辑无法在中实现 shouldComponentUpdate() 使命感 setState() 只有当新状态与前一状态不同时,才能避免不必要的重新渲染。
基本上,如果你修改 this.state 直接创建一种情况,这些修改可能会被覆盖。
与您的扩展问题1)和2)相关, setState() 这不是立即的它根据它认为正在发生的情况对状态转换进行排队,其中可能不包括对此状态的直接更改。状态。由于它是排队的,而不是立即应用的,所以完全有可能在这两者之间修改了某些内容,从而覆盖了您的直接更改。
如果没有其他问题,你最好考虑一下,不要直接修改 this.state 可以看作是良好的做法。您个人可能知道,您的代码与react的交互方式不会出现这些过度写入或其他问题,但您正在创建一种情况,其他开发人员或未来的更新可能会突然发现自己有奇怪或微妙的问题。

jfgube3f

jfgube3f2#

这个答案是提供足够的信息,以避免在react中直接改变/变异状态。

react遵循单向数据流。也就是说,react内部的数据流应该并且将被预期为循环路径。
react的数据流没有流量

为了使react像这样工作,开发人员让react类似于函数式编程。函数式编程的经验法则是不可变的。让我解释清楚。
单向流是如何工作的? states 是包含组件数据的数据存储区。
这个 view 组件的属性将基于状态进行渲染。
view 如果需要更改屏幕上的某些内容,则该值应由 store .
要实现这一点,react提供 setState() 接受 object 新的 states 并进行比较和合并(类似于 object.assign() )并将新状态添加到状态数据存储中。
每当状态存储区中的数据发生更改时,react将触发使用 view 消费并在屏幕上显示。
该循环将在组件的整个寿命期内持续。
如果您看到上面的步骤,它清楚地显示了当您更改状态时许多事情正在发生。所以,当你直接改变状态并调用 setState() 用一个空的物体。这个 previous state 会被你的变异所污染。因此,两个状态的浅层比较和合并将受到干扰或不会发生,因为您现在只有一个状态。这将中断react的所有生命周期方法。
因此,您的应用程序将出现异常甚至崩溃。大多数时候,它不会影响你的应用程序,因为我们用来测试它的所有应用程序都非常小。
基因突变的另一个缺点是 ObjectsArrays 在javascript中,当您分配一个对象或数组时,您只是引用该对象或该数组。当您对它们进行变异时,对该对象或该数组的所有引用都将受到影响。react在后台以智能的方式处理此问题,并简单地为我们提供一个api使其工作。
在react中处理状态时发生的最常见错误

// original state
this.state = {
  a: [1,2,3,4,5]
}

// changing the state in react
// need to add '6' in the array

// bad approach
const b = this.state.a.push(6)
this.setState({
  a: b
})

在上面的例子中, this.state.a.push(6) 将直接改变状态。将其分配给另一个变量并调用 setState 与下面显示的内容相同。无论如何,当我们改变状态时,将其分配给另一个变量并调用 setState 用这个变量。

// same as 
this.state.a.push(6)
this.setState({})

很多人都这样做。这是非常错误的。这破坏了react的美观,是一种糟糕的编程实践。
那么,在react中处理状态的最佳方法是什么?让我解释一下。
当您需要更改现有状态中的“某物”时,首先从当前状态获取该“某物”的副本。

// original state
this.state = {
  a: [1,2,3,4,5]
}

// changing the state in react
// need to add '6' in the array

// create a copy of this.state.a
// you can use ES6's destructuring or loadash's _.clone()
const currentStateCopy = [...this.state.a]

现在,变异 currentStateCopy 不会改变原始状态。做手术 currentStateCopy 并使用将其设置为新状态 setState() .

currentStateCopy.push(6)
this.setState({
  a: currentStateCopy
})

这很漂亮,对吗?
通过这样做,所有的引用 this.state.a 在我们使用之前不会受到影响 setState . 这使您可以控制代码,这将帮助您编写优雅的测试,并使您对生产中代码的性能充满信心。
回答你的问题,,
为什么我不能直接修改组件的状态?
是的,你可以。但是,您需要面对以下后果。
当您进行扩展时,您将编写不可管理的代码。
你会失去控制的 state 跨组件。
您将在react上编写自定义代码,而不是使用react。

不变性不是必需的,因为javascript是单线程的。但是,从长远来看,这是一个很好的实践,它将帮助你。

另外,我已经写了大约10000行可变的代码。如果它现在坏了,我不知道去哪里寻找,因为所有的值都在某个地方发生了变异。当我意识到这一点时,我开始编写不可变的代码。相信我!这是你能对产品或应用程序做的最好的事情。
希望这有帮助!

ecbunoof

ecbunoof3#

“最简单的答案”
为什么我不能直接修改组件的状态:
都是关于更新阶段的。
当我们更新组件的状态时,它的所有子组件也将被渲染。或者渲染整个组件树。
但是当我说我们的整个组件树被呈现时,这并不意味着整个dom被更新。当呈现一个组件时,我们基本上会得到一个react元素,所以这就是更新我们的虚拟dom。
react然后会查看虚拟dom,它还有一个旧虚拟dom的副本,这就是为什么我们不应该直接更新状态,这样我们可以在内存中有两个不同的对象引用,我们有旧虚拟dom和新虚拟dom。
然后react将找出发生了什么变化,并在此基础上相应地更新真实的dom。
希望能有帮助。

8yoxcaq7

8yoxcaq74#

避免每次创建一个 this.state.element 您可以将更新与一起使用 $set or $push 或者来自不变性助手的许多其他人
例如。:

import update from 'immutability-helper';

const newData = update(myData, {
  x: {y: {z: {$set: 7}}},
  a: {b: {$push: [9]}}
});
wwodge7n

wwodge7n5#

让我惊讶的是,目前的答案中没有一个是关于纯/备忘录组件的。这些组件仅在检测到其中一个道具的更改时重新渲染。
假设您直接改变状态,并将过度耦合对象传递给下面的组件,而不是值。此对象仍具有与前一个对象相同的引用,这意味着纯/memo组件不会重新渲染,即使您修改了其中一个属性。
由于从库中导入组件时,您并不总是知道使用的是哪种类型的组件,因此这也是坚持非变异规则的另一个原因。
下面是这种行为的一个示例(使用 R.evolve 要简化创建副本和更新嵌套内容,请执行以下操作:

class App extends React.Component {
  state = {some: {rather: {deeply: {nested: {stuff: 1}}}}};

  mutatingIncrement = () => {
    this.state.some.rather.deeply.nested.stuff++;
    this.setState({});
  }
  nonMutatingIncrement = () => {
    this.setState(R.evolve(
      {some: {rather: {deeply: {nested: {stuff: n => n + 1}}}}}
    ));
  }

  render() {
    return <div>
      Pure Component: <PureCounterDisplay {...this.state} />
      <br />
      Normal Component: <CounterDisplay {...this.state} />
      <br />
      <button onClick={this.mutatingIncrement}>mutating increment</button>
      <button onClick={this.nonMutatingIncrement}>non-mutating increment</button>
    </div>;
  }
}

const CounterDisplay = (props) => (
  <React.Fragment>
    Counter value: {props.some.rather.deeply.nested.stuff}
  </React.Fragment>
);
const PureCounterDisplay = React.memo(CounterDisplay);

ReactDOM.render(<App />, document.querySelector("#root"));
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/ramda@0/dist/ramda.min.js"></script>
<div id="root"></div>
3lxsmp7m

3lxsmp7m6#

设置状态触发组件的重新呈现。当我们想要一次又一次地更新状态时,我们必须设置状态,否则它无法正常工作。

xyhw6mcr

xyhw6mcr7#

我目前的理解基于此和以下答案:
如果你不使用 shouldComponentUpdate 或任何其他生命周期方法(如 componentWillReceiveProps , componentWillUpdate ,及 componentDidUpdate )比较新旧道具/状态的地方
然后
变异很好 state 然后打电话 setState() ,否则就不好了。

相关问题