这篇文章不是针对经验丰富的 React 专业人士,而是针对我们这些以网站为生且好奇 React 如何帮助我们理解用户界面更新的人。我一直对 React 很感兴趣,现在它在社区中获得了认可并获得了不错的评价,学习它的时机似乎已经成熟。前端开发中不断涌现出如此多的新技术,以至于有时很难知道学习新技术的努力是否会得到回报。在本文中,我将介绍我认为一些最有价值的实用见解,以便您能够开始使用它。
公平警告
我只花了大约一周时间学习这些材料。这是故意的。我是在接触这项技术不久后就写下这篇文章的,并且可以从那个角度来写它。在这种状态下,我更擅长记住一路走来的跌跌撞撞和发现。
我使用了 Wes Bos 的 React 入门 课程,以及 React.js 入门,适用于那些仅懂少量 jQuery 的人。我强烈推荐这些资源。
两天内,我做出了这个
查看代码笔 宝宝第一次尝试 React,作者 Sarah Drasner (@sdras) 在 CodePen 上。
它并不完美,但它比我在那段时间内独自做出的东西要好得多。
如果您是那种想直接深入学习所有内容的人,包括构建和必需的工具,那就太棒了!从这里开始吧。 我还会推荐这个非常棒的 Frontend Masters 课程:现代 Web 应用程序。
流言终结者:React 实用性版本
在开始之前,我想破除一些我认为是许多人阻碍的几个关键神话。
神话 #1:您必须使用内联样式才能使用 React。
不!根本不是。您可以像往常一样使用 CSS。我刚花了大量时间重构一个巨大的 CSS 代码库,我会说建立这一点非常重要。React 比较新,还没有像其他技术一样经受住设计刷新的考验。如果我不得不重新遍历数千行内联样式才能更新像 padding
和 line-height
这样的东西,那确实会让我成为一个悲伤的开发者。
也就是说,内联样式在某些情况下确实很有用。如果您有一个组件,其样式会根据其状态而改变(例如:数据可视化),那么使用内联样式绝对有意义,这样您就不必维护不切实际数量的静态样式(在多个地方)来处理所有可能的状态。
不过我认为,这将建立在应用程序使用的基础 CSS 之上,并且是例外而不是规则。不过,网络很大,所以没有绝对的规定。
神话 #2:您必须使用 JavaScript 语法来表示元素属性,这与 HTML 完全不同。
我喜欢 Wes Bos 的教学风格的一点是,他会引导观众转向更熟悉的方法和实现。我通常喜欢简单和代码更清晰的方式,尽管我理解其他人喜欢更高程度的抽象。
他建议我们使用 JSX 来编写我们的标记,它更像我们的朋友传统 HTML,因此我发现它更清晰。所以,不用这样写
return React.createElement("p", {className: "foo"}, "Hello, world!");
我们将这样写
return (<p className="foo">Hello, world!</p>);
两种方法都可以。但是,当我们的标记变得越来越复杂时,我发现 JSX 中的 HTML 熟悉性有助于我的理解。不过请记住,即使使用 JSX,也有一些细微的差别。
神话 #3:为了尝试 React,您必须理解所有构建工具。
确实,为了使用 React,您需要使用构建工具,所以这通常是教程的开头。但是,我建议,作为一个完全的初学者,您应该在 CodePen 上玩玩。这是一个快速迭代和学习的好方法,在将大量时间投入一项全新的技术之前。
在典型应用程序中使用 React
在使用 React 1.14+ 的典型应用程序中,我们将从需要 React 和 ReactDOM 开始
var React = require('react');
var ReactDOM = require('react-dom');
然后调用 ReactDOM.render
ReactDOM.render(routes, document.querySelector('#main'));
在 CodePen 中使用 React
在我们的例子中,我们只需从 JS 面板的下拉菜单中选择 React(单击面板顶部的齿轮图标),然后使用 Babel 作为编译器。

我们不需要 require
React 或 React DOM,因为我们没有路由任何内容,我们直接使用应用程序组件。我们的代码将简单地修改为
React.render(<App/>, document.querySelector("#main"));
<div id="main"></div>
此外,为了开始使用,您需要 Chrome 的 React Devtools 扩展,或 Firefox 的 React Devtools 扩展,它非常适合调试我们的虚拟 DOM。
在 CodePen 中,您可以使用 调试视图,它将正确地找到 React

