转载

每日一博 | ReactJS 开发入门

ReactJS的确是一个伟大的东西,就其核心而言,就是一个可以和html混合编写的jsx格式,以及一个良好有序(加载时和更新时)、并和html渲染实时捆绑的状态机制。学习和使用ReactJS,你并不需要对你现有的代码架构做太大的调整,只要引入react.js和react-dom.js两个文件,就能开始了。

JSX之为物

首先,不要对jsx这个东西产生抗拒,如果你感到不爽,你完全可以直接使用js,只是你需要写很多嵌套的代码,比如如下:

`jsx var Test = React.createClass({ displayName: 'Test', render: function () { return ( <div className="ui form"> <div className="field"> <label>hello world</label> </div> </div> ); } });

`

实际上对应生成的是这样的js代码:

`js var Test = React.createClass({ displayName: 'Test', render: function () { return React.createElement( 'div', { className: 'ui form' }, React.createElement( 'div', { className: 'field' }, React.createElement( 'label', null, 'hello world' ) ) ); } });

`

当然,如果你的html结构嵌套的更加复杂,对应的js,将变得更加恐怖。无论如何,html看起来还是更加直观的。

在jsx中,你可以使用如常见的模板语言一般,在html代码里面直接使用js的变量和函数。

`jsx var Test = React.createClass({ displayName: 'Test', getInitialState: function() { return { title: '', list: [ { name: 'Jan' }, { name: 'Easy' } ] }; }, render: function () { return ( <div> <div className="ui header"> {this.state.title} </div> <div className="ui list"> { _.map(this.state.list, function(item) { return ( <div className="item">{item.name}</div> ); }) } </div> </div> ); } });

`

哦,是的,一切都是你亲切而又熟悉的html,js,和className。

gulp实时编译JSX文件

JSX有如此这般的便利,唯一剩下的问题就是,如何将JSX进行转换。ReactJS官网提供了一种实时加载JSX的方式,但我自己觉得并不实用——毕竟现在前端类库,已经是从几个类库一跃到几十个类库了。通过gulp的watch是一个更好的选择。

严格意义上说,对react进行编译处理,只需要引入以下的三个库,就可以了:

`shell npm install gulp gulp-babel babel-preset-react --save-dev

`

比如我当前的演示项目:

每日一博 | ReactJS 开发入门

编辑gulpfile.js,加入以下代码:

```js var gulp = require('gulp'); var babel = require('gulp-babel');

gulp.task('jsx', function () { gulp.src('./jsx/*.jsx') .pipe(babel({ presets: ['react'] })) .pipe(gulp.dest('./jsx')); });

gulp.task('watch-jsx', ['jsx'], function () { gulp.watch([ './jsx/*.jsx' ], ['jsx']); }); ```

然后在命令行执行

gulp watch-jsx

,这时每次你修改中/jsx目录下的*.jsx文件,并保存时,他自动就会将jsx转换为js文件。

JSX的事件处理

使用JSX的另外一个好处是,不再需要依赖jquery这种DOM的框架了。

JSX生成的对象实际上还是一个DOM实例,所以一切关于DOM原生的知识就又可以派上用场了,当然,除非你极度依赖于jquery什么的。其实在ReactJS里面,类如Mootools这种原生对象注入的类库,会有更大的用处,jquery反而一无是处。

`js var hello_world = React.createClass({ displayName: 'hello_world.jsx', handleClick: function(event) { var link = event.target; }, render: function () { return ( <a className="hello" onClick={this.handleClick}> hello world </a> ); } });

`

ReactJS 0.14以后,取消了事件过程内return false来中断事件,要事件中断,请使用

e.preventDefault

e.stopPropagation

,详细的可以参考官方文档:https://facebook.github.io/react/docs/events.html。

这里特别注意this的作用域问题。

```php var Test = React.createClass({ displayName: 'Test', getInitialState: function() { return { title: '', list: [ { name: 'Jan' }, { name: 'Easy' } ] }; }, handleClick: function() {

},     render: function () {         return (             <div>                 <div className="ui header">                     {this.state.title}                 </div>                 <div className="ui list">                     {                         _.map(this.state.list, function(item) {                             return (                                 <a className="item" onClick={this.handleClick}>{item.name}</a>                             );                         })                     }                 </div>             </div>         );     } });

```

