3 Star 16 Fork 3

鱼樱前端 / 前端面试题

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
react.md 40.02 KB
一键复制 编辑 原始数据 按行查看 历史
鱼樱前端 提交于 2021-09-29 22:32 . 更新面试题

react面试题集锦

react基础

1、什么是React

  • React 是 Facebook 在 2011 年开发的前端 JavaScript 库。
  • 它遵循基于组件的方法,有助于构建可重用的UI组件。
  • 它用于开发复杂和交互式的 Web 和移动 UI。
  • 尽管它仅在 2015 年开源,但有一个很大的支持社区

2、React有什么特点?

​ React的主要功能如下:

  • 它使用虚拟DOM 而不是真正的DOM。
  • 它可以用服务器端渲染
  • 它遵循单向数据流或数据绑定。

3、列出React的一些主要优点

​ React的一些主要优点是:

  • 它提高了应用的性能
  • 可以方便地在客户端和服务器端使用
  • 由于 JSX,代码的可读性很好
  • React 很容易与 Meteor,Angular 等其他框架集成
  • 使用React,编写UI测试用例变得非常容易

4、 React有哪些限制?

​ React的限制如下:

  • React 只是一个库,而不是一个完整的框架
  • 它的库非常庞大,需要时间来理解
  • 新手程序员可能很难理解
  • 编码变得复杂,因为它使用内联模板和 JSX【JSX 是 JavaScript XML 的简写】

5、类组件和函数组件之间有什么区别

类组件( Class components ) 无论是使用函数或是类来声明一个组件,它决不能修改它自己的 props 。 所有 React 组件都必须是纯函数,并禁止修改其自身 props 。

React是单项数据流,父组件改变了属性,那么子组件视图会更新。 属性 props 是外界传递过来的,状态 state 是组件本身的,状态可以在组件中任意修改 组件的属性和状态改变都会更新视图

函数组件(functional component) 函数组件接收一个单一的 props 对象并返回了一个React元素

函数组件和类组件当然是有区别的,而且函数组件的性能比类组件的性能要高,因为类组件使用的时候要实例化,而函数组件直接执行函数取返回结果即可。为了提高性能,尽量使用函数组件。

区别函数组件、类组件 【特别的hooks后面说】

  • 是否有this
  • 是否有生命周期
  • 是否有状态state

6、React中的refs作用是什么?如何创建refs

Refs 是 React 提供给我们的安全访问 DOM 元素 或者 某个组件实例 的句柄

refs 并不是类组件的专属,函数式组件同样能够利用闭包暂存其值

Refs 是使用 React.createRef() 方法创建的

访问 this.myRef.current

函数组件添加refs:需要使用useRef和useImperativeHandle结合使用

useRef 返回的 ref 对象在组件的整个生命周期内保持不变,也就是说每次重新渲染函数组件时,返回的ref 对象都是同一个(使用 React.createRef ,每次重新渲染组件都会重新创建 ref)

forwardRef 转发

  • 因为函数组件没有实例,所以函数组件无法像类组件一样可以接收 ref 属性
  • forwardRef 可以在父组件中操作子组件的 ref 对象
  • forwardRef 可以将父组件中的 ref 对象转发到子组件中的 dom 元素上
  • 子组件接受 props 和 ref 作为参数
function Child(props,ref){
  return (
    <input type="text" ref={ref}/>
  )
}
Child = React.forwardRef(Child);
function Parent(){
  let [number,setNumber] = useState(0); 
  // 在使用类组件的时候,创建 ref 返回一个对象,该对象的 current 属性值为空
  // 只有当它被赋给某个元素的 ref 属性时,才会有值
  // 所以父组件(类组件)创建一个 ref 对象,然后传递给子组件(类组件),子组件内部有元素使用了
  // 那么父组件就可以操作子组件中的某个元素
  // 但是函数组件无法接收 ref 属性 <Child ref={xxx} /> 这样是不行的
  // 所以就需要用到 forwardRef 进行转发
  const inputRef = useRef();//{current:''}
  function getFocus(){
    inputRef.current.value = 'focus';
    inputRef.current.focus();
  }
  return (
      <>
        <Child ref={inputRef}/>
        <button onClick={()=>setNumber({number:number+1})}>+</button>
        <button onClick={getFocus}>获得焦点</button>
      </>
  )
}

useImperativeHandle

  • useImperativeHandle可以让你在使用 ref 时,自定义暴露给父组件的实例值
  • 在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用
  • 父组件可以使用操作子组件中的多个 ref
