这篇文章不会涵盖什么是React或者为什么你应该学习它。相反,这是对已经熟悉JavaScript并熟悉DOM API基础知识的人们对React.js的基础知识的实践介绍。
以下所有代码示例均标示为参考。它们纯粹是为了提供概念的例子。他们大多数可以写得更好一些。
React是围绕可重用组件的概念设计的。您定义小组件,并将它们放在一起以形成更大的组件。
所有小或大的组件都可重复使用,甚至跨不同的项目。
一个React组件(以其最简单的形式)是一个简单的JavaScript函数:。
示例1 https://jscomplete.com/repl?j=Sy3QAdKHW
function Button (props) {
// 返回一个DOM元素。例如:
return <button type="submit">{props.label}</button>;
}
// 将Button组件渲染到浏览器
ReactDOM.render(<Button label="Save" />, mountNode)
下面说明用于按钮标签的花括号。现在不要担心他们。ReactDOM
稍后也会进行说明,但是如果要测试这个例子和所有即将到来的代码示例,上面的render
函数就是你所需要的。
第二个参数ReactDOM.render
是React将要接管和控制的目标DOM元素。在jsComplete REPL中,您可以使用特殊变量mountNode
。
请注意以下关于示例1:
Button
函数组件的返回输出中写出了什么样的HTML 。这既不是JavaScript也不是HTML,甚至不是React.js。但是,它非常受欢迎,成为React应用的默认设置。它被称为JSX ,它是一个JavaScript扩展。JSX也是折衷!继续尝试并返回上面的函数中的任何其他HTML元素,并查看它们是如何支持的(例如,返回一个文本输入元素)。上面的示例1可以用纯粹的React.js来编写,没有JSX,如下所示:
示例2 - 不用JSX的React 组件 https://jscomplete.com/repl?j=HyiEwoYB-
function Button (props) {
return React.createElement(
"button",
{ type: "submit" },
props.label
);
}
// 要使用Button,您可以执行类似这样
ReactDOM.render(
React.createElement(Button, { label: "Save" }),
mountNode
);
该 createElement
函数是React顶级API中的主要函数。您需要学习的这个级别中共有7件事情中的1项。这是多么小的React的API。
很像DOM本身具有document.createElement
创建由标签名称指定的元素的函数,React的createElement函数是一个更高级别的函数,可以做document.createElement
能做的任何事。但它也可以用于创建一个元素来表示一个React组件。我们使用上面的例2中的组件Button
时,我们用了后者。
不同于document.createElement
,React 的createElement
接受第二个参数后的动态数量来表示创建元素的子代。所以createElement
实际上创建一个树。
这是一个例子:
示例3 - React的createElement API https://jscomplete.com/repl?j=r1GNoiFBb
const InputForm = React.createElement(
"form",
{ target: "_blank", action: "[https://google.com/search](https://google.com/search)" },
React.createElement("div", null, "Enter input and click Search"),
React.createElement("input", { className: "big-input" }),
React.createElement(Button, { label: "Search" })
);
// InputForm使用Button组件,所以我们也需要:
function Button (props) {
return React.createElement(
"button",
{ type: "submit" },
props.label
);
}
// 然后我们可以直接使用InputForm
ReactDOM.render(InputForm, mountNode);
请注意以上几个例子:
InputForm
不是React组件; 它只是一个React 元素。这就是为什么我们直接在ReactDOM.render
中调用它,而不是与<InputForm />
。React.createElement
函数在前两个之后接受多个参数。从第3个起始的参数列表包括创建元素的子项列表。React.createElement
,因为它都是JavaScript。React.createElement
当元素不需要属性或特性时,第二个参数可以为null或空对象。className
而不是class
输入元素。秘密地,我们都希望React的API将成为DOM API本身的一部分。因为,你知道,这好多了。上面的代码是您在包含React库时了解的内容。浏览器不处理任何JSX业务。然而,我们人类喜欢看HTML并且使用HTML而不是这些createElement
调用(想象一下建立一个网站仅使用document.createElement
,你可以!)。这就是为什么存在JSX妥协的原因。React.createElement我们可以用非常类似于HTML的语法来编写上面的表单:
示例4 - JSX(与示例3相比) https://jscomplete.com/repl?j=SJWy3otHW
const InputForm =
<form target="_blank" action="[https://google.com/search](https://google.com/search)">
<div>Enter input and click Search</div>
<input className="big-input" name="q" />
<Button label="Search" />
</form>;
// InputForm "仍然" 使用按钮组件, 所以我们也需要它。
// 无论是 JSX 或正常的形式
function Button (props) {
// Returns a DOM element here. For example:
return <button type="submit">{props.label}</button>;
}
// 然后, 我们可以在.render里直接使用 InputForm
ReactDOM.render(InputForm, mountNode);
请注意以上几点:
className
而不是class
。我们上面写的(例4)是JSX。然而,我们对浏览器的编译是它的编译版本(示例3)。为了实现这一点,我们需要使用预处理器将JSX版本转换为React.createElement
版本。
那就是JSX。这是一个妥协,允许我们以类似于HTML的语法编写我们的React组件,这是一个很好的交易。
上面标题中的“Flux”一词被选为韵词,但它也是Facebook 流行的非常受欢迎的 应用架构 的名称。最着名的实现是Redux。Flux完美适应React反应模式。
JSX,顺便说一下,可以自己单独使用。这不是一个React唯一的事情。
在JSX部分中,您可以使用一对花括号内的任何JavaScript表达式。
*示例5 - 在JSX中使用JavaScript表达式 https://jscomplete.com/repl?j=SkNN3oYSW
const RandomValue = () =>
<div>
{ Math.floor(Math.random() * 100) }
</div>;
// 使用它:
ReactDOM.render(<RandomValue />, mountNode);
任何JavaScript表达式都可以放在那些花括号内。这相当于JavaScript 模板文字中的${}
插值语法。
这是JSX中唯一的约束:只有表达式。所以,例如,你不能使用一个常规if语句,但三元表达式是可以的。
JavaScript变量也是表达式,所以当组件接收到一个属性列表(RandomValue
组件没有,props
是可选的)时,可以在花括号内使用这些属性。我们在Button上面的组件中做了这个(例1)。
JavaScript对象也是表达式。有时候,我们在花括号内使用一个JavaScript对象,这使得它看起来像是双花括号,但它只是一个大括号内的对象。一个用例是将CSS样式对象传递给React中的特殊样式属性:
示例6 - 传递给特殊的React样式的对象prop https://jscomplete.com/repl?j=S1Kw2sFHb
const ErrorDisplay = ({message}) =>
<div style={ { color: 'red', backgroundColor: 'yellow' } }>
{message}
</div>;
// 使用它:
ReactDOM.render(
<ErrorDisplay
message="These aren't the droids you're looking for"
/>,
mountNode
);
请注意我是如何解体的只有消息出来的属性参数。这是JavaScript。还要注意上面的style属性是一个特殊的属性(再次,它不是HTML,它更接近于DOM API)。我们使用一个对象作为style属性的值。该对象定义了样式,就像我们使用JavaScript一样(因为我们就是)。 甚至可以在JSX中使用React元素,因为这也是一个表达式。记住,一个React元素是一个函数调用:
示例7 - 在React元素里面使用 {} https://jscomplete.com/repl?j=SkTLpjYr-
const MaybeError = ({errorMessage}) =>
<div>
{errorMessage && <ErrorDisplay message={errorMessage} />}
</div>;
// MaybeError组件使用ErrorDisplay组件:
const ErrorDisplay = ({message}) =>
<div style={ { color: 'red', backgroundColor: 'yellow' } }>
{message}
</div>;
// 现在我们可以使用 MaybeError 组件:
ReactDOM.render(
<MaybeError
errorMessage={Math.random() > 0.5 ? 'Not good' : ''}
/>,
mountNode
);
上面的MaybeError
组件将仅显示ErrorDisplay
组件,如果有一个errorMessage
字符串传递给它,并且是空的div
。React认为{true}
, {false}
, {undefined}
和 {null}
是有效的元素孩子,不渲染任何内容。
您也可以在JSX内使用所有的JavaScript函数方法的集合(map
, reduce
, filter
, concat
, 等)。再次,因为它们返回表达式:
示例8 - 在{}中使用数组 map https://jscomplete.com/repl?j=SJ29aiYH-
const Doubler = ({value=[1, 2, 3]}) =>
<div>
{value.map(e => e * 2)}
</div>;
// 使用它
ReactDOM.render(<Doubler />, mountNode);
请注意,我如何将value
prop设置为上面的默认值,因为它只是Javascript。还要注意,我在div
输出了一个数组表达式。在React中这是可以的。它将在文本节点中放置2倍的值。
简单的函数组件非常适合简单的需求,但有时我们需要更多的函数。React支持通过JavaScript类语法 创建组件。这是Button使用类语法编写的组件(在示例1中):
示例9 - 使用JavaScript类创建组件 https://jscomplete.com/repl?j=ryjk0iKHb
class Button extends React.Component {
render() {
return <button>{this.props.label}</button>;
}
}
// 使用它(相同的语法)
ReactDOM.render(<Button label="Save" />, mountNode);
类语法很简单。定义一个React.Component
的扩展类(需要学习的另一个顶级的React API)。该类定义单个实例函数render(),并且该render函数返回虚拟DOM对象。每次我们使用Button上面的基于类的组件(例如,通过这样做<Button ... />
),React将从这个基于类的组件中实例化一个对象,并在DOM树中使用该对象。
这就是为什么我们在JSX中使用this.props.label
渲染输出的原因。因为每个组件都获得一个特殊的实例属性props,所以它被实例化时保存传递给该组件的所有值。
由于我们有一个与组件单次使用相关联的实例,我们可以根据需要自定义该实例。例如,我们可以通过使用常规JavaScriptconstructor
函数构建它来定制它:
示例10 - 自定义组件实例 https://jscomplete.com/repl?j=rko7RsKS-
class Button extends React.Component {
constructor(props) {
super(props);
this.id = Date.now();
}
render() {
return <button id={this.id}>{this.props.label}</button>;
}
}
// 使用它
ReactDOM.render(<Button label="Save" />, mountNode);
我们还可以定义类原型函数,并将它们随意使用,包括返回的JSX输出内:
示例11 — 使用类属性 https://jscomplete.com/repl?j=H1YDCoFSb
class Button extends React.Component {
clickCounter = 0;
handleClick = () => {
console.log(`Clicked: ${++this.clickCounter}`);
};
render() {
return (
<button id={this.id} onClick={this.handleClick}>
{this.props.label}
</button>
);
}
}
// Use it
ReactDOM.render(<Button label="Save" />, mountNode);
注意上面关于示例11的几件事情:
handleClick
函数使用JavaScript中新提出的类字段语法 编写。这仍然在第二阶段,但由于很多原因,它是访问组件装载实例(感谢箭头函数)的最佳选择。但是,您需要使用像Babel这样的编译器来配置它来了解第2阶段(或类字段语法)来获取上面的代码。jsComplete REPL已预先配置。clickCounter
实例变量。这允许我们完全跳过使用类构造函数调用。handleClick
函数指定为特殊onClick
React属性的值时,我们没有调用它。我们通过在引用的handleClick
函数。调用该级别的函数是使用React最常见的错误之一。// 错误:
onClick={**this.handleClick()**}
// 正确:
onClick={**this.handleClick**}
在React元素中处理事件时,与DOM API的方式有两个非常重要的区别:
onClick
而不是onclick
.。onClick={**handleClick**}
不是onClick="**handleClick"**
。使用自己的对象对DOM事件对象进行反射来优化事件处理的性能。但是在事件处理程序中,我们仍然可以访问DOM事件对象上可用的所有方法。React将包装的事件对象传递给每个句柄调用。例如,为了防止表单从默认提交操作中,您可以执行以下操作:
示例12 - 使用包装事件 https://jscomplete.com/repl?j=HkIhRoKBb
class Form extends React.Component {
handleSubmit = (event) => {
event.preventDefault();
console.log('Form submitted');
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<button type="submit">Submit</button>
</form>
);
}
}
// 使用它
ReactDOM.render(<Form />, mountNode);
以下仅适用于类组件(那些扩展自React.Component
)。函数组件有一个略有不同的故事。
render
另一个组件的调用中,或ReactDOM.render
。this.props
属性。那些属性正是我们在上面的步骤2中传递的。constructor
将调用该方法(如果已定义)。这是我们所说的第一个:组件生命周期方法。componentDidMount
生命周期方法。我们可以使用这种方法,例如,在DOM上做一些我们现在知道在浏览器中存在的东西。在此生命周期方法之前,我们处理的DOM都是虚拟的。componentWillUnmount
。以下也仅适用于类组件。有没有人提到有些人把表演式的组件叫做哑巴?
状态类字段是任何React类组件中的特殊字段。React监视每个组件状态以进行更改。但是对于React这样做有效,我们必须通过我们需要学习的另一个React API事件来更改状态字段this.setState
:
Example 13 - setState API https://jscomplete.com/repl?j=H1fek2KH-
class CounterButton extends React.Component {
state = {
clickCounter: 0,
currentTimestamp: new Date(),
};
handleClick = () => {
this.setState((prevState) => {
return { clickCounter: prevState.clickCounter + 1 };
});
};
componentDidMount() {
setInterval(() => {
this.setState({ currentTimestamp: new Date() })
}, 1000);
}
render() {
return (
<div>
<button onClick={this.handleClick}>Click</button>
<p>Clicked: {this.state.clickCounter}</p>
<p>Time: {this.state.currentTimestamp.toLocaleString()}</p>
</div>
);
}
}
// 使用它
ReactDOM.render(<CounterButton />, mountNode);
这是了解最重要的例子。它将基本完成您对“基本法”的基本知识。在这个例子之后,还有一些你需要学习的小事情,但是从这一点来看,它主要是你和你的JavaScript技能。
我们来看一下实例13,从类字段开始。它有两个。特殊state
字段被初始化与持有的对象clickCounter
以0
开始,并且currentTimestamp
以new Date()
开始。
第二类字段是一个handleClick
函数,我们传递给render方法中的button元素的onClick
事件。该handleClick
方法使用setState
修改此组件实例状态。注意到这一点。
我们正在修改状态的另一个地方在我们在componentDidMount
l生命周期方法内部启动的间隔定时器中。它每秒钟执行另一个调用this.setState
.。
在render方法中,我们使用了正常读取语法对状态的两个属性。没有专门的API。
现在,请注意,我们使用两种不同的方式更新了状态:
handleClick
函数中做了这个。这两种方式都是可以接受的,但是当您同时读取和写入状态时,首先是首选的(我们这样做)。在间隔回调期间,我们只写给状态,而不是读取它。当有疑问时,始终使用第一个函数参数语法。它竞争条件更安全,因为setState
实际上是一种异步方法。
我们如何更新状态?我们返回一个具有我们要更新的新值的对象。注意在两次调用中setState
,,我们只是从状态字段传递一个属性,而不是两者。这是完全可以的,因为setState实际上将您传递的内容(函数参数的返回值)与现有状态合并。因此,在调用时不指定属性setState意味着我们不希望更改该属性(而不是删除它)。
React从它对状态变化做出的事实(虽然不是反应性的,而是按计划)。有一个笑话,React应该被命名为 Schedule!
然而,当任何组件的状态更新时,我们用肉眼看到的是,React对该更新做出反应,并自动反映浏览器DOM中的更新(如果需要)。
将渲染函数的输入视为两者
当render函数的输入变化时,其输出可能会改变。
React保留了渲染历史的记录,当它看到一个渲染与前一个渲染不同时,它将计算它们之间的差异,并有效地将其转换为在DOM中执行的实际DOM操作。
您可以将React视为我们聘请的与浏览器通信的代理。以上面的当前时间戳显示为例。我们不是手动去浏览器并调用DOM API操作来每秒查找和更新p#timestamp
元素,而是在组件的状态上更改了一个属性,而React则代表我们与浏览器通信。我相信这是React流行的真正原因。我们讨厌浏览器(和所说的DOM语言的很多方言),React自愿为我们做所有的谈话,免费!
现在我们知道一个组件的状态,以及当这个状态改变了一些魔法的时候,让我们来学习关于该过程的最后几个概念。
componentWillReceiveProps
。shouldComponentUpdate
。这个方法是一个实际的问题,所以如果你需要定制或自己进行优化渲染过程中,你必须回答返回这个问题无论是true还是false。shouldComponentUpdate
指定,React默认是一个非常聪明的事情,在大多数情况下实际上足够好。componentWillUpdate
。然后React将计算新的渲染输出并将其与最后渲染的输出进行比较。componentDidUpdate
。生命周期方法实际上是逃避舱口。如果你没有做任何特别的事情,你可以创建没有他们的完整的应用。它们非常方便地分析应用中发生的情况,并进一步优化了React更新的性能。
仅此而已。相信与否,上面你学到了什么(或者部分内容,真的),你可以开始创建一些有趣的React应用。
往期精选文章 |
---|
ES6中一些超级好用的内置方法 |
浅谈web自适应 |
使用Three.js制作酷炫无比的无穷隧道特效 |
一个治愈JavaScript疲劳的学习计划 |
全栈工程师技能大全 |
WEB前端性能优化常见方法 |
一小时内搭建一个全栈Web应用框架 |
干货:CSS 专业技巧 |
四步实现React页面过渡动画效果 |
让你分分钟理解 JavaScript 闭包 |
小手一抖,资料全有。长按二维码关注京程一灯,阅读更多技术文章和业界动态。