React Hooks面试题整理
- React 加入 Hooks 的意义是什么?为什么 React 要加入Hooks 这一特性?
- 常用的Hooks有哪些?
- React Hooks api的原理:
- React Hooks如何模拟组件生命周期?
- Hooks相比HOC和Render Prop有哪些优点?
- Function Component与Class Component区别
- useEffect和useLayoutEffect区别?
- useState和setState区别?
- useReducer和redux区别?
- 如何自定义HOOK
- Hooks性能优化
React 加入 Hooks 的意义是什么?为什么 React 要加入Hooks 这一特性?
为了解决一些component问题:
- 组件之间的逻辑状态难以复用
- 大型复杂的组件很难拆分
- Class语法的使用不友好
比如说:
- 类组件可以访问生命周期,函数组件不能
- 类组件可以定义维护state(状态),函数组件不可以
- 类组件中可以获取到实例化后的this,并基于这个this做一些操作,而函数组件不可以
- mixins:变量作用于来源不清、属性重名、Mixins引入过多会导致顺序冲突
- HOC和Render props:组件嵌套过多,不易渲染调试、会劫持props,会有漏洞
有了Hooks:
- Hooks 就是让你不必写class组件就可以用state和其他的React特性;
- 也可以编写自己的hooks在不同的组件之间复用
Hooks优点:
- 没有破坏性改动:完全可选的。 你无需重写任何已有代码就可以在一些组件中尝试 Hook。100% 向后兼容的。 Hook 不包含任何破坏性改动。
- 更容易复用代码:它通过自定义hooks来复用状态,从而解决了类组件逻辑难以复用的问题
- 函数式编程风格:函数式组件、状态保存在运行环境、每个功能都包裹在函数中,整体风格更清爽、优雅
- 代码量少,复用性高
- 更容易拆分
Hooks缺点(Hoosk有哪些坑):
- hooks 是 React 16.8 的新增特性、以前版本的就别想了
- 状态不同步(闭包带来的坑):函数的运行是独立的,每个函数都有一份独立的闭包作用域。当我们处理复杂逻辑的时候,经常会碰到“引用不是最新”的问题
- 使用useState时候,使用push,pop,splice等直接更改数组对象的坑,demo中使用push直接更改数组无法获取到新值,应该采用析构方式
原因:push,pop,splice是直接修改原数组,react会认为state并没有发生变化,无法更新)
- useState 初始化只初始化一次
- useEffect 内部不能修改 state
- useEffect 依赖引用类型会出现死循环
- 不要在循环,条件或嵌套函数中调用Hook,必须始终在React函数的顶层使用Hook。这是因为React需要利用调用顺序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用Hook,就容易导致调用顺序的不一致性,从而产生难以预料到的后果
常用的Hooks有哪些?
-
useState()
状态钩子。为函数组建提供内部状态 -
useContext()
共享钩子。该钩子的作用是,在组件之间共享状态。 可以解决react逐层通过Porps传递数据,它接受React.createContext()的返回结果作为参数,使用useContext将不再需要Provider 和 Consumer -
useReducer()
状态钩子。Action 钩子。useReducer() 提供了状态管理,其基本原理是通过用户在页面中发起action, 从而通过reducer方法来改变state, 从而实现页面和状态的通信。使用很像redux -
useEffect()
副作用钩子。它接收两个参数, 第一个是进行的异步操作, 第二个是数组,用来给出Effect的依赖项 -
useRef()
获取组件的实例;渲染周期之间共享数据的存储(state不能存储跨渲染周期的数据,因为state的保存会触发组件重渲染),useRef传入一个参数initValue,并创建一个对象{ current: initValue }给函数组件使用,在整个生命周期中该对象保持不变
-
useMemo和useCallback
可缓存函数的引用或值,useMemo缓存数据,useCallback缓存函数,两者是Hooks的常见优化策略,useCallback(fn,deps)相当于useMemo(()=>fn,deps)
,经常用在下面两种场景:
1、要保持引用相等;对于组件内部用到的 object、array、函数等,
2、用在了其他 Hook 的依赖数组中,或者作为 props 传递给了下游组件,应该使用 useMemo/useCallback)
React Hooks api的原理:
用链表数据结构来做全局状态保持;判断依赖项决定是否要更新状态等等
useState 和 useReducer 都是关于状态值的提取和更新, useState 是 useReducer 的一个简化版,
1、两者的状态值都被挂载在组件实例对象 FiberNode 的 memoizedState 属性中
2、两者保存状态值的数据结构完全不同;类组件是直接把 state 属性中挂载的这个开发者自定义的对象给保存到 memoizedState 属性中;而 React Hooks 是用链表来保存状态的, memoizedState 属性保存的实际上是这个链表的头指针。
//链表的节点--Hook对象 react-reconciler/src/ReactFiberHooks.js
export type Hook={
memoizedState:any,//最新的状态值
baseState:any,//初始状态值,如`useState(0)`,则初始值为0
baseUpdate:Update<any,any>|null,
queue:UpdateQueue<any,any> | null,//临时保存对状态值的操作,更准确来说是一个链表数据结构中的一个指针
next:Hook | null,//指向下一个链表节点
}
React Hooks如何模拟组件生命周期?
Hooks模拟constructor
constructor(){
super()
this.state={count:0}
}
//Hooks模拟constructor
const [count setCount]=useState(0)
Hooks模拟componentDidMount
componentDidMount(){
console.log('I am mounted')
}
//Hooks模拟componentDidMount
useEffect(()=>console.log('mounted'),[])
//useEffect拥有两个参数,第一个参数作为回调函数会在浏览器布局和绘制完成后调用,因此它不会阻碍浏览器的渲染进程,第二个参数是一个数组,也是依赖项
//1、当依赖列表存在并有值,如果列表中的任何值发生更改,则每次渲染后都会触发回调
//2、当它不存在时,每次渲染后都会触发回调
//3、当它是一个空列表时,回调只会被触发一次,类似于componentDidMount
模拟shouldComponentUpdate
shouldComponentUpdate(nextProps,nextState){
console.log('shouldComponentUpdate')
return true //更新组件 反之不更新
}
// React.memo包裹一个组件来对它的props进行浅比较,但这不是一个hooks,因为它的写法和hooks不同,其实React.memo等效于PureComponent,但它只比较props
// 模拟shouldComponentUpdate
const MyComponent=React.memo(
_MyComponent,
(prevProps,nextProps)=>nextProps.count!==preProps.count
)
Hooks模拟componentDidUpdate
componentDidMount() {console.log('mounted or updated');}
componentDidUpate(){console.log('mounted or updated')}
//Hooks模拟componentDidUpdate
useEffect(()=>console.log('mounted or updated'))
//这里的回调函数会在每次渲染后调用,因此不仅可以访问componentDidUpdate,还可以访问componentDidMount,如果只想模拟componentDidUpdate,我们可以这样来实现
const mounted=useRef()
useEffect(()=>{
if(!mounted.current){mounted.current=true}else{console.log('I am didUpdate')}
})
//useRef在组件中创建“实例变量”,它作为一个标志来指示组件是否处于挂载或更新阶段。当组件更新完成后在会执行else里面的内容,以此来单独模拟componentDidUpdate
Hooks模拟componentWillUnmount
componentWillUnmount(){
console.log('will unmount')
}
//hooks
useEffect(()=>{
//此处并不同于willUnMount porps发生变化即更新,也会执行结束监听
//准确的说:返回的函数会在下一次effect执行之前,被执行
return ()=>{console.log('will unmount')}
},[])
//当在useEffect的回调函数中返回一个函数时,这个函数会在组件卸载前被调用。我们可以在这里清除定时器或事件监听器。
模拟的生命周期和class中的生命周期有什么区别吗?
1、默认的useEffect(不带[])中return的清理函数,它和componentWillUnmount有本质区别的,默认情况下return,在每次useEffect执行前都会执行,并不是只有组件卸载的时候执行。
2、useEffect在副作用结束之后,会延迟一段时间执行,并非同步执行
,和compontDidMount有本质区别。遇到dom操作,最好使用useLayoutEffect。
hooks 模拟的生命周期与class中的生命周期不尽相同,我们在使用时,还是需要思考业务场景下那种方式最适合。
Hooks相比HOC和Render Prop有哪些优点?
hoc和render prop都是一种开发模式,将复用逻辑提升到父组件,容易嵌套过多,过度包装
hooks是react的api模式,将复用逻辑取到组件顶层,而不是强行提升到父组件中。这样就能够避免 HOC 和 Render Props 带来的「嵌套地域」
Function Component与Class Component区别
1、原理:function组件能捕获渲染的值(captaure the rendered values),读取渲染闭包内的数据,而class组件在react通过this.的方式读取,this是可变的,所以总能获取最新的props
2、保存状态:Class把state属性挂载的对象保存到memoizedState属性中,而Function是用链表来保存状态的,memoizedState属性保存是链表的头指针
useEffect和useLayoutEffect区别?
1、useEffect是render结束后,callback函数执行,但是不会阻断浏览器的渲染,算是某种异步的方式吧。但是class的componentDidMount 和componentDidUpdate是同步的,在render结束后就运行,useEffect在大部分场景下都比class的方式性能更好.
2、useLayoutEffect里面的callback函数会在DOM更新完成后立即执行,但是会在浏览器进行任何绘制之前运行完成,阻塞了浏览器的绘制.
useState和setState区别?
用useState实现state和setState功能?
useReducer和redux区别?
- useReducer() 提供了状态管理,其基本原理是通过用户在页面中发起action, 从而通过reducer方法来改变state, 从而实现页面和状态的通信,使用很像redux
- useReducer是useState的代替方案,用于state复杂变化
- useReducer是单个组件状态管理,组价通讯还需要props
- redux是全局的状态管理,多组件共享数据
如何自定义HOOK
Hooks性能优化
- useMemo 缓存数据
- useCallback 缓存函数
- 相当于class组件的SCU和PureComponent