初探React
本文翻译自Cody Lindley的《React Enlightenmen》,已获得作者授权,这套书比较基础,看完以后能让你对React有一定的认识。本文是本书的第一章,主要是以一个浅显的例子引出究竟该如何理解React并且简要的介绍了React的几大要素,适合对React感兴趣,但是一直没真的去了解这个工具的童鞋阅读。
React是一个非常好的工具,基于它可以编写更加容易理解的JavaScript代码,构建并维护无状态或富状态组件。使用它可以通过React nodes划分整体的UI为一个个UI组件(React 组件),React nodes在形式上和HTML nodes很类似,通过渲染,它可以表现为多种形式(HTML/DOM,canvas,svg等等)。
上面这句话可能还不能很清晰的表达出React到底是什么,但是如果通过一个React实例来说明,那你一定能更加容易明白了,本章将为你展示React和React Component,如果在阅读过程中遇到不明白的地方,也不要着急,本书后面的章节会详细解释这些细节。
从我们熟悉的HTML标签<select>
说起
Html的<select>
标签有点React组件的味道,我们就从它开始学习React。
以下是一个有几个<option>
子元素的<select>
,对于类似于这样的HTML标签,相信大家都已经非常熟悉。
<select size="4"> <option>Volvo</option> <option>Saab</option> <option selected>Mercedes</option> <option>Audi</option> </select>
浏览器渲染上面的元素树时会产生一个包含一系列项目的文字列表.
此时浏览器,DOM,shadowDOM一起把 <select>
标签渲染为UI组件。<select>
是下拉列表,当用户点击其内的某一选项时,该选项会被选中,之所以被选中,是因为选中的状态被存储了(比如说,当你点击“Volvo”时,这个会被选中,而"Mercedes"的选中状态会被取消)。
使用React,我们可以做类似的事情,不过此时我们是使用React nodes 构成React组件 ,当然最终渲染后其还是被转化为了HTML DOM中的HTMl 元素。
下面我们使用React来模仿<select>
创建一个下拉列表。
定义第一个React组件
我使用React.createClass
函数创建一个叫做MySelect
的组件。通过下面的代码你可以看出MySelect
组件由一些样式和一个空的React <div>
节点组成。
var MySelect = React.createClass({ //定义MySelect组件 render: function(){ var mySelectStyle = { border: '1px solid #999',display: 'inline-block',padding: '5px' }; // {}使得在JSX中可以书写JS代码 return <div style={mySelectStyle}></div>; //这是一个React<div>节点 } });
例子中的<div>
很像HTML里的标签,不过它存在于JavaScript代码中,这种形式被称作JSX,JSX是一个可选的JavaScript语法拓展,它被React用来表示React nodes,其和真实渲染而成的DOM中的HTML有一定的映射关系,不过二者并非一一对应,它们之间存在一些差别(参看这里和这里)。
JSX语法必须被转化为JavaScript语句才能被浏览器的 JS引擎理解并执行,上述代码如果没有被转化,执行时会报错。
React官方供的转换JSX为ES5语句的工具叫做Babel。
上面的JSX语法中的<div>
通过Babel转换之后看起来是下面这样的
// 转换前 return <div style={mySelectStyle}></div>; //转换后 return React.createElement('div',{ style: mySelectStyle });
到目前为止,你需要记住的是,当你写了类似于HTML标签的React语句时,其实最终它都会通过Babel被转换为ES5语句,目前React中的ES6语句也同样会被转化为ES5的语句。
现在我们的<MySelect>
组件中只包含了一个空的React<div>
节点,这其实没什么意思,让我们来进一步完善它。
我接下来将定义一个名为<MyOption>
的组件,并将把这个组件当做<MySelect>
的子组件置于其中。以下是定义这两个组件的代码
var MySelect = React.createClass({ render: function(){ var mySelectStyle = { border: '1px solid #999',padding: '5px' }; return ( //下面的JSX语句中,一个组件包含有另外一个组件 <div style={mySelectStyle}> <MyOption value="Volvo"></MyOption> <MyOption value="Saab"></MyOption> <MyOption value="Mercedes"></MyOption> <MyOption value="Audi"></MyOption> </div> ); } }); var MyOption = React.createClass({ //定义MyOption组件 render: function(){ return <div>{this.props.value}</div>; //react div element,via JSX } });
上面的代码,让我们对<MyOption>
组件如何置于<MySelect>
组件之中,JSX如何使用有了进一步的了解。
使用React的属性(props) 为组件传递参数
观察上述代码 你可以看出<MyOption>
组件由一个React<div>
节点和表达式{this.props.value}
组成,JSX中的{}
意味着一段JavaScript语句将被执行,也就是说,你可以在{}
之中书写JavaScript 语句。
{}
大括号使得该位置有获取<MyOption>组件传递的属性或参数的权限(此处的{this.props.value}
)。当组件被渲染时,传递来的类似于HTML属性的参数将会被放置在该位置,本例中value = volve
将会被放置在该<div>
React 节点中。这些类似于HTML属性的参数被称作React属性(props),React使用它们传递无状态的或不可变的参数到组件之中。此例中,我们简单的把value
属性通过props传入<MyOption>
组件,这很类似向一个函数中传递参数(实际上JSX在幕后就是这么做的)。
组件的渲染过程:先被渲染为Virtual DOM,再被渲染为HTML DOM
到目前为止,我们仅仅只是定义了两个React组件,还并没有把它们渲染为Virtual DOM 更别说HTML DOM了。
我想强调的是,这两个React组件是由JavaScript定义的,理论上我们定义的是两个UI组件,它并不一定需要转换为DOM或者Virtual DOM,这个UI组件在理论上可以被渲染为Native mobile platorm上或者HTML canvas中,但是我在这里不打算细说这个,我想强调的是React是一个组织UI组件的平台,它是超越DOM,前端应用甚至Web平台的存在。
下面我们把<MySelect>
渲染为Virtual DOM,当然其最终会转化为HTML页面里的真实的DOM。
在下面的JavaScript代码中,我在最后一行添加了 ReactDOM.render()
函数,观察代码可以看出这个函数有两个参数,一是我打算渲染的组件,而是我想把组件渲染的位置(<div id="app"></div>
这是一个已经存在的DOM节点)。你可以通过点击Result按钮查看渲染结果,你会发现<MySelect>
组件已经被渲染进了HTML DOM中。
你也许已经注意到,我只是告诉React渲染那个组件,渲染到何处,React会自动渲染该组件内的子组件<MyOption>
。
在继续讲下一个知识点前,我们回想一下刚刚发生了什么,我们并没有直接操作DOM使得<MySelect>
组件转换为真实的DOM,或者说并没有使用类似于jQuery那样的代码直接操作DOM。在React中对真实DOM的一切操作都被抽象为了操作Virtual DOM。实际上,使用React,你需要做的就是描述你想要的Virtual DOM,至于如何把它装换为真实DOM,React都替你做好了。
使用React state
为了让 <MySelect>
组件更进一步模仿<select>
标签的功能,我们需要在为其添加state。毕竟保存选中某子元素的状态,是 <select>
的主要功能之一。
React中的 state
一个常见的用法就是用来表现组件的不同表观,以<MySelect>
组件为例来说,当某个子组件被选中时,或者是没有被选中的子组件时,其对外的表观应该是不同的,而这种变化在React中就是受state
影响的。
state
的改变通常是由事件(鼠标事件,键盘事件等等)或者网络事件(AJAX)所触发,state
同时也决定了UI何时需要被重新渲染(state改变一般就伴随着重新渲染)。
state
属性一般存在于构成UI组件的最顶层组件中。使用React提供的getInitialState()
方法我们可以设置 state
的默认值,就<MySelect>
组件来说,我们设置 return{selected:false}
表示没有文本被选中。这里提醒一句,getInitialState()
方法在组件被挂载前被触发,且在其整个生命周期里只会被触发一次。其返回的值会被作为this.state
的初始化值。
var MySelect = React.createClass({ getInitialState: function(){ //在此添加selected状态的默认值为false return {selected: false}; //this.state.selected = false; },render: function(){ var mySelectStyle = { border: '1px solid #999',padding: '5px' }; return ( <div style={mySelectStyle}> <MyOption value="Volvo"></MyOption> <MyOption value="Saab"></MyOption> <MyOption value="Mercedes"></MyOption> <MyOption value="Audi"></MyOption> </div> ); } }); var MyOption = React.createClass({ render: function(){ return <div>{this.props.value}</div>; } }); ReactDOM.render(<MySelect />,document.getElementById('app'));
基于上面的设置,接下我我将添加一个回调函数,这个函数的作用是当点击一个<option>
时,会使得select
状态的值被更改。在该函数中,我还将获得option中的文本值(通过event
参数)。并以此来展示如何在当前组件上setState
。这里我使用的是通过事件来调用回调函数,如果你熟悉jQuery,对此你一定也不会陌生。
var MySelect = React.createClass({ getInitialState: function(){ return {selected: false}; },select:function(event){// added select function if(event.target.textContent === this.state.selected){//remove selection this.setState({selected: false}); //update state }else{//add selection this.setState({selected: event.target.textContent}); //update state } },document.getElementById('app'));
为了使<MyOption>
组件可以调用select
函数,我们还必须把此函数传递给该组件,此时就用到props
了,要让该函数从<MySelect>
组件传递给<MyOption>
组件,我们要做两件事情,一是我们需要在<MySelect>
组件里的<MyOption>
组件上添加select={this.select}
。二是需要在<MyOption>
组件的<div>
节点上添加onClick={this.props.select}
。这样做很直观的,我们在想要点击的位置绑定上了click
事件,使得其可以调用<select>
函数。添加事件的方法类似于你在真实DOM中添加事件的方法,React考虑到了你的使用习惯。
var MySelect = React.createClass({ getInitialState: function(){ return {selected: false}; },select:function(event){ if(event.target.textContent === this.state.selected){ this.setState({selected: false}); }else{ this.setState({selected: event.target.textContent}); } },padding: '5px' }; return (//使用props把函数当做参数传递给 <MyOption> <div style={mySelectStyle}> <MyOption select={this.select} value="Volvo"></MyOption> <MyOption select={this.select} value="Saab"></MyOption> <MyOption select={this.select} value="Mercedes"></MyOption> <MyOption select={this.select} value="Audi"></MyOption> </div> ); } }); var MyOption = React.createClass({ render: function(){//add event handler that will invoke select callback return <div onClick={this.props.select}>{this.props.value}</div>; } }); ReactDOM.render(<MySelect />,document.getElementById('app'));
现在,我们已经可以通过点击某一个<option>
组件达到选中该组件的效果了。我们来想想,点击的过程发生了什么,当你点击某一个<option>
时,select
函数会被触发,并且设定了<MySelect>
组件中state
的值,不过遗憾的是,现在我们的点击并没有明显的反馈,接下来让我们加上反馈。
我想达到的效果是,当我们把当前状态传递给<Option>
组件,可以得到视觉上的反馈,让我们知道组件的state
改变了。
再次使用props
,这次我们将把selected
状态通过父组件<MySelect>
传递给子组件<MyOption>
,我们通过在父组件中的所有<MyOption>
上设置state={this.state.selected}
来达到这个效果,观察代码我们知道如果
this.props.state
和当前值this.props.value
匹配,该子组件就会被选中。如果被选中,给该子组件添加selectedStyle
类名,如果没选中,则添加unSelectedStyle
类名。
点击上面的例子,我们可以看到设置已经生效了。
虽然我们的React Select组件还没有完完全全的实现HTML <select>
的功能,不过我觉得你肯定慢慢对React是如何工作的有了一定的了解了。React就是这样一个帮你构建组件树的工具,这棵树能让你的思路清晰,构建明白,贫富状态组件管理合理。
再继续论述virtual DOM之前,我想说明的是在React中,JSX和Babel并非是一定需要使用的。通过写纯粹的JavaScript代码也可以很好的使用React。下面是JSX通过Babel转换后的代码,如果你选择不使用JSX,你需要写下面这样的代码。
var MySelect = React.createClass({ displayName: 'MySelect',getInitialState: function getInitialState() { return { selected: false }; },select: function select(event) { if (event.target.textContent === this.state.selected) { this.setState({ selected: false }); } else { this.setState({ selected: event.target.textContent }); } },render: function render() { var mySelectStyle = { border: '1px solid #999',padding: '5px' }; return React.createElement( 'div',{ style: mySelectStyle },React.createElement(MyOption,{ state: this.state.selected,select: this.select,value: 'Volvo' }),value: 'Saab' }),value: 'Mercedes' }),value: 'Audi' }) ); } }); var MyOption = React.createClass({ displayName: 'MyOption',render: function render() { var selectedStyle = { backgroundColor: 'red',color: '#fff',cursor: 'pointer' }; var unSelectedStyle = { cursor: 'pointer' }; if (this.props.value === this.props.state) { return React.createElement( 'div',{ style: selectedStyle,onClick: this.props.select },this.props.value ); } else { return React.createElement( 'div',{ style: unSelectedStyle,this.props.value ); } } }); ReactDOM.render(React.createElement(MySelect,null),document.getElementById('app'));
理解Virtual DOM的作用
我将以简要介绍Virtual DOM来结束我们的React初探之旅,我将主要谈谈Virtual DOM为我们带来了什么。
你可能已经注意到,在全文中我们只有一个地方的代码涉及到了真实DOM,是在使用ReactDOM.render()
方法的时候。使用的目的是把我们的组件插入到了HTML页面中<div id="app"></div>
。在今后React的使用过程中,这个可能也是唯一的一个你需要和真实DOM打交道的地方。这让我们在使用React的过程中,完全不用像以前使用jQuery那样考虑DOM了,通过DOM抽象,React可以说完全取代了jQuery,移除了对绝大多数对具体DOM的操作,使得前端的效能大大提升。
Virtual DOM是对真实DOM的抽象,这使得一些重操作成为可能。Virtual DOM依据state
和props
检测UI的改变,然后与当前的真实DOM进行比较,只做最小的改变来更新UI以达到我们想要的表现。换句话说当state
和props
改变时,真实的DOM并没有被重新渲染而是只是打了小的补丁。
下面的动画,能让你对此理解得更加深刻。
也许你已经发现,当状态改变时真实DOM中只有很小的部分需要被改变。真正被改变的地方只有图中有绿色轮廓的地方,整个组件并没有在每次改变state
时都刷新,React使得只有需要改变的地方被改变。
我想澄清一下,Virtual DOM并非什么革命性的理念。一个聪明的人利用jQuery使用精巧的技法也可以实现类似的效果。然而,通过使用React,你完全不用去管它了,Virtual DOM为你做了所有涉及渲染方面的工作,通过对真实DOM进行抽象,我们根本不需要再担心DOM或者编写代码直接操作DOM了。
到此,我们的React初探部分就要结束了,当我们再来回顾一下,React使得我们不再需要类似于jQuery之类的工具了。比起jQuery,Virtual DOM在很多方便都优异的多。当然React所拥有的并不只是Virtual DOM,这其实是其冰山一角。React最大的价值可能在于它提供了一种简单可维护的模式用以构建UI组件,想象一下通过使用可复用的组件来构建你的应用,这是多么美好的事情啊。
链接
希望本文能让你有所收获,欢迎大家随意提出修改意见。