React 详解
要点
🤔
1. J -ava- S -cript- X -ml
2. const element =渲染元素
3. 组件和道具
4. 状态和生命周期
5. 处理🖐🏽事件
6.✅ 条件渲染❌
7. 列表📝和键🔑
8. 表格
9. 提升状态
10. 组合与继承
要点
如果您曾经花过一点时间访问过React网站,您就会读到他们的标语……
用于构建用户界面的 JavaScript 库
UI 和状态管理是 React 致力于为前端开发者解决的主要问题,这也是 React 的宗旨。
在面试前端职位或温习相关概念时,我们经常会在浏览器上打开超过 100 个标签页。我想总结一下在与同行讨论 React 时需要涵盖的要点。
以下概述旨在涵盖 React 的主要概念,理解这些概念对于高效工作非常重要。
这篇文章确实很长,但它更多的是想提供一些参考,让你更好地利用阅读时间。希望你喜欢。
让我们开始吧!🏊🏽♂️
在构建 JavaScript 应用程序时,我们希望处理数据。
JS 中的数据通常由原始值构成,包括:
- 数字
- 字符串
- 布尔值
- 无效的
- 不明确的
- 大整数
- 符号
作为开发者,我们在应用程序的最底层使用这些值。JS 中的这些原始值是不可变的,这意味着它们无法被更改。另一方面,保存这些原始值的变量可以被重新赋值。
作为工程师,尤其是作为对网络一切事物充满好奇的爱好者,这对我们意味着什么?
🤔
我们需要一种方式来管理应用程序的数据,也就是我们收集并提供给用户的信息,从而最大程度地减少我们的麻烦。作为一名工程师,您总是在权衡各种解决方案的利弊,它们的效率是否比可读性和易用性更重要?您会发现这个问题的答案一直在不断变化。
对于以下解释,我将按照 React 自己的开发人员阐述概念的顺序进行,并在此过程中添加额外的示例和分解(🤘🏽)。
主要概念
1. J -ava- S -cript- X -ml
我们应该始终努力理解基础知识。虽然我个人理解 JSX 的大部分 JavaScript 方面,但我很少接触 XML。所以我非常感兴趣:什么是 XML?
XML是可扩展标记语言 (Extensible Markup Language)的缩写。如果你正在想:“Kurt,XML 听起来很像 HTML”,那么你就猜对了。它们之间关系密切!
“可扩展”部分是由于 XML 允许您(作为开发人员)定义自己的标签,以满足您自己的特定需求。
这方面非常有力,构建 React 的 Facebook 开发人员也意识到了这一点。
好吧,说了这么多,但你更擅长视觉学习。我们来看一些 JSX 代码吧!🔥⋔
我们上面看到的是什么?
这里我们有所谓的功能组件,或“哑组件”,因为最佳实践是不在这些组件中包含太多逻辑。
我们所拥有的只是一个分配给常量App的匿名箭头函数,然后通过我们的export default App语句将其作为模块导出。
我们将进一步研究 React 中的 App.js 文件,但现在要明白,它与位于应用程序目录顶层的 Index.js 文件一起作为主要事实来源。
在我们的匿名箭头函数中,我们返回一个 div 元素。好的,到目前为止一切都很好,我们之前都处理过 div。但是这个 div 里面到底是什么呢?
<PostList /> 👀
在应用文件的顶部,我们PostList
从一个PostList.js
文件导入组件。得益于 ES6 JS 的强大功能,我们能够使用模块导入来引入在其他地方定义的功能。太棒了!
为了获得更彻底的心理图像,让我们看看我们已经抽象出来的逻辑。
我们抽象出了 44 行代码!这使得我们在开发应用程序时更容易专注于重要的事情。
JSX 允许我们使用类似 XML 的标签<OurUniqueTag/>
来构建我们在 React 中使用的组件和元素。
等一下,我们似乎还没有讨论过组件或元素。
让我们从元素开始,因为组件是由元素构建的!
2. const element =渲染元素
与 JavaScript 语言最低级别的原始值类似,“元素是 React 应用程序的最小构建块”。

