React Hooks面试题
React Hooks面试题整理React 加入 Hooks 的意义是什么?为什么 React 要加入Hooks 这一特性?常用的Hooks有哪些?React Hooksapi的原理:React Hooks如何模拟组件生命周期?模拟的生命周期和class中的生命周期有什么区别吗?Hooks相比HOC和Render Prop有哪些优点?Function Component与Class Compone
React 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,就容易导致调用顺序的不一致性,从而产生难以预料到的后果
if else条件判断里使用hooks有什么问题
- if else里面不能用hooks,hooks是有顺序的
- 不能用在if else 或者循环里面 还有非顶层的函数内部
- hooks在初始化时候是以链表形式存储的,后续更新都是按照这个链表顺序执行的
hooks闭包的坑有哪些?如何解决
问题
:每次 render 都有一份新的状态,数据卡在闭包里,捕获了每次 render 后的 state,也就导致了输出原来的 state
解决
:可以通过 useRef 来保存 state。前文讲过 ref 在组件中只存在一份,无论何时使用它的引用都不会产生变化,因此可以来解决闭包引发的问题。
常用的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)useEffect为什么有时候会出现无限重复请求的问题
- 可能1 在effect里做数据请求未设置依赖参数,没有依赖项effect 会在每次渲染后执行一次,然后在 effect 中更新了状态引起渲染并再次触发 effect
- 可能2 所设置的依赖项总是会变
- 解决:useCallback包一层,或者useMemo
useEffect的依赖项里类数组根据什么来判断有没有值变化
浅比较
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区别
对于class com来说:
- 首先state是不可变(Immutable),setState后生成一个全新的state引用
- 但class com通过this.state方式读取state,所以每次代码执行都会拿到最新的state引用
对于function com来说:
- useState 产生的数据也是不可变 Immutable 的,通过数组第二个参数 Set 一个新值后,原来的值在下次渲染时会形成一个新的引用。
- 但它state没有通过this.的方式读取,每次执行都读取当时渲染闭包环境的数据,虽然最新的值跟着最新的渲染变了,但旧的渲染里,状态依然是旧值
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是用在处理DOM的时候,当你的useEffect里面的操作需要处理DOM,并且会改变页面的样式,就需要用这个,否则可能会出现出现闪屏问题, useLayoutEffect里面的callback函数会在DOM更新完成后立即执行
,但是会在浏览器进行任何绘制之前运行完成,阻塞了浏览器的绘制
useState和setState区别?
//setState
this.setState(
{count:this.state.count+1},
()=>{
console.log(this.state.count) //通过回调函数监听到最新的值
})
//useState
const[count,setCount]=useState(0)
setCount(1)
useEffect(()=>{
console.log(count) //通过useEffect监听最新的值
},
[count])
1、setState是通过回调函数来获取更新的state,useState是通过useEffect() 来获取最新的 state
2、二者第一个参数都可以传入函数
3、setState()可以在第2个参数传入回调,useState()没有第2个参数
4、setState()自动具备浅合并功能,useState()更新引用需要手动浅合并
两者参数对比
setState(updater,[,callback])
updater:object/function - 用于更新数据
callback:function - 用于获取更新后最新的state值
useState(initState)
const [state ,setState] = useState(initState)
state:状态
setState(updater): 修改状态的方法
updater:object/function - 用于更新数据
initState : 状态的初始值
useState中的第二个参数更新状态和class中的this.setState区别?
-
useState 通过数组第二个参数 Set 一个新值后,新值会形成一个新的引用,捕获当时渲染闭包里的数据 State
-
setState 是通过 this.state 的读取 state,每次代码执行都会拿到最新的 state 引用
用useState实现state和setState功能?
//方法一 模拟setState传入updater和callback
const [n1,setN1]=useState<any>(0)
const [n1,setN2]=useState<any>(0)
setN1((num)=>{
setN2(num+1)
//返回n1修改后的值
return num+1
})
//方法二 自定义hooks,配合引入useRef
export const useXState =(initState)=>{
const [state,setState]=useState(initState)
let isUpdate=useRef()
const setXState=(state,cb)=>{
setState(prev=>{
isUpdate.current=cb
return typeof state==='function'?state(prev):state
})
}
}
useEffect(()=>{
if(isUpdate.current){
isUpdate.current()
}
//useEffect不可以每次渲染组件都执行,因此在每次渲染之后都需要判断其是否值得执行
})
return [state,setXState]
//useRef的特性来作为标识区分是挂载还是更新,当执行setXstate时,会传入和setState一模一样的参数,并且将回调赋值给useRef的current属性,这样在更新完成时,我们手动调用current即可实现更新后的回调这一功能
useReducer和redux区别?
- useReducer() 提供了状态管理,其基本原理是通过用户在页面中发起action, 从而通过 reducer方法来改变state, 从而实现页面和状态的通信,使用很像redux
- useReducer是useState的代替方案,用于state复杂变化
- useReducer是单个组件状态管理,组价通讯还需要props
- redux是全局的状态管理,多组件共享数据
如何自定义HOOK
import { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
Hooks性能优化
- useMemo 缓存数据
- useCallback 缓存函数
- 相当于class组件的SCU和PureComponent
怎么在高阶组件里面访问组件实例?
1、属性代理。高阶组件通过包裹的React组件来操作props,更改 props,可以对传递的包裹组件的WrappedComponent的props进行控制
2、通过 refs 获取组件实例
https://blog.csdn.net/kellywong/article/details/106430977
更多推荐
所有评论(0)