Mar*_*tus 55 javascript reactjs
据我所知,React教程和文档毫不含糊地警告说,状态不应该直接变异,并且一切都应该通过setState.
我想明白为什么,确切地说,我不能直接改变状态,然后(在同一个函数中)调用this.setState({})只是为了触发render.
例如:以下代码似乎工作得很好:
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;
Run Code Online (Sandbox Code Playgroud)
我完全遵循以下约定,但我想进一步了解ReactJS如何实际运行,哪些可能出错或者是否与上述代码不是最佳的.
this.setState文档下的注释基本上确定了两个陷阱:
this.setState它可能会替换(覆盖?)你所做的突变.我不知道在上面的代码中会发生这种情况.setState可能会this.state以异步/延迟的方式有效地改变,因此this.state在调用后this.setState立即访问时,您无法保证访问最终的突变状态.我知道,如果this.setState是更新功能的最后一次调用,这不是问题.Pra*_*avi 37
React遵循单向数据流.意思是,反应中的数据流应该并且预计将处于循环路径中.
React的数据流没有流量
为了使React像这样工作,开发人员使React类似于函数式编程.函数式编程的拇指规则是不变性.让我大声清楚地解释一下.
单向流如何工作?
states 是包含组件数据的数据存储.view一个部件的渲染基于状态.view需要在屏幕上更改某些内容时,应该从中提供该值store.setState()了一个函数,它接受一个object新函数,并在之前的状态下states进行比较和合并(类似于object.assign()),并将新状态添加到状态数据存储.view消耗并显示在scree上.该循环将在整个组件的整个生命周期内持续进行.
如果您看到上述步骤,它会清楚地显示更改状态时会发生很多事情.因此,当您直接改变状态并setState()使用空对象调用时.该previous state会与你的基因突变被污染.由于这个原因,两个状态的浅比较和合并将会受到干扰或不会发生,因为现在你只有一个状态.这将破坏React的所有生命周期方法.
因此,您的应用程序将出现异常甚至崩溃.大多数情况下,它不会影响您的应用程序,因为我们用于测试它的所有应用程序都非常小.
和突变的另一个缺点Objects和Arrays在JavaScript中,当您分配一个对象或数组,你要做的仅仅是该对象或数组的一个参考.当你改变它们时,对该对象或那个数组的所有引用都将受到影响.React在后台以智能方式处理它,并简单地为我们提供API以使其工作.
处理反应中的状态时最常见的错误
//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
})
Run Code Online (Sandbox Code Playgroud)
在上面的例子中,this.state.a.push(6)将直接改变状态.将其分配给另一个变量并调用setState与下面显示的相同.当我们无论如何改变状态时,没有必要将它分配给另一个变量并setState使用该变量进行调用.
//same as
this.state.a.push(6)
this.setState({})
Run Code Online (Sandbox Code Playgroud)
大多数人这样做.这是错的.这打破了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]
Run Code Online (Sandbox Code Playgroud)
现在,变异currentStateCopy不会改变原始状态.执行操作currentStateCopy并将其设置为使用的新状态setState()
currentStateCopy.push(6)
this.state({
a: currentStateCopy
})
Run Code Online (Sandbox Code Playgroud)
这美化吧?
通过这样做,所有引用this.state.a都不会受到影响,直到我们使用setState.这使您可以控制代码,这将帮助您编写优雅的测试,并使您对生产中的代码性能充满信心.
要回答你的问题,
为什么我不能直接修改组件的状态?
是的,你可以.但是,您需要面对以下后果.
state组件的控制.PS.我写了大约10000行可变的React js代码.如果它现在中断了,我不知道在哪里查看,因为所有的值都在某处变异.当我意识到这一点时,我开始编写不可变代码.相信我!这是你可以对产品或应用程序做的最好的事情.
希望这可以帮助!
Our*_*rus 29
反应文档setState有这个说:
永远不要
this.state直接变异,因为setState()之后的调用可能会取代你所做的突变.把this.state它看作是不可变的.
setState()不会立即变异this.state但会创建待定状态转换.this.state调用此方法后访问可能会返回现有值.无法保证呼叫的同步操作,
setState并且可以对呼叫进行批处理以获得性能提升.
setState()除非实现条件渲染逻辑,否则将始终触发重新渲染shouldComponentUpdate().如果正在使用可变对象并且无法实现逻辑shouldComponentUpdate(),则setState()仅在新状态与先前状态不同时调用将避免不必要的重新呈现.
基本上,如果this.state直接修改,则会创建一些可能会覆盖这些修改的情况.
与您的扩展问题1)和2)相关,setState()不是立竿见影的.它根据其认为正在进行的操作将状态转换排队,其中可能不包括对其的直接更改this.state.由于它排队而不是立即应用,因此完全有可能在两者之间修改某些内容,以便直接更改被覆盖.
如果没有别的,你可能会更好,只考虑不直接修改this.state可以被视为良好的做法.您可能会亲自了解您的代码与React交互的方式,使得这些覆盖或其他问题不会发生,但您正在创建一种情况,其他开发人员或未来的更新可能突然发现自己有奇怪或微妙的问题.
最简单的答案“
为什么我不能直接修改组件的状态:
都是关于更新阶段。
当我们更新组件的状态时,它的所有子组件也将被渲染。或者我们渲染的整个组件树。
但是当我说我们的整个组件树都被渲染时,并不意味着整个 DOM 都被更新了。当一个组件被渲染时,我们基本上会得到一个 react 元素,所以它正在更新我们的虚拟 dom。
React 然后会查看虚拟 DOM,它也有旧虚拟 DOM 的副本,这就是为什么我们不应该直接更新状态,所以我们可以在内存中有两个不同的对象引用,我们有旧虚拟 DOM 作为以及新的虚拟 DOM。
然后 React 会找出发生了什么变化,并基于此更新真实的 DOM。
希望能帮助到你。
令我惊讶的是,当前的答案都没有谈论 pure/memo 组件(React.PureComponent或React.memo)。这些组件仅在检测到其中一个道具发生变化时重新渲染。
假设您直接改变状态并将过度耦合对象传递给下面的组件,而不是值。该对象仍然具有与前一个对象相同的引用,这意味着 pure/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>
Normal Component: <CounterDisplay {...this.state} />
<br />
Pure Component: <PureCounterDisplay {...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"));Run Code Online (Sandbox Code Playgroud)
<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>Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
13736 次 |
| 最近记录: |