大厂面经---详解react hooks面试题【React系列02】

前端人

共 9788字,需浏览 20分钟

 · 2021-03-31


关注公众号 前端人,回复“加群

添加无广告优质学习群

1. 简单介绍下什么是hooks,hooks产生的背景?hooks的优点?

hooks是针对在使用react时存在以下问题而产生的:

  • 组件之间复用状态逻辑很难,在hooks之前,实现组件复用,一般采用高阶组件和 Render Props,它们本质是将复用逻辑提升到父组件中,很容易产生很多包装组件,带来嵌套地域。
  • 组件逻辑变得越来越复杂,尤其是生命周期函数中常常包含一些不相关的逻辑,完全不相关的代码却在同一个方法中组合在一起。如此很容易产生 bug,并且导致逻辑不一致。
  • 复杂的class组件,使用class组件,需要理解 JavaScript 中 this 的工作方式,不能忘记绑定事件处理器等操作,代码复杂且冗余。除此之外,class组件也会让一些react优化措施失效。

针对上面提到的问题,react团队研发了hooks,它主要有两方面作用:

  • 用于在函数组件中引入状态管理和生命周期方法
  • 取代高阶组件和render props来实现抽象和可重用性

优点也很明显:

  • 避免在被广泛使用的函数组件在后期迭代过程中,需要承担一些副作用,而必须重构成类组件,它帮助函数组件引入状态管理和生命周期方法。

  • Hooks 出现之后,我们将复用逻辑提取到组件顶层,而不是强行提升到父组件中。这样就能够避免 HOC 和 Render Props 带来的「嵌套地域」

  • 避免上面陈述的class组件带来的那些问题

2. 知道hoc和render props吗,它们有什么作用?有什么弊端?

Render Props 组件和高阶组件主要用来实现抽象和可重用性。

弊端就是高阶组件和 Render Props 本质上都是将复用逻辑提升到父组件中,很容易产生很多包装组件,带来的「嵌套地域」。

由于所有抽象逻辑都被其他 React 组件所隐藏,应用变成了一棵没有可读性的组件树。而那些可见的组件也很难在浏览器的 DOM 中进行跟踪。

2.1 Render Props

什么是Render Props

render props模式是一种非常灵活复用性非常高的模式,它可以把特定行为或功能封装成一个组件,提供给其他组件使用让其他组件拥有这样的能力。他把组件可以动态渲染的地方暴露给外部,你不用再关注组件的内部实现,只要把数据通过函数传出去就好。

使用场景:

  • 通用业务逻辑的抽取
  • 当两个平级组件之间需要单向依赖的时候,比如两个同级组件A、B,A组件需要跟随B组件的内部状态来改变自己的内部状态,我们就说A依赖B;或者B依赖A

2.2 Hoc

hoc是 React 中用于重用组件逻辑的高级技术,它是一个函数,能够接受一个组件并返回一个新的组件。
实现高阶组件的两种方式:

  • 属性代理。高阶组件通过包裹的React组件来操作props
  • 反向继承。高阶组件继承于被包裹的React组件

2.2.1属性代理

a. 什么是属性代理

属性代理组件继承自React.Component,通过传递给被包装的组件props得名

