
嗨,habrozhiteli! 原始版本于2017年秋季发布,但仍被视为探索React的最佳书。 作者不断更新和修改
Github存储库中该书的代码。
我们建议您在文章中熟悉“状态及其在React的交互性质中的作用”一文。
如果您只需要阅读本书中的一章,则应该选择这一章! 没有状态,React组件仅保留高级静态模式。 我希望您能与我分享我的热情,因为理解本章中的概念将使您能够构建更多有趣的应用程序。
想象一下,您正在构建一个具有自动完成功能的输入字段(图4.1)。 输入数据时,该字段应向服务器发出请求,以获取有关在网页上显示输出的适当选项的信息。 到目前为止,您已经使用过属性,并且知道更改属性可以获取不同的视图。 但是,无法在当前组件的上下文中更改属性,因为在创建组件时会传递它们。

换句话说,属性在当前组件中是不可变的,这意味着除非重新创建组件并从父级传递新值,否则您无法更改此组件中的属性(图4.2)。 但是从服务器接收的信息需要存储在某个地方,然后应在视图中显示新的选项列表。 如果无法更改属性,如何更新视图?
一种可能的解决方案是,每当您收到服务器的新响应时,就使用新属性渲染元素。 但是随后您必须将逻辑放置在组件外部-并且组件不再自给自足。 显然,如果无法更改属性值,并且自动补全应该是自足的,则无法使用属性。 然后出现一个问题:如何在不重新创建组件(createElement()或JSX <NAME />)的情况下更新视图以响应事件? 国家解决的就是这个问题。
服务器响应准备就绪后,回调代码将相应地更改组件的状态。 您将必须自己编写此代码。 但是,状态更新后,React将自动为您更新视图(仅在应更新视图的地方,即使用状态数据的地方)。
使用React组件的状态,您可以构建交互式,有意义的React应用程序。 状态是一个基本概念,可让您构建可存储数据并根据数据中的更改自动更新视图的React组件。
React组件的状态是什么?
React状态是可变的组件数据存储-用户界面和逻辑的面向功能的独立块。 “可变性”表示状态值可以更改。 使用视图中的状态(render())并在以后更改值,可以影响视图的外观。
隐喻:如果您想象一个功能形式的组件,并将其属性和状态传递给输入,则该功能的结果将是对用户界面的描述(表示)。 属性和状态扩展了视图,但是它们用于不同的目的(请参见第4.3节)。
使用状态时,可以按名称访问它们。 名称是this.state对象的属性(即,对象键或对象属性-而不是组件属性),例如this.state.autocompleMatches或this.state.inputFieldValue。
状态数据通常用于在视图中显示动态信息,以扩展视图的呈现。 返回到自动完成字段的先前示例:状态响应于服务器的XHR请求而改变,而XHR请求则通过在该字段中输入数据来启动。 当视图中使用的状态改变时,React确保视图被更新。 实际上,当状态更改时,只有表示的相应部分发生更改(更改为单个元素,甚至更改为单个元素的属性值)。
DOM中的所有其他内容保持不变。 这要归功于虚拟DOM模型(请参阅1.1.1节),React在对帐过程中使用该模型来确定增量(更改的总数)。 这个事实使您可以以声明式的方式编写代码。 React为您完成所有例程。 更改演示文稿的主要阶段在第5章中进行了讨论。
React开发人员使用状态来生成新的用户界面。 组件属性(this.props),普通变量(inputValue)和类属性(this.inputValue)不适合此操作,因为更改其值(在当前组件的上下文中)不会触发视图的更改。 例如,以下代码段是反模式,这表明更改除状态之外的任何位置的值都不会导致视图刷新:
// : ! let inputValue = 'Texas' class Autocomplete extends React.Component { updateValues() ← { ( ) this.props.inputValue = 'California' inputValue = 'California' this.inputValue = 'California' } render() { return ( <div> {this.props.inputValue} {inputValue} {this.inputValue} </div> ) } }
现在让我们看看如何使用React组件的状态。
与国家合作
要使用状态,您必须能够访问值,更新它们并设置初始值。 让我们从引用React组件中的状态开始。
进入州
状态对象是组件的属性,您应该通过此链接(例如this.state.name)访问它。 您还记得,可以使用大括号({})在JSX代码中访问和显示变量。 同样,在render()中,您可以呈现this.state(类似于非标准组件的任何其他变量或类属性),例如{this.state.inputFieldValue}。 此语法类似于访问this.props.name中的属性的语法。

