热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

是时候理清React开发中的一些疑惑了

React其实很好上手,我在最初使用时并未去了解其一些细节性的东西,但是好像在项目中也一直能正常运作。但是那时总会有一种不安感,深感自己对React的使用逻辑并未理解得非常清晰,本

React其实很好上手,我在最初使用时并未去了解其一些细节性的东西,但是好像在项目中也一直能正常运作。但是那时总会有一种不安感,深感自己对React的使用逻辑并未理解得非常清晰,本文的目的就在于理清这种使用逻辑,当然个人见解定有偏颇,如果你有一些建议,也希望您能在讨论区予以指教,如果你到现在还没有怎么接触过React,推荐可以跟着官方文档的例子体会下React再来看本文,也许这样收获更大一些,后文的链接里还有一个更加高级的例子也是非常好的入门教程。

为什么要使用 React

这是一个老生常谈的问题了,可能大家在众多的教程、文章里已经了解过了React的好处,比如说它的虚拟DOM可以被高效的渲染,比如说它的组件化使得项目结构非常清晰,代码复用非常容易,比如说它的数据管理机制也能让你清晰的知晓数据的状态,而React本身就是被这种清晰的数据所驱动的。

“We built React to solve one problem: building large applications with data that changes over time.”

详细谈论这些优点前,我想说说React给我带来的改变。

在使用React之前,我也一直在使用jQuery,它对节点的操作非常方便,如果仅仅只是普通网页的开发,jQuery无疑是非常好的,但是如果开发的是WebApp,jQuery并不能增强你对全局的把控能力,在学习使用React没多久以后突然有一天我感觉以前所做的开发都好像在玩一些小打小闹的游戏,而使用React也让自己明白了为什么我被称作前端工程师,这个框架让我找到了工程师的归属感,当我再看自己的项目时和一个建筑工程师看自己的设计图的感觉没了太多差别,我知道我的这个组件是房子的总体框架,这个组件是大厅,这个组件是椅子,还有与大厅同级的卧室组件,厨房组件,与椅子同级的桌子组件,家电组件。

我可以用优雅的桌子,椅子,床,台灯来布置一个温馨的卧室;
我可以把桌子压扁拉长变成电视柜,把椅子拉宽加上软软的海绵变成沙发,再把台灯提高,换一个像样的遮光罩它就是落地灯了,再加上一些客厅独有的家电,客厅的感觉也就出来了;
用相同的思路,一个温馨的家就出来了。

其实想想,如果只考虑客厅和卧室(不考虑里面的那些桌子椅子之类的组件)那么除却它们的长、宽、摆放位置这些参数不同,它们又有什么区别呢?

回到React,它即带给我们对整体的把控能力,也让我们可以通过修改数据(参数)以表现不同的细节达到不同的效果,从最大的房子的框架到每个桌子椅子的样子,一切都在我们的掌握。下面就慢慢说说React是如何帮我们达到把握全局,了解细节的。

从虚拟 DOM(Virtual DOM)说起

想象这么一个场景,客厅里有一把我们不是很喜欢的椅子,想换一把,最合适的做法当然就是改造一下,或者把这把丢了重新买一把新的,为了换一把椅子而重新组装整个房子一看就是不聪明的做法。Virtual DOM为我们提供了一种高效的渲染机制,使得我们可以只改变我们想改变的地方,而尽量不去影响其它无关的组件。它是React高性能的基础。

虚拟 DOM 究竟是什么?

要说明Virtual DOM究竟是什么,不得不提到React DOM模块的一个方法,ReactDOM.reader()

这个方法就像打开一道门的钥匙,门的两边就是Virtual DOM和Html DOM,我们在浏览器中看到的肯定是Html DOM,Virtual DOM存在于隔着这道门的系统内存之中,Html DOM和Virtual DOM之间存在着映射关系。
就像Html DOM由各种节点构成,Virtual DOM也是由一种被称为React node的节点构成。

每个React组件中还有另外一个render()方法(不同于ReactDOM.reader()),我的理解是这个方法用于将ReactNode构建为Virtual DOM。下面再来详细看看React node

React node

“a light, stateless, immutable, virtual representation of a DOM node.”

React node其实并非真实的节点,实际上它们可以看做是真实节点在Virtual DOM中的代表,Virtual DOM就是由ReactNodes构成,真实的DOM就是依据它们所构建;

