react是什么
a javascript library for building user interface
- 打造用户界面
- 响应各种事件
注意:面试装B 可以直接看react源码上的change log ,如果看看那些一次更新很多的版本,看自己能理解的log 。记住版本好,精确到小数位。
react 虚拟dom
- 先将代码转成js对象,再把js对象转化成真实dom,这个js对象就是虚拟dom
<div class="box">
<h1>标题</h1>
<p><span>内容</span></p>
</div>
对应的虚拟dom
const vdom = {
type : 'div',
props: {class:'box' },
chlidren : [
{
type : 'h1',
chlidren: '标题'
},
{
type : 'p',
chlidren: {
type : 'span',
chlidren: '内容'
}
}
]
}
扩展:
上面的vdom 即可以叫对象,也可以叫json
JSON scheme也叫JSON模式 。 大家基于json的格式标准定义数据结构并用于传输。
在JS 里面 json 的就是 JavaScript的对象字面量的表达式。
如下
var a = {
name : '张三',
age: 11
}
这里定义的a 即是对象,也是json。
由于互联网的普及,JS的这个JSON模式也被广泛应用在其他后端语言的接口定义规范
其他的接口规范有 SOAP,WebService 基于xml的传输格式。
fiber原理解析
在react 16.0之前 diff比较 直接使用 虚拟dom树进行 循环+递归(广度遍历,深度优先方式)。
缺点:
- 由于代码是同步执行,所以会一直循环+递归直到结束。主线程会一直占用。
- 当大量组件实例存在时候,执行效率会变的越来越慢。
- 导致用户的UI交互卡顿
新增fiber 关系链
fiber 的引入是扩展,原来的虚拟dom依然保留
fiber 的优势
- 将对比dom的操作拆细,碎片化执行
- 碎片化的任务,可以根据需要被暂停。
- 利用浏览器空闲时间执行
碎片化使用的是 浏览器API -> requestIdleCallback ,他会在空闲的时候执行,根据优先级可暂停当前任务,交给更高级的任务。
fiber 结构定义
export type Fiber = {
// Fiber 类型信息
type: any,
// 指向父节点,或者render该节点的组件
return: Fiber | null,
// 指向第一个子节点
child: Fiber | null,
// 指向下一个兄弟节点
sibling: Fiber | null,
}
利用以上的 链表结构,就可以快速找到当前的下一个上一个,从而实现暂停与继续。
执行逻辑
- 根据虚拟dom,生成fiber对象。
- 根据fiber 再去做渲染真实的dom 也就是diff
核心代码
// 创建任务队列
const taskQueue = createTaskQueue()
// 空闲时间执行的具体方法
const performTask = deadline => {
// 执行任务, workLoop方法后续补充
workLoop(deadline)
//实现持续调用
if (subTask | !taskQueue.isEmpty()) {
requestIdleCallback(performTask)
}
}
// 暴露的render方法
export const render = (element, dom) => {
// 1.添加任务=》 构建fiber对象
taskQueue.push({
dom,
props: { children: element }
3)
// 2.指定浏览器空闲时间执行performTask 方法
requestIdleCallback(performTask)
}
// 子任务
let subTask = null
// commit操作标志
let pendingCommit = null
const workLoop = deadline => {
// 1.构建根对象
if (!subTask) {
subTask = getFirstTask()
}
// 2.通过while循环构建其余对象
while (subTask && deadline.timeRemaining() > 1) {
subTask = executeTask(subTask)
}
// 3.执行commit操作,实现Dom挂载
if (pendingCommit) {
commitAllWork(pendingCommit)
}
}
const getFirstTask = () => {
// 获取任务队列中的任务
const task = taskQueue.pop()
// 返回Fiber对象
return {
props: task.props,
stateNode: task.dom
// 代表虚拟Dom挂载的节点
tag: "host_root",
effects: [],
child: null
}
}
react虚拟dom了解吗?
用来描述真实dom的一个json对象。
你了解react fiber吗?
用于提升dom 做diff算法时效率所引入的新对象与数据结构。
Fiber的优势是什么?
可以中断,碎片化执行,diff的时候原主线程不受影响,UI不卡顿。
Fiber怎么做到⽐之前的渲染要快的?
传统的diff 需要整颗树 完成的遍历才能更新渲染 时间复杂度(o3),用fiber就可以跨层级跳转不用全部遍历,很快找到对应节点进行更新。 时间复杂度(o)
你了解react虚拟dom渲染机制吗?
react 生命周期
16.0之前
- 初始化 -> 挂载props -> 初始化state -> render -> 完成
-
获取数据完毕 -> 更新state -> diff -> render -> 完成
更新state的方式
- 组件⾃⼰setState触发更新
- ⽗组件触发传给⼦组件的props值变化,引发⼦组件更新
常用的几个钩子
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
- shouldComponentUpdate
shouldComponentUpdate 可以控制当前ui是否需要diff ,这也是人为可以干预react渲染效率的唯一手段。
// 父组件重新render,重传props,无论porps值有没有改变,子组件都会接收并改变
class SomeComponent extends Component {
shouldComponentUpdate(nextProps) {
//应该使用这个方法,否则无论props是否有变化都将会导致组件跟着重新宣染
if (nextProps.someThings === this.props.someThings) {
return false;
}
render() {
return <div>this.props. someThings)</div>;
}
}
}
class SomeComponent extends Component {
constructor(props) {
super(props);
this.state = {
someThings: props.someThings
}
}
componentWillReceiveProps(nextProps) {
//父组件重传props时就会调用这个方法
this.setState({ someThings: nextProps.someThings });
}
render() {
return <div>(this.state.someThings)</div>
}
}
常见问题:
React⽣命周期
React⽣命周期函数以及各个函数的应⽤
React 16.0前后⽣命周期函数的变化为什么
React对于⽣命周期函数的变化你怎么理解
React性能优化(虚拟dom渲染上优化)
diff算法
React diff算法策略
分为3种不同的类型进行对比
- 针对树结构(tree diff):对UI层的DOM节点跨层级的操作进行忽略。(数量少)
- 针对组件结构(component diff):拥有相同类的两个组件生成相似的树形结构,拥有不同类的两个组件会生成不同的属性结构。
- 针对元素结构(element-diff):对于同一层级的一组节点,使用具有唯一性的id区分(key属性)
比较过程
- 通过state计算出新的fiber节点
- 对⽐节点的tag和key确定节点操作(修改,删除,新增,移动)
- effectTag标记fiber对象
- 收集所有标记的fiber对象,形成effctList
- Commit阶段⼀次性处理所有变化的节点
核心调用方法
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber null, newChild: any,
lanes: Lanes,
): Fiber null
常见问题:
- diff原理和策略?
- fiber diff的策略?
- react diff的优势?
状态管理篇
react 自己不带状态管理工具,都是第三方库自己实现的。
flux
flux是最早的状态管理工具
- UI产⽣动作消息,将动作传递给分发器
- 分发器⼴播给所有store
-
订阅的store做出反应,传递新的state给UI
redux
- 单⼀数据源,整个应⽤state存储在⼀个单⼀store树中
- State状态为只读,不应该直接修改state,⽽是通过action触发state修改
- 没有dispatch,使⽤纯函数进⾏状态修改,需要开发者书写reducers纯函数进⾏处理,reducer通过当前状态树和action进⾏计算,返回⼀个新的state
redux 使用 包裹需要使用的地方
<Provider store = {store} >
<App />
</Provider >
flux 与 redux 对比
mobx
- 定义状态并使其可观察
- 创建视图以响应状态变化
- 更改状态(⾃动响应UI变化)
通过注解的方式编写
MobX 支持单向数据流,也就是动作改变状态,而状态的改变会更新所有受影响的视图。
当状态改变时,所有衍生都会进行原子级的自动更新。因此永远不可能观察到中间值。
所有衍生默认都是同步更新。这意味着例如动作可以在改变状态之后直接可以安全地检查计算值。
计算值是延迟更新的。任何不在使用状态的计算值将不会更新,直到需要它进行副作用(1/0)操作时。如
果视图不再使用,那么它会自动被垃圾回收。
所有的计算值都应该是纯净的。它们不应该用来改变状态。
通过装饰器实现快速响应
1. 为什么要选择hooks
- 组件间复用状态逻辑很难
- 复杂的组件变得难以维护
- class 难以学习和理解
hooks 是一个数组,里面存的是函数,不需要学习class 复杂的状态或者响应式的技术。
React hook:not magic, just arrays
react 实际上也是只提供简单的虚拟dom 和
重要的钩子
useState: 用于定义组件的 State,对标到类组件中this.state的功能
useEffect:通过依赖触发的钩子函数,常用于模拟类组件中的componentDidMount, componentDidUpdate, componentWillUnmount方法其它内置钩子:
useContext:获取context对象
useReducer:类似于Redux 思想的实现,但其并不足以替代Redux,可以理解成一个组件内部的redux,并不是持久化存储,会随着组件被销毁而销毁;属于组件内部,各个组件是相互隔离的,单纯用它并无法共享数据;配合useContext的全局性,可以完成一个轻量级的Redux
useCallback:缓存回调函数,避免传入的回调每次都是新的函数实例而导致依赖组件重新宣染,具有性能优化的效果;
useMemo: 用于缓存传入的props,避免依赖的组件每次都重新宣染;
useRef: 获取组件的真实节点;
useLayoutEffect: DOM更新同步钩子。用法与useEffect类似,只是区别于执行时间点的不同。useEffect属于异步执行,并不会等待DOM真正渲染后执行,而useLayoutEffect则会真正渲染后才触发;可以获取更新后的state;
自定义钩子(useXxxxx):基于Hooks可以引用其它Hooks这个特性,我们可以编写自定义钩子。
useState
function RenderFunctionComponent() {
const [firstName, setFirstName] = useState("Rudi");
const [lastName, setLastName] = useState("Yardley");
return (
<Button onClick=f() => setFirstName("Fred"))> Fred</Button >
);
}
源码实现
创建两个空的array,state and setters
第⼀次渲染,循环所有useState,把state和setter⽅法分别压⼊两个数组
更新state,触发render,cursor被重置,根据useState的声明顺序依次拿出state值,更新UI
第⼀次执⾏,state有三个元素
更新state触发render
此时tag=false,不会执⾏if流程,也就是没有useState
最后导致setNum2不起作⽤
结论:不要在循环、判断逻辑中使⽤useState,最好是在函数顶部使⽤
useEffect
副作⽤函数?
- useEffect函数第⼀个参数为callback函数,就是要执⾏的函数
- 第⼆个参数为⼀个数组,数组中是要监听的变量
- []空数组之所以会起到componentDidMount的作⽤,是因为每次更新,[]空数组没有任何监听的变量,也就是不存在变化⼀说,所以只会被执⾏⼀次
- useEffect函数返回的第⼀个函数作为销毁state使⽤
useLayoutEffect
useLayoutEffect主要在useEffect函数结果不满意时才被⽤到,⼀般的经验是当处理dom改变带的副作⽤才会被⽤到,该Hook执⾏时,浏览器并未对dom进⾏渲染,较useEffect执⾏要早
useEffect 和 useLayoutEffect差异
- useEffect和useLayoutEffect在Mount和Update阶段执⾏⽅法⼀样,传参不⼀样
- useEffect异步执⾏,⽽useLayoutEffect同步执⾏
- Effect⽤HookPassive标记useEffect,⽤HookLayout标记useLayoutEffect
useMemo
传递⼀个函数和依赖项,当依赖项发⽣变化,执⾏函数,该函数需要返回⼀个值
useCallback
返回⼀个函数,当监听的数据发⽣变化才会执⾏回调函数
useRef
useReducer
- 聚合参数,以达到开发效率的提升以及代码的简洁
- useReducer在re-render时候不会改变存储位置,state作为props传给⼦component不会产⽣diff的效率问题(useMemo优化)
通过useReducer优化
useXXX ⾃定义
与React组件不同的是,自定义Hook不需要具有特殊的标识。我们可以自由的决定它的参数是什么,以及它应该返回什么(如果需要的话)。换句话说,它就像一个正常的函数。但是它的名字应该始终以use开头,这样可以一眼看出其符合Hook的规则。
class 和 hooks 对比
Class
- 代码逻辑清晰(构造函数、componentDidMount等)
- 不容易内存泄漏
Hooks
- 需要配合变量名和注释
- 容易发生内存泄漏