一,常用的hook有哪些:
useEffect, useState, useCallback, useMemo, useRef, useContext, useReducer
二,useCallback和useMemo的区别:
useCallback和useMemo都可以做性能优化,做缓存,区别是:
- useCallback允许在多次渲染中缓存函数,而useMemo在每次重新渲染时缓存的则是计算的结果
- useCallback常和memo一起使用,用来减少不必要的渲染
三,useRef如何用,或在什么条件下会使用
useRef能帮助引用一个不需要渲染的值: const ref = useRef(initialValue)
initialValue:ref对象的current属性的初始值。可以是任意类型的值,这个参数在首次渲染后被忽略。
- 使用用ref引用一个值
- 通过ref操作DOM
- 避免重复创建ref的内容
在组件顶层调用useRef以声明一个ref。
useRef返回一个只有一个属性的对象:
- current: 初始值为传递的initialValue。之后可以将其设置为其他值。如果将ref对象作为一个JSX节点的ref属性传递给React, React将为它设置current属性。
- 在后续的渲染中,useRef将返回同一个对象。
Refs是一种脱围机制,包括管理焦点、滚动位置或调用React未暴露的浏览器API.
四、React 18 setState自动批处理
批处理是指为了获得更好的性能,在数据层,将多个状态更新批量处理,合并成一次更新(在视图层,将多个渲染合并成一次渲染)
-
React 18之前,我们只在React事件处理函数中进行批处理更新。默认情况下,在promise, setTimeout, 原生事件处理函数中, 或任何其它事件内的更新都不会进行批处理:
-
在React 18中,上面的例子只会有一次render,因为所有的更新都将自动批处理。这样无疑是很好的提高了应用的整体性能。
批处理是一个破坏性改动,如果你想退出批量更新,可以使用flushSync。
注意:flushSync函数内部的多个setState仍然为批量更新,这样可以精准控制那些不需要的批量更新。
五、如何避免函数重复执行
- useCallback将函数进行缓存 ??
防止接口重复调用
- 防抖
- 当用户连续多次点击同一按钮,最后一次点击过后一段时间再去请求接口
- 每次调用方法后产生一个定时器,定时器结束后再发请求。如果重复调用方法,就取消当前定时器,创建一个新的定时器,等结束后再发请求
- 可以用封装好的第三方工具函数,如lodash的debounce方法来简化代码
六、es6中set和map:
- set:
- 类似于数组,但成员的值都是唯一的,没有重复的值,我们一般称为集合。
- Set本身是一个构造函数,用来生成Set数据结构: const s = new Set();
- Set的实例关于增删改查的方法: add(), delete(), has(), clear()
- Set实例遍历的方法:
- keys(): 返回键名的遍历器
- values(): 返回键值的遍历器
- entries(): 返回键值对的遍历器
- forEach(): 使用回调函数遍历每个成员
- map:
- Map类型是键值对的有序列表,而键和值都可以是任意类型
- Map本身是一个构造函数,用来生成Map数据结构:const m = new Map();
- Map结构的实例针对增删改查的方法: size属性, set(), get(), has(), delete(), clear()
- Map结构原生提供三个遍历器生成函数和一个遍历方法:
- keys(): 返回键名的遍历器
- values(): 返回键值的遍历器
- entries(): 返回所有成员的遍历器
- forEach(): 遍历Map的所有成员
七、promise及promise.all自己实现
Promise:异步编程的一种解决方案
优点:链式操作降低了编码难度 & 代码可读性明显增强
-
promise对象仅有三种状态:
- pending (进行中)
- fulfilled (已成功)
- rejected (已失败)
-
特点:
- 对象的状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态
- 一旦状态改变(从pending变为fulfilled和从pending变为rejected),就不会再变,任何时候都可以得到这个结果
-
用法:
- Promise对象是一个构造函数,用来生成Promise实例
- const promise = new Promise(function(resolve, reject) {});
- Promise构造函数接受一个函数作为参数,改函数的两个参数分别是resove和reject
- Promise构造出来的实例存在以下方法:
- then()
- catch()
- finally()
-
then是实例状态发生改变时的回调函数,第一个参数是resolved状态的回调函数,第二个参数书rejected状态的回调函数
-
then方法返回的是一个新的Promise实例,也就是promise能链式书写的原因
getJSON('/posts.json').then(function(json){
return json.post
}).then(function(post){
// ...
});
-
Promise构造函数存在以下方法:
- all()
- race()
- allSettled()
- resolve()
- reject()
- try()
-
Promise.all()方法用于将多个Promise实例,包装成一个新的Promise实例
- const p = Promise.all([p1, p2, p3]);
- 接受一个数组(迭代对象)作为参数,数组成员都应为Promise实例
const all = (arr) => { const list = [] let total = 0; rettun new Promise((resolve, rejuct) => { for (let i = 0; i < arr.length; i++) { arr[i] .then(res => { list[i] = res total += 1 if (total === arr.length) { resolve(list) } }) .catch(err => { rejuct(err) }) } }) }
八、函数柯里化
将一个接受多个参数的函数转化为一系列链式函数调用,每个函数接受原函数的一个参数,并返回一个新的参数,直到所有参数都被接收为止
- 在js中,函数可以像变量一样被赋值给变量,作为参数传递给其他函数,或者从函数中返回,这是实现柯里化的基础
- 闭包允许一个函数访问并操作其外部作用域中的变量。在柯里化中,每次返回的新函数都会形成一个闭包,保持对外部函数中变量的引用,以便在后续调用中继续使用
- 在箭头函数中使用...args可以捕获所有传入的参数作为一个数组,这使得函数可以接受一个任意数量的参数,非常适用于实现柯里化求和这样的功能
在实际应用中,它可以用于创建更加灵活和重用的函数。
function add(x) {
return function(y) {
console.log(x+y)
}
}
const sum = add(4)
console.log(sum(8)) // 12
九、为什么hook不能写在嵌套或循环语句中?
-
React依赖于Hooks的调用顺序来管理状态和生命周期。如果违反这条规则,会导致Hooks的调用顺序不一致,从而引发难以调试的Bug.
-
React内部使用一个“调用顺序链表”来跟踪和管理hooks。每次组件渲染时,React都会按照Hooks的调用顺序来更新链表中的值。
-
在第一次渲染时,React会记录Hooks的调用顺序;
-
在后续渲染中,React会严格按照这个顺序来匹配Hooks的状态和副作用。
-
如果在循环、条件或嵌套中调用Hooks,会导致Hooks的调用顺序不一致,从而破坏React对Hooks的管理。
-
因为hook的执行机制是从上到下顺序执行,放到一个任务队列中去,如果写在嵌套或循环中,那么会造成这个任务内部的混乱
-
加入react中加入key值,那么就可以写进嵌套或循环中
将hooks放在嵌套或循环语句中会导致每次条件改变或循环迭代的时候,hooks都可能被重新创建,这会导致状态不稳定、内存泄露等问题。
-
-
如何避免这些问题?
- 始终在函数组件的顶层调用Hooks:
- 不要在循环、条件或嵌套函数中调用Hooks。
- 确保每次渲染时Hooks的调用顺序完全相同。
- 将条件逻辑转移到Hooks内部:
- 如果需要在某些条件下使用Hooks,可以将条件逻辑转移到Hooks内部。
- 总结:
- React 依赖于 Hooks 的调用顺序来管理状态和副作用。
- 在循环、条件或嵌套函数中调用 Hooks 会导致调用顺序不一致,从而引发 bug。
- 始终在函数组件的顶层调用 Hooks,并将条件逻辑移到 Hooks 内部。
- 使用 ESLint 插件可以帮助检测和避免违规行为。
十、Fiber架构
-
为每个任务增加了优先级,优先级高的任务可以中断低优先级的任务。然后再重新,注意是重新执行优先级低的任务;
-
增加了异步任务,调用requestIdleCallback api,浏览器空闲的时候执行
-
dom diff树变成了链表,一个dom对应两个fiber(一个链表),对应两个对列,这都是为找到被中断的任务,重新执行
-
从架构角度看,Fiber是对React核心算法(即调和过程)的重写
-
从编码角度看,Fiber是React内部所定义的一种数据结构,它是Fiber树结构的节点单位,也就是React 16新架构下的虚拟DOM
-
一个fiber就是一个js对象,包含了元素的信息,该元素的更新操作对列、类型
-
React Fiber是React 16提出的一种更新机制,使用链表取代了树,将虚拟dom连接,使得组件更新的流程可以被中断恢复;它把组件渲染的工作分片,到时会主动让出渲染主线程