本文主要梳理一下我对 React 框架基础内容的认识,后面也会总结一些深度内容的认识。固然,笔者水平也有限,若是你发现不妥之处,望斧正!html
不少人都说 React 上手要难一些,Vue 上手要简单一些。也有不少人在问 React 和 Vue 的区别是什么。我想说一下个人理解。前端
首先,在框架设计层面,React 是 MVC 软件架构中的 View,它只负责视图层的东西,而对于数据和路由等,则由 Redux 和 Router 等来完成。这也就是为何 React 官方文档说 “React 是一个用于构建用户界面的 JavaScript 库”。而 Vue 则不一样,Vue 是基于 MVVM 架构设计的一个框架,因此在架构层面,Vue 和 React 的思想就是不一样的。react
React 的设计哲学很简单,不可变(Immutable)思想贯穿了整个框架的设计。React 能够说没引进什么新的概念,让开发者可以以相似写原生 JavaScript 代码的方式来使用 React 进行开发。这也就是不少人说 React 难的缘由,即 JavaScript 基础知识。git
而不少人说 Vue 容易上手,可能有些同窗就迷惑了,我以为 Vue 不简单啊。笔者认为,说 Vue 简单是由于 Vue 将不少底层逻辑封装好了,直接给你对应的 API,你调用对应 API 就能够完成对应的工做。因此,你不须要有很扎实的 JavaScript 基础,你只要记住或者可以查阅到对应 API 就能够用 Vue 完成一些基础性的开发。因此有些后端的同窗能够看看 Vue 官方文档就能上手基础的前端开发。github
React 之我见,那必定要说个人见解,我喜欢 React。我喜欢其简洁的设计理念和类原生的开发体验,让我可以感受到本身是在写代码。那么 Vue 我没有深刻学习,但这是一个很是优秀的前端框架,目前也已经安排进了下一阶段的学习任务中。算法
JSX 和虚拟 DOM 是必说内容。JSX 也就是 JavaScript 的一个语法扩展,能够经过 Babel 对 JSX 进行转译。后端
const title = <h1 className="title">Hello, world!</h1>;
复制代码
例如这样一段 JSX 代码会被 Babel 转译成:浏览器
const title = React.createElement(
'h1',
{ className: 'title' },
'Hello, world!'
);
复制代码
从这里咱们能够看出,React.createElement
方法的参数是这样的:性能优化
createElement( tag, attrs, child1, child2, child3 );
复制代码
那么咱们能够实现一下 createElement
:前端框架
function createElement( tag, attrs, ...children ) {
return {
tag,
attrs,
children
}
}
复制代码
而后这样就能够对其进行调用:
// 将上文定义的 createElement 方法放到对象 React 中
const React = {
createElement
}
const element = (
<div> hello<span>world!</span> </div>
);
console.log( element );
复制代码
此处引用 hujiulong 的 从零开始实现一个 React(一):JSX 和虚拟 DOM,详细内容推荐看其原文。
也就是说,createElement
方法返回的东西就是所谓的虚拟 DOM。说到虚拟 DOM 就必说 diff 算法,这个咱们以后再讲。
render
其实没有太多好说的,其做用就是将虚拟 DOM 渲染成真实的 DOM,只是在实现的过程当中有很是多的细节状况要处理。
在 React 中,咱们能够经过 Class 和 Function 两种方式来写组件。以 Class 的方式写组件的时候,咱们须要继承 React.Component。这里主要涉及 React.Component 的模拟实现和 React 的生命周期。模拟实现推荐看 从零开始实现一个 React(二):组件和生命周期。至于 React 的生命周期,以后我会总结一篇文章,推荐你们去看 React 官方文档。
为何要 diff?由于 DOM 操做十分昂贵,也就是很是耗时耗性能。可是 DOM 操做就必定很慢很耗性能吗?其实不必定,只是广泛来说是这样的,详细内容推荐看 JJC 的知乎回答:前端为何操做 DOM 是最耗性能的呢。简单讲,浏览器中 DOM 对象是使用 C++ 开发的,性能确定不慢,有些状况下性能甚至高于操做 JavaScript 对象。而大多数时候操做 DOM 是很耗性能的,这是由于 JavaScript 对象的属性(properties)映射到的是 DOM 对象的特性(attributes)上。
咱们操做 JavaScript 对象只是修改一个对象,而修改 DOM 对象时还会修改对象的 attributes,那么性能就消耗在 JavaScript 对象和 DOM 对象的转换和同步上。而且,操做 DOM 还会影响页面的渲染,进而会再一次下降了性能。虽然 DOM 的性能有可能比操做普通对象还要快,但 99% 的场景下,操做 DOM 对象仍是昂贵的。因此,高效的 diff 势在必行。
咱们能够经过实现高效的 diff 算法,对比出操做先后有差别的 DOM 节点,而后只对更改了的 DOM 进行更新。这样能够大大减小没有必要的 DOM 操做,进而大幅提高性能。
React 的 diff 算法其实很简单:对比当前真实 DOM 和虚拟 DOM,在对比过程当中直接更新真实 DOM。而且这个比对是同级比对,当发现这一级的 DOM 不一样时,会直接更新该节点及其全部子节点。这种 diff 策略其实是出于性能上的取舍。首先,DOM 操做不多出现跨层级的状况,因此只须要同级比对就能够知足大多数状况,也就没有必要对比全部的 DOM 节点,由于那样须要 O(n^3) 的时间复杂度,代价过高。
React diff 算法的模拟实现推荐参见:从零开始实现一个 React(三):Diff 算法
首先明确,所谓异步的 setState 并非真的异步,只是其行为相似于异步操做的行为。下面咱们来详细说说。
首先,将 setState 的行为设置为所谓异步的形式的出发点依旧是性能优化,由于若是每次经过 setState 更改了状态都对组件进行一次更新,会很浪费性能。而经过将屡次 setState 合并在一块儿,进行一次更新渲染,则会大大提高性能。
将多个 setState 的调用合并成一个来执行,也就意味着 state 并不会被当即更新。例以下面这例子:
class App extends Component {
constructor() {
super();
this.state = {
num: 0
}
}
componentDidMount() {
for ( let i = 0; i < 100; i++ ) {
this.setState( { num: this.state.num + 1 } );
console.log( this.state.num ); // 会输出什么?
}
}
render() {
return (
<div className="App"> <h1>{ this.state.num }</h1> </div>
);
}
}
复制代码
这里定义了一个组件 APP
,在组件挂载后,会循环 100 次。但当咱们真的对这个组件进行渲染会发现,渲染结果为 1,而且控制台中输出了 100 次 0。也就是说,每次循环中拿到的 state 都仍是更新以前的。也就是说 React 对 setState 进行了优化,但若是咱们就是想要马上得到更新后的 state 怎么办呢?React 提供了一种解决方案,setState 接收的参数还能够是一个函数,经过这个函数咱们能够拿到更新以前的状态,而后经过这个函数的返回值获得下一个状态:
componentDidMount() {
for ( let i = 0; i < 100; i++ ) {
this.setState( prevState => {
console.log( prevState.num );
return {
num: prevState.num + 1
}
} );
}
}
复制代码
对于这个地方,有两个要点:一是如何对多个 setState 调用进行合并。二是我怎么断定一次合并哪些 setState 呢?接下来咱们一一解答。
setState 的合并是经过队列实现的。经过建立一个队列来保存每次 setState 的数据,而后每隔一段时间,清空和这个队列并渲染组件。此处的模拟实现推荐阅读:从零开始实现一个 React(四):异步的 setState
接下来就是一次到底合并哪些 setState。换句话说,咱们会将一段时间内的 setState 调用合并成在一块儿执行,那么这段时间的长短取决于什么?其实此处使用的是 JavaScript 的事件队列机制,也就是事件循环(Event Loop)。关于事件循环的详细内容可参见阮一峰的 JavaScript 运行机制详解:再谈 Event Loop。事件循环的核心概念就是同步任务,异步任务,微任务以及宏任务。
在 React 的 setState 中,利用 JavaScript 的事件循环机制对多个 setState 调用进行合并。首先建立一个队列保存每次 setState 的数据,在一次事件循环的全部同步任务以后,清空着队列,将队列中的全部 setState 进行合并,并进行一次性更新渲染。这样在一次事件循环的,最多只会执行一次合并操做,而且只会渲染一次组件。
因此呢,这也就是为何我说,所谓的 setState 的异步行为并非真正的异步,只是不会对每个 setState 操做进行实时更新,而是经过队列的方式对一次事件循环中的全部同步任务的 setState 调用进行合并,合并成一个以后进行一次更新和渲染,因此效果上看上去是异步的,但并非 setTimeout 或 setInterval 这种真正的异步操做。
至此,React 的基本概念已经梳理完毕,感谢阅读。笔者水平有限,若是你发现任何问题,欢迎评论指正!
以上内容会以新文章的方式发布。