This article is aimed at people who have already had their first approach to React, and who, as beginners, have doubts about how setState
works and how to use it correctly. It should also help mid to senior devs use cleaner and more abstracted ways of setting state, and make higher-order-functions handle and abstract state.
本文针对的是已经对React有第一个方法的人,并且作为初学者,对setState
工作方式以及如何正确使用它有疑问。 它还应帮助中高级开发人员使用更简洁抽象的状态设置方法,并使高阶功能处理和抽象状态。
Just read and have fun!
只是阅读并玩得开心!
So grab a cup of coffee and keep reading! ?
因此,请喝杯咖啡并继续阅读! ?
setState()的基本概念 (Basic Concepts of setState( ))
React Components let you split the user interface (UI) into independent, reusable pieces, so you can think about each piece in isolation.
React Components使您可以将用户界面(UI)分成独立的,可重复使用的部分,因此您可以独立考虑每个部分。
Conceptually, components are like JavaScript functions. They accept arbitrary inputs (called “props”) and return React elements describing what should appear on the screen.
从概念上讲,组件就像JavaScript函数。 它们接受任意输入(称为“ props”),并返回描述应该在屏幕上显示的内容的React元素。
If you need to give the user the opportunity to input something or in some way change the variables the component is receiving as props, you’ll need setState
.
如果您需要让用户有机会输入某些东西或以某种方式更改组件作为道具接收的变量,则需要setState
。
Whether you declare a Component as a function or a class, it must never modify its own props.
无论您将Component声明为函数还是类,它都绝不能修改自己的道具。
All React Components must act like pure functions with respect to their props. This means functions that never try to change their inputs and always return the same result for the same inputs.
所有React组件 就其道具而言,必须像纯功能一样发挥作用。 这意味着函数从不尝试更改其输入,并且始终为相同的输入返回相同的结果。
Of course, application UIs are dynamic and change over time. That’s why state
was created.
当然,应用程序UI是动态的,并且会随着时间而变化。 这就是创建state
的原因。
State
allows React components to change their output over time in response to user actions, network responses, and anything else, without violating this rule.
State
允许React组件随时间更改其输出,以响应用户操作,网络响应和其他任何情况,而不会违反此规则。
Components defined as classes have some additional features. Local state is a feature available only to class Components.
定义为类的组件具有一些其他功能。 本地状态是仅对组件类可用的功能。
setState
is the API method provided with the library so that the user is able to define and manipulate state over time.
setState
是库提供的API方法,以便用户能够随着时间的推移定义和操作状态。
使用setState()时的三个经验法则 (Three Rules of Thumb When Using setState( ))
不要直接修改状态 (Do Not Modify State Directly)
状态更新可能是异步的 (State Updates May Be Asynchronous)
React may batch multiple setState()
calls into a single update for performance.
React可以将多个setState()
调用批处理到单个更新中以提高性能。
Because this.props
and this.state
may be updated asynchronously, you should not rely on their values for calculating the next state.
由于this.props
和this.state
可以异步更新,因此您不应依赖于它们的值来计算下一个状态。
You should always do this kind of manipulation with a functional approach, supplying the state
and props
and returning the new state
based on the former.
您应该始终使用功能性方法进行这种操作,提供state
和props
并基于前者返回新state
。
状态更新已合并 (State Updates are Merged)
When you call setState()
, React merges the object you provide into the current state
.
调用setState()
,React将您提供的对象合并到当前state
。
In the example below, we’re updating the variable dogNeedsVaccination
independently of the other state
variables.
在下面的示例中,我们独立于其他state
变量来更新变量dogNeedsVaccination
。
The merging is shallow, so this.setState({ dogNeedsVaccination: true })
leaves the other variables intact, replacing only the value of dogNeedsVaccination
.
合并很浅,因此this.setState({ dogNeedsVaccination: true })
保持其他变量不变,仅替换dogNeedsVaccination
的值。
尊重数据流并避免陈述最大值 (Respect the Data Flow and Avoid State the Max)
Data flows down! Neither parent nor child components can know if a certain component is stateful or stateless, and they shouldn’t care whether it is defined as a function or a class.
数据流下来! 父组件和子组件都无法知道某个组件是有状态的还是无状态的,因此它们不必关心将其定义为函数还是类。
That’s why state
is often called local or encapsulated. It is not accessible to any component other than the one that owns and sets it.
这就是为什么state
通常被称为局部或封装的原因。 除了拥有和设置它的组件之外,任何其他组件都无法访问它。
When you setState
a prop and use it in your component, you’re breaking the flow of the rendering props. If for some reason the prop passed into your component changed in the parent component, the child will not re-render auto-magically ?!
当你setState
的道具和你的组件使用它,你打破渲染道具的流动。 如果由于某种原因传递到组件的prop在父组件中发生了变化,则子代将不会自动重新渲染?!
Let’s check an example:
我们来看一个例子:
Here you have a Home
Component which is generating a magic number each 1000ms and setting it into its own state
.
在这里,您有一个Home
组件,该组件每1000ms生成一个幻数并将其设置为自己的state
。
After that it renders the number and invokes three Child
Components (Siblings) that will receive the magic number with the objective of displaying it using three different approaches:
之后,它呈现数字并调用三个Child
组件(兄弟姐妹),它们将接收魔术数字,目的是使用三种不同的方法显示该数字:
第一种方法 (First Approach)
Component ChildOfHome
is respecting the React props cascade flow and, considering that the objective is only to show the magic number, it’s rendering the props
received directly.
组件ChildOfHome
尊重React道具的级联流,并且考虑到目标仅是显示幻数,因此它直接呈现了接收到的props
。
第二种方法 (Second Approach)
Component ChildOfHomeBrother
receives the props
from its parent and, invoking componentDidMount
, sets the magic number into state
. Then it renders the state.magicNumber
.
组件ChildOfHomeBrother
从其父ChildOfHomeBrother
接收props
,并调用componentDidMount
,将幻数设置为state
。 然后,呈现state.magicNumber
。
This example doesn’t work because render()
doesn’t know that a prop
has changed so it is not triggering the re-rendering of the component. As the component is not re-rendered anymore, componentDidMount
is not invoked and the display is not updated.
此示例不起作用,因为render()
不知道prop
已更改,因此它不会触发组件的重新渲染。 由于不再重新渲染componentDidMount
因此不会调用componentDidMount
,也不会更新显示。
第三种方法 (Third Approach)
Usually when we try to make it work using the second approach we think something is missing. Instead of taking a step back we keep on adding stuff to the code to make it work!
通常,当我们尝试使用第二种方法使其工作时,我们认为有些东西丢失了。 我们没有退后一步,而是继续在代码中添加内容以使其正常工作!
So in this third approach we’ve added componentDidUpdate
to check if there’s a change in props
to trigger the re-rendering of the component. This is unnecessary and leads us to unclean code. It also brings with it performance costs that will be multiplied by the number of times we do this in a big App where we have a lot of chained Components and side effects.
因此,在第三种方法中,我们添加了componentDidUpdate
来检查props
是否发生变化以触发组件的重新渲染。 这是不必要的,并导致我们产生不干净的代码。 它还带来了性能成本,该性能成本将乘以我们在大型App中执行此操作的次数,而该App中存在许多链接的组件和副作用。
This is wrong unless you need to allow the user to change the prop value received.
除非您需要允许用户更改收到的prop值,否则这是错误的。
If you don’t need to change the prop value, always try to keep things working according to the React flow (First Approach).
如果您不需要更改prop值,请始终尝试根据React流(第一种方法)使事物正常工作。
You can check a working webpage with this example I’ve prepared for you in Glitch. Take a look and have fun ?
您可以使用我在Glitch中为您准备的示例查看正常工作的网页。 看一下玩得开心吗?
Also check out the code in the Home.js
and HomeCodeCleaned.js
(without the HTML stuff) in my repo about this article.
还要在有关本文的回购中查看Home.js
和HomeCodeCleaned.js
的代码(无HTML内容)。
如何设置状态 (How to setState)
So at this point I think it’s time to get our hands dirty!
因此,在这一点上,我认为是时候弄脏我们的双手了!
Let’s play a little bit with setState
and improve on that! Just follow along and grab another cup of coffee!
让我们玩一下setState
并对此进行改进! 只要跟着,再拿一杯咖啡!
Let’s create a small form to update user data:
让我们创建一个小表格来更新用户数据:
Here’s the code for the example above:
这是上面示例的代码:
We are setting state
as an object, and there’s no problem because our current state doesn’t depend on our last state.
我们将state
设置为对象,这没有问题,因为我们的当前状态不依赖于上一个状态。
What if we create one more form field to introduce and display Last Name?
如果我们再创建一个表单字段来介绍和显示“姓氏”怎么办?
Nice! We’ve abstracted the handleFormChange
method to be able to handle all the input fields and setState
.
真好! 我们已经抽象出handleFormChange
方法,以便能够处理所有输入字段和setState
。
What if we add a toggle button to mark the data as valid or invalid and a counter to know how many changes we’ve done to the state?
如果我们添加一个切换按钮以将数据标记为有效或无效,并添加一个计数器来知道我们对该状态做了多少更改,该怎么办?
Yeah! We are rocking! We’ve abstracted a lot of stuff!
是的 我们在摇摆! 我们已经提取了很多东西!
Hmmm… Let’s say I do not want a checkbox to control the isValid
variable but a simple toggle button.
嗯……假设我不想使用复选框来控制isValid
变量,而是想要一个简单的切换按钮。
Let’s also separate the counter handler from this method. It works well, but in more complex situations where React needs to batch/group changes, it’s not a good policy to rely on the this.state.counter
variable to add one more. This value can change without you being aware of it.
让我们还将计数器处理程序与此方法分开。 它运行良好,但是在React需要批量/分组更改的更复杂的情况下,依靠this.state.counter
变量添加一个不是一个好的策略。 该值可能会更改,而您不会意识到。
We’re using a shallow copy of it at the instant the operation is invoked, and at that certain point in time you don’t know if its value is the one you were expecting or not!
在操作被调用的那一刻,我们正在使用它的浅表副本,在那个特定的时间点,您不知道它的值是否是您所期望的值!
Let’s go a little bit functional!
让我们开始一些功能!
Okay — We’ve lost abstraction because we’ve separated the handlers, but it’s for a good reason!
好的-我们已经失去了抽象,因为我们已经分离了处理程序,但这是有充分理由的!
So at this time we keep the handleFormChange
passing an object to the setState
API method. But the handleCounter
and handleIsValid
methods are now functional and start by grabbing the current state and then, depending on that state, changing it to the next one.
因此,此时,我们保持handleFormChange
将对象传递给setState
API方法。 但是handleCounter
和handleIsValid
方法现在可以使用,并且首先获取当前状态,然后根据该状态将其更改为下一个状态。
This is the correct way of changing the state
of variables that depend on the previous state.
这是更改依赖于先前状态的变量state
的正确方法。
What if we want to console.log()
state changes of the firstName
and lastName
input forms each time a change occurs? Let’s give it a try!
如果我们希望每次发生更改时, firstName
和lastName
输入表单的console.log()
状态更改怎么办? 试一试吧!
Nice! Each time the handleFormChange
occurs (which means a new key press happened) the logFields()
method is invoked and logs the current state into the console!
真好! 每次handleFormChange
发生(这意味着发生了新的按键按下)时, logFields()
调用logFields()
方法并将当前状态记录到控制台中!
Let’s check the browser console:
让我们检查浏览器控制台:
Wait! What happened here folks? The console log is one change before the current form input! Why is this happening?
等待! 亲人们,这里发生了什么? 控制台日志是当前表单输入之前的一项更改! 为什么会这样呢?
setState是异步的! (setState is async!!)
We already knew this but now we’re seeing it with our eyes! What’s happening there? Let’s take a look at the handleFormChange
and logFields
methods above.
我们已经知道这一点,但是现在我们可以用眼睛看到它了! 那里发生了什么事? 让我们看一下上面的handleFormChange
和logFields
方法。
So the handleFormChange
method receives the event name and value, then does a setState
of this data. Then it calls the handleCounter
to update the counter info, and in the end invokes the logFields
method. The logFields
method grabs the currentState
and returns ‘Eduard’ instead of ‘Eduardo’.
因此, handleFormChange
方法接收事件名称和值,然后对该数据进行setState
。 然后,它调用handleCounter
更新计数器信息,最后调用logFields
方法。 logFields
方法获取currentState
并返回“ Eduard”而不是“ Eduardo”。
The thing is: setState
is async and doesn’t act in the moment. React is doing its job and executes the logFields
method first, leaving setState
for the next event loop.
事实是: setState
是异步的,暂时不起作用。 React正在完成其工作,并首先执行logFields
方法,将setState
留给下一个事件循环。
But how can we avoid this kind of situation?
但是,如何避免这种情况呢?
Well, the setState
API has a callback
to avoid this situation:
好吧, setState
API有一个callback
来避免这种情况:
If we want the logFields()
to take into account the recent changes we’ve made to the state, we need to invoke it inside the callback, like this:
如果我们希望logFields()
考虑到对状态所做的最近更改,则需要在回调内部调用它,如下所示:
Okay, now it’s working!
好的,现在可以了!
We’re telling React: “Hey React! Beware that when you invoke the logFields
method I want you to have the state
already updated okay? I trust you!”
我们告诉React:“嗨,React! 请注意,当您调用logFields
方法时,我希望state
已经更新好吗? 我信任你!”
React says: “Okay Edo! I’m going to handle all this batch of stuff I usually do in the backyard with the setState
thingy and only when I’m finished with that I’ll invoke logFields()
! Cool man! Relax!”
React说:“好的,江户! 我将使用setState
来处理通常在后院执行的所有此类工作,只有在完成后,我logFields()
调用logFields()
! 帅气的男人! 放松!”
And as a matter of fact — it worked!
事实上,它奏效了!
Okay everyone! By this time we’ve handled the major pitfalls of setState
.
大家好! 到这个时候,我们已经解决了setState
的主要陷阱。
Do you have the courage to go beyond the wall? Grab a cup of coffee and let’s get really kewl…
您是否有勇气超越壁垒? 喝杯咖啡,让我们真正地草吧……
用setState()看中 (Getting Fancy with setState( ))
Now that we have handleCounter
and handleIsValid
methods, and setState()
expressed with functions, we can compose the state update with other functions! Me likez composition! Let’s have some fun!
现在我们有了handleCounter
和handleIsValid
方法,并使用函数表示了setState()
,我们可以与其他函数一起handleIsValid
状态更新了! 我喜欢作文! 让我们玩得开心!
We can take the logic inside setState
to a function outside the class component. Let’s call it toggleIsValid
. ☝️
我们可以将setState
内部的逻辑带到类组件外部的函数。 让我们将其toggleIsValid
。 ☝️
Now this function can live outside the class component, anywhere in your app.
现在,此功能可以在类组件之外,应用程序中的任何位置使用。
What if we use a higher order function?
如果我们使用高阶函数怎么办?
Wow! Now we’re not invoking the toggleIsValid
function anymore. We’re invoking an abstract higher order function called toggleKey
and passing a key (string in this case) into it.
哇! 现在,我们不再调用toggleIsValid
函数。 我们正在调用一个抽象的高阶函数,名为toggleKey
,并将一个键(在这种情况下为字符串)传递给它。
How do we need to change the toggleIsValid
function now?
我们现在需要如何更改toggleIsValid
函数?
What?! Now we have a function called toggleKey
that receives a key
and returns a new function which changes state according to the supplied key.
什么?! 现在我们有了一个名为toggleKey
的函数,该函数接收一个key
并返回一个新函数,该函数根据提供的键更改状态。
This toggleKey
can be in a library or in a helper file. It can be invoked in a lot of different contexts to change the state of whatever you want to its opposite.
此toggleKey
可以在库中或在帮助文件中。 可以在许多不同的上下文中调用它,以将所需状态更改为相反状态。
Great!
大!
Let’s do the same with the increment counter handler:
让我们对增量计数器处理程序进行相同的操作:
Yeah! It works! So nice. Let’s get crazy now…
是的 有用! 很好。 现在让我们发疯…
射月取回 (Shooting the Moon and Getting Back)
What if we create a generic makeUpdater
function that receives the transformation function you want to apply, takes the key, and returns the state function managing the state with the transformation function and the key? Little bit confused? Let’s go!
如果我们创建一个通用的makeUpdater
函数来接收要应用的转换函数,获取密钥并返回使用转换函数和密钥管理状态的状态函数,该怎么办? 有点困惑? 我们走吧!
Ok that’s enough…Let’s stop here. ?
好的,足够了……让我们在这里停止。 ?
You can check all the code we’ve done in this GitHub repo.
您可以检查我们在此GitHub存储库中完成的所有代码。
最后但并非最不重要的 (Last But Not Least)
Don’t forget to avoid the max using state and respect React rendering props cascade.
不要忘记避免最大使用状态,并尊重React渲染道具的级联。
Don’t forget setState
is async.
不要忘记setState
是异步的。
Don’t forget setState
can take an object or a function
不要忘记setState
可以接受对象或函数
Don’t forget that you should pass a function in when your next state depends on your previous state.
不要忘记,当您的下一个状态取决于您之前的状态时,您应该传递一个函数。
参考书目 (Bibliography)
- React Documentation React文档
Reach Tech Courses by Ryan Florence, which I really recommend.
我真的推荐Ryan Florence的技术课程 。
Thank you very much!
非常感谢你!
翻译自: https://www.freecodecamp.org/news/get-pro-with-react-setstate-in-10-minutes-d38251d1c781/