在需要改变真实的DOM时,React其实是先修改虚拟DOM,然后和真实的DOM做比较,在真实DOM中只改变需要改变的地方,这种补丁机制只改变局部,不改变整体,因此对系统性能的消耗较小,对虚拟DOM的修改会在状态改变时触发,后文会详细说明这种状态机制。可能大家也已经听说了二者之间的比较是基于diff算法,知乎上有一篇详细解析React的这个算法的文章,推荐大家阅读。

一般来说创建React node有两种方法,如下

// 方法1,使用React 内置的工厂方法创建
var reactNodeLi = React.DOM.li({id:'li1'}, 'one');
//方法2,使用Javascript创建node的方法
var reactNodeLi = React.createElement('li', null, 'one');

最近有一本开源的电子书React Enlightenment里有一章对React node有详细的介绍,也推荐大家阅读。

React提供的另外一种简洁,直观的创建React node的方法,那就是JSX,其实提到React,大家好像都会想到JSX,因为它实在是太方便了,其实使用React其实并非必须使用JSX,不过使用它真的能让我们的工作更加轻松。

JSX

var App = React.createClass({
render: function() {
return

My name is { this.props.name }

;
}
});

上面例子里return中的那一部分就是JSX了,初看JSX的语法,可能大家会想到前端开发中经常使用到的模板,不过JSX并非模板,它应该算是React对JS语法的拓展,需要编译后才能正确使用它,JSX的构建是非常简洁明了的,在此就不再赘述。

再说 Babel

刚刚已经提及JSX是需要编译才能被浏览器识别的,它就是被Babel编译的,具体说来是被babel-preset-react来编译的。不过Babel的最主要目的其实并非编译JSX,Babel应该算是一个编译平台,其主要目的是转换你在代码中使用了的ES6甚至ES7语法为浏览器识别的ES5语法(babel-core,babel-preset-es2015模块),编译React倒像是其的附加功能。初学者有时候会觉得使用React困难,配置合适的开发环境可是就是原因之一。以前翻译过一篇基础的配置webpack的文章,具体可以点这里。

说到组件了(Component)

除却高性能,组件是另外一个React非常吸引人的地方,组件的可复用性,可组合性以及其对模块化开发的天然适应性,使得我们的项目非常直观,便于理解和管理。拿到一个项目,最开始要想的就是如何来划分组件。当然划分肯定需要一些依据,先来看看React自己对组件的分类。

划分并创建组件

我在最初使用React时,我的项目里的所有的组件都是通过React.createClass()创建,所有的组件在里面可能都拥有getInitialState(),componentDidMount()等方法,当然这样用其实一点也没有问题。但是这样写,除非对项目非常熟悉,否则我们并不能很容易的就区分组件之间的层级关系。而且随着项目的复杂化,也不利于数据的管理。

Stateful Component

之前在使用React重构百度新闻webapp前端看到智能组件和木偶组件二词,我觉得它们可能可以分别对应到Stateful ComponentStateless Component,在此引用一下该文里的说法。

智能组件 它是数据的所有者,它拥有数据、且拥有操作数据的action,但是它不实现任何具体功能。它会将数据和操作action传递给子组件,让子组件来完成UI或者功能。这就是智能组件,也就是项目中的各个页面。

这是一个完整的组件,在这种组件里可能会出现所有的React提供的方法(包括各种生命周期函数life cycle methods,各种事件响应函数等等)

创建:

//ES5 写法
var App = React.createClass({
getInitialState(){
return{
name:"Tom",
...
}
},
componentDidMount(){
this.setState({
name:"Jim"
})
},
render: function() {
return

My name is { this.state.name }

;
}
});
// ES6 写法
class SearchBar extends React.Component {
constructor(props) {//props需要作为参数传入
super(props);//需要使用super,如果没有this就会是undefined
this.state = {
searchTerm: '',
};
this.handleInputChange = this.handleInputChange.bind(this);//为事件绑定this,这是ES6语法所要求的,ES5并没相关要求
}
handleInputChange(event) {
this.setState({
searchTerm: event.target.value,
});
}
render() {
return ;
}
}

两种写法其实没有本质区别,ES6语法也会通过Babel转换为ES5语法后被执行,但是两种写法里确实存在一些不一样的地方,比如说使用ES6时需要单独绑定this,ES6语法里方法之间不能使用逗号,等等。网上可以查到很多相关资料,在此不做赘述。

Stateless Component

木偶组件:它就是一个工具,不拥有任何数据、及操作数据的action,给它什么数据它就显示什么数据,给它什么方法,它就调用什么方法,比较傻。这就是木偶组件,即项目中的各个组件。