运行起来
您需要了解的一些基本构建块如下
// App
var App = React.createClass({
render: function() {
return (
<div className="foo">Hello, world!</div>
)
}
});
ReactDOM.render(<App/>, document.querySelector("#main"));
查看代码笔 Hello World React,作者 Sarah Drasner (@sdras) 在 CodePen 上。
让我们分解一下。在最后一行,我们找到了 main
div id,并且在上面我们渲染了 <App />
组件,它将加载我们所有的 React 应用程序。您现在可以使用 Devtools 中的 React 选项卡查看我们创建的 DOM 元素。
我们已将 <App />
作为第一个组件附加。这里要注意的第一点是,此标签是大写的 - 虽然这不是必需的,但它是 React 组件的最佳实践。第二点是,它是自闭合的。React 中的标签必须关闭,方法是使用额外的闭合标签(例如 </div>
),或自闭合标签(例如 <hr>
将变为 <hr />
)。这只是 JSX 的工作方式,否则会抛出错误。
请注意顶部创建应用程序组件的结构。您将非常习惯这种语法,因为我们今天构建的所有内容都将基于这个关键构建块。
最后一点需要注意的是,我们没有使用 `class`,而是使用 `className` 属性在元素上。如果你习惯于编写网页的 HTML 标记,这可能是一个需要注意的地方。幸运的是,它很容易解决。有关 JSX 及其语法的更多信息,这些文档 非常好的资源。
// App
var App = React.createClass({
render: function() {
return (
<Header />
)
}
});
// Header
var Header = React.createClass({
render: function() {
return (
<div className="foo">Hello, world!</div>
)
}
});
ReactDOM.render(<App/>, document.querySelector("#main"));
查看 CodePen 上 Sarah Drasner 创建的 Hello World React (@sdras) 。
在下一步中,我们将用一个组件扩展 app。Header 可以命名为任何你想要的名称,为了清楚起见,我们将其命名为它在文档中的角色。你可以看到,如果我们还想添加一个导航元素,那就很容易添加了。以下是同一个页面,只是使用了一个标准的 Bootstrap 行按钮作为 `<Nav />` 组件,以及一个更语义化的 `h1` 来显示我们的“Hello, World!”,而不是 `div`。
// App
var App = React.createClass({
render: function() {
return (
<div>
<Nav />
<Header />
</div>
)
}
});
// Nav
var Nav = React.createClass({
render: function() {
return (
<ul className="nav nav-pills">
<li role="presentation" className="active">Home</li>
<li role="presentation">Profile</li>
<li role="presentation">Messages</li>
</ul>
)
}
});
// Header
var Header = React.createClass({
render: function() {
return (
<h1 className="foo">Hello, world!</h1>
)
}
});
ReactDOM.render(<App/>, document.querySelector("#main"));
查看 CodePen 上 Sarah Drasner 创建的 Hello World React (@sdras) 。
非常简单,对吧?它就像任何网页的积木一样。我们创建了 nav 和 header,并将这些组件应用于 app 组件,该组件在 body 上渲染。最后一个示例中唯一的真正需要注意的地方是你在 `<Header />` 和 `<Nav />` 周围看到的奇怪的额外 div。这是因为我们必须始终返回单个元素。我们不能有两个兄弟元素,所以我们将它们包装在一个 div 中。
既然你了解了构建块,你就可以轻松地使用 React 和 React 组件拼凑出一个页面。我建议你重构一个你已经制作的页面布局,这样你就可以学习了。将样式表放在里面,然后一点一点地拼凑出剩下的部分,直到你重构了它的骨架。这是一个很好的练习。
有一种方法可以 自动化此过程,但我建议在你先自己完成构建的过程之前不要使用这样的工具。这样,如果你以后遇到问题,你就知道发生了什么。
常见嫌疑人:变量和事件处理
让我们先进行一些简单的事件处理并添加一些简单的变量,看看效果如何。
再次考虑第一个演示(我们将在整篇文章中使用它)
查看代码笔 宝宝第一次尝试 React,作者 Sarah Drasner (@sdras) 在 CodePen 上。
博客文章组件有一些非常简单的标记,所以让我们从它开始
// Blog Post
var Post = React.createClass({
render : function() {
return (
<div className="blog-post">
<h3 className="ptitle">{this.props.ptitle}<small>{this.props.date}</small></h3>
<img className="thumbnail" src={this.props.pimg} />
<p>{this.props.postbody}</p>
<div className="callout callout-post">
<ul className="menu simple">
<li>Author: {this.props.author}</li>
<li>Comments: {this.props.comments}</li>
<li>Tags: {h.getTaggedName()}</li>
</ul>
</div>
</div>
)
}
});
让我们添加一个任意变量和事件处理程序,看看它是如何工作的。首先要考虑的是,如果我们使用 JSX,花括号将让 React 知道我们准备使用一些 JavaScript。因此,我们的变量将类似于 ` {var} `。
我们将在 render 函数调用内部添加变量语句,但在返回标记之前。然后,我们可以通过调用变量来访问它,在本例中为 ` {com} `。单击事件是一个内联 ` onClick ` 处理程序,这可能与你习惯的最佳实践背道而驰。这是一个我们选择的名称,而 render 是一个框架方法。我们调用 ` {this.tryClick} `,并在同一个组件中编写一个名为 ` tryClick `(一个任意名称)的方法,如下所示
// Blog Post
var Post = React.createClass({
tryClick : function() {
alert('just trying out click events lalala');
},
render : function() {
var com = "Comments";
return (
<div className="blog-post">
<h3 className="ptitle">{this.props.ptitle}<small>{this.props.date}</small></h3>
<img className="thumbnail" src={this.props.pimg} />
<p>{this.props.postbody}</p>
<div className="callout callout-post">
<ul className="menu simple">
<li>Author: {this.props.author}</li>
<li>{com}: {this.props.comments}</li>
<li>Tags: {h.getTaggedName()}</li>
</ul>
</div>
</div>
)
}
});
React 中事件的语法以“on”开头,并且使用驼峰命名法。例如
- click = onClick
- mouseEnter = onMouseEnter
- keyPress = onKeyPress
你可以找到所有受支持事件的完整列表。
将 React 与其他库结合使用(在本例中为 Greensock)
我喜欢使用 GSAP 进行动画制作。你可以在 React 中使用其他库,所以让我们将 GSAP 和 React 结合起来,看看效果如何。
我们希望在正确的时间访问其他库,因此我们必须确保它们在第一个 render 方法之后立即被调用。我们为此使用的方法称为 ` componentDidMount `。我们将在后面更多地解释其他生命周期方法,但你现在需要知道的是,该方法是在组件插入文档后立即调用的。
如果你习惯使用 jQuery,你熟悉从 DOM 中获取元素。在本例中,即使我们正在对 DOM 元素进行补间动画,我们也将使用 React 的 ` getDOMNode()` 而不是直接获取它们。你还会看到,我们实际上是在 app 组件中调用进行补间动画的函数,并且只是将其传递给我们的盒子。(你可能需要点击重新运行才能看到动画。)
查看 CodePen 上 Sarah Drasner 创建的 794b375a8725b039483c83fb63a4bd5a (@sdras) 。
// App
var App = React.createClass({
componentDidMount: function() {
var sq1 = this.refs.first.getDOMNode();
var sq2 = this.refs.second.getDOMNode();
var sq3 = this.refs.third.getDOMNode();
var sq4 = this.refs.fourth.getDOMNode();
var sq5 = this.refs.fifth.getDOMNode();
var allSq = [sq1, sq2, sq3, sq4, sq5];
TweenLite.set(allSq, {css:{transformPerspective:400, perspective:400, transformStyle:"preserve-3d"}});
TweenMax.to(sq1, 2.5, {css:{rotationX:230, z:-600}, ease:Power2.easeOut}, "+=0.2");
TweenMax.to(sq2, 2.5, {css:{rotationY:230, z:-150}, ease:Power4.easeOut}, "+=0.2");
TweenMax.to(sq3, 2.5, {css:{rotationX:500, z:150}, ease:Power2.easeInOut}, "+=0.2");
TweenMax.to(sq4, 2.5, {css:{rotationY:500, z:-150}, ease:Power4.easeOut}, "+=0.2");
TweenMax.to(sq5, 2.5, {css:{rotationX:1000, z:100}, ease:Power2.easeOut}, "+=0.2");
},
render: function() {
return (
<div className="scene">
<Box ref="first"></Box>
<Box ref="second"></Box>
<Box ref="third"></Box>
<Box ref="fourth"></Box>
<Box ref="fifth"></Box>
</div>
)
}
});
// Box
var Box = React.createClass({
render: function() {
return (
<div className="squares"></div>
)
}
});
ReactDOM.render(<App/>, document.querySelector("#main"));
你可能习惯于通过 jQuery 或原生 JavaScript 访问 DOM。我们仍然可以这样做,但在这里,我们通过 ` getDOMNode()` 和 ` refs` 访问 DOM 的部分,例如这行代码:` var sq1 = this.refs.squares1.getDOMNode(); `。你可以在 React 文档 中阅读更多关于此内容的信息。
有更多特定于 React 的方法可以将动画添加到你的项目中——一个非常棒的方法是 Cheng Lou 的 React-Motion,另一个值得一提的是 React-GSAP-Enhancer。
创建全局辅助函数
你甚至可以编写辅助函数,这些函数可以轻松地被多个组件访问。我们将使用与 ` this.functionName ` 相同的点表示法来使用它们。我们存储辅助函数(在本例中,我们在 CodePen 演示的开头存储,但在实际应用程序中,它们将存储在单独的文件中),并像你在组件结构中一样将每个函数声明为一个对象。
类似于第一个演示中博客文章中格式正确的日期,将是
var h = {
getTime: function() {
var month = ["jan", "feb", "march"]; // …. and so on
var d = new Date();
var mon = month[d.getMonth()];
var day = d.getDate();
var year = d.getFullYear();
var dateAll = mon + " " + day + ", " + year;
return dateAll;
}
};
用法如下:` {h.getTime()} `
好东西:管理状态
好了!现在我们已经掌握了构建块,让我们来学习一些很酷的东西。
React 当然擅长使用易于理解的小巧组件构建应用程序,但它真正擅长的是管理这些组件的状态。让我们深入了解一下。
对于这一部分,我们将介绍了解如何访问和使用变化的事物时需要理解的两个关键部分。
- 第一个是**状态**。组件本身拥有它,这意味着它对组件进行了范围限定,我们将这样引用它:` {this.state.foo} `。我们可以通过调用 ` this.setState()` 来更新状态。
- 第二个是指我们如何从父组件传递只读数据到子组件(例如,app 是第一个示例中 ` header ` 的父组件)。我们将它称为**属性**,就像属性一样,我们将直接在组件中使用它,使用 ` {this.props.foo} `。子组件无法更改此数据。
只要我们更改了这两者中的任何一个,并且我们的组件依赖于它,React 就会重新渲染它需要更新的组件部分。
虚拟 DOM 的妙处在于,React 会找出哪些 DOM 节点需要更新。
我已经阅读了很多关于状态的内容,但我认为 Wes 的解释最清晰,所以我将用他的话来解释:**如果你习惯使用 jQuery,你将在 DOM 中存储所有数据。React 通过将数据存储在对象(状态)中,然后根据对象渲染事物,完全避免了这种情况。这就像一个巨大的主控台,所有东西都基于它。**
尝试属性
让我们先尝试 ` this.props `,我们将 ` {this.props.title} ` 添加到 ` Header ` 组件,然后我们可以在 app 中访问它。我们将添加一个字符串来创建标题
// App
var App = React.createClass({
render: function() {
return (
<Header greeting="Hello, world!" />
)
}
});
// Header
var Header = React.createClass({
render: function() {
return (
<div className="foo">
<h1>{this.props.greeting}</h1>
</div>
)
}
});
ReactDOM.render(<App/>, document.querySelector("#main"));
我们也可以在名为 getDefaultProps
的生命周期方法中为道具添加默认值。这样做很棒,因为有时您不希望必须为每个组件指定道具。一个例子是,如果用户尚未设置默认的个人资料头像,例如 Twitter 上的鸡蛋,或者将列表设置为一个空数组,该数组将在稍后填充。
var ProfilePic = React.createClass({
getDefaultProps: function() {
return {
value: 'twitter-egg.jpg'
};
...
});
做点事情
现在让我们以此为基础,与状态进行一些交互。如果我们最终要更改 this.state
,我们必须准备好它。为此,我们将使用 getInitialState
,它允许我们定义状态的初始值。如果没有它,我们就无法添加状态。
我们只需要根据用户的选择更改背景。
查看 CodePen 上 Sarah Drasner 的作品 928579628fd2ecb0f98aff9c08774e93 (@sdras) on CodePen。
// App
var App = React.createClass({
/*setting state*/
getInitialState: function() {
return {
bgColor: "teal"
};
},
/*changes state*/
handleColorChange: function (color) {
// when we set state directly, react doesn't know
// about it. that's why we use setState
this.setState({ bgColor: color });
},
/*for the lifecycle methods*/
updateBackgroundColor: function () {
var body = document.querySelector('body')
body.style.background = this.state.bgColor
},
/*lifecycle methods*/
componentDidMount: function () {
this.updateBackgroundColor()
},
componentDidUpdate: function () {
this.updateBackgroundColor()
},
render: function() {
return (
/* by calling the title here on the component, we can access this.props on header */
<div className="foo">
<h1>Hello, World!</h1>
<label>What color?
<ColorPicker value={this.state.bgColor} onColorChange={this.handleColorChange}/>
</label>
</div>
)
}
});
// ColorPicker component
var ColorPicker = React.createClass({
propTypes: {
value: React.PropTypes.string.isRequired,
onColorChange: React.PropTypes.func
},
handleChange: function(e) {
e.preventDefault();
var color = e.target.value
// If whoever rendered us (the ColorPicker) is interested
// when the color changes, let them know
if (this.props.onColorChange)
this.props.onColorChange(color);
},
render: function() {
return (
<select value={this.props.value} onChange={this.handleChange}>
<option value="orangered">orangered</option>
<option value="teal">teal</option>
<option value="orange">orange</option>
<option value="indigo">indigo</option>
<option value="red">red</option>
</select>
)
}
});
ReactDOM.render(<App/>, document.querySelector('#main'));
handleChange
函数。
需要考虑的一个重要部分是,我们需要 在普通的 DOM 环境中,原生选择器不需要您执行类似的操作,因为它会自动更新到用户的选择。在 React 中,所有状态都由组件管理,因此如果您需要一个输入或选择器来监听更改,您必须编写它。这意味着组件负责状态,而不是用户。
这对于少量回报来说似乎需要做很多工作,但我们特意将这些示例保持得相当简单。React 的强大之处在于处理复杂性。尽管我喜欢 jQuery(我仍然喜欢 jQuery,它不是布尔值),但当某些内容发生更改时,您必须经常执行检查。React 通过消除这种需求来解决这个问题,因为数据流方向的简单性,这也使得在高度复杂的应用程序中更容易推理。
状态概念
既然我们已经掌握了最基本的内容,那么让我们快速了解一下我们为什么要这样做,以及一些最佳实践。
当我们更新状态并重新渲染低级别组件时,React 才能发挥最佳效果。从根本上说,我们希望这些组件基本上是无状态的,并从上层组件接收数据。我们层次结构顶部的组件,例如我们的应用程序,在它们主要是状态化时效果最佳。这样,它们就可以管理大多数交互逻辑,并使用道具传递状态。也就是说,仍然会有一些情况需要每个组件都有自己的状态。但是,重要的是要记住,兄弟节点不应该直接相互通信,它们应该与它们的父组件通信。

因此,我们希望尽可能将状态保存在组件中。您实际上永远不必担心何时渲染内容。您只需要担心状态何时发生变化,React 会为您处理渲染。
这种最佳实践意识形态直接导致了我建议您不要在 getInitialState
中使用道具的原因,这里有更多关于为什么这被认为是一种反模式的信息。
我们不想过度使用状态。组件的渲染速度很快,但是如果您开始尝试管理过多的状态,性能可能会下降。有一些方法可以解决这个问题,一旦它真正成为问题,但是最好一开始就不要陷入困境。
本文的其余部分将介绍如何最好地管理这些状态,并将不可变的关注点保持在层次结构中的更高层级,同时仍然引用我们层次结构中较低层的无状态组件。
引用
我们还可以使用称为引用的东西访问有关元素的信息。我们通过将其附加到任何组件来使用引用。它将在 render()
方法中返回,然后我们可以在 render()
方法之外引用它。这非常有用。
再考虑一下第一个 CodePen。让我们花一分钟时间思考一下我们是如何创建这些新博客文章的,并在此过程中使用引用。
在每个表单输入中,我们都有一个引用,例如
<input type="text" ref="name" placeholder="Full Name required" required />
在表单元素本身,我们调用
onSubmit={this.createPost}
它引用了渲染函数上面的 createPost
函数,并使用引用来存储来自该提交的信息
var post = {
name : this.refs.name.value,
...
}
然后我们有办法使用 this.props
在应用程序状态中引用它
this.props.addPost(post);

这非常方便,因为现在我们有一个名为 post 的变量,其中包含存储其名称(此处显示)、日期、详细信息等的 对象。我们还没有将这些内容存储在应用程序状态中,我们只是创建了一种方法让应用程序使用它。让我们接下来讨论一下,以及 keys
。
我使用这个示例作为一般讨论引用的方法,但是在现实应用程序中,您可能想考虑使用类似于form-serialize 的东西来序列化表单字段以通过 AJAX 提交。
键和使用引用
在我们上一节中,我们创建了一种方法来存储我们在表单中收集的数据,而不会将其传递出去。这是因为在 React 中,我们希望在层次结构的顶层管理这个状态。为了存储这个状态,我们使用一个空对象启动我们的初始状态。最终,我们将在其中存储我们的帖子数据。
getInitialState : function() {
return {
posts: {}
}
},
然后我们有我们的 addPost 函数。请注意,我们在这里管理它,而不是在表单上,即使我们是从表单中调用的它。我们为状态提供一个时间戳以保持帖子的唯一性,并且还设置了帖子的状态。
addPost: function(post) {
var timestamp = (new Date()).getTime();
// update the state object
this.state.posts['post-' + timestamp] = post;
// set the state
this.setState({ posts : this.state.posts });
},
数组中的每个子元素都应该具有唯一的 key.prop
。这很重要,因为 React 会尽可能地重用现有的 DOM。React 使用 keys
来跟踪它应该更新 DOM 中的哪些元素。它使我们不必在重新渲染时重新绘制所有 DOM 元素。这有助于提高我们应用程序的性能。
现在让我们再看一下 App,看看我们如何处理从表单中新存储的数据
// App
var App = React.createClass({
getInitialState : function() {
return {
posts : {}
}
},
addPost : function(post) {
var timestamp = (new Date()).getTime();
// update the state object
this.state.posts['post-' + timestamp] = post;
// set the state
this.setState({ posts : this.state.posts });
},
renderPost : function(key){
return <NewPost key={key} index={key} details={this.state.posts[key]} />
},
render : function() {
...
return (
<div>
<Banner />
...
<div className="list-of-posts">
{Object.keys(this.state.posts).map(this.renderPost)}
</div>
<Submissions addPost={this.addPost}/>
</div>
</div>
)
}
});
您可以看到,当我们渲染应用程序时,我们使用 Object.keys 创建一个新数组。然后我们使用 .map()
以及我们之前创建的 renderPost
函数。对于那些不熟悉 .map()
的人来说,它对于从现有数组中创建数组非常有用,您可以将其视为循环。
<div className="list-of-posts">
{Object.keys(this.state.posts).map(this.renderPost)}
</div>
现在让我们创建我们刚刚调用的那个 renderPost 函数。我们将其作为参数传递键,并将它设置为键、索引,并创建一个名为 details 的对象来保存关于帖子状态的信息。
renderPost: function(key) {
return <NewPost key={key} index={key} details={this.state.posts[key]} />
},
我们传递一个 key
和一个 index
似乎很奇怪,但是在组件内部,我们无法访问 key prop
,因此我们传递另一个名为 index
的属性。此 renderPost 函数只是为数组中的每个项目创建一个 <NewPost />
组件,并传递下游 details,我们可以访问它。这很棒,因为它意味着如果我们的状态中有什么东西更新了,它就会传播到下游,并且我们可以在一个地方管理它。
这是 <NewPost />
组件,我们使用从应用程序传递下来的 details 来渲染所有信息。您可以看到我们之前为正确格式化日期而创建的辅助函数,在本文中用作 {h.getTime()}
/*
NewPost
<NewPost />
*/
var NewPost = React.createClass({
render : function() {
var details = this.props.details;
return (
<div className="blog-post">
<h3 className="ptitle">{details.title}<small>{h.getTime()}</small></h3>
<img className="thumbnail" src={details.image} alt={details.name}/>
<p>{details.desc}</p>
<div className="callout callout-post">
<ul className="menu simple">
<li>Author: {details.name}</li>
<li>Comments: 0</li>
<li>Tags: {h.getFunName()}</li>
</ul>
</div>
</div>
)
}
});

查看代码笔 宝宝第一次尝试 React,作者 Sarah Drasner (@sdras) 在 CodePen 上。
所有内容一起
既然我们理解了键,让我们花一分钟时间看看全局图景。我们讨论了 getInitialState
、componentDidMount
和 render
,但是 React 还有一种挂载方法。它被称为 componentWillMount
。它与 componentDidMount
类似,但是它在组件首次渲染之前立即执行。

此图表并不包含 React 中所有可用的内容,而只是对我们迄今为止所涵盖内容的概览,足以让您开始动手操作。
总结
关于 React,您需要学习的内容远不止本文,这仅仅是开始。希望这篇初学者指南能帮助其他人了解如何开始使用 React。这篇文章长达 20 页,我们甚至没有涉及大量其他重要且相关的主题,包括但不限于 ES6、Browserify、Gulp、Webpack,或各种替代实现。
为了进一步学习,我强烈建议你深入学习Wes Bos 的课程,他为 CSS-Tricks 的读者提供 10% 的折扣,代码为 CSSTRICKS。另外还可以观看Frontend Masters上的视频。Michael Jackson 有一个很棒的培训课程,你可以注册(我们将在 3 月份邀请他到旧金山的 Trulia 参加活动!敬请关注报名信息)。还有 Artemij Fedosejev 写的一本很棒的书,叫做React Essentials,以及 Artem Sapegin 整理的这份资源列表,值得收藏。不要低估React 文档的价值,它也是很好的资源。祝你学习愉快!
非常感谢Michael Jackson、Wes Bos和Val Head校对本文。
谢谢,Sarah,很棒的介绍。我有点困惑(还没有机会详细阅读整个教程)——你的应用程序是否也应该添加评论,或者唯一的功能是用于向博客添加帖子的表单?
你好 Lubos,
实际的应用程序应该执行这个演示中没有的很多功能——它没有与后端连接,标签不是真实的,评论不是真实的,我们可以为帖子创建单独的页面。我故意让这个示例保持简洁,因为目的是展示构建的最初步骤。我希望它尽可能简单,以便让用户了解核心概念。谢谢!
当然,这完全没问题,只是不太确定它是否应该做些什么,或者是不是有什么问题。也许可以在演示旁边添加一个简短的说明来澄清这一点。无论如何,很棒的教程。
阅读起来很愉快。谢谢。
我写了一些关于学习 React 的内容,读者在从网站迁移到应用程序时可能会发现这些内容很有用。
http://developer.telerik.com/featured/5-steps-for-learning-react-application-development/
谢谢 Cody!很棒的资源,我相信人们会发现它在这里很有用。
谢谢你的 React 简介。我已经评估了像这样的库/框架一段时间了,一直在等待哪个框架会脱颖而出。
在你选择 React 之前,你试过 Angular 吗?如果有,你为什么选择 React?
我之所以这样问,是因为我是 Prototype.js 的早期采用者,并因此受到了伤害。我仍然在清理一些由它留下的项目中的混乱……。
另外,在 Firefox(Linux Mint 上的 v 44)中,没有一个 CodePen 能够正常工作,我希望这是一个编程错误,而不是 React 的错误。(它们在 Chrome 中可以正常工作……)
你好 Vanderson,
我刚刚在 Mac 上的 Firefox 中重新检查了所有演示,它们都可以正常工作——我需要去寻找 Linux 进行一些测试,对此我表示歉意。
我试过 Angular,是的——Angular 和 React 的比较有点像拿苹果和橘子比——Angular 是一个 MVC,而 React 只是 MVC 中的 View。
我强烈建议你在将 React 应用于生产代码之前进行尝试,这正是你所述原因。它只是一个工具,像其他工具一样,像其他工具一样有优点,也有一些比较复杂的地方(这是我个人的意见)。它需要一些真正的调整,而不是将东西存储到 DOM 中并从中获取,尤其是虚拟 DOM。我相信不同的程序员和不同的编程任务会(并且已经)以不同的方式接受它。
希望这有帮助!
好吧,现在示例可以正常工作了……之前它们在 CodePen 上一直处于“正在加载……”状态。谁知道发生了什么事。
我不知道是怎么找到的,但我正在查看 riot.js、vue.js 和 mithral。在阅读了一些资料后,我发现了 riot.js 关于将 React + Polymer 结合在一起的解释。当我第一次在演示文稿中看到一些示例时,我立刻就被 Polymer 迷住了。
我喜欢 riot.js 背后的理论(当然,我今天才开始研究它)的原因是标准的概念。我担心 Facebook 可能会在明年更改 React 的核心元素,因为这最符合他们的利益,或者像 Angular 1 那样,他们意识到他们有一些需要修复的根本缺陷。
谢谢你的鼓励,让我重新考虑 DOM 交互。第一次学习 React 时,这让我有点害怕。但我猜这是你尝试过之后就会明白的事情?(即使 Riot 也赞扬 React 的核心概念)
(抱歉在这里发送垃圾邮件)
刚刚注意到 riot.js 强制执行单向数据绑定,这是 Facebook 团队在太晚的时候才意识到会导致应用程序出现问题的。
想想实现 Flux 需要多少工作:(例如,必须构建一个自定义调度器)
https://fbdocs.cn/flux/docs/todo-list.html
如果 React 只是强制执行单向数据绑定,那么就不需要 Flux。至少从这个演示文稿和对文档的快速阅读中,我得到了这样的结论。
https://fbdocs.cn/react/blog/2014/05/06/flux.html
这似乎与 Angular 1、Prototype.js 等存在着相同的根本缺陷……我是否遗漏了什么?我只是觉得 React 因为 Flux 而闻到了代码的味道。
很棒的内容:) 我还在继续学习,但是……在“常见嫌疑人:变量和事件处理”部分之前的演示中,发布的代码中的元素不包含
<a>
元素,也没有得到你在 CodePen 中提供的 Bootstrap 样式(其中包含<a>
元素)。这不是什么大问题,但我有点困惑,因为我认为我一直在完美地按照步骤进行操作哈哈。谢谢这篇很棒的文章 :D
很棒的教程!你没有陷入常见的在一个教程中介绍太多概念的情况。例如,如果你正在学习 React,那么你应该尽量从示例中排除“其他内容”,这意味着使用最小的 ES2015 集,并尽可能少地使用抽象(减少冗余的库)。人们应该首先编写冗长的代码,当他们了解了代码的工作原理后,再开始使用库来隐藏这些细节。
我写了一篇博客文章“不要让学习 React 比它应该的更难”,这篇文章很受欢迎,因为人们似乎在学习 React 时遇到了困难。我希望读者会发现它“令人欣慰”。:)
很棒的文章。我不知道为什么我之前没有深入研究这个问题,但现在你让我着迷了!
太好了!很高兴它对你有用。谢谢!
你好 Sarah,
很棒的文章!
我认为对于“神话 #2:你必须使用 JavaScript 语法来编写元素属性,这与 HTML 完全不同”这句话,你混淆了一些事情:属性与组件。
你说的没错,对于组件,你可以选择使用 JSX 而不是更冗长的 JS 语法。
但是,如果我理解正确,元素属性(即使在 JSX 中)也始终需要使用 JS 语法(即 camelCase)。例如,你必须使用
className
而不是class
(因为class
是 JS 中的保留关键字),或者使用dataMyAttrName
而不是data-my-attr-name
。希望这有帮助。
谢谢你的关注!但不是,我一点也不困惑。我确实是指元素属性,如果你看看提供的示例,即使使用 camelCase,JSX 也比那些第一次看到 React 文档的人想象的更接近通用的 HTML 语法。我确实提到了它们是不同的,并在文章后面进一步解释了这一点。
“这篇文章长达 20 页,我们甚至还没有……”
干得好,Sarah!不过,我不确定如此高的复杂性对于整个网络社区来说是否是一件好事。曾经有一段时间,语言(HTML、CSS)遵循 W3C 的指导原则,即最小能力原则。
另外,还可以查看 Luke Plant 的精彩文章,我们需要更不强大的语言。
对于任何使用 Node.JS 的人,我强烈建议这些开发人员认真考虑eBay 的开源 Marko.js。与 React 相比,Marko 遵循最小能力原则。 Marko Widgets 是 eBay 对 React 的答案,Marko 的学习曲线明显更低。
Marko 是 eBay 的一个开源 HTML 基模板引擎,可用于在服务器(Node.js)或 Web 浏览器中渲染模板。它速度极快且轻量级(压缩后约 3.75 KB),同时还支持流式传输和异步渲染。开发人员可以扩展 HTML 语法,使用自定义标签和自定义属性来引入新的可重复使用的构建块。Marko 编译器生成与 Node.js 兼容的 JavaScript 模块,这些模块易于阅读、理解和调试。但是,eBay 在宣传方面做得非常少(不像 Facebook 一样竭力宣传 React,从而使高复杂度流行起来)。
Marko 的设计特别遵循最小能力原则,与 React 相比,Marko Widgets 使编写 SPA 类 Web 应用程序变得轻而易举。为了让你了解一下,eBay 即将发布 Marko v3,新语法将如下所示
与React JSX相比
eBay 已发布基准测试,表明 Marko 在服务器端的性能比 React 更快且更成熟。不仅如此,eBay 在过去的一年中一直在生产环境中对 Marko.js 进行实战测试,并且正在将所有核心服务迁移到 Node.js 和 Marko。
对于使用 Node.js 的人来说,我强烈推荐 Marko 比 React 更优秀,我们也可以希望未来的其他前端语言能重视 *最小权能原则*。我认为 React 正在朝错误的方向发展,IMO。但希望这种情况会改变。
干杯
莎拉,我关注你的很多作品,我已经使用 React 几个月了,我认为这是对 React 的一个极好的介绍,尤其是对于那些可能对一些概念持怀疑态度或感到困惑的人。
我认为一篇很棒的后续文章可以涵盖可以与 React 配合使用的库,例如 Redux,以帮助管理应用程序状态。我个人很喜欢 Redux 的方法,但我很好奇想看看更多,我听说 Cycle.js 和 RxJS 很不错。如果你有任何想法,我很想听听。
谢谢!
嗨!这很棒,只是想让你注意一下如何在示例中处理状态。
在“Usual Suspects”的第一个演示中,有一个
this.state.posts[...] = post
,它会改变 React 状态,并且绕过了setState
,你可以直接做一些类似的事情:this.setState({posts: [...this.state.posts, post]})
(这是 ES6 解构语法,这意味着:...array
=array[0], array[1], array[2], ..., array[end]
)它也适用于对象,你可以做一些类似的事情:
user: {...this.state.user, profilePicture: newProfilePicture }
尽管如此,它仍然很棒。:)
嗨,我注意到了一些我不太理解的事情。当你提供键的示例时,你将帖子存储在一个对象中,并将键分配给该对象。然后你遍历该对象,检索键,并传递整个对象并通过键再次访问属性。
原始
建议的重构
从我的角度来看,在这种情况下数组更好,因为你正在存储多个相同类型的对象,并且我们不需要用键来访问它们。你选择对象的原因吗?(我可能自己在这里遗漏了一些东西)
从返回的类中删除
renderPost
可能是个好主意,因为这将是一个可以在 render 函数之外使用的方法。我们只需要将帖子作为原始 render 函数的一部分进行渲染。换句话说,你可以从外部访问它——而且由于不需要从外部访问它,所以可能不应该将其声明为方法。最后,没有必要将键传递给索引。如果你需要标识符,可以传递整个帖子对象。(post
而不是post.data
)啊,是的,Raito Bezarius 关于改变状态说得很有道理。使用
setState
函数应该始终优先于直接设置状态。(另外,对于格式不佳的代码表示歉意。我没有意识到空格在这里的作用方式!)