React深入理解笔记

一、React的工作原理

1、UI = f(data){}

UI 就是界面 ,这个界面就是函数执行的结果,是按照函数式编程理念来的,我们所做的东西就是要实现这个函数,改变UI,是通过改变函数中的参数data才行的,改变data,让data来驱动这个函数,这个函数来影响(产生)UI。所以在React中,无论你实现一个组件也好,或者完成其它功能,都是要实现一个函数,改变函数中的data,让data来驱动这个函数,这个函数来影响(产生)UI,也就是说你通过改变data来改变UI,这才是React响应式的理念。

data 就是 props和state

开发者 只需要维护可变的数据 state (what) ,让 react 框架帮助我们处理 DOM 操作(what)。

React 通过 diffing 算法计算如何更新视图。而 diffing 算法有个 的假设前提,开发人员会提供给长列表的每个子项一个 ID,帮助算法进行对比,如下图所示:

react-key

通过key保证节点的唯一性,避免了重复的渲染,可以通过上图来进行说明,React认为,如果key都相同,那么就不需要重新的计算并且渲染了,只要key不相同了(比如 新插入了一个 x 这个节点),就需要单独渲染这个 x 节点就行了。

在同一组组件中key的两个要素

1、key 在兄弟节点中是唯一的。

2、key的值是稳定的,key的值必须是固定的,不要总是改变key的值,在实际开发中,key的值不要设置数组的下标索引值,因为这样是不稳定的值,例如上图所示,之前 B的索引值是 1,但是通过改变之后,索引值就变成了 2。

3、三元表达式不需要使用key来进行标记,因为无论三元表达式的条件成立与否,都会有一个对应的值,在对应的位置进行占位。

完成的渲染流程

初始化的渲染流程分为 3 步。

第一步,开发者使用 JSX 语法写 React,babel 会将 JSX 编译为浏览器能识别的 React JS 语法。这一步,一般配合 webpack 在本地进行。

第二步,执行 ReactDOM.render 函数,渲染出虚拟DOM。

第三步,react 将虚拟DOM,渲染成真实的DOM。

页面更新的流程同样也是 3 步。

第一步,当页面需要更新时,通过声明式的方法,调用 setState 告诉 react什么数据变了。

第二步,react 自动调用组件的 render 方法,渲染出虚拟 DOM。

第三步,react 会通过 diffing 算法,对比当前虚拟 DOM 和需要更新的虚拟 DOM 有什么区别。然后重新渲染区别部分的真实 DOM。

ReactRender
  1. 什么是模块化:从 代码 的角度,去分析问题,把我们编程时候的业务逻辑,分割到不同的模块中来进行开发,这样能够方便代码的重用
  2. 什么是组件化:从 UI 的角度,去分析问题,把一个页面,拆分为一些互不相干的小组件,随着我们项目的开发,我们手里的组件会越来越多,最后,我们如果要实现一个页面,可能直接把现有的组件拿过来进行拼接,就能快速得到一个完整的页面, 这样方便了UI元素的重用组件是元素的集合体

什么是虚拟DOM(Virtual DOM)

虚拟DOM(VDOM)是一种编程概念,是指虚拟的视图被保存在内存中,并通过诸如ReactDOM这样的库与“真实”的DOM保持同步。这个过程被称为reconciliation。

这种编程方法使用了React的声明式API:你需要告诉React你想让视图处于什么状态,React则负责确保DOM与该状态相匹配。因此你在构建你的应用时不必自己去完成属性操作、事件处理、DOM更新,React会替你完成这一切。

由于“虚拟DOM”更像一种模式而不是特定的技术,有时候我们也会用它表示其他的意思。在React的世界中,由于 “虚拟DOM” 和React元数都是用于表示视图的对象,因此常常被关联在一起。然而React也使用被称为“fibers”的对象来存放组件树的附加信息。在React中,它们也被认为是“虚拟DOM”实现的一部分。

影子DOM(Shadow DOM)和虚拟DOM(Virtual DOM)是相同的概念吗?

不,它们并不是相同的概念。影子DOM是一种浏览器技术,主要被设计用来为Web组件中的变量和CSS提供封装。虚拟DOM是由JavaScript库在浏览器API之上实现的一种概念。