这种组件里只会出现,React提供的render()方法,用于构建虚拟DOM,其创建方式除了ES5,ES6的写法,还可以使用Stateless Functions方法创建。

创建:

//ES5
var HelloMessage = React.createClass({
render(){
return

Hello {props.name}
//多个节点时需要加括号
}
})
//ES6
class HelloMessage extends React.Component {
constructor(props) {//props需要作为参数传入
super(props);//需要使用super,如果没有this就会是undefined
}

render() {
return
Hello {props.name}
;
}
}
//Stateless Functions
function HelloMessage(props) {
return
Hello {props.name}
;
}
//ES6 Stateless Functions
const HelloMessage = (props) =>
Hello {props.name}
;

模块和组件

如若需要,所有的React组件都是可以当做模块被导出的,不过就就我本人看来,一般所导出的模块都是由一个或者若干个组件组成的功能单元。不过说到这里更想说明的一点时,React其实是很依赖类似于webpack这样的模块管理工具的,所以想要用好React,其实也需要对模块的定义,以及模块管理工具有一点的了解。

有生命的组件

React里的组件是活的,组件不仅仅有类似于出生,成长,死亡的过程,还有心脏和血液。

生命周期函数 life cycle methods

组件的生命周期函数可以分为三个阶段:

  • Mounting Phase(此阶段的函数在一个组件的生命中只会执行一次)(挂载阶段)

    - getInitialState()
    - componentWillMount()
    - componentDidMount()

  • Updating Phase(此阶段的函数在一个组件的生命中可别多次执行)(更新阶段)

    - componentWillReceiveProps()
    - shouldComponentUpdate()
    - componentWillUpdate()
    - componentDidUpdate()

  • Unmount Phase (此阶段的函数在一个组件的生命中只会执行一次)(卸载阶段)

    - componentWillUnmount()

关于各个函数的具体意义,在此不在赘述,一个比较容易出错的地方是弄明白各个函数的执行顺序,下面给出一个参考列表。

- Mounting Phase:
1. Initialize / Construction
2. getDefaultProps() (React.createClass) or MyComponent.defaultProps (ES6 class)
3. getInitialState() (React.createClass) or this.state = ... (ES6 constructor)
4. componentWillMount()
5. render()
6. Children initialization & life cycle kickoff
7. componentDidMount()
- Updating Phase follows this order:
1. componentWillReceiveProps()
2. shouldComponentUpdate()
3. render()
4. Children Life cycle methods
5. componentWillUpdate()
- Unmount Phase follows this order:
1. componentWillUnmount()
2. Children Life cycle methods
3. Instance destroyed for Garbage Collection

组件的生命之源-state

用过React的人都知道,this.setState({})可能算是React里使用最多的方法了,每次使用都会根据所更新的数据重构Virtual DOM已达到更新组件的目的,使得组件充满活力,满足我们的各种要求。

state在getInitialState()阶段被初始化,之后通过其它生命周期函数(componentWillUpdate()里不能使用)或React事件调用的函数,可以使用利用this.setState({})更新某一state的值。

我在最初使用React时总觉得,使用了过多的this.setState({})会不会导致React变得性能低下,不过阅读了这篇文章打消了我的一些疑惑。

组件的血液-props

为什么把props比作血液呢,因为它本身自己并不会变化,它就像是一个传输的中介,把父组件的方法,属性传递给子组件。一般在子组件中它可能有三方面的作用

  • 作为子组件的属性

    作为属性

  • 作为参数

    {"我的名字是"+this.props.name}
    ;

  • 传递方法

    传递方法
    ;

配合state,props可以用来改变子组件的表现形式,如果用来传递方法,props可以在子组件中调用父组件的方法。

在开发时,props还可以配合propTypes使用,这样可以使得props的使用更加准确(如下例),也使得组件更加健壮。