这里是一个错误的示范,this.handleClick在_.map的闭包中,实际上指向了另外一个空间——所以,这个例子就算你点爆了你的浏览器,this.handleClick也是不会触发的。

这个问题,又存在一个反向的情况,即假定你绑定了某个本地变量为某个实例,在onClick这里调用这个实例的某个方法句柄。当onClick被触发的时候,他能成功执行到这个方法,但是本地对象却变成了这个DOM元素,而不是你预想的那个实例。这其实是对象冒充,他调用这个方法的时候指定了call或apply的主体执行对象。所以这里值得注意,但同时他又是一个可以使用各种奇淫巧技的入口,你懂的——执行方法主体虽然绑定了,然而局部上下文环境却是生效的,噢噢噢我不能说得太多。

状态初始化

状态(state)是ReactJS的精华之所在,基于这个状态,不但是一个React实例的数据载体,他还可以起到名副其实的控制状态,并且,通过更新状态,会触发React实例的ComponentUpdate,从而进行局部的重新渲染。

基本使用,你可以将状态视为数据的载体,在render的过程中使用。

```js var Test = React.createClass({ displayName: 'Test', getDefaultProps: function() { return { name: '' }; }, getInitialState: function() { return { name: this.props.name }; }, render: function () { return (

{this.state.name}

); } });

var element = ReactDOM.render(<Test name="hello" />, document.getElementById('hello'));

```

上述这是一个较为完整的示例。

首先创建了一个React Class(实际上是React Component)。

<Test name="hello" />

,创建了一个React Element,到目前为止,这还是一个虚拟的元素。虚拟元素并不具有Class实例的方法。

在执行了ReactDOM.render所返回的实例,才是将React Element添加进DOM树,并正式返回一个React Class的实例,这时,React的所有相关方法才开始生效。

在React Classs中:

getDefaultProps

方法,用于声明一个React 实例的默认属性,这里我声明了一个name。而在创建React Element时,我实际设置了name=hello。这个

getDefaultProps

方法,保证了你的React实例默认的属性结构,以减少一些繁琐的判断。

getInitialState

方法,是获取初始化的状态值,这里我将state.name取值于props.name,当然这未必是一个推荐的做法。

变更状态

你可以使用setState和replaceState的方法,来对React实例进行状态的变更。如上的例子:

`js element.setState({ name: 'Tom' });

`

页面显示的内容会随着你执行setState而发生变化,当然这是一个不太高明的例子。

状态的变更,是会触发组件重新执行render的(所以说组件内的局部渲染并不准确)。这是一个非常透明的机制,他除了能简单的控制输出数据的变化,还可以起到组件的状态化切换的效果。

比如我们经常做登录的登录界面:

`js var LoginForm = React.createClass({ displayName: 'LoginForm', getInitialState: function() { return { isPost: false, account: '', password: '' }; }, handleSubmit: function() { if (!this.state.isPost) { this.setState({ isPost: true }); // 注意,这里设置了状态 } }, render: function () { return ( <form method="post" onSubmit={this.handleSubmit}> <label>账号</label> <input type="text" value={this.state.account} disabled={this.state.isPost} /><br/> <label>密码</label> <input type="password" value={this.state.password} disabled={this.state.isPost} /><br/> <button disabled={this.state.isPost}>登录</button> {this.state.isPost ? <p>'登录中,请稍候!'</p> : ''} </form> ); } });

`

这是一个非常简单的示例,当用户提交表单的时候,会触发更新状态isPost为true,当状态变更以后,所有输入的控件都会切换到disabled,并且在表单底部,会出现

登录中,请稍候!

提示。

接口函数

componentWillMount,一个组件被装载前(ReactDOM),会触发,只会被执行一次。

componentDidMount,一个组件装载完成后,触发,只会被执行一次。

componentWillReceiveProps(nextProps),当一个组件接收到新的props的时候会被触发,这个其实就是用于当已经渲染了一个组件,需要更新这个组件的初始化参数时可使用这个接口(通过

<Test name="" />

的方式)。

shouldComponentUpdate(nextProps, nextState),这是判断接口,必须返回true或false,这个接口是用来判断组件是否该触发更新。

componentWillUpdate(nextProps, nextState),每次接收到新的props或state时,会触发,但是不包含初始化渲染的时候。官网特别提示,不要在这个函数内执行setState,他会触发循环更新……

componentDidUpdate(prevProps, prevState),当状态更新已经写入DOM元素后,触发执行。

componentWillUnmount(),当一个DOM元素被卸载的时候触发。

补完整示例

为了方便内部示例传阅,补一个完整示例。

`shell mkdir test_reactjs npm init // 然后一路回车 npm install gulp gulp-babel babel-preset-react --save-dev bower init // 一路回车 bower install react --save

`

这样基础的环境已经足够了,编辑gulpfile.js

```js var gulp = require('gulp'); var babel = require('gulp-babel');

gulp.task('jsx', function () { gulp.src('./jsx/*.jsx') .pipe(babel({ presets: ['react'] })) .pipe(gulp.dest('./jsx')); });

gulp.task('watch-jsx', ['jsx'], function () { gulp.watch([ './jsx/*.jsx' ], ['jsx']); }); ```

添加一个html文件

```html

```

然后加入/jsx/LoginForm.jsx

```js var LoginForm = React.createClass({ displayName: 'LoginForm', getInitialState: function() { return { isPost: false, account: '', password: '' }; }, handleSubmit: function(event) { event.preventDefault(); if (!this.state.isPost) { this.setState({ isPost: true }); // 注意,这里设置了状态 } }, handleChange: function(event) { var target = event.target; var dataField = target.getAttribute('data-field'), updateData = {}; updateData[dataField] = target.value; this.setState(updateData) }, render: function () { return (

); } });

ReactDOM.render( , document.getElementById('hello')); ```

最后执行gulp jsx,刷新页面,你就能看到效果了。

执行gulp watch-jsx,你就可以一边修改一边预览了。

个人经验

至此,你已经基本了解了ReactJS的内容,可以开始尝试写一些自己的组件了。

官网的例子呢,显得十分高大上,比如getting start那个Comment Box,组件内套着组件。

但从实际出发,这里有一个开发者经常容易出现的问题,就是过度封装,最后导致自己在不同的组件中不停的切换调试,而找不到问题的根源。而在React里,最显著的问题就是props和state之间的转换问题,当你调用 将参数传入时,是props,你还需要再处理从props到state之间的转换,也就是当你有更多封装,你需要关注到底这个组件的props是不是有效的转换为state了,这是一个很繁琐的问题,随着你封装的越具体,调试的复杂度和维度随之增长,真的得不偿失。经常会发生,某个组件处理不得当,导致某个部分无法实时更新数据。

所以,个人建议,最好的做法是,对你的界面做一个整体规划,整体框架性渲染,统一在一个类中,在这个框架中,划分出具体的区域,然后将不同的组件加载渲染这些对应的区域中区,而不要想着在一个框架内,嵌入不同的组件,借助一个整体的state去联动多个嵌套组件的update,那样你要调试的工作量实在太大了,真是调到天荒地老。

比如,我的项目中,规划出Page和Component两个逻辑分区,Page表述的是大框架,Page更新,就全页都更新。而Component则是具体的组件,并且我为组件写了特别的加载方法,他不是通过嵌入在Page的html代码中的方式来加载的,而是通过特定的事件触发,来找到对应的Container来装载的,这样有助于我更灵活和准确的来调试Page和Component。

补充的说,用ReactJS开发,尤其是如果你是全部用ReactJS来开发的话(比如我手头的一个项目,因为需要用WebSocket做一些通信,我希望页面本身维持不刷新),其实开发的情况就和开发手机App无差别了,因为所有的数据你都需要本地获取一次。如何优化异步加载获取数据的合理性就变得更加重要,而ReactJS并不提供这方面的解决方案。于是乎,我用了一天的时间,封装了一个非常简易的前端Model,这个Model只是用于装载数据和维持数据实例的检索,而不关心具体的数据逻辑(数据逻辑还是交给后端处理吧),这方面前端做的再精细也枉然。

不得不说,ReactJS真的是一个非常伟大的东西,他的确解放了前端的开发工作量,基于ReactJS封装的一个组件,重用性远比用html或者别的什么机制写成的方式输出的组件,模板那些就更别提了。非常推荐前端,乃至后端的开发人员都尝试接触一下,对于拓宽自己的开发方式、思维,还是有很大帮助的。

原文  http://my.oschina.net/janpoem/blog/631314?fromerr=uU1kliZx
正文到此结束
Loading...