Diff算法

  • tree diff:新旧DOM树,逐层对比的方式,就叫做 tree diff,每当我们从前到后,把所有层的节点对比完后,必然能够找到那些 需要被更新的元素;
  • component diff:在对比每一层的时候,组件之间的对比,叫做 component diff;当对比组件的时候,如果两个组件的类型相同,则暂时认为这个组件不需要被更新,如果组件的类型不同,则立即将旧组件移除,新建一个组件,替换到被移除的位置;
  • element diff:在组件中,每个元素之间也要进行对比,那么,元素级别的对比,叫做 element diff;
  • key:key这个属性,可以把 页面上的 DOM节点 和 虚拟DOM中的对象,做一层关联关系;

fiber是React 16中新的引擎。它的主要目的是使虚拟DOM能够进行增量渲染。

React Fiber 是对 React 核心算法的重新实现。

React Fiber 的目标是提高其对动画,布局和手势等领域的适用性。它的最重要的特性是 incremental rendering(增量渲染):它能够将渲染 work 拆分成多块并将这些任务块分散到多个帧执行。

其他的核心特性还包括当新的更新到来时暂停、中止或恢复 work 的能力;为不同类型的更新设置优先级的能力;以及新的并发原语。

什么是 reconciliation?

  • reconciliation

    React 用来比较两棵树的算法,它确定树中的哪些部分需要被更新。

    • update

      用来渲染 React 应用的数据的改变。通常是 setState 的结果。最终结果将是一次重新渲染

    React 的 API 的核心理念是将更新想象成为对整个应用的重新渲染。这允许开发者声明式地推导,而不用担心应用如何从一个状态高效的过渡到另一个状态(A 到 B,B 到 C,C 到 A 等等)。

    实际上,对于每个更改重新渲染整个应用程序只适用于最简单的应用程序。在实际的应用中,对性能会有非常大的开销。

    Reconciliation 是被大家广泛知晓的 "virtual DOM" 背后的算法。

    • 不同的组件类型被认为将生成本质上不同的树。React 将不尝试去进行差异比较,而是直接完全替换旧的树。
    • 对于列表的差异比较使用 key 来优化性能。Keys 应当是稳定、可预测且唯一的。
  • Scheduling(调度)

    Scheduling

    • scheduling

      决定 work 何时应该被执行的过程。

    • work

      任何计算结果都必须被执行。Work 通常是更新的结果(e.g. setState).

      在 React 目前的实现中,React 递归地遍历树并且调用 render 方法在 event loop 的一次迭代中更新整棵树。不过在将来,它将延迟一些更新来防止掉帧。

      这是 React 设计中一个公共的主题。一些受欢迎的库实现了“推”的方法,计算会在新数据到来的时候被执行。但是 React 坚持“拉”的方法,计算能够被推迟直到需要的时候。

      React 不是一个通用的数据处理库。它只是设计来构建用户界面的库。我们认为知晓哪些计算是现在就相关的,哪些不是是在应用程序中有独一无二位置的内容。

      如果一些东西在屏幕外,我们可以推迟任何与其相关的逻辑。如果数据到来的速度比帧率块,那么我们可以合并并批量更新。我们可以对来自用户与界面互动的工作(如一个按钮点击的动画)和相对不太重要的背后工作(如远程加载数据渲染新的内容)进行优先级的排序来阻止掉帧。

    总结一下核心点如下:

    • 在 UI 中,并非所有的更新都需要立即生效。实际上,这样做是浪费的,可能会造成掉帧从而影响用户体验。
    • 不同类型的更新有不同的优先级。一个动画更新相比于来自数据的更新通常需要更快地被执行。
    • 一个以“推”为基础的方案要求应用程序(你,工程师)来决定如何调度工作。而一个以“拉”为核心的方案允许框架(如:React)更智能,来为你做这些决定。

    iber 的主要目标是让 React 能够享受到调度带来的好处。明确地说,我们需要能够做到以下几件事:

    • 暂停任务并能够在之后恢复任务。
    • 为不同的任务设置不同的优先级。
    • 重新使用之前完成的任务。
    • 如果不在需要则可以终止一个任务。

    为了做到这些事,我们首先需要一个方式来拆分这些任务为一个个任务单元。从某种意义上来说,这些任务单元就是 fiber。一个 fiber 代表了一个 任务单元.

    view = fn(data)
    

    因此渲染 React 应用程序就类似于调用一个包含其他函数调用的函数。