import React,{useState,useEffect,createRef,useRef,forwardRef,useImperativeHandle} from 'react';

function Child(props,parentRef){
    // 子组件内部自己创建 ref 
    let focusRef = useRef();
    let inputRef = useRef();
    useImperativeHandle(parentRef,()=>(
      // 这个函数会返回一个对象
      // 该对象会作为父组件 current 属性的值
      // 通过这种方式,父组件可以使用操作子组件中的多个 ref
        return {
            focusRef,
            inputRef,
            name:'计数器',
            focus(){
                focusRef.current.focus();
            },
            changeText(text){
                inputRef.current.value = text;
            }
        }
    });
    return (
        <>
            <input ref={focusRef}/>
            <input ref={inputRef}/>
        </>
    )

}
Child = forwardRef(Child);
function Parent(){
  const parentRef = useRef();// {current:''}
  function getFocus(){
    parentRef.current.focus();
    // 因为子组件中没有定义这个属性,实现了保护,所以这里的代码无效
    parentRef.current.addNumber(666);
    parentRef.current.changeText('<script>alert(1)</script>');
    console.log(parentRef.current.name);
  }
  return (
      <>
        <ForwardChild ref={parentRef}/>
        <button onClick={getFocus}>获得焦点</button>
      </>
  )
}

7、state 和 props有什么区别

state 和 props都是普通的JavaScript对象

props 是一个从外部传进组件的参数,主要作为就是从父组件向子组件传递数据,它具有可读性和不变性,只能通过外部组件主动传入新的 props 来重新渲染子组件,否则子组件的 props 以及展现形式不会改变。

state 的主要作用是用于组件保存、控制以及修改自己的状态,它只能在 constructor 中初始化,它算是组件的私有属性,不可通过外部访问和修改,只能通过组件内部的 this.setState 来修改,修改 state 属性会导致组件的重新渲染

8、**什么是高阶组件 ** 【案例:React-Redux connect 】

一个高阶组件只是一个包装了另外一个 React 组件的 React 组件

高阶组件本质上一个js函数,且该函数接受一个组件作为参数,并返回一个新的组件。基本上,这是从React的组成性质派生的一种模式,我们称它们为 “纯”组件, 因为它们可以接受任何动态提供的子组件,但它们不会修改或复制其输入组件的任何行为。

const EnhancedComponent = higherOrderComponent(WrappedComponent);

高阶组件作用

  • 代码复用,逻辑抽象,抽离底层准备(bootstrap)代码
  • 渲染劫持
  • State 抽象和更改
  • Props 更改

高阶组件两种形式

属性代理(Props Proxy):高阶组件操控传递给 WrappedComponent 的 props,

反向继承(Inheritance Inversion):高阶组件继承(extends)WrappedComponent

Props Proxy【属性代理】

  • 更改 props
  • 通过 refs 获取组件实例
  • 抽象 state
  • 把 WrappedComponent 与其它 elements 包装在一起
function CompHOC(WrappedComponent) {
  return class Comp extends React.Component {
    render() {
      return <WrappedComponent {...this.props}/>
    }
  }
}
<WrappedComponent {...this.props}/>
// is equivalent to [相当于]
React.createElement(WrappedComponent, this.props, null)

Inheritance Inversion 【反向继承】

  • 渲染劫持(Render Highjacking)
  • 操作 state
function HOC(WrappedComponent) {
  return class Enhancer extends WrappedComponent {
    render() {
      return super.render()
    }
  }
}

反向继承允许高阶组件通过 *this* 关键词获取 WrappedComponent,意味着它可以获取到 state,props,组件生命周期(component lifecycle)钩子,以及渲染方法(render)

渲染劫持 【*渲染 指的是 WrappedComponent.render 方法】

  • 『读取、添加、修改、删除』任何一个将被渲染的 React Element 的 props
  • 在渲染方法中读取或更改 React Elements tree,也就是 WrappedComponent 的 children
  • 根据条件不同,选择性的渲染子树
  • 给子树里的元素变更样式
HOC.displayName = `HOC(${getDisplayName(WrappedComponent)})`
//or
class HOC extends ... {
  static displayName = `HOC(${getDisplayName(WrappedComponent)})`
  ...
}
function getDisplayName(WrappedComponent) {
  return WrappedComponent.displayName || 
         WrappedComponent.name || 
         Component
}

实际上你不用自己写这个方法,因为 recompose 库已经提供了

9、react组件

