react-你是如何理解单向数据流的?
1.你是如何理解单向数据流的?
组件的状态:状态可以理解为数据,与props类似,但是state是私有的,并且完全受控于当前组件,因此:组件状态指的就是⼀个组件⾃⼰维护的数据。
数据驱动UI:意思很简单,就是:页⾯所展现的内容,完全是受状态控制的。这也就是mvvm的理念,UI的改变,全部交给框架本⾝来做,我们只需要管理好数据(状态)就好了。
那么在 React 中,如何对状态进⾏管理呢?这就是本章节的重点,也是整个 React 学习的重点:组件的状态管理。
1. 什么是数据流?
数据流就是:数据在组件之间的传递。
2. 单向数据流是什么意思?
单向数据流就是:数据在某个节点被改动后,只会影响⼀个⽅向上的其他节点。
3. 为什么是⾃顶向下的?
就是说:数据只会影响到下⼀个层级的节点,不会影响上⼀个层级的节点。⽤下⾯的图来说就是:L2数据改变,只会影响到L3,不会影响到L1或者其他的节点。这就是⾃顶向下的单向数据流。那么我们在react框架中,就可以明确定义单向数据流:规范数据的流向,数据由外层组件向内层组件进⾏传递和更新。
4. 为什么是单向的?不能是双向的么?
因为:我们设想这样的情景:
⽗组件的数据通过props传递给⼦组件,⽽⼦组件更新了props,导致⽗组件和其他关联组件的数据更新,UI渲染也会随着数据⽽更新。毫⽆疑问,这是会导致严重的数据紊乱和不可控制的。
不能是双向的。
因此绝⼤多数框架在这⽅⾯做了处理。⽽ React 在这⽅⾯的处理,就是直接规定了 Props 为只读的,⽽不是可更改的。这也就是我们前⾯看到的数据更新不能直接通过 this.state 操作,想要更新,就需要通过 React 提供的专门的 this.tState() ⽅法来做。
单向数据流其实就是⼀种框架本⾝对数据流向的限制。
5. 单向数据流有什么作⽤呢?
保证数据的可控性。
2.tState 是同步还是异步的呀?
1. tState 本⾝的默认⾏为是什么?
其实也很简单,我们都知道,tState可以传递对象形式的状态,也可以传递函数形式的状态。⽽不论状态是对象形式还是函数形式,它都会先将所有状态保存起来,然后进⾏状态合并,所有状态合并完成后再进⾏⼀次性 DOM 更新。
如果状态是对象形式,后⾯的状态会直接覆盖前⾯的状态。类似于 Object.assign() 的合并操作。
对于对象状态这⼀点,我们看下代码:
运⾏以上代码,Dom 中展⽰的结果为 1。很显然两次 tState 只有⼀次⽣效了。
真的吗?其实两次都有⽣效,只不过这两次 tState 在执⾏前,被合并成了⼀个。你不能说到底是那个⽣效,你可以说两个都没⽣效,因为最终执⾏的是被合并的那个代码。
如果状态是函数形式,那么依次调⽤函数进⾏状态累积,所有函数调⽤完成后, 得到最终状态,最终进⾏⼀次性 DOM 更新。
明显不⼀样的结果就能说明,两次都执⾏了,因为函数状态并不会合并,⽽是以此运⾏。
以上就是tState的默认⾏为。
2. tState 同步 OR 异步
从 API 层⾯上说,它就是普通的调⽤执⾏的函数,⾃然是同步 API 。
因此,这⾥所说的同步和异步指的是 API 调⽤后更新 DOM 是同步还是异步的。
通过结果我们可以发现,⾮常奇怪的⼀个现象:
第⼀次事件执⾏显然为异步的,先打印了两个 0,Dom 随之改变为 1 ;
第⼆次同样是异步的,但是我们发现多次执⾏没效果 (异步?);
⽽第三次⼜是同步执⾏的了;
先说结论,⾸先,同步和异步主要取决于它被调⽤的环境。
如果 tState 在 React 能够控制的范围被调⽤,它就是异步的。
⽐如:合成事件处理函数, ⽣命周期函数, 此时会进⾏批量更新, 也就是将状态合并后再进⾏ DOM 更新。
如果 tState 在原⽣ JavaScript 控制的范围被调⽤,它就是同步的。
⽐如:原⽣事件处理函数中, 定时器回调函数中, Ajax 回调函数中, 此时 tState 被调⽤后会⽴即更新 DOM 。
为什么会这样呢?
其实,我们看到的所谓的 “异步”,是开启了 “批量更新” 模式的。
“批量更新” 模式:可以减少真实dom渲染的次数。所以只要是react能够控制的范围,出于性能因素考虑,⼀定是批量更新模式。批量更新会先合并状态,再⼀次性的做dom更新。
那么假设没有批量更新呢?
从⽣命周期的⾓度来看,每⼀次的tState都是⼀个完成的更新流程,这⾥⾯就包含了重新渲染(re-render)在内的很多操作。⼤体流程是这样的:
shouldComponentUpdate->componentWillUpdate->render->componentDidUpdate;
re-render 本⾝涉及到对dom的操作,它会带来较⼤的性能开销假如说:‘⼀次tState’就会触发⼀个完整的更新流程 这个结论成⽴,那么每⼀次tState的调⽤都会触发⼀次re-render,我们的视图没很可能没刷新⼏次就卡死了,渲染就会出现下⾯这样的流程:
因此,tState 异步(或者说是批量更新)的⼀个重要动机就是避免频繁的 re-render。
在实际的 React 运⾏时中,tState 异步的实现⽅式有点类似于浏览器⾥的 Event-Loop:
每来⼀个tState,就把它塞进⼀个队列⾥。等时机成熟,再把队列⾥的 state 结果做合并,最后只针对最新的 state 值⾛⼀次更新流程。这个过程,叫作“批量更新”,批量更新的过程正如下⾯代码中的箭头流程图所⽰:
只要我们的同步代码还在执⾏,“进队列” 这个动作就不会停⽌。因此就算我们在React 中写了⼀个 N 次的 tState 循环,也只是会增加 state 任务⼊队的次数,并不会带来频繁的 re-render。当 N 次调⽤结束后,仅仅是 state 的任务队列内容发⽣了变化, state 本⾝并不会⽴刻改变。
如果为⾮批量更新模式,调⽤多少次 tState 就会渲染多少次真实 DOM,性能较低。
但是我们在某些条件下需要对 JS 控制的区域实现批量更新 ( 异步更新 DOM ) ,那应该怎么做呢?强制批量更新:我们只需要将代码包裹在 unstable_batchedUpdates ⽅法的回调函数中就可以实现强制批量更新。
具体使⽤⽅式也很简单,从 react-dom 中引⼊进来,然后将代码放⼊调⽤函数中就可以了。
3.React 加⼊ Hooks 的意义是什么?
1. React 加⼊ Hooks 的意义是什么?或者说⼀下为什么 React 要加⼊Hooks 这⼀特性?
2. 最后举例说⼀下 Hooks 的基本实现原理。
其实这样的问题并没有什么标准答案,但是我们可以换位思考,站在⾯试官的⾓度想⼀下“为什么会问这样的问题?”。⽆⾮就是想考察我们对 Hooks 最基本的使⽤情况以及对 Hooks 设计理念的个⼈思考。
⽂档中的 “动机” 就很好的解释了为什么 React 要加⼊ Hooks 特性,总结来说就是三个基本要素:
1.组件之间的逻辑状态难以复⽤。
2⼤型复杂的组件很难拆分。
3:Class 语法的使⽤不友好。
总的来说,实际上就是类组件在多年的应⽤实践中,发现了很多⽆法避免⽽⼜难以解决的问题。⽽相对类组件,函数组件⼜太过于简陋,⽐如:
类组件可以访问⽣命周期⽅法,函数组件不能;
类组件中可以定义并维护 state(状态),⽽函数组件不可以;
类组件中可以获取到实例化后的 this,并基于这个 this 做各种各样的事情,⽽函数组件不可以。
但是,函数式编程⽅式在 JS 中确实⽐ Class 的⾯向对象⽅式更加友好直观,那么只要能够将函数的组件能⼒补齐,也就解决了上⾯的问题。⽽如果直接修改函数组件的能⼒,势必会造成更⼤的成本,最好的⽅式就是开放对应接⼝进⾏调⽤,⾮侵⼊式引⼊组件能⼒,也就是我们现在看到的 Hooks 了。
明⽩了原因,⾯试中的问题也就迎刃⽽解了,基本思路就是先阐述在没有 Hooks 的时候,类组件有哪些问题,函数组件有哪些不⾜,⽽Hooks 就是解决这些问题出现的。
5. Hooks 的设计理念是什么呢?
最重要的是,Hook 和现有代码可以同时⼯作,你可以渐进式地使⽤他们,⽽不⽤急着迁移到 Hook。尤其是对于现有的、复杂的 class 组件,我们建议避免任何“⼤规模重写”。在开始“⽤ Hook 的⽅式思考”之前,我们需要做⼀些思维上的转变。
6.React 为什么选择⽤jsx?
这⾥问 “为什么 React 选择使⽤ JSX ?”,其引申含义是 “为什么不⽤ A、B、C?”
举个例⼦,你⼆婶⼉给你介绍了俩对象,⼀个温婉可爱⼩鸟依⼈,⼀个上得厅堂下得厨房,结果你依然选择单⾝不找对象。你⼆婶⼉就问你为啥呀?你如果说单⾝有多好,你⼀定会被怼。怎么回答呢?温柔的太粘⼈,贤惠的长得丑,然后再说单⾝有多好。
套路就是,之所以选择 x,是因为 y 和 z 不好,然后接着说明 x 怎么怎么好。
但是,放到技术上,要答好这个问题“为什么 React 选择使⽤ JSX ?”,你需要先了解 React 可选的其他解决⽅案,然后才能知道有什么不好的地⽅。
其实相关⽅案有很多,最直观的就是模板。Vue 和 AngularJS 都选择使⽤模板⽅案,⽽ React 团队认为引⼊模板是⼀种不佳的实现。你觉得模板不好吗?我觉得还⾏啊,你觉得丑,我觉得美若天仙啊。这不仅仅是眼光不同,更多的是基于不同的⾓度来思考,再结合⾃⾝的特性做出的选择。
React 团队之所以认为模板不是最佳实现,原因在于,React 团队认为模板分离了技术栈,分散了组件内的关注点。其次,模板还会引⼊更多的概念,类似模板语法、模板指令等。
JSX 并不会引⼊太多新的概念,它仍然是 JavaScript,就连条件表达式和循环都仍然是 JavaScript 的⽅式,更具有可读性,更贴近HTML。对于关注点分离这个问题,我们可以⽤两段代码来展⽰:
上⾯的两段代码分别使⽤了 React 及 Vue 的单⽂件组件来呈现。在 React 中,声明的 Urs 类就是⼀个组件,全部的⽅法、数据及 UI 视图,可以以任意的⽅式呈现。⽽在 Vue 的组件中,很明确地要将 UI 部分写⼊ template 模板标签中(当然还可以在 component ⽅法中使⽤ template 字符串 ),功能及数据相关的要写⼊ script 标签中。⽽相对应的数据展⽰能⼒,则需要使⽤模板指令进⾏呈现,如:@click 指令绑定点击事件,v-for 循环遍历数据及样式结构;⽽在 JSX 中,全部都是 JavaScript 的,没什么规矩可⾔。
那么 JSX 到底是什么呢?
我们知道它不是字符串也不是 HTML,⽽是⼀个 JavaScript 的语法扩展,⽤于描述组件 UI。实际上,官⽅⼿册上早就说的很清楚了,JSX 仅仅只是 ateElement(component, props, ...children) 函数的语法糖,最终会被编译为 ateElement() 函数调⽤,并返回⼀个被称为 “React 元素” 的普通 JavaScript 对象。
我们⽤⼀段简单的代码展⽰⼀下,具体来看看:
上⾯的代码中,我们直接将 JSX 的内容打印到控制台,效果如下: