React实践初体验
这里提一下刚入门(断断续续研究了两周时间)的我关注到的一些点:
关于JSX
React发明的JSX的语法可以看作是原生JavaScript的基础上创建了一系列语法糖(原生JavaScript可不允许混入HTML代码),在实际运行时它会先被编译成JavaScript代码,再被进一步编译运行。既然是语法糖,当然是可以越过它直接使用原生JavaScript来写React程序的(参考这里),不过还是建议直接用这些语法糖,方便并且大家都这么用(符合规范)。
关于JSX的debug
由于JSX是编译到JavaScript之前的代码,debug会比较麻烦,目前的一个解决方案是使用React官方的一个开发用的浏览器插件以及console.log
。
关于JSX的编译
把JSX编译到JavaScript的过程既可以事先做好,再把编译过的js代码作为资源来加载,也可以事先不做,让页面直接加载JSX资源,并用浏览器来编译JSX代码。要让浏览器做这件事需要额外加载browser.js
(使浏览器支持babel),并且加载的JSX资源类型需要是"text/babel"
(作为比较,原生JS的类型为"text/javascript"
)。当然从效率上来说,事先编译好肯定更优。
关于React和Node.js
对于应用于前端和应用于后端(这里所说的应用到后端是指后端是Node.js),React库是一模一样的,不信可以对比两者用到的React的源码。你看到的用法上有时候会有一些差异,其实并不是由于应用在前端或后端而导致的差异,而是是否使用了ES6的语法的差异(见下面一条)。
关于React和ES6
使用pre-ES6和ES6的语法写React是有一些区别的,你可以理解为pre-ES6的代码是ES6代码的“polyfill”的结果。 关于它们的区别以及如何从pre-ES6迁移到ES6版的React代码,可以参考这篇文章。 这里简单总结下,ES6相对pre-ES6而言:
- 不再使用
getDefaultProps
来初始化组件的属性,而是通过ES6的class定义属性的方式来定义默认的组件属性; - 使用
class NewComponent extends React.Component
来创建组件,而不再用React.createClass
; - 不再自动帮你绑定组件中定义的函数,而需要你在
constructor
函数中显示地去绑定; - 不再使用
getInitialState
来初始化组件的状态,而是在constructor
函数中对state
属性进行赋值操作来取代。
最后,来个例子,比如下面的两种方式创建组件(编译出来)是一样的:
let MyComponent = React.createClass({
getDefaultProps: function () {
return {unit: 's'};
},
getInitialState: function () {
return {
result: 0
};
},
_handleClick: function (event) {
this.setState({
result: this.state.result + 1
});
},
render: function () {
return (
<div>
<button onClick={this._handleClick}>+1{this.props.unit}</button>
<span>{this.state.result}</span>
<span>{this.props.unit}</span>
</div>
);
}
});
class MyComponent extends React.Component {
static defaultProps = {unit: "s"};
constructor() {
super();
this.state = {
result: 0
};
this._handleClick = this._handleClick.bind(this);
}
_handleClick() {
this.setState({
result: this.state.result + 1
});
}
render() {
return (
<div>
<button onClick={this._handleClick}>+1{this.props.unit}</button>
<span>{this.state.result}</span>
<span>{this.props.unit}</span>
</div>
);
}
}
关于React和React Native
前者应用于网页,后者应用于移动端(iOS,Android),后者试图使用和前者一样的接口和规则来写移动端的app(有点类似Electron让JavaScript可以写桌面应用,以后是不是只要会JavaScript就啥都能做了…)。
关于状态流
一个事件的响应可能就会影响组件的状态,从而使得组件进行重新渲染(比如鼠标点击了某个按钮使输入框数字加一,按钮被点击为事件,输入框中的数字为其状态)。React的核心之一就是根据状态的改变来自动渲染组件,而开发者则只需要关注和管理各个组件的状态即可。
对应到React代码中,组件的状态更新必须要通过setState
函数来实现(React内部作了绑定setState
==执行==>render
),而事件的响应函数则没有固定的hook函数(比如并没有说handleClick
就一定是鼠标点击的响应函数),它需要开发者在render
函数中显式地指定,比如:
class Button extends Component {
myHandleClick() {
console.log('Clicked!');
}
render() {
return (
<button className="Button" onClick={this.myHandleClick.bind(this)}>{this.props.name}</button>
);
}
}
React的设计思路天然地适合下面这样的状态流(没错,就是简化版的flux):
Action ==> Store ===> View
^ |
|== Action <==|
这里的View对应过来就是React里面的render
函数,而Action就是某个View对应的一些事件。假想一下我们用一个全局变量来存储View的状态,那么在事件的handler函数中可以对这个全局变量进行修改,而render
函数则会根据这个全局变量的值来进行渲染,这整个流程是一个单向的过程。
关于React的hook函数
上面提到了React的组件类有一些特殊的函数,它们会在特定的情况下被调用,这些被预留好名称的函数其实就是hook函数(关于React组件的hook可以看源码里的lib/ReactClass.js)。各种hook函数其实也对应了React组件的生命周期,在不同的阶段,不同的hook函数会被调用(关于React组件的生命周期,看这里)。 组件从创建到渲染的过程中调用的hook(自上而下对应被调用的时间顺序):
getDefaultProps
:组件初始化时被调用,之后再被渲染不会调用(注意:ES6方式创建的React组件是没有这个hook的);getInitialState
:组件初始化时被调用,之后再被渲染不会调用(注意:ES6方式创建的React组件是没有这个hook的);componentWillMount
:组件初始化之后,在render
方法之前被调用,之后再被渲染不会调用;render
:组件每次渲染都会调用;componentDidMount
:组件初始化之后,在render
方法之后被调用,之后再被渲染不会调用。
组件因状态改变重新渲染调用的hook:
shouldComponentUpdate
:组件状态改变后调用的第一个函数;componentWillUpdate
:组件状态改变后,在render
方法之前被调用;componentDidUpdate
:组件状态改变后,在render
方法之后被调用。
组件因属性改变重新渲染调用的hook:
componentWillReceiveProps
:组件属性改变后调用的第一个函数,之后会调用shouldComponentUpdate
等状态改变会调用的hook(即属性改变相当于一种特殊的状态改变,并比状态改变多调用一个hook)。
组件被销毁时调用的hook:
componentWillUnmount
:组件的析构函数。
组件其他的hook:
setState
: 用于设置组件的状态,如果状态发生改变了会在之后调用组件状态改变后的一系列hook函数。
组件还有一些hook是作为属性而不是函数存在的:
mixins
statics
propTypes
contextTypes
childContextTypes
关于组件之间的通信
React的设计其实就是为了屏蔽一些组件之间互相通信的情况,大部分的这些情况导致了代码难以维护和管理。所以,React组件之间的通信是受限的,比较常见的方式是由父组件向子组件传递信息(通过组件的属性来传递):
// This is the parent component
let MyForm = React.createClass({
render() {
let coolContent = "Never lose your passion.";
return (
<div>
<MyText content={coolContent}/>
</div>
);
}
});
// This is the child component
let MyText = React.createClass({
render() {
return (
<span>{this.props.content}</span>
);
}
});
其他的方式可以参考这篇文章。 上面的各种方式适合比较小的项目,组件没有那么多,也没有那么多的状态需要管理。 对于比较庞大的项目,可以使用现在比较流行的状态管理的框架flux或redux。
关于flux
和redux
两者都是组件状态管理的框架,主要解决的问题是方便组件状态的存放和修改。前者是Facebook在推出React同时推出的配套的状态管理的解决方案,而后者则是之后参考了flux的原理形成的一套框架(准确说应该是flux的一种实现)。这两者的比较网上有很多文章,不多说。特别提一点是,它们虽然都是从React出现之后诞生的,但它们都不依赖React这个框架,即它们只是一套状态管理的解决方案(框架),天生适用于React,但不仅限于React。
Comments