@hpp157
2016-09-26T09:51:44.000000Z
字数 13607
阅读 2994
标签(空格分隔)react
为了理解React是如何在浏览器中运行的,我们用纯react的方法开始学习,暂时不会介绍JSX语法,或者XML,会将它放在下一章。你可能在编译JSX为React的时候见到过纯React,但是如果你花费些时间思考下这些场景的背后发生了什么的话,你会迅速的得到提高。这就是我们在这一章中的目标看下react的底层,来了解react是怎么工作的
为了让react在浏览器中工作,我们需要引入两个库:React和ReactDOM,react是一个创建view的库,ReactDOM是一个用来在浏览器中渲染UI的库。
我们也需要一个HTML元素,让ReactDOM用来渲染UI,
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"> <title>Pure React Samples</title> </head>
<body>
<!-- Target Container -->
<div class="react-container"></div>
<!-- React Library & React DOM-->
<script src="https://fb.me/react-15.1.0.js"></script>
<script src="https://fb.me/react-dom-15.1.0.js"></script>
<script>
// Pure React and JavaScript Code
</script>
</body>
</html>
这是在浏览器中运行react的最小设置,你可以把js放到单独的文件中,但是必须在React加载之后引入。
虚拟DOM是一个Javascript的对象,告诉react如何在浏览器中构建UI。
HTML仅仅是一个让浏览器在构建DOM时遵守的指导说明,比如说,你必须要构建一个名为recipe的HTML,你很可能这样做:
<section id="baked-salmon"> <h1>Baked Salmon</h1>
<ul class="ingredients">
<li>1 lb Salmon</li>
<li>1 cup Pine Nuts</li>
<li>2 cups Butter Lettuce</li>
<li>1 Yellow Squash</li>
<li>1/2 cup Olive Oil</li>
<li>3 cloves of Garlic</li>
</ul>
<section class="instructions">
<h2>Cooking Instructions</h2>
<p>Preheat the oven to 350 degrees.</p>
<p>Spread the olive oil around a glass baking dish.</p>
<p>Add the salmon, Garlic, and pine nuts to the dish</p>
<p>Bake for 15 minutes.</p>
<p>Add the Butternut Squash and put back in the oven for 30 mins.</p>
<p>Remove from oven and let cool for 15 minutes.
Add the lettuce and serve.</p>
</section>
</section>
在HTML中,元素互相在一个层次中,就像一本家谱一样。我们可以说root元素有三个children,1个,一个无序列表ingredients,和一个section。对于web开发者来说,创建这个HTML层次很简单,随着Ajax和单页面应用的出现,开发中必须依赖与DOM API来改变UI
有了React,我们不再直接和DOM API打交道,我们通过使用React元素定义一个虚拟DOM,这个虚拟DOM提供DOM的结构说明,也提供怎样构建浏览器DOM的方法,React用这些方法和DOM API交互
DOM API
这是一个javascript用来和浏览器交互的API, 如果你使用过document.createElement
或者document.appendChild
,那么你已经用过DOM API了
浏览器DOM由DOM元素组成,相似的,ReactDOM是React元素组成的。DDOM元素和React元素可能看起来一样,但是实际上它们有很大不同。一个React元素是一个关于actual DOM长什么样子的说明,换句话说,React element是一个浏览器DOM怎样被创建的说明书。
我们可以使用React.createElement
创建一个react元素来表示
React.createElement("h1".null,"Baked Salmon")
第一个参数定义我们想要创建的元素的类型,本例中,我们想要的是标题元素,第三个参数代表的是元素的children,所有在这儿的节点都是该元素的children。第二个参数表示元素的属性,这个1级标题没有任何属性。
当React被渲染后,React会把这个元素转换为DOM 元素
<h1>Baked Salmon</h1>
要是一个元素elemenmt有属性的话,就可以在创建元素时,写在第二个参数里 。下面就是一个HTML 的有id
和data-type
属性的1级标签
React.createElement("h1",{id:"recipe-0",'data-type':"title"},"Baked Salmon"
<h1 data-reactroot id="recipe-0" data-type="title">Baked Salmon</h1>
这些属性也同样的添加到了DOM元素上,Baked Salmon这个child text 作为element text 被添加 。可能你注意到多了一个data-reactroot
,它用来说明这个你React组件的root元素。
react元素是什么?它仅仅是一个Javascript的字面直接量(literal)告诉React怎么创建DOM元素。
{
$$typeof: Symbol(React.element),
"type": "h1",
"key": null,
"ref": null,
"props": {"children": "Baked Salmon"},
"_owner": null,
"_store": {}
}
这就是一个React元素,react使用的_owner,_store,$$typeof这些字段。key和ref字段对于React元素很重要,我们会在第5章介绍。让我们仔细看下type和props字段。
{
...
"type":"h1",
"props":{"children":"Baked Salmon"},
}
type属性告诉React关于HTML或者SVG元素是什么类型,props属性表示要创建的DOM所需要的数据和子元素。
ReactDOM包含了在浏览器中渲染React元素的必要工具。如renderToString,renderToStaticMarkup,这些会在12章详细的讲解,它们是从虚拟DOM中生成HTML的必需方法。
我们可以用ReactDOM.render来渲染一个react元素,包括它的children,到DOM里边试下。我们想渲染到元素要作为ReactDOM.render到第一个参数,第二个参数是target目标节点。
var dish = React.createElement("h1",null,"Hunter");
reactDOM.render(dish,doucment.getElementById('react-container'));
这里是要向一个id为react-container的div中添加一个,html如下:
<body>
<div id="react-container">
<h1>Hunter</h1>
</div>
</body>
这就是你需要做的所用工作,创建一个元素,渲染到DOM上,下一节,我们会介绍怎么使用props.children
ReactDOM可以让你向DOM中渲染单个(single)元素,React标记这个元素为data-reactroot
,剩下的所有React元素使用嵌套组合进这个单元素中。
react渲染child
元素时使用props.children
,我们渲染一个text元素作为h1元素的child,因此,props.children
被设置为“hunter”,此外,我们可以渲染其它的react元素作为children,弄成一个组件树,每个分支开始的地方有一个根组件(root component)
我们看下包含ingredients的一个无序列表
<ul>
<li>1 lb Salmon</li>
<li>1 cup Pine Nuts</li>
<li>2 cups Butter Lettuce</li>
<li>1 Yellow Squash</li>
<li>1/2 cup Olive Oil</li>
<li>3 cloves of Garlic</li>
</ul>
本例中,无序列表是root element根元素,它有6和children,我们可以用React.createElement来表示这个ul和它的children
React.createElement(
"ul",
null,
React.createElement("li", null, "1 lb Salmon"),
React.createElement("li", null, "1 cup Pine Nuts"),
React.createElement("li", null, "2 cups Butter Lettuce"),
React.createElement("li", null, "1 Yellow Squash"),
React.createElement("li", null, "1/2 cup Olive Oil"),
React.createElement("li", null, "3 cloves of Garlic")
)
react创建一个这些child元素的数组,并给数组的prop.children设置value
若我们想查看React元素的结果,我们会看到react表示的每个list item和一个props.children的数组,看下下面创建的元素:
{
"type": "ul",
"props": {
"children": [
{ "type": "li", "props": { "children": "1 lb Salmon" } ... },
{ "type": "li", "props": { "children": "1 cup Pine Nuts"} ... },
{ "type": "li", "props": { "children": "2 cups Butter Lettuce" } ... },
{ "type": "li", "props": { "children": "1 Yellow Squash"} ... },
{ "type": "li", "props": { "children": "1/2 cup Olive Oil"} ... },
{ "type": "li", "props": { "children": "3 cloves of Garlic"} ... }
] ...
}
}
现在我们可以看到每个列表的item都是一个child,本章前面介绍了在浏览器中运行react的准备工作,如果我们要用react创建出这个无序列表ingredients,我们要调用一系列的createElement
React.createElement("section", {id: "baked-salmon"},
React.createElement("h1", null, "Baked Salmon"),
React.createElement("ul", {"className": "ingredients"},
React.createElement("li", null, "1 lb Salmon"),
React.createElement("li", null, "1 cup Pine Nuts"),
React.createElement("li", null, "2 cups Butter Lettuce"),
React.createElement("li", null, "1 Yellow Squash"),
React.createElement("li", null, "1/2 cup Olive Oil"),
React.createElement("li", null, "3 cloves of Garlic")
),
React.createElement("section", {"className": "instructions"},
React.createElement("h2", null, "Cooking Instructions"),
React.createElement("p", null, "Preheat the oven to 350 degrees."),
React.createElement("p", null, "Spread the olive oil around a glass baking dish."),
React.createElement("p", null, "Add the salmon, Garlic, and pine..."),
React.createElement("p", null, "Bake for 15 minutes."),
React.createElement("p", null, "Add the Butternut Squash and put..."),
React.createElement("p", null, "Remove from oven and let cool for 15 ....")
) )
react中的className
我们注意到任何有HTML类属性的元素使用的是它的className表示属性而不是class,这是因为class在js中是一个保留字,所以不得不用className来定义html元素的类属性。
这个例子看上去就是纯react,纯react就是最终在浏览器中运行的代码,虚拟DOM是一个react元素的组件树,react元素是一个react用来在浏览器中如何创建一个UI的指导说明。
使用react的优点之一就是它隔离了UI和数据,因为react是纯js 的,我们可以添加JS逻辑来帮助我们创建React组件树,比如,ingerdients可以被存储在一个数组中,我们可以映射这个数组到React元素。
让我们回顾下这个ingredients无序列表
React.createElement("ul", {"className": "ingredients"},
React.createElement("li", null, "1 lb Salmon"),
React.createElement("li", null, "1 cup Pine Nuts"),
React.createElement("li", null, "2 cups Butter Lettuce"),
React.createElement("li", null, "1 Yellow Squash"),
React.createElement("li", null, "1/2 cup Olive Oil"),
React.createElement("li", null, "3 cloves of Garlic")
);
列表ingredients使用的数据data,可以很轻易用js数组来表示。
var items = [
"1 lb Salmon",
"1 cup Pine Nuts",
"2 cups Butter Lettuce",
"1 Yellow Squash",
"1/2 cup Olive Oil",
"3 cloves of Garlic"
]
我们可以使用数组到map函数在数据data周围构建一虚拟DOM
React.createElement(
"ul",
{className:"ingredients"},
items.map(ingredient =>
React.createElement("li",null,ingredient)
)
这段代码给数组中的每个ingredient创建了一个React元素,每个列表item的children里面的string作为text,每个ingredient的value作为list item显示。
当运行这个代码的时候,会出现waring:each child in an array or iterator should have a unique "key" prop...
的错误
当我们通过迭代一个数组来建立一个child元素的list时候,React需要每个元素有一个key属性,key属性用来让react更新DOM的时候更有效率。我们会在第五章讲到keys和为什么需要key。现在的话,可以先给每个元素添加一个key属性,我们可以把数组的索引设置为unique value,添加这个key属性会让控制台产生的错误消失掉。
React.createElement("ul",{className:"ingredients"},
items.map(ingredient,i)=>
React.createElement("li",{key:i},ingerdient)
)
每个用户界面都是由部分构成掉整体,这个rcipe例子由一些recipe组成,每个recipe由部分组成,组件允许我们使用和ODM一样的结构。
在react中,我们把每个部分看成是一个组件,组件允许我们复用相同的DOM结构。
当你想用React构建用户界面时,要把界面分割成几个可以复用的小部分,比如,recipes下面由title,ingredients list,和一个instructions,这些都是一个大的recipe的一部分。
图:4-3(看不见的话,用浏览器打开)
考虑下怎么来扩展它,如果我们显示一个recipe,我们的组件肯定可以支持,但如果要显示1000个recipes,我们需要创建1000个组件的实例。
让我们用第三种方法来创建组件:createClass
,ES6的classes和s tateless functional 组件
2013年,React刚发布的时候,只能用一个方法创建组件,那就是:React.createClass()
。
后来出现其它方法的时候,React.createClass()
依旧是一个创建组件非常有效的办法。
我们看下每个recipe包含的ingredients list,我们可以用React.createClass为数组里的每个ingredient创建一个React组件,它包含一个无序列表元素ul,ul里包含子列表项。
组件允许我们复用UI,在渲染函数里,我们可以使用this关键字来指向组件实例(component instance).访问属性用this.props
.
const IngredientsList = React.createClass({
displayName: "IngredientsList",
render() {
return React.createElement("ul", {"className": "ingredients"},
React.createElement("li", null, "1 lb Salmon"),
React.createElement("li", null, "1 cup Pine Nuts"),
React.createElement("li", null, "2 cups Butter Lettuce"),
React.createElement("li", null, "1 Yellow Squash"),
React.createElement("li", null, "1/2 cup Olive Oil"),
React.createElement("li", null, "3 cloves of Garlic")
)
}
})
const list = React.createElement(IngredientsList, null, null)
ReactDOM.render(
list,
document.getElementById('react-container')
)
说明:看下react 工具面板里的ReactDOM,我们已经使用我们的组件创建了一个叫做ingredients list的元素。
<IngredientsList>
<ul className="ingredients">
<li>1 lb Salmon</li>
<li>1 cup Pine Nuts</li>
<li>2 cups Butter Lettuce</li>
<li>1 yellow Squash</li>
<li>1/2 yellow Squash</li>
<li>3 cloves of Garlic</li>
</ul>
</IngredientsList>
说明:数据(Data)可以作为属性(properties)传给React组件。我们可以通过把数据data整成一个数组,并传入到ingredients list组件中来创建一个可以复用的ingredients list组件。
const IngredientsList =React.createClass({
displayName:"IngredientsList",
render(){
return React.createElement("ul",{className:"ingredients"},
this.props.items.map(ingredient,i)=>
React.createElement("li",{key:i},ingredient)
)
}
})
const items=[
"1 lb Salmon",
"1 cup Pine Nuts",
"2 cups Butter",
"1 yellow Squash",
"1/2 yellow Squash",
"3 cloves of Garlic",
]
ReactDOM.render(
list,
doucment.getElementById('react-container')
)
说明:组件就是对象,它们可以像类一样来封装代码。我们创建一个方法,用这个方法来创建列表
const IngredientsList =React.createClass({
displayName:"IngredientsList",
renderListItem(ingredient,i){
return React.createElement("li",{key:i},ingredient)
}
render(){
return React.createElement("ul",{className:"ingredients"},
this.props.items.map(this.renderListItem)
}
})
这和MVC中的view层很像,和UI有关的所有代码都被封装在一个组件里。
现在我们已经可以用我们的组件创建一个React元素,并且把这个元素作为属性传递到元素列表中,注意现在的元素类型是string,它是component class directly.
component class as types
当我们渲染HTML或SVG元素的时候,我们使用string,当用组件创建元素时,我们直接使用component class.这就是为什么IngredientsList没有用引号的原因。我们传递class到createElement中因为它是一个组件,React会用这个class创建我们组件的实例。
第二章提到过,ES6语法的关键特征之一就是有了class的概念,React.Component是一个抽象类(abstract class)可以让我们来创建新的react组件。我们通过继承来创建自定义的组件
class IngredientsList extends React.Component{
renderListItem(ingredient,i){
return React.createElement("li",{key:i},ingredient)
}
render(){
return React.createElement(
"ul",
{className:"ingredient"},
this.props.items.map(this.renderListItem))
}
}
无状态函数式组件指的是函数而不是对象,因此,它们没有this
作用域,但是它们简单,是纯函数,我们在程序中应该尽可能的多用它。在无状态函数式组件不够健壮的情况下,我们可能要重新使用class或者createClass
。除此以外,用的越多越好。
无状态函数式组件是:接收属性的函数
并且返回一个DOM。该组件是我们实践函数式编程的规则的最佳途径。你应该力求让每个无状态函数式组件都是一个纯函数(pure function)。它们接收属性返回DOM且不产生副作用。
const IngredientsList =props=>
React.createElement("ul",{className:"ingredients"},
props.items.map(ingredient,i)=>
React.createElement("li",{key:i},ingredient)
)
)
我们要用ReactDOM
渲染这个组件,这就是一个函数,它通过props arguments
获取数据,并且为props data
中的每个item返回一个无序列表.
无状态函数式组件和常量(
Const
)
当创建无状态函数式组件的时候经常用常量(const)而不是变量(not a var),你可以把这看成是一个约定,它不是必须的。把函数声明为常量可以防止以后我们把函数再弄成变量。
既然我们已经可以把data作为属性传递到组件中,我们就可以创建UI来把data和逻辑分离开来,数据隔离开可以让我们更加容易的操作DOM,当改变任何一个数据时,我们就改变了应用的状态(satate)
想象一下,应用中的所有数据都存储在一个Javascript对象中,每次改变这个对象的时候都要把data当作props发送给组件并预渲染UI,这意味着ReactDOM将要做大量的工作。
为了让React有合理的工作量,ReactDOM.render要更加智能些才行,事实上它确实智能。ReactDOM只是改变需要改变的DOM,而不是清空并重构整个DOM
比如说,有一个app,用一个smaile和一个frowny来表示5个team的情绪。我们可以用一个数组来表示5个team成员的情绪:
["smile","smile","frown","smile","frown"];
这个数组data用来构建像下面这样的UI:
当team有重要工作,周末必须加班时,我们可以通过改变数组中的数据来反应新的情绪变化。
["frown","frown","frown","frown ","frown"];
我们需要多少变化才能让第一个smile数组变成第二个数组frown?答案是
我们要三个变化,分别是改动smile数组的第一个,第二个,和第四个item的值。
现在考虑下,我们怎么更新DOM来反应这些改变。一种不怎么有效率的方法是清楚掉之前的所有旧DOM,重建新的。
<ul>
<li class="smile">smile</li> <li class="smile">smile</li> <li class="frown">frown</li> <li class="smile">smile</li> <li class="frown">frown</li>
</ul>
第一步:清楚所有数据
<ul>
</ul>
第二步:开始循环frown数组中的item
<ul>
<li class="frown">frown</li>
</ul>
第三步:添加第二个item
<ul>
<li class="frown">frown</li>
<li class="frown">frown</li>
</ul>
这样,一直到第5个item添加完。
如果我们通过擦出数据重建DOM的方法,我们要创建b插入5个新的DOM元素。把元素插入到DOM中是一个开销很大的操作——它非常慢。作为对比,若更新已经存在的DOM元素就非常快了。
ReactDOM.render只更新需要更新的DOM元素,在我们的例子中,有3个元素需要更新,所以,ReactDOM.render就只会更新3个DOM元素。
目前为止,我们只学了React.createElement来创建元素。另一种是用Factories.一个factory是一个特殊的对象(special object),可以用来隐藏实例化对象的具体细节( abstract away the details of instantiating objects) 。在React中,我们用factories来帮助我们创建React元素实例。
React已经内置了许多常用的DOM元素,包括HTML和SVG。你可以使用React.createFactory
函数来在具体的组件周围创建factories(to build your own factories around specific components)
为什么我们要用factory?Factories是一个createElement的替代品。我们可以用React的HTML elememnt factories
创建本章开头的那个1级标题。
<h1>Baked Salmon</h1>
我们这次不用createElement
方法用React的factory
来创建元素.
React.DOM.h1(null,"Baked Salmon")
本例中,第一个参数是属性,第二个参数是children,我们一个用一个DOM factory
来创建一个无序列表。
React.DOM.ul({"className":ingredients"},
React.DOM.li(null,"1 lb Salmon"),
React.DOM.li(null,"1 cup Pine Nuts"),
React.DOM.li(null,"2 cups Butter Letuce"),
React.DOM.li(null,"1 yellow Squash"),
React.DOM.li(null,"1/2 cup Olive Oil"),
React.DOM.li(null,"3 cloves of Garlic")
)
本例中,第一个参数是className属性,元素的其它参数都添加到children这个数组中,我们也可以从里面分离出数据,并用factories
来改进这个例子
var items=[
"1 lb pizza",
"2 cup Pine Nuts",
"2 cups Butter Lettuce",
"1 Yellow Squash",
"1/2 cup Olive Oil",
"2 coloves of Garlic"
]
var list = React.DOM.ul(
{className:"ingredients"},
items.map(ingredient,key)=>
React.DOM.li({key},ingredeint)
)
)
ReactDOM.render(
list,
document.getElementById('react-container')
)
如果你想像调用函数一样使用组件的话,你需要创建一个factory
const { render } = ReactDOM;
const IngredientsList =({list})=>
React.createElement('ul',null,
list.map(ingredient,i)=>
React.createElement('li,{key:i},ingredient)
)
)
const Ingredients = React.createElementFactory(IngredientsList)
const list=[
"1 lb pizza",
"2 cup Pine Nuts",
"2 cups Butter Lettuce",
"1 Yellow Squash",
"1/2 cup Olive Oil",
"2 coloves of Garlic"
]
render(
Ingredients({list}),
document.getElementById('react-container')
)
在这个例子中,我们可以使用Ingredients factory
快速的渲染一个React元素,Ingredients
是一个函数,它接收属性和children作为参数。
如果你没有使用过JSX,你可能发现factories经常调用react.creaetElement。其实最方便最容易的方法就是用JSX标签来定义react元素。如果你用了JSX了,你可能从来不会在用到factory了。
这章中,我们用createELement和crateFactory来创建React组件,下一章,我们来看怎么用JSX来简化它。