无状态组件和有状态组件

无状态组价和有状态组件**是否维护state值**来划分的

无状态组件: 是最基础的组件形式,纯静态展示的作用,如固定的按钮,标签等,它的基本组成结构就是属性(props),在有一个渲染函数(render),不涉及到状态的更新,所以无状态组件的复用性最强

**有状态组件:**在无状态组件的基础之上,如果内部包含state并且外部数据发生变化时会随之发生变化,此时就是有状态组件。有状态组件是有生命周期的,用来在不同的时刻触发状态(state)的更新

image-20210807142124587

函数组件和类组件

函数组件和类组件的区分方式是**由定义的形式**来划分的。函数组件使用函数定义组件,类组件使用ES6 class定义组件

函数组件:函数组件的写法就是普通函数的写法。函数组件比类组件更加简洁; 而且函数组件看似这是返回一个DOM结构,其实函数组件用的是无状态组件的思想。在函数组件中你无法使用state也不能用生命周期,这就决定了函数组件是展示型组件,

函数组件】更容易理解,它的功能只是接收属性,渲染页面,不执行与UI无关的其他逻辑,而且函数组件中没有this,无需考虑this带来的问题

类组件】的写法是用的es6的class方法。类组件的功能比函数组件强大,类组件可以维护自身的state状态,它有不同的生命周期方法,可以让开发者在组建不同的生命周期阶段对组件做出更多的控制(挂载,更新,卸载)

纯组件

纯(Pure) 组件是可以编写的最简单、最快的组件。它们可以替换任何只有 render() 的组件。这些组件增强了代码的简单性和应用的性能

无论是 memo 还是 pureComponent,都是会对过去的 props,state,和现在的 props state 进行一次浅比较。这两个对象的长度是否相等,每个 key 是否两个对象都有,是不是都是一个引用,至于说,这个引用是不是也是相同的,浅比较不会去做,比方说,你的 props 更新了,但是更新的这个 value 是一个引用,那么浅比较依然会认为这两个 props 是相同的,因此不会去更新

PureComponent,ShouldComponentUpdate,还有 memo 都是为了减少子组件不必要的渲染而存在的,达到提升 react 性能的目的

纯函数 [案例:Redux reducer ]

一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,我们就把这个函数叫做纯函数 函数的返回结果只依赖于它的参数 函数执行过程里面没有副作用

let add = (a, b)=>a+b
add(1,2) // 3

高阶函数

高阶函数是对其他函数进行操作的函数,操作可以是将它们作为参数,或者是返回它们。 简单来说,高阶函数是一个接收函数作为参数或将函数作为输出返回的函数

案例:【Array.prototype.map,Array.prototype.filter 和 Array.prototype.reduce 是语言中内置的一些高阶函数】

function add(x,y,z) {
    return z(x) + z(y);
}
add(-3, -4, Math.abs); // ==> Math.abs(-3) + Math.abs(-4) ==> 7

Math.abs(x) 函数返回指定数字 x 的绝对值

受控组件和非受控组件

image-20210807145743706

非受控组件,即组件的状态不受React控制的组件 【即不受setState()的控制】

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class Demo extends Component {
    render() {
        return (
            <input />
        )
    }
}

ReactDOM.render(<Demo/>, document.getElementById('app'))

受控组件,受控组件就是组件的状态受React控制

class Demo extends Component {
    constructor(props) {
        super(props);
        this.state = {
            value: props.value
        }
    }

    handleChange(e) {
        this.setState({
            value: e.target.value
        })
    }

    render() {
        return (
            <input value={this.state.value} onChange={e => this.handleChange(e)}/>
        )
    }
}

高阶组件(HOC)

HOC本质上一个js函数,且该函数接受一个组件作为参数,并返回一个新组件,HOC并不是一个 React API 。 它只是一种设计模式,类似于装饰器模式。

react生命周期(15/16/17版本)

10、setState 什么时候同步什么时候异步

  • setState 只在合成事件(react为了解决跨平台,兼容性问题,自己封装了一套事件机制,代理了原生的事件,像在jsx中常见的onClick、onChange这些都是合成事件)和钩子函数(生命周期)中是“异步”的,在原生事件和 setTimeout 中都是同步的
  • setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果
  • setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新

react生命周期

react 15 生命周期

React15使用的是栈调和器,栈调和器是递归执行调和操作的,更新一旦开始,中途就无法中断。倘若递归层级过深,则可能会造成浏览器渲染卡顿

image-20210807151633232

