3

初探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>

浏览器渲染上面的元素树时会产生一个包含一系列项目的文字列表.

在JSFiddle中查看详情

此时浏览器,DOMshadowDOM一起把 <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',

            display: 'inline-block',

            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中。

点击JSFiddle查看更多

你也许已经注意到,我只是告诉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的初始化值。

基于上面的描述,修改 <MySelect> 组件的代码如下


var MySelect = React.createClass({

    getInitialState: function(){ //在此添加selected状态的默认值为false

        return {selected: false}; //this.state.selected = false;

    },

    render: function(){

        var mySelectStyle = {

            border: '1px solid #999',

            display: 'inline-block',

            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

        }   

    },

    render: function(){

        var mySelectStyle = {

            border: '1px solid #999',

            display: 'inline-block',

            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'));

为了使<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});

        }   

    },

    render: function(){

        var mySelectStyle = {

            border: '1px solid #999',

            display: 'inline-block',

            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类名。

点击在JSFiddle中查看

点击上面的例子,我们可以看到设置已经生效了。

虽然我们的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',

      display: 'inline-block',

      padding: '5px'

    };

    return React.createElement(

      'div',

      { style: mySelectStyle },

      React.createElement(MyOption, { state: this.state.selected, select: this.select, value: 'Volvo' }),

      React.createElement(MyOption, { state: this.state.selected, select: this.select, value: 'Saab' }),

      React.createElement(MyOption, { state: this.state.selected, select: this.select, value: 'Mercedes' }),

      React.createElement(MyOption, { state: this.state.selected, select: this.select, 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, onClick: this.props.select },

        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依据stateprops检测UI的改变,然后与当前的真实DOM进行比较,只做最小的改变来更新UI以达到我们想要的表现。换句话说当stateprops改变时,真实的DOM并没有被重新渲染而是只是打了小的补丁。

下面的动画,能让你对此理解得更加深刻。

patch

也许你已经发现,当状态改变时真实DOM中只有很小的部分需要被改变。真正被改变的地方只有图中有绿色轮廓的地方,整个组件并没有在每次改变state时都刷新,React使得只有需要改变的地方被改变。

我想澄清一下,Virtual DOM并非什么革命性的理念。一个聪明的人利用jQuery使用精巧的技法也可以实现类似的效果。然而,通过使用React,你完全不用去管它了,Virtual DOM为你做了所有涉及渲染方面的工作,通过对真实DOM进行抽象,我们根本不需要再担心DOM或者编写代码直接操作DOM了。

到此,我们的React初探部分就要结束了,当我们再来回顾一下,React使得我们不再需要类似于jQuery之类的工具了。比起jQuery,Virtual DOM在很多方便都优异的多。当然React所拥有的并不只是Virtual DOM,这其实是其冰山一角。React最大的价值可能在于它提供了一种简单可维护的模式用以构建UI组件,想象一下通过使用可复用的组件来构建你的应用,这是多么美好的事情啊。

链接

原文链接
本文在GitBook上的链接

希望本文能让你有所收获,欢迎大家随意提出修改意见。


zhangwang
7.9k 声望1.8k 粉丝

前端,摄影,阅读,好奇