2、一切皆为组件

2.1 组件的生命周期
React声明周期

组件之间通过props 得到任何数据,都是只读的,不能重新赋值

jsx语法中的标签,并不是直接渲染到了页面上,而是先转换成了React.createElement 这样的js代码,然后再通过render渲染到了页面中

es6 class 类中 静态方法挂载到了constructor构造器中,实例方法挂载到了原型对象中

class Person {
    constructor(name,age){
        this.name = name
        this.age = age
    }
    // say 是实例方法
    say(){
        console.log('您好')
    }
    
    // info 是静态方法
    static info = 123 
}

class Chinese extends Person {
    // 使用extends 关键字实现继承,子类的constructor构造函数中,必须显示调用super()方法,这个super表示父类中constructor的引用
    constructor(name,age,color,lang) {
        super(name,age)
    }
}

var p1 = new Person('zs',12)
console.log(p1)

var c1 = new Person('ls',12,'yellow','汉语')
console.log(c1)
c1.say()
console.log(Chinese.info)

react无状态组件和有状态组件的区别
无状态组件:无状态组件(Stateless Component)是最基础的组件形式,由于没有状态的影响所以就是纯静态展示的作用。一般来说,各种UI库里也是最开始会开发的组件类别。如按钮、标签、输入框等。它的基本组成结构就是属性(props)加上一个渲染函数(render)。由于不涉及到状态的更新,所以这种组件的复用性也最强。

有状态组件:在无状态组件的基础上,如果组件内部包含状态(state)且状态随着事件或者外部的消息而发生改变的时候,这就构成了有状态组件(Stateful Component)。有状态组件通常会带有生命周期(lifecycle),用以在不同的时刻触发状态的更新。这种组件也是通常在写业务逻辑中最经常使用到的,根据不同的业务场景组件的状态数量以及生命周期机制也不尽相同。

什么时候用 无状态组件?什么时候用有状态组件?

1、如果一个组件,只需要通过外界传递过来的props,渲染固定的页面,就适合使用function创建出来的无状态组件。

2、如果一个组件需要存放自己的私有数据,同时需要在组件的不同阶段执行不同的业务逻辑,就适合使用有状态组件。

在React中,我们通常通过props和state来处理两种类型的数据。props是只读的,只能由父组件设置。state在组件内定义,在组件的生命周期中可以更改。基本上,无状态组件使用props来存储数据,而有状态组件(也称为智能组件)使用state来存储数据。

总的来说:无状态函数式写法 优于React.createClass,而React.Component优于React.createClass。能用React.Component创建的组件的就尽量不用React.createClass形式创建组件。

在React中创建组件有三种方式:

  • ES5写法:React.createClass
  • ES6写法:React.Component
  • 无状态的函数写法,又称为纯组件

3、声明式编程

声明式与命令式

  • 命令式编程:命令“机器”如何去做事情(how),这样不管你想要的是什么(what),它都会按照你的命令实现。
  • 声明式编程:告诉“机器”你想要的是什么(what),让机器想出如何去做(how)。

jQuery是命令式的编程,jQuery让你去使用它的API,去做一些事情,如果jQuery的API改变了,我们写的的代码也要改变,因为API发生变化了,我们需要修改之前的API,所以这样的设计是很不友好的。

React中的写的code不大可能调用系统级别的API,相反,我们是实现一些函数,去响应,我们写的代码,只是声明我们要做什么,怎么去做,就是React来完成了,我们不用去关心,即使React的API改变了,也不会影响。

三、JSX的优势和局限

1、把相关的code放到一起,好维护,这样就是一个单独的组件所必须的。
2、render函数是一个纯函数,没有做渲染的事情,它只不过就是返回了一些指令,然后通过这些指令由React通过DOM或者虚拟DOM来进行操作,所以react返回的结果都是通过 React.createElement产生的结果

