React Hooks面试题

React Hooks面试题整理

React 加入 Hooks 的意义是什么?为什么 React 要加入Hooks 这一特性?

为了解决一些component问题:

  1. 组件之间的逻辑状态难以复用
  2. 大型复杂的组件很难拆分
  3. 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面试题

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
上一篇:React 属性 函数式组件 & Hooks(学习第三天)


下一篇:react hooks--useReducer用法