// 属性代理,把高阶组件接收到的属性传递给传进来的组件
function HOC(WrappedComponent) {
  return class PP extends React.Component {
    render() {
      return <WrappedComponent {...this.props}/>
    }
  }

b. 属性代理的用途

  • 更改 props,可以对传递的包裹组件的WrappedComponent的props进行控制
  • 通过 refs 获取组件实例
/*
可以通过 ref 获取关键词 this(WrappedComponent 的实例)
当 WrappedComponent 被渲染后,ref 上的回调函数 proc 将会执行,此时就有了这个 WrappedComponent 的实例的引用
*/
function refsHOC(WrappedComponent) {
  return class RefsHOC extends React.Component {
    proc(wrappedComponentInstance) {
      wrappedComponentInstance.method()
    }
    render() {
      const props = Object.assign({}, this.props, {ref: this.proc.bind(this)})
      return <WrappedComponent {...props}/>
    }
  }

  • 把 WrappedComponent 与其它 elements 包装在一起

2.1.2 反向继承

反向继承是继承自传递过来的组件

function iiHOC(WrappedComponent) {
  return class Enhancer extends WrappedComponent {
    render() {
      return super.render()
    }
  }

反向继承允许高阶组件通过 this 关键词获取 WrappedComponent,意味着它可以获取到 state,props,组件生命周期(component lifecycle)钩子,以及渲染方法(render),所以我们主要用它来做渲染劫持,比如在渲染方法中读取或更改 React Elements tree,或者有条件的渲染等。

2.1.3 高阶组件相关的面试题

  1. 这怎么在高阶组件里面访问组件实例**
    答案见上面

  2. 你的项目中怎么使用的高阶组件**
    a. 项目中经常存在在配置系统中配置开关/全局常量,然后在页面需要请求配置来做控制,如果在每个需要调用全局设置的地方都去请求一下接口,就会有一种不优雅的感觉,这个时候我就想到利用高阶组件抽象一下。
    b. 项目开发过程中,经常也会遇到需要对当前页面的一些事件的默认执行做阻止,我们也可以写一个高阶组件等。

  3. hooks和hoc和render props有什么不同?

它们之间最大的不同在于,后两者仅仅是一种开发模式,而自定义的hooks是react提供的API模式,它既能更加自然的融入到react的渲染过程也更加符合react的函数编程理念。

  1. 介绍下常用的hooks?
  • useState(),状态钩子。为函数组建提供内部状态
// 我们实现一个简易版的useState
let memoizedStates = [ ]  // 多个useState 时需要使用数组来存
let index = 0
function useState (initialState) {
   memoizedStates[index] = memoizedStates[index] || initialState
   let currentIndex = index;
   function setState (newState) {
      memoizedStates[currentIndex] = newState
      render()
   }
   return [memoizedStates[index++], setState]

  • 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用在计算值的缓存,注意不用滥用。经常用在下面两种场景(要保持引用相等;对于组件内部用到的 object、array、函数等,如果用在了其他 Hook 的依赖数组中,或者作为 props 传递给了下游组件,应该使用 useMemo/useCallback)

  • useLayoutEffect:会在所有的 DOM 变更之后同步调用 effect,可以使用它来读取 DOM 布局并同步触发重渲染

  • 描述下hooks下怎么模拟生命周期函数,模拟的生命周期和class中的生命周期有什么区别吗?

// componentDidMount,必须加[],不然会默认每次渲染都执行
useEffect(()=>{
}, [])

// componentDidUpdate
useEffect(()=>{
document.title = `You clicked ${count} times`;
return()=>{
// 以及 componentWillUnmount 执行的内容 
}
}, [count])

//  shouldComponentUpdate, 只有 Parent 组件中的 count state 更新了,Child 才会重新渲染,否则不会。
function Parent({
   const [count,setCount] = useState(0);
   const child = useMemo(()=> <Child count={count} />, [count]);
   return <>{count}</>
}

function Child(props{
    return <div>Count:{props.count}</div>

这里有一个点需要注意,就是默认的useEffect(不带[])中return的清理函数,它和componentWillUnmount有本质区别的,默认情况下return,在每次useEffect执行前都会执行,并不是只有组件卸载的时候执行。
useEffect在副作用结束之后,会延迟一段时间执行,并非同步执行,和compontDidMount有本质区别。遇到dom操作,最好使用useLayoutEffect。

  1. hooks中的坑,以及为什么?
  • 不要在循环,条件或嵌套函数中调用Hook,必须始终在React函数的顶层使用Hook。这是因为React需要利用调用顺序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用Hook,就容易导致调用顺序的不一致性,从而产生难以预料到的后果。

  • 使用useState时候,使用push,pop,splice等直接更改数组对象的坑,demo中使用push直接更改数组无法获取到新值,应该采用析构方式,但是在class里面不会有这个问题。(这个的原因是push,pop,splice是直接修改原数组,react会认为state并没有发生变化,无法更新)  这里的坑很多的,经常出现的就是每次修改数组的时候:

const [firstData, setFirstData]: any = useState([]);
const handleFirstAdd = () => {
  // let temp = firstData
  // 不要这么写,直接修改原数组相当于没有更新
  let temp = [...firstData];
  // 必须这么写,多层数组也要这么写
  temp.push({
    value"",
  });
  setFirstData(temp);
};

function Indicatorfilter({
  let [num, setNums] = useState([0123]);
  const test = () => {
    // 这里坑是直接采用push去更新num,setNums(num)是无法更新num的,必须使用num = [...num ,1]
    num.push(1);
    // num = [...num ,1]
    setNums(num);
  };
  return (
    <div className="filter">
      <div onClick={test}>测试</div>
      <div>
        {num.map((item, index) => (
          <div key={index}>{item}</div>
        ))}
      </div>
    </div>

  );
}

class Indicatorfilter extends React.Component<anyany{
  constructor(props: any) {
    super(props);
    this.state = {
      nums: [123],
    };
    this.test = this.test.bind(this);
  }
  test() {
    // class采用同样的方式是没有问题的
    this.state.nums.push(1);
    this.setState({
      numsthis.state.nums,
    });
  }
  render() {
    let { nums } = this.state;
    return (
      <div>
        <div onClick={this.test}>测试</div>
        <div>
          {nums.map((item: any, index: number) => (
            <div key={index}>{item}</div>
          ))}
        </div>
      </div>

    );
  }

useState设置状态的时候,只有第一次生效,后期需要更新状态,必须通过useEffect

TableDeail是一个公共组件,在调用它的父组件里面,我们通过set改变columns的值,以为传递给TableDeail的columns是最新的值,所以tabColumn每次也是最新的值,但是实际tabColumn是最开始的值,不会随着columns的更新而更新

const TableDeail = ({
    columns,
}:TableData) => {
    const [tabColumn, setTabColumn] = useState(columns) 
}

正确的做法是通过useEffect改变这个值

const TableDeail = ({
    columns,
}:TableData) => {
    const [tabColumn, setTabColumn] = useState(columns) 
    useEffect(() =>{setTabColumn(columns)},[columns])

原文地址:https://blog.csdn.net/kellywong/article/details/106430977

最后

关注公众号,置顶公众号,鬼哥,一起前端进阶

  1. 公众号里回复关键词资料包领取我整理的进阶资料包
  2. 公众号里回复关键词加群,加入前端进阶群
  3. 文章点个在看,支持一下把!

点击关注我们↓


题库小程序


浏览 39
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报