React快速入门可以看阮一峰的一篇日志和对应的demo

这里提一下刚入门(断断续续研究了两周时间)的我关注到的一些点:

关于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>
        );
    }
});

其他的方式可以参考这篇文章。 上面的各种方式适合比较小的项目,组件没有那么多,也没有那么多的状态需要管理。 对于比较庞大的项目,可以使用现在比较流行的状态管理的框架fluxredux

关于fluxredux

两者都是组件状态管理的框架,主要解决的问题是方便组件状态的存放和修改。前者是Facebook在推出React同时推出的配套的状态管理的解决方案,而后者则是之后参考了flux的原理形成的一套框架(准确说应该是flux的一种实现)。这两者的比较网上有很多文章,不多说。特别提一点是,它们虽然都是从React出现之后诞生的,但它们都不依赖React这个框架,即它们只是一套状态管理的解决方案(框架),天生适用于React,但不仅限于React。