我怎么突然开始说 DOM 了?构建块,一切都是关于构建块的。
DOM代表“DocumentObjectModel”,与图形用户界面一样,它是 HTML 和 XML 的编程接口。
它不是网页,而是网页的一种表现形式,让您可以神奇地挥动开发魔杖🧙🏽♂️并更改文档结构、样式和内容。
DOM 用于允许编程语言连接到页面的数据结构是节点和对象。
import ReactDOM from 'react-dom'
ReactDOM.render(
<App/>,
document.querySelector('#root')
)
如果您使用 React 进行开发,那么您必须<App />
使用 ReactDOM 的渲染方法来包装您的。
为了向用户展示功能强大的酷炫网站,我们必须持续更新 DOM。然而,这些动态更新本身也存在一些 bug。
每次更新时,浏览器必须刷新 CSS、刷新 DOM 节点树,最终刷新正在显示的屏幕。在 React 出现之前,我们需要编写大量的 JavaScript 代码来完成这些繁重的工作,如果不小心,这些繁重的工作就会变得非常明显。
我们的 JSX 元素代表 DOM 元素,并在由 ReactDOM.render() 渲染后被解析为网页上的那些元素。
当 React 最初渲染元素时,它也会构建一个代表DOM 或当前树的“树” 。
协调实际上是我们设法在这里保留的一个高级 React 概念。您可以在React 文档中找到更多信息,不过我们会在这里稍微讨论一下。
当比较两棵树的差异时,React 首先比较两个根元素。具体行为取决于根元素的类型。
当 React 进行更新,需要重新渲染或刷新时,会创建第二个workInProgress树,用于表示 DOM最终的样子。处理完workInProgress
DOM 的更新后,它currentTree
会协调所有差异。
这就是使用 React 的优势,性能优化。
您的应用程序在网络上的性能通过此过程的两个关键方面进行优化
- 分组 DOM 更新
- React 等待所有更新都处理完毕后才将它们放入 workInProgress 树中。
- 选择性更新
- React 能够应用差异算法来快速选择需要更新的数据。
现在,让我们回到组件🏃🏽♂️
3. 组件和道具
在我们上面的代码片段中,有一个我们导入的组件,由 JSX 元素组成。
我们看到了可以从 App 文件中抽象出来的 44 行代码。这样的组件使我们能够将用户界面拆分成可重用的构建块。
从概念上讲,组件就像 JavaScript 函数。它们接受任意输入(称为“props”),并返回描述屏幕上应显示内容的 React 元素。——React 文档
const Comment = (props) => {
return (
<div className="Comment">
<UserInfo user={props.author} />
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
在这个组件中,我们将其props
作为参数传递给数组函数。
Props,或称属性,是遍历 React 节点树的数据对象,目的是向组件提供浏览器 DOM 所需的信息repaint
。
但是这些 props 是从哪里来的呢?为了理解这一点,我们需要花一点时间来了解一下 state。
4. 状态和生命周期
在我们的 React 应用程序中,我们经常会在对象中设置初始状态。
// PREFERRED & COMMON WAY
state = {
isClicked: true,
initialGreeting: "hello, there!"
}
//OR BUILT WITH A CONSTRUCTOR
constructor(props){
super(props)
this.state = {
isClicked: true,
initialGreeting: "hello, there!"
}
}
您的状态应该位于类组件内,通常看起来像下面的代码。
class
下面是一个类 React 组件实例的示例。组件与 组件functional
(其核心纯粹是一个箭头函数)之间的区别在于,React 类组件预先封装了生命周期方法。
class Clock extends React.Component {
render() {
return (
<div>
// Here's some text!
</div>
);
}
}
这也是为什么开发人员可能会将类组件称为“智能组件”,而将函数组件称为“哑组件”。类组件用于传递所有逻辑,而函数组件则更像是容器或用于构建简单的模块。
但是生命周期方法是什么?
当 React 开始工作时,它首先会检查组件的状态(如果组件是类组件)。React 不会因为检查哑组件而浪费资源。
你可以为状态设置默认值来启动应用,就像我们在示例中看到的那样,或者你也可以根据需要传入 props。首选方法是使用简单的状态对象,而不是使用构造函数。虽然构造函数在创建引用或方法绑定时会很方便,但那是另一个话题了。
让我们列出当前可用的生命周期方法并附上一些简短的描述。
componentDidMount()
- 初始渲染后,调用方法
- 用于加载/设置数据
- 确保在发送 AJAX 请求之前,确实有一个组件可以渲染它
shouldComponentUpdate(nextProps, nextState)
- 仅当组件所需的 props 发生变化时才更新组件
- 问题:不允许你的组件定期更新
componentDidUpdate(prevProps, prevState, snapshot)
- 这使我们能够处理之前查看 DOM 时检查过的当前 DOM 树的已提交更改
componentWillUnmount
- 根据 React 文档:“在组件被销毁时释放它们所占用的资源非常重要。”
- 此方法主要用于清除消耗重要应用程序资源的残留行为
哎呀,说了这么多,还有其他一些有用的方法,比如getSnapshotBeforeUpdate
、、和,你应该花点时间看看。但我们列表中提到的方法是构建一个优秀应用程序所需的主要方法。getDerivedStateFromError
componentDidCatch
getDerivedStateFromProps
主要要点是这些生命周期方法允许我们更新应用程序数据或状态。
国家三大规则
- 不要直接修改状态
- this.state.comment =“nopity-nope nope”
- this.setState({words:“更好!”})
- 状态更新可以是异步的
- 记住使用一种通过对象接受函数的 setState 形式。
- this.setState((state, props) => ({words: state.words}))
- 也可以是常规函数
- 状态更新已合并
- 更新后的状态将合并到当前节点树中,然后您可以在任意位置多次设置 setState({})。
记住:在 React 中,数据是向下流动的。想象一下瀑布,父组件中创建的状态通过 props 向下传递到子组件。
5. 处理🖐🏽事件
描述事件处理程序
这部分的好处在于不需要太多的脑力劳动。React 中的事件处理方式大部分与常规 JS 事件类似。
我们主要应该考虑用于描述 React 事件的语法糖。需要记住的是,它们是驼峰命名的。
- 常规 JS 事件
<button onclick="rainDownMoney()">
- React 事件处理程序
<button onClick={this.raindDownMoney}>
合成事件
你的事件处理程序将传递 SyntheticEvent 的实例,它是浏览器原生事件的跨浏览器包装器。它与浏览器原生事件具有相同的接口,包括 stopPropagation() 和 preventDefault(),但这些事件在所有浏览器中的工作方式相同。—— React 文档
事件池
- 重点提示:您无法以异步方式访问合成事件
- 由于事件池
- 这意味着您的 SyntheticEvent 对象被重复使用以提高性能。
- 触发回调函数后,附加到合成事件的属性将变为空。
event.persist()
- 将允许您以异步方式访问事件道具。
在 React 中绑定 JS 的 THIS
在 JavaScript 中,类方法并不绑定到它们的 THIS 值。现在,很多时候,训练营都在花大量时间复习和深入研究这个概念。不过,我们先来快速了解一下。
来自MDN:Function.prototype.bind()
bind() 最简单的用法是创建一个函数,无论如何调用,都会使用特定的 this 值。JavaScript 新手常犯的一个错误是从对象中提取方法,然后调用该函数,并期望它使用原始对象作为 this(例如,在基于回调的代码中使用该方法)。然而,如果不特别注意,原始对象通常会丢失。使用原始对象从函数创建一个绑定函数,可以巧妙地解决这个问题:
上面的例子来自 MDN,我们应该从中得出的是全局“窗口”对象和范围在这里发挥作用。
我们的函数retrieveX()在全局范围内被调用,并且 this 的值是module.getX
从this.x = 9
文件顶部定义的值中获取的,而不是从我们模块对象内部的 x 获取的。
解决方案:retrieveX.bind(module)
绑定 this 允许我们将 THIS 值固定为我们想要的值。
This
取决于在运行时绑定或关联变量、函数和数据时函数的调用方式。This
始终默认为全局对象,或浏览器中的窗口。相信我,如果您忘记绑定,控制台中会清晰地显示错误。
两种绑定方式
- 公共类字段语法(实验性)
class LoggingButton extends React.Component {
handleClick.
// EXPERIMENTAL
handleClick = () => {
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
- 箭头函数!
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
return (
<button onClick={(e) => this.handleClick(e)}>
Click me
</button>
);
}
}
事件和 this 的绑定在你刚开始使用 React 时可能会导致大多数 bug,如果以后忘记绑定,后果可能更严重。我之前就把箭头函数和公共类字段的语法搞混了,所以最好选择其中一种,并在你的应用中坚持使用。
6.✅ 条件渲染❌
还记得使用组件是如何让我们减少文件中的代码混乱吗?条件渲染,或者根据应用程序的状态/属性显示元素,可以让我们编写更少的代码并使其更加清晰。
class LoginControl extends React.Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {isLoggedIn: false};
}
handleLoginClick() {
this.setState({isLoggedIn: true});
}
handleLogoutClick() {
this.setState({isLoggedIn: false});
}
render() {
const isLoggedIn = this.state.isLoggedIn;
let button;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}
return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
);
}
}
ReactDOM.render(
<LoginControl />,
document.getElementById('root')
);
向 JSX 添加表达式
有几种很酷的方法可以将逻辑添加到 JSX 中
&&
带有逻辑运算符 的内联 IF- IF 条件,渲染
true
后的元素&&
- 如果条件
false
,则忽略
- IF 条件,渲染
return (
<div>
<h1>Hello!</h1>
// start of condition
{unreadMessages.length > 0
&&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
// end of condition
</div>
);
- IF-Else 三元运算符(需要 3 个操作数)
- 条件 ? 如果为真则返回 : 如果为假则返回
return (
<div>
// start of condition
{
isLoggedIn ?
(<LogoutButton onClick={this.handleLogoutClick} />)
:
(<LoginButton onClick={this.handleLoginClick} />)
}
// end of condition
</div>
null
- 如果您希望在条件为假时不发生任何事情,您也可以随时交换
null
原始值。 - 这不会影响生命周期方法
- 如果您希望在条件为假时不发生任何事情,您也可以随时交换
7. 列表📝和键🔑
关于建立列表,您应该了解两个要点。
- 显示项目列表通常借助函数来完成
map()
。 - 被映射的元素需要唯一的键,但它们不需要是全局唯一的。
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
);
return (
<ul>
// if we watned to make things look messy
// we could also directly embed
// our functioninside
// of the brackets
{listItems}
</ul>
);
}
8. 表格
- 受控组件
- 在常规 HTML 表单中
- 元素(例如 input、textArea、select)保持自己的状态
- 反应方式
- 可变状态保存在 state prop 中,由
setState()
- 问题
- React 应该负责处理
singl source of truth
数据。上图中我们看到两种不同的数据结构在相互竞争。让我们借助受控组件将它们结合起来。
- React 应该负责处理
处理程序函数
即使你给函数取了其他名字,也不会对函数本身造成影响,但通常的做法是根据函数的功能来命名,例如handleSubmit()
。组件之所以能够被控制,是因为我们用构造函数设置了初始状态,并用我们自己的 onChange 事件处理程序对其进行了修改,该处理程序会setState()
根据我们定义的条件触发我们定义的函数。这样,我们就拥有了控制权。
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
9. 提升状态
这是另一个学习曲线陡峭、攀登艰难的领域。但最终一切都会逐渐明朗,尤其是在你花了大量时间阅读文档之后。
以下是将状态从子组件提升到其直属父组件时要遵循的基本步骤。
- 在父组件中定义一个函数
- 将其作为函数传递给你的子组件
- 将子组件中改变的状态传递给 prop,其中包含父组件的函数,这样数据就会遍历你的节点树,一直回到你的唯一真实来源
10. 组合与继承
React 团队并没有说哪个更好,所以为了澄清起见,我们也不会这么说。但是 React 的开发团队建议在大多数情况下使用组合,在少数情况下使用继承。这些是架构方法,与我们的父组件和子组件相关。
- 继承(从父类扩展属性)
- 在面向对象语言中,这是指子类从其父类获取属性的时候。
- COMPOSITION(引用其他类实例中的对象)
- 描述引用另一个类的对象作为实例的类。
- 重点是什么?
- 代码重用
让我们来看看Mosh Hamedani的一些示例,他是一位很棒的 React 开发者和博主。我强烈推荐你多看看他的作品。
//PARENT
export default class Heading extends React.Component {
render () {
return (
<div>
<h1>{this.props.message}</h1>
</div>
)
}
}
Heading.propTypes = {
message: PropTypes.string
}
Heading.defaultProps = {
message: 'Heading One'
}
//CHILD #1
export default class ScreenOne extends React.Component {
render () {
return (
<div>
<Heading message={'Custom Heading for Screen One'}/>
</div>
)
}
}
// CHILD #2
export default class ScreenTwo extends React.Component {
render () {
return (
<div>
<Heading message={'Custom Heading for Screen Two'}/>
</div>
)
}
}
我们在这里看到的是,我们定义了一个父组件,它依赖于传入的 props 进行更新。这是一个可自定义的值,可以根据显示它的子组件进行更改。如果 props 发生变化,显示的消息也会随之改变。
下面是一个继承的例子,不用太详细,继承就是从父组件扩展 props。但事情可能会变得很复杂。
class CreateUserName extends UserNameForm {
render() {
const parent = super.render();
return (
<div>
{parent}
<button>Create</button>
</div>
)
}
}
坚持创作方法,你会没事的。
太棒了,我们终于读完了!还有其他一些令人兴奋的概念,比如 Context,HigherOrderComponents
以及更多内容,Hooks
我会在其他文章中介绍。但这并不意味着它们就不那么重要了。我希望这篇文章能够解答你在使用 React 时遇到的许多 bug。