3、jsx中的{}中只能是一个表达式,不能是一个语句,因为jsx可以通过babel转换成React.createElement的形式进行渲染,但是React.createElement中的参数只能是一个表达式,不能是一个语句(比如for循环、if判断等),同时jsx中的render函数的{}中不能使用push()reverse()等数组方法,因为render应该是一个纯函数,纯函数不应该有副作用,渲染的应该是state或者props,如果使用了push()reverse()等数组方法,那么就会直接修改state或者props里面的数据,而并不是产生新的数据

四、什么使用props什么时候使用state

image-20181121213406519

在react中data包括:satae和props,props是从外部的组件中传过来的数据,而satae是组件内部状态,一个组件的自己的satae可以作为传递给它子组件的props的数据来源,一个组件想要改变自己的状态只能通过setState来进行改变自己的状态,一个组件不能够直接通过修改自己的props来改变自己的更新状态,也不能修改自己的porps,因为修改自己的props会很乱的。但凡props能够搞定的事情,尽量不要使用state。

将父级所传递的props,作为子级的state,是会产生值引用对象影响问题,也就是我更改子级state,父级props被相应更改

五、详解React组件的生命周期

React组件的生命周期三种过程:

  • mount:从无到有的过程
  • update: 重新渲染,又分为state change引发的,和props引发的
  • unmount:从有到无

mount过程:
getDefaultProps
getInitialState
componentWillMount
render
componentDidMount:这个函数只能在浏览器端执行,但是使用react做服务器端渲染的时候,不能在服务器端执行(因为在服务器端突出出来的是一个字符串,根本上就没有一个DOM-tree被mount的说法,所以不会执行),

因state改变引发的update过程:

shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate

因父组件想要render这个组件改变引发的update过程:

componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate

如果当父组件不需要重新渲染,shouldComponentUpdate 会 返回一个false,那样后面的componentWillUpdate、render、componentDidUpdate 就不会执行。

无论是mount过程还是update过程中的render函数之前的声明周期,获取的到的state或者是props都是之前未改变的数据,只有render函数之后,才是数据改变之后的状态,

六、为什么尽量构建无状态组件

无状态组件就是一个纯函数,尽量让组件成为一个纯函数形式的无状态组件,因为可以减少一些bug出现,我们应该把一些有状态组件(就是里面含有state)集中起来进行管理,让其子组件是一个无状态组件,这样好管理,好维护,

七、创建高阶组件(HoC,Higher-Order Component)

HoC 高阶组件使用场景

把一个通用的功能,使用高阶组件进行共用,这样就不必写同样的功能代码了。

八、组件之间通讯

父组件通过属性的形式向子组件传递参数,子组件通过props接受父组件传递过来的参数

子组件如果想和父组件通信,子组件要调用父组件传递过来的方法

兄弟组件之间的通讯可以借助于父组件作为中介,进行传递数据(父组件中写两个函数,这两个函数分别传给两个子组件,然后互相之间进行相互引用,)

任意组件之间的通讯可以通过设置一个全局变量来作为中介,进行传递数据。

如果这个项目比较打的话,当然也可以使用redux进行组件之间的通讯。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,829评论 1 331
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,603评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,846评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,600评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,780评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,695评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,136评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,862评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,453评论 0 229
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,942评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,347评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,790评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,293评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,839评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,448评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,564评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,623评论 2 249

推荐阅读更多精彩内容

  • 作为一个合格的开发者,不要只满足于编写了可以运行的代码。而要了解代码背后的工作原理;不要只满足于自己的程序...
    六个周阅读 8,351评论 1 33
  • 40、React 什么是React?React 是一个用于构建用户界面的框架(采用的是MVC模式):集中处理VIE...
    萌妹撒阅读 965评论 0 1
  • 本笔记基于React官方文档,当前React版本号为15.4.0。 1. 安装 1.1 尝试 开始之前可以先去co...
    Awey阅读 7,517评论 14 128
  • 3. JSX JSX是对JavaScript语言的一个扩展语法, 用于生产React“元素”,建议在描述UI的时候...
    pixels阅读 2,752评论 0 24
  • 明朝青花瓷 新疆盛产西红柿 销往哈萨克斯坦和俄罗斯 西班牙盛产葡萄红酒 新疆喀什shi 斯坦因强盗 在敦煌,斯坦因...
    多美丽阅读 415评论 0 0