1、挂载过程 constructor() componentWillMount() componentDidMount()

2、更新过程 componentWillReceiveProps(nextProps) shouldComponentUpdate(nextProps,nextState) componentWillUpdate (nextProps,nextState) render() componentDidUpdate(prevProps,prevState)

3、卸载过程 componentWillUnmount()

其具体作用分别为: 1、constructor() 完成了React数据的初始化。

2、componentWillMount() 组件已经完成初始化数据,但是还未渲染DOM时执行的逻辑,主要用于服务端渲染。

3、componentDidMount() 组件第一次渲染完成时执行的逻辑,此时DOM节点已经生成了。

4、componentWillReceiveProps(nextProps) 接收父组件新的props时,重新渲染组件执行的逻辑。

5、shouldComponentUpdate(nextProps, nextState) 在setState以后,state发生变化,组件会进入重新渲染的流程时执行的逻辑。在这个生命周期中return false可以阻止组件的更新,主要用于性能优化。

6、componentWillUpdate(nextProps, nextState) shouldComponentUpdate返回true以后,组件进入重新渲染的流程时执行的逻辑。

7、render() 页面渲染执行的逻辑,render函数把jsx编译为函数并生成虚拟dom,然后通过其diff算法比较更新前后的新旧DOM树,并渲染更改后的节点。

8、componentDidUpdate(prevProps, prevState) 重新渲染后执行的逻辑。

9、componentWillUnmount() 组件的卸载前执行的逻辑,比如进行“清除组件中所有的setTimeout、setInterval等计时器”或“移除所有组件中的监听器removeEventListener”等操作。

react 16 17生命周期

React16使用的是全新的Fiber调和器,这就依托于 React16的重点了一Fiber架构,目前简要概括就是,React16能够 实现中断调和,分批次异步地调和。从而达到不因为JS执行时间过久影响渲染卡顿

image-20210807151741218

react 17 生命周期 与16差不多 【React16.8 常用Hook新特性】

具体参考博文:https://www.cnblogs.com/lhl66/p/14583735.html

React17 1、支持混用 2、更改事件委托:原来的 react 的事件系统是挂在 document 上的,为了支持混用将会修改到你挂载的 rootNode 中 3、useEffect 的回调修改为异步调用:useEffect 的副作用清理函数是在 effect 执行之后立马执行的,但是在使用中发现了如果回调中的操作比较耗时,会造成一些性能问题,现在 useEffect 的 副作用清理函数会在 render 后执行了。 4、返回一致的 undefined 错误 class 和 function 最好使用 return null 来代替

react 18 新特性 新api

  • 自动批处理以减少渲染【批处理是 React将多个状态更新分组到单个重新渲染中以获得更好的性能】
  • Suspense 的 SSR 支持
  • startTransition
  • ReactDOM.createRoot【ReactDOM.createRoot(root).render(jsx)】

import  {  startTransition  }  from  'react' ;
 
// 显示输入的
setInputValue ( input ) ;
 
// 将内部的任何状态更新标记为转换 省去节流 输入框搜索调用接口
startTransition ( ( )  =>  { 
  // Transition: 显示结果
  setSearchQuery ( input ) ; 
} ) ;

react路由

react redux

react hooks 【react版本v16.8后】

1、为什么 React 要加入Hooks

  • 组件之间的逻辑状态难以复用

  • 大型复杂的组件很难拆分

  • Class语法的使用不友好

    比如:

类组件可以访问生命周期,函数组件不能

类组件可以定义维护state(状态),函数组件不可以

类组件中可以获取到实例化后的this,并基于这个this做一些操作,而函数组件不可以

mixins:变量作用于来源不清、属性重名、Mixins引入过多会导致顺序冲突

HOC和Render props:组件嵌套过多,不易渲染调试、会劫持props,会有漏洞

有了hooks

Hooks 就是让你不必写class组件就可以用state和其他的React特性;

也可以编写自己的hooks【自定义hooks】在不同的组件之间复用

优点:

没有破坏性改动:完全可选的。 你无需重写任何已有代码就可以在一些组件中尝试 Hook。100% 向后兼容的。 Hook 不包含任何破坏性改动。

更容易复用代码:它通过自定义hooks来复用状态,从而解决了类组件逻辑难以复用的问题

函数式编程风格:函数式组件、状态保存在运行环境、每个功能都包裹在函数中,整体风格更清爽、优雅

代码量少,复用性高

更容易拆分