我们使用您学到的知识来实现无花果时钟。 4.3。 我们的目标是创建一个自治的组件类,任何人都可以轻松导入并在其应用程序中使用。 时钟应显示当前时间。
该项目具有以下结构:
/clock index.html /jsx script.jsx clock.jsx /js script.js clock.js react.js react-dom.js
我使用带有跟踪标志(-w)和目录(-d)的Babel CLI将所有JSX源文件从clock / jsx编译到目标clock / js文件夹,并在检测到更改时重新编译。 另外,我将该命令另存为父文件夹ch04的package.json文件中的npm脚本,以执行ch04的npm run build-clock命令:
"scripts": { "build-clock": "./node_modules/.bin/babel clock/jsx -d clock/js -w" },
当然,时间不会停滞不前(无论我们是否喜欢)。 因此,您需要不断更新视图,并为此可以使用状态。 将其命名为currentTime并尝试呈现状态,如清单4.1所示。
清单4.1。 JSX状态渲染
class Clock extends React.Component { render() { return <div>{this.state.currentTime}</div> } } ReactDOM.render( <Clock />, document.getElementById('content') )
您将收到一条错误消息:Uncaught TypeError:无法读取null的属性“ currentTime”。 通常,JavaScript错误消息与溺水的人具有与一杯冷水差不多的好处。 至少在这种情况下,JavaScript显示有意义的消息是件好事。
该消息表明currentTime的值未定义。 与属性不同,状态未在父级中设置。 在render()中调用setState也会失败,因为它将创建一个循环(setState-> render-> setState ...)-React将报告错误。
初始状态的分配
您已经看到,在render()中使用状态数据之前,必须初始化状态。 要设置初始状态,请在构造函数中使用this.state,并使用ES6类React.Component的语法。 请记住使用属性调用super(); 否则,父级(React.Component)中的逻辑将不起作用:
class MyFancyComponent extends React.Component { constructor(props) { super(props) this.state = {...} } render() { ... } }
分配初始状态时,还可以添加其他逻辑-例如,使用new Date()设置currentTime的值。 您甚至可以使用toLocaleString()来获取用户当前位置的正确日期/时间格式,如下所示(ch04 /时钟)。
清单4.2。 时钟组件构造器
class Clock extends React.Component { constructor(props) { super(props) this.state = {currentTime: (new Date()).toLocaleString()} } ... }
this.state的值必须是一个对象。 我们不会在ES6中详细介绍构造函数(); 请参阅
github.com/azat-co/cheatsheets/tree/master/es6上的附录D和ES6摘要。 最重要的是,与其他OOP语言一样,在创建类的实例时会调用构造函数(即构造函数())。 构造方法的名称应为: 考虑这是ES6的规则之一。 此外,在创建构造方法()方法时,几乎应始终将super()调用包括在其中,否则将不会执行父方法的构造方法。 另一方面,如果未定义构造方法(),则默认情况下将假定对super()的调用。
名称currentTime是可选的; 您稍后必须在读取和更新此状态时使用相同的名称。
状态对象可以包含嵌套对象或数组。 以下示例向状态添加了一系列图书说明:
class Content extends React.Component { constructor(props) { super(props) this.state = { githubName: 'azat-co', books: [ 'pro express.js', 'practical node.js', 'rapid prototyping with js' ] } } render() { ... } }
当基于类创建一个React元素时,constructor()方法仅被调用一次。 因此,您只能使用一次this.state来直接设置状态-在构造函数()方法中。 不要在其他地方直接使用this.state = ...设置或更新状态,因为这可能导致无法预料的后果。
因此,您仅获得初始值,而该初始值很快就会过时-仅需1秒。 谁需要一只不显示当前时间的手表? 幸运的是,存在一种用于更新当前状态的机制。
状态更新
状态通过this.setState(数据,回调)类的方法更改。 调用此方法时,React将数据与当前状态合并并调用render(),然后调用回调。
在setState()中定义回调回调很重要,因为该方法是异步工作的。 如果应用程序依赖于新状态,则可以使用此回调来确保新状态变得可用。
如果仅假设状态已更新而没有等待setState()完成,即在执行异步操作时同步工作,则可能会发生错误:程序依赖于更新状态值,但是状态仍然很旧。
到目前为止,我们已经从状态中抽出时间。 您已经知道如何设置初始状态,但是应该每秒更新一次,对吧? 为此,请使用浏览器计时器函数setInterval()(http://mng.bz/P2d6),它将每n毫秒更新一次状态。 setInterval()方法在几乎所有现代浏览器中都以全局方式实现,这意味着可以在没有任何其他库或前缀的情况下使用它。 一个例子:
setInterval(()=>{ console.log('Updating time...') this.setState({ currentTime: (new Date()).toLocaleString() }) }, 1000)
要开始倒计时,只需调用一次setInterval()。 我们仅为此目的创建launchClock()方法; launchClock()将在构造函数中调用。 清单4.3显示了该组件的最终版本(ch04 / clock / jsx / clock.jsx)。
可以在任何地方调用setState()方法,而不仅仅是在launchClock()方法(在构造函数中调用)中,如示例所示。 通常,从事件处理程序中调用setState()方法,或者在接收或更新数据时将其作为回调。
提示尝试使用形式为this.state.name ='new name'的命令更改代码中的状态将不会导致任何结果。 它不会导致重新渲染和更新您真正想要的DOM模型。 在大多数情况下,没有setState()的直接状态更改是反模式,应避免。
重要的是要注意,setState()方法仅更新传递给它的状态(部分或合并的状态,但没有完全替换的状态)。 它不会每次都替换整个状态对象。 因此,如果三个状态中只有一个已更改,则其他两个将保持不变。 在以下示例中,userEmail和userId不会更改:
constructor(props) { super(props) this.state = { userName: 'Azat Mardan', userEmail: 'hi@azat.co', userId: 3967 } } updateValues() { this.setState({userName: 'Azat'}) }
如果打算更新所有三个状态,则必须通过将这些状态的新值传递给setState()来显式地执行此操作。 (同样在旧的代码中,现在不再起作用了,有时会找到this.replaceState()方法;它已正式弃用1。正如您可能会猜到的那样,它用所有属性替换了整个状态对象。)
请记住,调用setState()会启动render()的执行。 在大多数情况下,它可以工作。 在某些特殊情况下,代码依赖于外部数据,您可以通过调用this.forceUpdate()来启动重新渲染。 但是,这样的决定是不可取的,因为依赖外部数据(而不是状态)会使组件可靠性降低,并依赖于外部因素(紧密绑定)。
如前所述,可以在this.state条目中访问状态对象。 在JSX中,输出值用大括号({})括起来,因此,要在视图中(即在render方法的return命令中)声明状态属性,请使用符号{this.state.NAME}。
当您在视图中使用状态数据(例如,在输出中,在if / else命令中,将其用作子代的属性值或属性值),然后传递setState()新值时,就会发生React Magic。 ah! React为您更新了所有必要的HTML标记。 您可以在DevTools控制台中对此进行验证,在该控制台中应显示周期“正在更新...”和“渲染...”。 很棒的是,这只会影响绝对最小的必需DOM元素。
»这本书的更多信息可以
在出版商的网站上找到»
目录»
摘录小贩优惠券20%
-React支付纸质版本的书后,将通过电子邮件发送该书的电子版本。