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进行组件之间的通讯。

猜你喜欢

转载自blog.csdn.net/it_cgq/article/details/84951209