Hooks缺点

  • hooks 是 React 16.8 的新增特性、16.8前版本的不可用
  • 状态不同步(闭包带来的坑):函数的运行是独立的,每个函数都有一份独立的闭包作用域。当我们处理复杂逻辑的时候,经常会碰到“引用不是最新”的问题
  • 使用useState时候,使用push,pop,splice等直接更改数组对象的坑,demo中使用push直接更改数组无法获取到新值,应该采用析构方式原因:push,pop,splice是直接修改原数组,react会认为state并没有发生变化,无法更新)
  • useState 初始化只初始化一次
  • useEffect 内部不能修改 state
  • useEffect 依赖引用类型会出现死循环
  • 不要在循环,条件或嵌套函数中调用Hook,必须始终在React函数的顶层使用Hook。这是因为React需要利用调用顺序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用Hook,就容易导致调用顺序的不一致性,从而产生难以预料到的后果

2、怎么避免react hooks的常见问题

安装ESLint 插件 约束 [Hook 规则]

npm install eslint-plugin-react-hooks --save-dev
// 你的 ESLint 配置
{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error", // 检查 Hook 的规则
    "react-hooks/exhaustive-deps": "warn" // 检查 effect 的依赖
  }
}
  • 不要在useEffect里面写太多的依赖项,划分这些依赖项成多个单一功能的useEffect。其实这点是遵循了软件设计的“单一职责模式”
  • 如果你碰到状态不同步的问题,可以考虑下手动传递参数到函数

hooks闭包的坑有哪些?如何解决 【很多时候你不得不使用useRef方案】

问题:每次 render 都有一份新的状态,数据卡在闭包里,捕获了每次 render 后的 state,也就导致了输出原来的 state

解决:可以通过 useRef 来保存 state。前文讲过 ref 在组件中只存在一份,无论何时使用它的引用都不会产生变化,因此可以来解决闭包引发的问题。

  • 复杂业务的时候,使用Component代替hooks

3、hooks模拟class生命周期场景

constructor(){
  super()
  this.state={count:0}
}
// Hooks模拟constructor
const [count setCount]=useState(0)
componentDidMount(){
 console.log('componentDidMount')
}
// Hooks模拟 componentDidMount
useEffect(()=>console.log('componentDidMount'),[])
// useEffect拥有两个参数,第一个参数作为回调函数会在浏览器布局和绘制完成后调用,因此它不会阻碍浏览器的渲染进程,第二个参数是一个数组,也是依赖项
// 当依赖列表存在并有值,如果列表中的任何值发生更改,则每次渲染后都会触发回调
// 当它不存在时,每次渲染后都会触发回调
// 当它是一个空列表时,回调只会被触发一次,类似于componentDidMount
shouldComponentUpdate(nextProps,nextState){
   console.log('shouldComponentUpdate')
   return true //更新组件 反之不更新
}
// React.memo包裹一个组件来对它的props进行浅比较,但这不是一个hooks,因为它的写法和hooks不同,其实React.memo等效于PureComponent,但它只比较props

// 模拟 shouldComponentUpdate
const MyComponent = React.memo(
  whateverComponent,
  (prevProps,nextProps) => nextProps.count !== preProps.count
)
componentDidMount() {console.log('componentDidMount');}
componentDidUpate(){console.log('componentDidUpate')}
// Hooks模拟 componentDidUpdate
useEffect(()=>console.log('componentDidMount or componentDidUpate'))
// 这里的回调函数会在每次渲染后调用,因此不仅可以访问componentDidUpdate,还可以访问componentDidMount,如果只想模拟componentDidUpdate,我们可以这样来实现
const mounted = useRef()
useEffect(() => {
 if(!mounted.current){
    	mounted.current = true
    } else {
        console.log('componentDidUpate')
    }
})
// useRef在组件中创建“实例变量”,它作为一个标志来指示组件是否处于挂载或更新阶段。当组件更新完成后在会执行else里面的内容,以此来单独模拟componentDidUpdate
componentWillUnmount(){
  console.log('componentWillUnmount')
}
// Hooks模拟 componentWillUnmount
useEffect(()=>{
// 此处并不同于willUnMount porps发生变化即更新,也会执行结束监听
// 准确的说:返回的函数会在下一次effect执行之前,被执行
 return ()=>{console.log('componentWillUnmount')}
},[])
// 当在useEffect的回调函数中返回一个函数时,这个函数会在组件卸载前被调用。我们可以在这里清除定时器或事件监听器

