资讯专栏INFORMATION COLUMN

深入React知识点整理(一)

Gilbertat / 2750人阅读

摘要:以我自己的理解,函数式编程就是以函数为中心,将大段过程拆成一个个函数,组合嵌套使用。越来越多的迹象表明,函数式编程已经不再是学术界的最爱,开始大踏步地在业界投入实用。也许继面向对象编程之后,函数式编程会成为下一个编程的主流范式。

使用React也满一年了,从刚刚会使用到逐渐探究其底层实现,以便学习几招奇技淫巧从而在自己的代码中使用,写出高效的代码。下面整理一些知识点,算是React看书,使用,感悟的一些总结:

函数式编程

React事件系统

高阶组件

组件性能优化

React源码初探

VirtualDOM 模型

1. 函数式编程

函数式编程是一种如何编写程序的方法论,与之对应的就是命令式编程

以我自己的理解,函数式编程就是以函数为中心,将大段过程拆成一个个函数,组合嵌套使用。这个思想在JavaScript中很常见。举个阮一峰老师的例子:

我们有一个数学表达式:

(1 + 2) * 3 - 4

将上述表达式不假思索的转换成代码:

const a = 1 + 2;
const b = a * 3;
const c = b - 4;

以函数式编程思想:将运算过程定义成不同的函数,如下:

const result = substract(multiply(add(1, 2), 3), 4);

是不是感觉很高端但又一脸懵逼。没错,函数式编程在处理大段过程中就显得很容易理解,但是简单逻辑中就显得复杂,因为封装起来的函数需要时间去阅读。

对上述表达式进行变形:

 add(1,2).multiply(3).subtract(4);

是不是也很熟悉。函数式编程在JavaScript中应用确实很普遍。

目前最当红的Python、Ruby、Javascript,对函数式编程的支持都很强,就连老牌的面向对象的Java、面向过程的PHP,都忙不迭地加入对匿名函数的支持。越来越多的迹象表明,函数式编程已经不再是学术界的最爱,开始大踏步地在业界投入实用。

也许继"面向对象编程"之后,"函数式编程"会成为下一个编程的主流范式(paradigm)。未来的程序员恐怕或多或少都必须懂一点。

这里不做多介绍,有兴趣可以看看:

函数式编程初探

什么是函数式编程思维?

函数式编程

2.React事件系统 React事件与DOM事件
React 基于 Virtual DOM 实现了一个 SyntheticEvent (合成事件)层,我们所定义的事件处理器会接收到一个 SyntheticEvent 对象的实例,它完全符合 W3C 标准,不会存在任何 IE 标准的兼容性问题。并且与原生的浏览器事件一样拥有同样的接口,同样支持事件的冒泡机制,我们可以使用 stopPropagation() 和 preventDefault() 来中断它。

所有事件都自动绑定到最外层上。如果需要访问原生事件对象,可以使用 nativeEvent 属性。

使用React的时候都知道,React有一套自己的事件系统,典型的特征就是元素绑定事件都要使用React提供的事件接口:

// in html


// in React

React的合成事件实际上是做了一层事件委托(事件代理):

它并不会把事件处理函数直接绑定到真实的节点上,而是把所有事件绑定到结构的最外层,使用一个统一的事件监听器,这个事件监听器上维持了一个映射来保存所有组件内部的事件监听和处理函数。当组件挂载或卸载时,只是在这个统一的事件监听器上插入或删除一些对象;当事件发生时,首先被这个统一的事件监听器处理,然后在映射里找到真正的事件处理函数并调用。这样做简化了事件处理和回收机制,效率
也有很大提升。

也就是说React使用了一个事件代理,所有事件绑定都只是事件代理保存了一个映射,事件发生的时候,调用处理函数,并没有真正的使用原生事件。我们来看一个例子:

componentDidMount () {
    document.querySelector("#testEvent").addEventListener("click", (e)=>{
        console.log("dom event");
        console.log(e);
    })
}
componentDidUnMount () {
    document.querySelector("#testEvent").removeEventListener("click");
}
handleClick (e) {
    console.log("react event");
    console.log(e);
}
render () {
    return (
        
Test React Event
Test dom Event
); }

这里有两个div,使用React绑定事件和原生DOM事件,两种事件绑定方法不同导致相同的效果,完全不同的原理。

使用原生DOM绑定打印的事件就是原生的,React事件打印出来的事件:

可以看到是个Proxy对象,里面有触发事件的target和处理事件的handler,这就是React的合成事件。

另外如果在react中绑定原生事件,组件卸载的时候记得解除绑定,避免内存泄漏。

React的合成事件还有一个优点在于不需要处理浏览器事件兼容性,方便操作。

