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,就容易导致调用顺序的不一致性,从而产生难以预料到的后果

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

Logo

基于 Vue 的企业级 UI 组件库和中后台系统解决方案,为数万开发者服务。

更多推荐