总结:

  • 默认的useEffect(不带[])中return的清理函数,它和componentWillUnmount有本质区别的,默认情况下return,在每次useEffect执行前都会执行,并不是只有组件卸载的时候执行

  • useEffect在副作用结束之后,会延迟一段时间执行,并非同步执行,和compontDidMount有本质区别。遇到dom操作,最好使用useLayoutEffect。

  • hooks 模拟的生命周期与class中的生命周期不尽相同,我们在使用时,还是需要思考业务场景下那种方式最适合

4、Hooks性能优化

  • useMemo 缓存数据
  • useCallback 缓存函数
  • 相当于class组件的SCU【shouldComponentUpdate】和PureComponent

5、hooks基础语法基础使用,常用hooks API

useState
function Button() {
  const [buttonText, setButtonText] = useState("点击前");
  const handleClick = () => {
    setButtonText("点击后");
  };
  return <button onClick={handleClick}>{buttonText}</button>;
}
useEffect
import React, { useState, useEffect } from "react";

const SetIntervalHooksComp = (props) => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    let timer = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    // 当在 useEffect 的回调函数中返回一个函数时,这个函数会在组件卸载前被调用
    return () => clearInterval(timer);
    // count发生变化时再次执行
  }, [count]);

  return <div>{count}</div>;
};
export default SetIntervalHooksComp;
useRef

import React, { useRef, useEffect } from "react";

function UseRefHooksComp() {
  // 初始化一个useRef
  const content = useRef(null);

  useEffect(() => {
    // 通过.current获取
    console.log(content.current);
  }, []);

  return <div ref={content}>useRef用法</div>;
}

export default UseRefHooksComp;
useContext
import React, { useContext } from "react";
const UseContextHooksComp = () => {
  const AppContext = React.createContext({});

  const A = () => {
    const { name } = useContext(AppContext);
    return <p>我是A的子组件--{name}</p>;
  };

  const B = () => {
    const { name } = useContext(AppContext);
    return <p>我是B组件的名字--{name}</p>;
  };

  return (
    <AppContext.Provider value={{ name: "useContext-hook测试" }}>
      <A />
      <B />
    </AppContext.Provider>
  );
};
export default UseContextHooksComp;
useReducer
import React, { useReducer } from "react";

const UseReducerComp = () => {
  const reducer = (state, action) => {
    console.log(state, "action", action);
    if (action.type === "add") {
      return {
        ...state,
        count: state.count + 1,
      };
    } else {
      return state;
    }
  };

  const addcount = () => {
    dispatch({
      type: "add",
    });
  };

  const [state, dispatch] = useReducer(reducer, { count: 0 });
  return (
    <>
      <p>{state.count}</p>
      <button onClick={addcount}>count++</button>
    </>
  );
};
export default UseReducerComp;
useMemo
import React, { Component, useState, useMemo } from "react";

class UseMemoComp extends Component {
  render() {
    return (
      <div>
        <p>useMemoComp test</p>
        <WithNoMemo />
        <WithMemo />
      </div>
    );
  }
}
// 不使用 useMemo
function WithNoMemo() {
  const [count, setCount] = useState(1);
  const [val, setValue] = useState("");

  function expensive() {
    console.log("compute");
    let sum = 0;
    for (let i = 0; i < count * 100; i++) {
      sum += i;
    }
    return sum;
  }

  return (
    <div>
      <h4>
        {count}-{val}-{expensive()}
      </h4>
      <div>
        <button onClick={() => setCount(count + 1)}>+c1</button>
        <input value={val} onChange={(event) => setValue(event.target.value)} />
      </div>
    </div>
  );
}

// 使用useMemo来执行昂贵的计算,然后将计算值返回,并且将count作为依赖值传递进去。这样,就只会在count改变的时候触发expensive执行,在修改val的时候,返回上一次缓存的值
function WithMemo() {
  const [count, setCount] = useState(1);
  const [val, setValue] = useState("");
  const expensive = useMemo(() => {
    console.log("compute");
    let sum = 0;
    for (let i = 0; i < count * 100; i++) {
      sum += i;
    }
    return sum;
  }, [count]);

  return (
    <div>
      <h4>
        {count}-{val}-{expensive}
      </h4>
      {val}
      <div>
        <button onClick={() => setCount(count + 1)}>+c1</button>
        <input value={val} onChange={(event) => setValue(event.target.value)} />
      </div>
    </div>
  );
}
export default UseMemoComp;
useCallback

基础用法