原生事件分成三个部分:事件捕获,目标事件处理,事件冒泡。IE9以下不支持事件捕获,所以React没有实现它,仅支持事件冒泡。

有些事件React没有实现,window.resize事件。

所以,请尽量避免在 React 中混用合成事件和原生 DOM 事件。因为两者是不同的事件系统,阻止 React 事件冒泡的行为只能用于 React 合成事件系统中,且没办法阻止原生事件的冒泡。反之,在原生事件中的阻止冒泡行为,却可以阻止 React 合成事件的传播。

3.高阶组件

高阶组件是React中比较有特点的一类问题,高阶组件(High Order Component)文章里多带带进行了详细介绍。

这里只是补一张图:组合式组件开发实践

4.组件性能优化
从过往的经验与实践中,我们都知道影响网页性能最大的因素是浏览器的重绘(reflow)和重排版(repaint)。React 背后的 Virtual DOM 就是尽可能地减少浏览器的重绘与重排版。

关于浏览器重绘和重排版问题,请看我之前的文章:浏览器渲染页面过程与页面优化

这里要介绍的就是:

多使用纯函数:无依赖;相同输入相同输出;重复使用。

PureComponent:本质上讲,PureComponent就是重写了shouldComponentUpdate,对nextPropsnextState与当前state和props做浅比较,性能上优化。

Immutable:使用Immutable共享数据节点,节省渲染。

key:列表渲染指定key,相同key不渲染;尽量不要使用index当key,最好是id。

react-addons-pref:插件量化性能优化效果。

对这块有兴趣的,推荐几篇文章:

React组件性能调优

React性能优化总结

高性能 React 组件

5.React源码初探

React项目目录构成如下图:

addons:工具方法插件:PureRenderMixinCSSTransitionGrouoFragmentLinkedStateMixin

isomorphic:包含一系列同构方法。

shared:公用方法和常用方法。

test:测试方法。

core/tests:边界错误的测试用例。

renderers:React的核心代码,包含大部分功能实现,因此进行多带带分析。

renderers包包含内容:

dom:包含client,server和shared。

client:包含DOM操作方法(findDOMNode,setInnerHTML,setTextContent等)以及事件方法。这里的事件方法主要是一些非底层的实用性事件方法,
如事件监听(ReactEventListener)、常用事件方法(TapEventPlugin、EnterLeaveEventPlugin)以及一些合成事件(SyntheticEvents
等)。

server:主要包含服务端渲染的实现和方法(如 ReactServerRendering、ReactServerRenderingTransaction
等)。

shared:包含文本组件(ReactDOMTextComponent)、标签组件(ReactDOMComponent)、
DOM 属性操作(DOMProperty、DOMPropertyOperations)、CSS 属性操作(CSSProperty、
CSSPropertyOperations)等。

shared:包含event和reconciler。

event:包含一些更为底层的事件方法,如事件插件中心(EventPluginHub)、事件注册
(EventPluginRegistry)、事件传播(EventPropagators)以及一些事件通用方法。
React 自定义了一套通用事件的插件系统,该系统包含事件监听器、事件发射器、事
件插件中心、点击事件、进/出事件、简单事件、合成事件以及一些事件方法。

reconciler:称为协调器,它是最为核心的部分,包含 React 中自定义组件的实现
(ReactCompositeComponent)、组件生命周期机制、setState 机制(ReactUpdates、
ReactUpdateQueue)、DOM diff 算法(ReactMultiChild)等重要的特性方法。

这里简单介绍React目录构成以及每块的功能,大致了解,需要的时候找到对应位置深入研究。

React 也能够实现 Virtual DOM 的批处理更新,当操作 Virtual DOM 时, 不会马上生成真实的DOM,而是会将一个事件循环(event loop)内的两次数据更新进行合并,这样就使得 React 能够在事件循环的结束之前完全不用操作真实的 DOM。
6.VirtualDOM 模型

VirtualDOM是React的一个核心,也是React一个著名的特点,之前我有篇文章对此有过简单的介绍,以及如何简单实现根据VirtualDOM渲染页面:React学习报告,可以做基本入门查看。

VirtualDOM与真实DOM的关系很简单:

真实DOM可以理解为是xml格式存储DOM,VirtualDOM可以理解为json格式的存储DOM。

只需要存储节点的关键信息:类型,id,class,属性,style,事件,嵌套关系等即可,按照一定的转换规则将json转成DOM。

流程关系:jsx语法->识别jsx语法生成VirtualDOM树->根据渲染规则生成真实DOM->HTML。

Virtual DOM中的节点成为ReactNode,分成ReactELement,ReactFragment,ReactText。ReactElement又分成ReactComponentElemnt和ReactDOMElement。

