本文翻译自:Functional setState is the future of React – freeCodeCamp.orgreact
译者注:昨天本身有遇到一个 setState 的坑,就是【React 踩坑记】setState 这样用可能会出错!这篇文章里记录的,上网 Google 了下看到这一篇,关于 setState,这篇文章讲解的很详细深刻👍,因此翻译到掘金来,让更多人能够看到。git
更新:我在React Rally上就此主题进行了后续讨论。虽然这篇文章更多的是关于“函数式的 setState”模式,但更多的是关于深刻理解setState。github
我在React Rally 上关于 setState 作的一个分享 Justice Mba - Demystifying setState() - YouTubeweb
React在JavaScript中推广了函数式编程,这致使了大量的框架采用了React使用的基于组件的UI模式。现在,函数式热潮正在蔓延到整个 web 开发生态系统中。编程
译者注:上述内容翻译以下:数组
JavaScript生态系统正在从“本周的新框架”转变为“新的(更快的)本周的React克隆”安全
ReactJS新闻@ReactJSNews
阿里巴巴发布了他们本身的类React框架,彷佛是更轻量更快的 - 可是确定有一个缺点!github.com/alibaba/rax框架
但React团队远没有放松。他们继续深刻挖掘,探索更多的函数式宝石。异步
因此今天我向你透露一个隐藏在React中的函数式宝石 - 函数式 setState!ide
好吧,这个名字只是我刚刚编造的......并且这并非全新的东西或秘密。不,不彻底是。其实它是React内置的一种模式,只有不多有开发人员知道这种模式。 它历来没有名字,但如今它能够叫作 - 函数式 setState!
正如Dan Abramov所描述的,函数式 setState 就是一种这样的模式:
“与组件类分开声明状态更改。”
咦?
React是一个基于组件的UI库。组件基本上是一个接受一些属性并返回UI元素的函数。
function User(props) {
return (
<div>A pretty user</div>
);
}
复制代码
组件可能须要拥有并管理其状态。在这种状况下,您一般将组件编写为类。而后你的状态存在于类的constructor
函数中:
class User {
constructor () {
this.state = {
score : 0
};
}
render () {
return (
<div>This user scored {this.state.score}</div>
);
}
}
复制代码
为了管理状态,React提供了一个名为setState()
的特殊方法。用法以下:
class User {
...
increaseScore () {
this.setState({score : this.state.score + 1});
}
...
}
复制代码
请注意setState()
的工做原理。您传递一个包含要更新的 state 部分的对象。换句话说,您传递的对象将具备与组件 state 中的键对应的键,而后setState()
经过将对象合并到 state 来更新或设置 state。这就是“set-State”
还记得咱们说的setState()
的工做原理吗?那么,若是我告诉你能够传递一个函数来代替传递一个对象呢?
是的。setState()
也接受一个函数来做为参数。该函数接受组件的先前 state 和 当前的 props,它用于计算并返回下一个 state。以下所示:
this.setState(function (state, props) {
return {
score: state.score - 1
}
});
复制代码
请注意,setState()
是一个函数,咱们将另外一个函数传递给它(函数式编程...函数式 setState)。乍一看,代码可能看起来很丑陋,只有设置状态的步骤太多了。但为何还得这样作呢?
关键在于,状态更新多是异步的。
想一想调用setState()
时会发生什么。React将首先将传递给setState()
的对象合并到当前状态。而后它将开始合并。它将建立一个新的React Element树(UI的对象表示),将新树与旧树进行区分,根据传递给setState()
的对象找出已更改的内容,而后最终更新DOM。
呼!这么多工做!实际上,这甚至是一个简化过的总结。可是相信React:
React does not simply “set-state”.
因为涉及的工做量很大,调用setState()
可能不会当即更新您的状态。
React能够将多个
setState()
的调用批处理成单个更新来提升性能。
上面这句话是什么意思?
首先,“屡次调用setState()
”可能意味着在一个函数内屡次调用setState()
,以下所示:
state = {score : 0};
// 屡次调用`setState()
increaseScoreBy3 () {
this.setState({score : this.state.score + 1});
this.setState({score : this.state.score + 1});
this.setState({score : this.state.score + 1});
}
复制代码
如今,当React遇到“屡次调用setState()
”,而不是整整三次执行“set-state”时,React将避免我上面描述的大量工做并巧妙地对本身说:“不! 我不打算愚公移山,每次都更新一些状态。我宁愿获得一个容器,将全部这些切片包装在一块儿,只需更新一次。“这就是批处理!
请记住,传递给setState()
的是一个普通对象。如今,假设任什么时候候React遇到“屡次调用setState()
”,它经过提取传递给每一个setState()
调用的全部对象来完成批处理,将它们合并在一块儿造成一个对象,而后使用该单个对象来执行setState()
。
在JavaScript中,合并对象可能以下所示:
const singleObject = Object.assign(
{},
objectFromSetState1,
objectFromSetState2,
objectFromSetState3
);
复制代码
这种模式称为对象组合。
在JavaScript中,“合并”或组合对象的方式是:若是三个对象具备相同的键,则传递给Object.assign()的最后一个对象的键值将做为该键最终的值。例如:
const me = {name : "Justice"},
you = {name : "Your name"},
we = Object.assign({}, me, you);
we.name === "Your name"; //true
console.log(we); // {name : "Your name"}
复制代码
由于you
是合并到we
的最后一个对象,因此you
对象中的name
值 - “Your name” - 将覆盖me
对象中name
的值。
所以,若是使用对象做为参数屡次调用setState()
——每次传递一个对象——React将合并。换句话说,它将用咱们传递的多个对象中组成一个新对象。 若是任何对象包含相同的键,则存储具备相同键的最后一个对象的键的值。对吧?
这意味着,鉴于咱们上面的increaseScoreBy3
函数,函数的最终结果将只是1
而不是3
,由于 React 没有当即按咱们调用setState()
的顺序更新状态。首先,React将全部对象组合在一块儿,结果以下:{score:this.state.score + 1}
,而后只使用新组合的对象进行“set-state”一次。 像这样:User.setState({score:this.state.score + 1}
。
要很是清楚,将对象传递给setState()
不是问题所在。真正的问题在于当你想要基于前一个状态计算下一个状态时,将对象传递给setState()
。因此中止这样作。这不安全!
由于
this.props
和this.state
能够异步更新,因此不该该依赖它们的值来计算下一个状态。
索菲亚·舒梅克(Sophia Shoemaker)的这个例子能够演示这个问题。 演示它,并注意这个例子中的坏和好的解决方案。
若是你没有花时间演示上面的例子,我强烈建议你仍是先看一下,由于它将帮助你掌握这篇文章的核心概念。
当你演示了上面的例子,你无疑看到函数式setState解决了咱们的问题。但到底是怎么作的呢?
咱们来咨询React的核心成员 - Dan。
请注意他给出的答案。
当你使用函数式setState ...
更新将被放进一个队列,而后按调用顺序执行。
所以,当React遇到“屡次函数式setState()
调用”时,React按照“调用它们的顺序”对函数进行排队,而不是将对象合并在一块儿,(固然,并无要合并的对象)。
以后,React继续经过调用“队列”中的每一个函数来更新状态,将它们传递给先前的状态 - 即,在第一个函数setState()调用以前的状态(若是当前是第一个函数setState()正在执行)或队列中前一个函数setState()调用的最新更新的状态。
下面咱们未来模拟一个setState()方法,这是为了让你了解React正在作什么。另外,为了减小冗长,咱们将使用ES6。若是须要,您随时能够编写ES5版本。
首先,让咱们建立一个组件类。而后,在其中,咱们将建立一个假的setState()方法。此外,咱们的组件将具备increaseScoreBy3()方法,该方法将执行多功能setState。最后,咱们会像 React 所作的那样实例化该类。
class User{
state = {score : 0};
//let's fake setState
setState(state, callback) {
this.state = Object.assign({}, this.state, state);
if (callback) callback();
}
// 屡次函数式 setState 的调用
increaseScoreBy3 () {
this.setState( (state) => ({score : state.score + 1}) ),
this.setState( (state) => ({score : state.score + 1}) ),
this.setState( (state) => ({score : state.score + 1}) )
}
}
const Justice = new User();
复制代码
请注意,setState还接受可选的第二个参数 - 回调函数。若是有传递这个参数,React 在更新状态后调用它。
如今,当用户触发increaseScoreBy3()
时,React会将多个函数式 setState 放入队列。咱们不会在这里伪造这种逻辑,由于咱们的重点是什么才真的使函数式setState安全。可是你能够把“排队”过程的结果想象成一个函数数组,以下所示:
const updateQueue = [
(state) => ({score : state.score + 1}),
(state) => ({score : state.score + 1}),
(state) => ({score : state.score + 1})
];
复制代码
最后,让咱们来模拟更新过程:
// 按顺序递归调用 state 的更新
function updateState(component, updateQueue) {
if (updateQueue.length === 1) {
return component.setState(updateQueue[0](component.state));
}
return component.setState(
updateQueue[0](component.state),
() =>
updateState( component, updateQueue.slice(1))
);
}
updateState(Justice, updateQueue);
复制代码
没错,这不是一个很棒的代码,你确定能够写出更好的代码。但这里的关键焦点是每次 React 执行函数 setState 中的函数时,React 都会经过向其传递更新 state 的新副原本更新您的状态。这使得函数 setState 能够基于前一次的 state 来设置新的 state。 在这里,我用完整的代码建立了一个bin。
我把这个例子补充完整,便于大家能够更好地理解它。
class User{
state = {score : 0};
//fake setState
setState(state, callback) {
console.log("state", state);
this.state = Object.assign({}, this.state, state);
if (callback) callback();
}
}
const Justice = new User();
const updateQueue = [
(state) => ({score : state.score + 1}),
(state) => ({score : state.score + 1}),
(state) => ({score : state.score + 1})
];
// 按顺序递归调用 state 的更新
function updateState(component, updateQueue) {
if (updateQueue.length === 1) {
return component.setState(updateQueue[0](component.state));
}
return component.setState(
updateQueue[0](component.state),
() =>
updateState( component, updateQueue.slice(1))
);
}
复制代码
运行一下这段代码,确保你看懂它。当你回来时咱们会看到是什么让函数式的setState真正变得闪闪发光。
到目前为止,咱们已经深刻探讨了为何在React中执行多个函数式setStates是安全的。可是咱们实际上尚未完成函数式setState的完整定义:“声明状态更改与组件类分开”。
多年来,setting-state 的逻辑——即咱们传递给setState()的函数或对象 - 老是存在于组件类中,这更像是命令式的而非声明式的。
那么今天,我向你展现新出土的宝藏 - 最好的React秘密:
感谢Dan Abramov!
这是函数式setState的强大功能。在组件类以外声明状态更新逻辑。而后在组件类中调用它。
// outside your component class
function increaseScore (state, props) {
return {score : state.score + 1}
}
class User{
...
// inside your component class
handleIncreaseScore () {
this.setState( increaseScore)
}
...
}
复制代码
这是声明性的!您的组件类再也不关心状态更新。它只是声明它想要的更新类型。
要深入理解这一点,请考虑那些一般具备许多状态切片的复杂组件,在不一样操做更新每一个切片。有时,每一个更新功能都须要多行代码。全部这些逻辑都将存在于您的组件中。但之后再也不是这样了!
另外,我喜欢让每一个模块都尽量短。若是你像我同样以为你如今的模块太长了,您能够将全部状态更改逻辑提取到其余模块,而后导入并在组件中使用它。
import {increaseScore} from "../stateChanges";
class User{
...
// inside your component class
handleIncreaseScore () {
this.setState( increaseScore)
}
...
}
复制代码
如今,您甚至能够在另外一个组件中重用increaseScore函数,只需导入它。
你还能够用函数式setState作什么?
让测试变得简单!
你也能够传递额外的参数来计算下一个状态(这个让我大吃一惊...... )
期待更多......
嘿,丹!最后(再说)一句话(来展望下 React)?
若是你已经看到这里,你可能会像我同样兴奋。当即开始尝试使用函数式setState!
快乐撸码!