import React, { useState, useCallback } from "react";
const set = new Set();

// 每次修改count,set.size就会 +1,这说明useCallback依赖变量count,count变更时会返回新的函数;而val变更时,set.size不会变,说明返回的是缓存的旧版本函数
export default function UseCallbackComp() {
  const [count, setCount] = useState(1);
  const [val, setVal] = useState("");

  const callback = useCallback(() => {
    console.log(count, "count"); // 不会打印
  }, [count]);

  set.add(callback);

  return (
    <div>
      <h4>{count}</h4>
      <h4>{set.size}</h4>
      <div>
        <button onClick={() => setCount(count + 1)}>add</button>
        <input value={val} onChange={(event) => setVal(event.target.value)} />
      </div>
    </div>
  );
}

场景:有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新

import React, { useState, useCallback, useEffect } from 'react';
function Parent() {
    const [count, setCount] = useState(1);
    const [val, setVal] = useState('');
 
    const callback = useCallback(() => {
        return count;
    }, [count]);
    return <div>
        <h4>{count}</h4>
        <Child callback={callback}/>
        <div>
            <button onClick={() => setCount(count + 1)}>+</button>
            <input value={val} onChange={event => setVal(event.target.value)}/>
        </div>
    </div>;
}
 
function Child({ callback }) {
    const [count, setCount] = useState(() => callback());
    useEffect(() => {
        setCount(callback());
    }, [callback]);
    return <div>
        {count}
    </div>
}
useImperativeHandle

useImperativeHandle 应当与 forwardRef 一起使用

import React, {
  Component,
  useRef,
  useImperativeHandle,
  forwardRef,
} from "react";

// forwardRef 引用传递(Ref forwading)是一种通过组件向子组件自动传递 引用ref 的技术
const MyFunctionComponent = forwardRef((props, ref) => {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
  }));
  return <input ref={inputRef} />;
});

class UseImperativeHandleComp extends Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }
  componentDidMount() {
    this.textInput.current.focus();
  }
  render() {
    return <MyFunctionComponent ref={this.textInput} />;
  }
}

export default UseImperativeHandleComp;

useLayoutEffect

import React, { useState, useLayoutEffect, useEffect } from "react";

export default function UseLayoutEffectComp() {
  const [count, setCount] = useState(0);

  const onClick = () => {
    setCount((i) => i + 1);
  };
  useEffect(() => {
    console.log("useEffect"); // 后打印
  });

  useLayoutEffect(() => {
    // 改成 useEffect 试试
    console.log("useLayoutEffect"); // 先打印
  });

  return (
    <div>
      <h1>count: {count}</h1>
      <button onClick={onClick}>Click</button>
    </div>
  );
}

// 总结:
// useEffect在浏览器渲染完成后执行
// useLayoutEffect在浏览器渲染前【在DOM更新后执行】执行

// useLayoutEffect总是比useEffect先执行
// useLayoutEffect里面的任务最好影响了Layout(布局)

// 注意:为了用户体验最好优先使用useEffect

6、自定义hooks 和 自定义hooks的用法

  • 通过自定义 Hook,可以将组件逻辑提取到可重用的函数中
  • 自定义 Hook 是一个函数,其名称必须以 “use” 开头,函数内部可以调用其他的 Hook
  • 自定义 Hook 是一种自然遵循 Hook 设计的约定,而并不是 React 的特性
  • 两个组件中使用相同的 Hook 不会共享 state
  • 在一个组件中多次调用 useStateuseEffect,它们是完全独立的,每次调用 Hook,它都会获取独立的 state
  • 自定义 Hook 解决了以前在 React 组件中无法灵活共享逻辑的问题

编写一个 useReducer 的 Hook,使用 reducer 的方式来管理组件的内部 state

function useReducer(reducer, initialState) {
  const [state, setState] = useState(initialState);

  function dispatch(action) {
    const nextState = reducer(state, action);
    setState(nextState);
  }

  return [state, dispatch];
}
// 官网 demo 使用 自定义 useReducer
function Todos() {
  const [todos, dispatch] = useReducer(todosReducer, []);

  function handleAddClick(text) {
    dispatch({ type: 'add', text });
  }

  // ...bla bla...
}

编写 和 使用 自定义hook

import React, { useState, useEffect } from 'react'

const useMousePosition = () => {
    const [position, setPosition] = useState({x: 0, y: 0 })
    useEffect(() => {
        const updateMouse = (e) => {
            setPosition({ x: e.clientX, y: e.clientY })
        }
        document.addEventListener('mousemove', updateMouse)
        return () => {
            document.removeEventListener('mousemove', updateMouse)
        }
    })
    return position
}