下面是 ReactNode 中不同类型节点所需要的基础元素:

type ReactNode = ReactElement | ReactFragment | ReactText;
type ReactElement = ReactComponentElement | ReactDOMElement;
type ReactDOMElement = {
 type : string,
 props : {
 children : ReactNodeList,
 className : string,
 etc.
 },
 key : string | boolean | number | null,
 ref : string | null
};
type ReactComponentElement = {
 type : ReactClass,
 props : TProps,
 key : string | boolean | number | null,
 ref : string | null
};
type ReactFragment = Array;
type ReactNodeList = ReactNode | ReactEmpty;
type ReactText = string | number;
type ReactEmpty = null | undefined | boolean; 

这里以DOM标签(ReactDOMComponent)为例,介绍VirtualDOM模型如何创建节点:

属性更新
当执行 mountComponent 方法时,ReactDOMComponent 首先会生成标记和标签,通过 this.createOpenTagMarkupAndPutListeners(transaction) 来处理 DOM 节点的属性和事件。

如果节点绑定了事件,则针对当前的节点添加代理,调用enqueuePutListener(this,propKey, propValue, transaction)

存在样式的话,样式合并Object.assign({}, props.style),然后通过CSSPropertyOperations.createMarkupForStyles(propValue, this)创建样式。

通过DOMPropertyOperations.createMarkupForProperty(propKey, propValue)创建属性。

通过DOMPropertyOperations.createMarkupForID(this._domID)创建唯一标识。

其实,早有开发者向 React 官方提过问题,建议去掉这个鸡肋的属性标识(data-reactid)这终于在 React 15.0版本上实现了。据官方宣称,去除 data-reactid 使得 React 性能有了 10% 的提升。
更新子节点
当执行 mountComponent 方法时,ReactDOMComponent 会通过 this._createContentMarkup(transaction, props, context) 来处理 DOM 节点的内容。

先是删除不需要的子节点和内容。如果旧节点存在,而新节点不存在,说明当前节点在更新后被删除,此时执行方法 this.updateChildren(null, transaction, context);如果旧的内容存在,而新的内容不存在,说明当前内容在更新后被删除,此时执行方法 this.updateTextContent("")。

再是更新子节点和内容。如果新子节点存在,则更新其子节点,此时执行方法 this.updateChildren(nextChildren,transaction, context);如果新的内容存在,则更新内容,此时执行方法 this.updateTextContent("" + nextContent)

当卸载组件时,ReactDOMComponent 会进行一系列的操作,如卸载子节点、清除事件监听、清空标识等。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/92140.html

相关文章

  • 深入React识点整理(二)

    摘要:承接上文,深入知识点整理一使用也满一年了,从刚刚会使用到逐渐探究其底层实现,以便学习几招奇技淫巧从而在自己的代码中使用,写出高效的代码。有限状态机,表示有限个状态以及在这些状态之间的转移和动作等行为的模型。 承接上文,深入React知识点整理(一)使用React也满一年了,从刚刚会使用到逐渐探究其底层实现,以便学习几招奇技淫巧从而在自己的代码中使用,写出高效的代码。下面整理一些知识点,...

    villainhr 评论0 收藏0
  • Record 前端开发知识整理分享

    showImg(https://segmentfault.com/img/remote/1460000018716142?w=200&h=200); showImg(https://segmentfault.com/img/remote/1460000018716143);showImg(https://segmentfault.com/img/remote/1460000010953710);...

    TZLLOG 评论0 收藏0
  • 本关于 React.js 的小书

    摘要:因为工作中一直在使用,也一直以来想总结一下自己关于的一些知识经验。于是把一些想法慢慢整理书写下来,做成一本开源免费专业简单的入门级别的小书,提供给社区。本书的后续可能会做成视频版本,敬请期待。本作品采用署名禁止演绎国际许可协议进行许可 React.js 小书 本文作者:胡子大哈本文原文:React.js 小书 转载请注明出处,保留原文链接以及作者信息 在线阅读:http://huzi...

    Scorpion 评论0 收藏0
  • 深入理解js

    摘要:详解十大常用设计模式力荐深度好文深入理解大设计模式收集各种疑难杂症的问题集锦关于,工作和学习过程中遇到过许多问题,也解答过许多别人的问题。介绍了的内存管理。 延迟加载 (Lazyload) 三种实现方式 延迟加载也称为惰性加载,即在长网页中延迟加载图像。用户滚动到它们之前,视口外的图像不会加载。本文详细介绍了三种延迟加载的实现方式。 详解 Javascript十大常用设计模式 力荐~ ...

    caikeal 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<