简单来讲,就是virtual dom & react diff。
咱们都知道在前端开发中,js运行很快,dom操做很慢,而react充分利用了这个前提。在react中render的执行结果是树形结构的javascript对象,当数据(state || props)发生变化时,会生成一个新的树形结构的javascript对象,这两个javascript对象咱们能够称之为virtual dom。而后对比两个virtual dom,找出最小的有变化的点,这个对比的过程咱们称之为react diff,将这个变化的部分(patch)加入到一个队列中,最终批量更新这些patch到dom中。javascript
关于提高性能,不少人说virtual dom能够提高性能,这一说法其实是很片面的。由于咱们知道,直接操做dom是很是耗费性能的,可是即便咱们用了react,最终依然要去操做真实的dom。而react帮咱们作的事情就是尽可能用最佳的方式有操做dom。若是是首次渲染,virtual dom不具备任何优点,甚至它要进行更多的计算,消耗更多的内存。
react自己的优点在于react diff算法和批处理策略。react在页面更新以前,提早计算好了如何进行更新和渲染DOM,实际上,这个计算过程咱们在直接操做DOM时,也是能够本身判断和实现的,可是必定会耗费很是多的精力和时间,并且每每咱们本身作的是不如React好的。因此,在这个过程当中React帮助咱们"提高了性能"。
因此,我更倾向于说,virtual dom帮助咱们提升了开发效率,在重复渲染时它帮助咱们计算如何更高效的更新,而不是它比DOM操做更快。html
咱们在实现一个React组件时能够选择两种编码方式,第一种是使用JSX编写,第二种是直接使用React.createElement编写。实际上,上面两种写法是等价的,jsx只是为React.createElemen方法的语法糖,最终全部的jsx都会被babel转换成React.createElement。
可是请注意,babel在编译时会判断jsx中组件的首字母,当首字母为小写时,其被认定为原生dom标签,createElement的第一个变量被编译为字符串。当首字母为大写时,其被认定为自定义组件,createElement的第一个变量被编译为对象。前端
在react16中,废弃了三个will属性componentWillMount,componentWillReceiveProps,comonentWillUpdate,可是目前还未删除,react17计划会删除,同时经过UNSAFF_前缀向前兼容。
在 React 中,咱们能够将其生命周期分为三个阶段。java
react diff会帮助咱们计算出virtual dom中真正变化的部分,并只针对该部分进行实际dom操做,而非从新渲染整个页面,从而保证了每次操做更新后页面的高效渲染。传统diff算法经过循环递归对节点进行依次对比,效率低下,算法复杂度达到 O(n^3)。react diff基于一下三个策略实现了O(n)的算法复杂度。react
基于以上三个前提策略,React分别对tree diff、component diff以及element diff 进行算法优化,事实也证实这三个前提策略是合理且准确的,它保证了总体界面构建的性能。git
首先说一下element diff的过程。好比有老的集合(A,B,C,D)和新的集合(B,A,D,C),咱们考虑在不增长空间复杂度的状况下如何以O(n)的时间复杂度找出老集合中须要移动的元素。
在react里的思路是这样的,遍历新集合,初始化lastIndex=0(表明访问过的老集合中最右侧的位置),表达式为max(prev.mountIndex, lastIndex),若是当前节点在老集合中的位置即(prev.mountIndex)比lastIndex大说明当前访问节点在老集合中就比上一个节点位置靠后则该节点不会影响其余节点的位置,所以不用添加到差别队列中,即不执行移动操做,只有当访问的节点比 lastIndex 小时,才须要进行移动操做。
部分源码为github
var lastIndex = 0; var nextIndex = 0; for (name in nextChildren) { var prevChild = prevChildren && prevChildren[name]; // 老节点 var nextChild = nextChildren[name]; // 新节点 if (prevChild === nextChild) { // 若是新节点存在老节点集合里 // 移动节点 this.moveChild(prevChild, nextIndex, lastIndex); lastIndex = Math.max(prevChild._mountIndex, lastIndex); prevChild._mountIndex = nextIndex; } else { if (prevChild) { // 若是不存在在 lastIndex = Math.max(prevChild._mountIndex, lastIndex); // 删除节点 this._unmountChild(prevChild); } // 初始化并建立节点 this._mountChildAtIndex( nextChild, nextIndex, transaction, context ); } nextIndex++; } // 移动节点 moveChild: function(child, toIndex, lastIndex) { if (child._mountIndex < lastIndex) { this.prepareToManageChildren(); enqueueMove(this, child._mountIndex, toIndex); } }
React Fiber是React对核心算法的一次从新实现。
在协调阶段阶段,之前因为是采用的递归的遍历方式,这种也被称为Stack Reconciler,主要是为了区别Fiber Reconciler取的一个名字。这种方式有一个特色: 一旦任务开始进行,就没法中断,那么js将一直占用主线程,一直要等到整棵virtual dom树计算完成以后,才能把执行权交给渲染引擎,那么这就会致使一些用户交互、动画等任务没法当即获得处理,就会有卡顿,很是的影响用户体验。
页面是一帧一帧绘制出来的,当每秒绘制的帧数(FPS)达到60时,页面是流畅的,小于这个值时,用户会感受到卡顿。1秒60帧,因此每一帧分到的时间是1000/60 ≈ 16ms。因此咱们书写代码时力求不让一帧的工做量超过 16ms。若是任意一个步骤所占用的时间过长,超过16ms了以后,用户就能看到卡顿。web
简单来讲就是时间分片 + 链表结构。而fiber就是维护每个分片的数据结构。
Fiber利用分片的思想,把一个耗时长的任务分红不少小片,每个小片的运行时间很短,在每一个小片执行完以后,就把控制权交还给React负责任务协调的模块,若是有紧急任务就去优先处理,若是没有就继续更新,这样就给其余任务一个执行的机会,惟一的线程就不会一直被独占。
所以,在组件更新时有可能一个更新任务尚未完成,就被另外一个更高优先级的更新过程打断,优先级高的更新任务会优先处理完,而低优先级更新任务所作的工做则会彻底做废,而后等待机会重头再来。因此 React Fiber把一个更新过程分为两个阶段:算法
高阶组件(HOC)是React中用于复用组件逻辑的一种高级技巧。HOC自身不是React API的一部分,它是一种基于 React 的组合特性而造成的设计模式。具体而言,高阶组件是参数为组件,返回值为新组件的函数。
请注意,HOC 不会修改传入的组件,也不会使用继承来复制其行为。相反,HOC 经过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有反作用。
我理解的高阶组件是,将组件以参数的方式传递给另一个函数,在该函数中,对组件进行包装,封装了一些公用的组件逻辑,实现组件的逻辑复用,该函数被称为高阶组件。可是请注意,高阶组件不该修改传入的组件行为。
属性代理segmentfault
function ppHOC(WrappedComponent) { return class PP extends React.Component { render() { const newProps = { user: currentLoggedInUser } return <WrappedComponent {...this.props} {...newProps}/> } } }
反向继承
function hoc(ComponentClass) { return class HOC extends ComponentClass { render() { if (this.state.success) { return super.render() } return <div>Loading...</div> } } } export default class ComponentClass extends Component { state = { success: false, data: null }; async componentDidMount() { const result = await fetch(...请求); this.setState({ success: true, data: result.data }); } render() { return <div>主要内容</div> } }
术语 “render prop” 是指一种技术,用于使用一个值为函数的 prop 在 React 组件之间的代码共享。
带有渲染属性(Render Props)的组件须要一个返回 React 元素并调用它的函数,而不是实现本身的渲染逻辑。
我理解的渲染属性是,提供渲染页面的props给子组件,共享能够共享子组件的状态,复用子组件的状态,并告诉子组件如何进行渲染。
import React from 'react' import ReactDOM from 'react-dom' import PropTypes from 'prop-types' // 与 HOC 不一样,咱们可使用具备 render prop 的普通组件来共享代码 class Mouse extends React.Component { static propTypes = { render: PropTypes.func.isRequired } state = { x: 0, y: 0 } handleMouseMove = (event) => { this.setState({ x: event.clientX, y: event.clientY }) } render() { return ( <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}> {this.props.render(this.state)} </div> ) } } const App = React.createClass({ render() { return ( <div style={{ height: '100%' }}> <Mouse render={({ x, y }) => ( // render prop 给了咱们所须要的 state 来渲染咱们想要的 <h1>The mouse position is ({x}, {y})</h1> )}/> </div> ) } }) ReactDOM.render(<App/>, document.getElementById('app'))
React Hooks 是 React 16.7.0-alpha 版本推出的新特性,它可让你在不编写class的状况下使用state以及其余的 React特性。React Hooks要解决的问题是状态共享,是继render-props和hoc以后的第三种状态共享方案,不会产生JSX嵌套地狱问题。这个状态指的是状态逻辑,因此称为状态逻辑复用会更恰当,由于只共享数据处理逻辑,不会共享数据自己。
let memoizedState = []; // hooks 存放在这个数组 let cursor = 0; // 当前 memoizedState 下标 function useState(initialValue) { memoizedState[cursor] = memoizedState[cursor] || initialValue; const currentCursor = cursor; function setState(newState) { memoizedState[currentCursor] = newState; render(); } return [memoizedState[cursor++], setState]; // 返回当前 state,并把 cursor 加 1 } function useEffect(callback, depArray) { const hasNoDeps = !depArray; const deps = memoizedState[cursor]; const hasChangedDeps = deps ? !depArray.every((el, i) => el === deps[i]) : true; if (hasNoDeps || hasChangedDeps) { callback(); memoizedState[cursor] = depArray; } cursor++; }
super表明父类的构造函数,javascript规定若是子类不调用super是不容许在子类中使用this的,这不是React的限制,而是javaScript的限制,同时你也必须给super传入props,不然React.Component就无法初始化this.props
在 React 的类组件中,当咱们把事件处理函数引用做为回调传递过去,事件处理程序方法会丢失其隐式绑定的上下文。当事件被触发而且处理程序被调用时,this的值会回退到默认绑定,即值为 undefined,这是由于类声明和原型方法是以严格模式运行。
SyntheticEvent是react合成事件的基类,定义了合成事件的基础公共属性和方法。react会根据当前的事件类型来使用不一样的合成事件对象,好比鼠标单机事件 - SyntheticMouseEvent,焦点事件-SyntheticFocusEvent等,可是都是继承自SyntheticEvent。在合成事件中主要作了如下三件事情。
组件挂载阶段,根据组件内的声明的事件类型-onclick,onchange等,给document上添加事件addEventListener,并指定统一的事件处理程序dispatchEvent。
经过virtual dom的props属性拿到要注册的事件名,回调函数,经过listenTo方法使用原生的addEventListener进行事件绑定。
事件存储,就是把react组件内的全部事件统一的存放到一个二级map对象里,缓存起来,为了在触发事件的时候能够查找到对应的方法去执行。先查找事件名,而后找对对应的组件id相对应的事件。以下图:
由执行机制看,setState自己并非异步的,而是在调用setState时,若是react正处于更新过程,当前更新会被暂存,等上一次更新执行后再执行,这个过程给人一种异步的假象。
ReactComponent.prototype.setState = function(partialState, callback) { // 将setState事务放进队列中 this.updater.enqueueSetState(this, partialState); if (callback) { this.updater.enqueueCallback(this, callback, 'setState'); } }; enqueueSetState: function (publicInstance, partialState) { // 获取当前组件的instance var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState'); // 将要更新的state放入一个数组里 var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []); queue.push(partialState); // 将要更新的component instance也放在一个队列里 enqueueUpdate(internalInstance); } function enqueueUpdate(component) { // 若是没有处于批量建立/更新组件的阶段,则处理update state事务 if (!batchingStrategy.isBatchingUpdates) { batchingStrategy.batchedUpdates(enqueueUpdate, component); return; } // 若是正处于批量建立/更新组件的过程,将当前的组件放在dirtyComponents数组中 dirtyComponents.push(component); }
这里的partialState能够传object,也能够传function,它会产生新的state以一种Object.assgine()的方式跟旧的state进行合并。
由这段代码能够看到,当前若是正处于建立/更新组件的过程,就不会马上去更新组件,而是先把当前的组件放在dirtyComponent里,因此不是每一次的setState都会更新组件。这段代码就解释了咱们常据说的:setState是一个异步的过程,它会集齐一批须要更新的组件而后一块儿更新。而batchingStrategy 又是个什么东西呢?
ReactDefaultBatchingStrategy.js
var ReactDefaultBatchingStrategy = { // 用于标记当前是否出于批量更新 isBatchingUpdates: false, // 当调用这个方法时,正式开始批量更新 batchedUpdates: function (callback, a, b, c, d, e) { var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates; ReactDefaultBatchingStrategy.isBatchingUpdates = true; // 若是当前事务正在更新过程在中,则调用callback,既enqueueUpdate if (alreadyBatchingUpdates) { return callback(a, b, c, d, e); } else { // 不然执行更新事务 return transaction.perform(callback, null, a, b, c, d, e); } } };
前端路由的原理思路大体上都是相同的,即实如今无刷新页面的条件下切换显示不一样的页面。而前端路由的本质就是页面的URL发生改变时,页面的显示结果能够根据URL的变化而变化,可是页面不会刷新。目前实现前端路由有两种方式:
路径中hash值改变,并不会引发页面刷新,同时咱们能够经过hashchange事件,监听hash的变化,从而实现咱们根据不一样的hash值展现和隐藏不一样UI显示的功能,进而实现前端路由。
HTML5的History接口,History对象是一个底层接口,不继承于任何的接口。History接口容许咱们操做浏览器会话历史记录。
而history的pushState和repalce方法能够实现改变当前页面显示的url,但都不会刷新页面。
未完待续~
参考文档:
react生命周期详解
React diff
react 16新特性
react fiber1
react fiber2
react hooks
react 事件机制
setState机制1
setState机制2
react-router原理
集合