export default useMousePosition

// 在需要的地方引入然后调用即可
import React from 'react'
import useMousePosition from './useMousePosition'

function App() {
    const position = useMousePosition()
    return (
        <div>
            <div>x: {position.x}</div>
            <div>y: {position.y}</div>
        </div>
    )
}
常用自定义hooks举例
function userDebounce(fn,delay,deps=[]){
    let {current} = useRef({fn, timer: null})
    setEffect(() => {
        current.fn = fn;
    },[fn]);

    return userCallback(function(...args){
        if(current.timer) clearTimeout(crurent.timer);
        current.timer = setTimeout(() => {
            current.fn.call(this,...args);
        },delay);
    },deps);
}
function useThrottle(fn,interval){
    let {current} = useRef({fn,timer:null});
    setEffect(() => {
        current.fn = fn;
    },[fn])

    return useCallback(function(...args){
        if(!current.timer){
            current.timer = setTimeout(() => {
                delete current.tiemer;
            },interval);
            current.fn.call(this,...args);
        }
    },deps);
}
// eleRef 是用 useRef 获得的 React元素引用
function useScrollPos(eleRef){
    const [pos,setPos] = useState([0,0]);
    useEffect(() => {
        function onScroll)(){
            setPos([eleRef.current.scrollLeft,eleRef.current.scrollTop]);
        }
        eleRef.current.addEventListener('scroll', onScroll);
        return () => {
            eleRef.current.removeEvenlistener('scroll',onScroll);
        }
    },[]);

    return pos;
}
function useTitle(title){
    setEffect(() => {
        document.tilte = title;
    },[])
}
function useWinSize() {
  const [size, setSize] = useState({
    width: document.documentElement.clientWidth,
    height: document.documentElement.clientHeight
  })
  const onResize = useCallback(() => {
    setSize({
      width: document.documentElement.clientWidth,
      height: document.documentElement.clientHeight
    })
  }, [])

  useEffect(() => {
    window.addEventListener('resize', onResize)
    return () => {
      window.removeEventListener('reisze', onResize)
    }
  }, [])

  return size
}
// 相比 setInterval, useInterval 就一个用处, delay 可以动态修改
function useInterval(callback, delay) {
  const savedCallback = useRef();

  useEffect(() => {
    savedCallback.current = callback;
  });

  useEffect(() => {
    function tick() {
      savedCallback.current();
    }

    let timer = setInterval(tick, delay);
    return () => clearInterval(timer);
  }, [delay]);
}

7、hooks useMemo 和 useCallback的区别

useMemo:

useMemo是在render期间执行的。所以不能进行一些额外的副操作,比如网络请求等

传递一个创建函数和依赖项,创建函数会需要返回一个值,只有在依赖项发生改变的时候,才会重新调用此函数,返回一个新的值

用来缓存数据,当组件内部某一个渲染的数据,需要通过计算而来,这个计算是依赖与特定的state、props数据,我们就用useMemo来缓存这个数据,以至于我们在修改她们没有依赖的数据源的情况下,多次调用这个计算函数,浪费计算资源。

useCallback

useCallback返回一个函数,只有在依赖项变化的时候才会更新(返回一个新的函数)

缓存一个函数,这个函数如果是由父组件传递给子组件,或者自定义hooks里面的函数【通常自定义hooks里面的函数,不会依赖于引用它的组件里面的数据】,这时候我们可以考虑缓存这个函数,好处:

  • 不用每次重新声明新的函数,避免释放内存、分配内存的计算资源浪费
  • 子组件不会因为这个函数的变动重新渲染。【和React.memo搭配使用】

等价 useMemo表示useCallback

useCallback(fn,[m]);

等价于

useMemo(() => fn, [m]);

结论

useCallback缓存的是函数,useMemo 缓存的是函数的返回结果。

useCallback 是来优化子组件的,防止子组件的重复渲染。

useMemo 可以优化当前组件也可以优化子组件,优化当前组件主要是通过 memoize 来将一些复杂的计算逻辑进行缓存。当然如果只是进行一些简单的计算也没必要使用 useMemo

1
https://gitee.com/yuyingqianduan/front-end-interview-questions.git
git@gitee.com:yuyingqianduan/front-end-interview-questions.git
yuyingqianduan
front-end-interview-questions
前端面试题
master

搜索帮助