const AlbumList = (props) => {
const albums = props.albums.map((album) =>

  • {album.name}
  • );
    return (

      {albums}

    );
    };
    AlbumList.propTypes = {
    albums: React.PropTypes.array.isRequired,
    };

    一点小结

    本文只总结了我对React的最基础的部分的一些思考,类似于高阶组件,Redux这类的目前我并未接触过多的知识和以及一些类似于React中的事件这类的较容易理解的知识没做过多的叙述,至于Routing这类构建app的知识,以后有机会一定会再和大家分享。

    对于刚刚接触React的童鞋,可能看完依旧是云里雾里,不过本文实在算不上教程,初学者可能还是得看比较靠谱的教程。

    之前看过一个一个比较好的React学习路径推荐在此也分享给大家,希望对大家的React学习有帮助

    • 学习React的基本知识;

    • 熟悉npm

    • 熟悉Javascript的打包工具

    • 了解ES6

    • 学习Routing和flux(redux)

    最后还要做一个小广告,或者其实也算是对自己的一个激励和监督,之前和 React Enlightenment这本开源书的作者联系,他也非常愿意自己的书能让更多人有收获,所以就同意我把这本书翻译为汉语了。这本书目前一共八章,这本书,上周我看就看完了,感觉有很大收获,对初学者也比较友好,应该好好看一道,React肯定就入门了,我打算是每三四天翻译一章,然后也发布在此处,欢迎大家关注,希望和大家一起进步。

    参考

    • A Primer on the React Ecosystem: Part 1 of 3

    • A Primer on the React Ecosystem: Part 2 of 3

    • React 源码剖析系列 - 不可思议的 react diff

    • React Enlightenment

    • 入门 Webpack,看这篇就够了

    • React 源码剖析系列 - 解密 setState

    • React Enlightenment译文地址


    推荐阅读
    • Ihavethefollowingonhtml我在html上有以下内容<html><head><scriptsrc..3003_Tes ... [详细]
    • [大整数乘法] java代码实现
      本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
    • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
    • 如何自行分析定位SAP BSP错误
      The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
    • 阿里Treebased Deep Match(TDM) 学习笔记及技术发展回顾
      本文介绍了阿里Treebased Deep Match(TDM)的学习笔记,同时回顾了工业界技术发展的几代演进。从基于统计的启发式规则方法到基于内积模型的向量检索方法,再到引入复杂深度学习模型的下一代匹配技术。文章详细解释了基于统计的启发式规则方法和基于内积模型的向量检索方法的原理和应用,并介绍了TDM的背景和优势。最后,文章提到了向量距离和基于向量聚类的索引结构对于加速匹配效率的作用。本文对于理解TDM的学习过程和了解匹配技术的发展具有重要意义。 ... [详细]
    • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
    • Final关键字的含义及用法详解
      本文详细介绍了Java中final关键字的含义和用法。final关键字可以修饰非抽象类、非抽象类成员方法和变量。final类不能被继承,final类中的方法默认是final的。final方法不能被子类的方法覆盖,但可以被继承。final成员变量表示常量,只能被赋值一次,赋值后值不再改变。文章还讨论了final类和final方法的应用场景,以及使用final方法的两个原因:锁定方法防止修改和提高执行效率。 ... [详细]
    • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
    • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
    • 计算机存储系统的层次结构及其优势
      本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
    • 本文介绍了一些好用的搜索引擎的替代品,包括网盘搜索工具、百度网盘搜索引擎等。同时还介绍了一些笑话大全、GIF笑话图片、动态图等资源的搜索引擎。此外,还推荐了一些迅雷快传搜索和360云盘资源搜索的网盘搜索引擎。 ... [详细]
    • 如何使用计算机控制遥控车的步骤和电路制作方法
      本文介绍了使用计算机控制遥控车的步骤和电路制作方法。首先,需要检查发送器的连接器和跳线,以确定命令的传递方式。然后,通过连接跳线和地面,将发送器与电池的负极连接,以实现遥控车的前进。接下来,制作一个简单的电路,使用Arduino命令将连接到跳线的电线接地,从而实现将Arduino命令转化为发送器命令。最后,通过焊接晶体管和电阻,完成电路制作。详细的步骤和材料使用方法将在正文中介绍。 ... [详细]
    • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
      本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
    • Java中包装类的设计原因以及操作方法
      本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
    • Python爬虫中使用正则表达式的方法和注意事项
      本文介绍了在Python爬虫中使用正则表达式的方法和注意事项。首先解释了爬虫的四个主要步骤,并强调了正则表达式在数据处理中的重要性。然后详细介绍了正则表达式的概念和用法,包括检索、替换和过滤文本的功能。同时提到了re模块是Python内置的用于处理正则表达式的模块,并给出了使用正则表达式时需要注意的特殊字符转义和原始字符串的用法。通过本文的学习,读者可以掌握在Python爬虫中使用正则表达式的技巧和方法。 ... [详细]
    author-avatar
    Phluency美广互动
    这个家伙很懒,什么也没留下!
    PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
    Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有