react面试题

一、类组件和函数组件的区别(面试常考)

简单理解(所有同学都要掌握)

1、类组件有生命周期,函数组件没有

2、类组件需要继承 Class,函数组件不需要

3、类组件可以获取实例化的 this,并且基于 this 做各种操作,函数组件不行

4、类组件内部可以定义并维护 state, 函数组件都称为无状态了,那肯定不行。

进阶理解(挑战高薪)

函数式组件捕获了渲染时所使用的值,这是两类组件最大的不同

创建组件LearnComponentDiff
import React from "react";

import FCComponent from "./fc-component";
import ClassComponent from "./class-component";

export default class LearnComponentDiff extends React.Component {
  state = {
    user: "王凯",
  };
  render() {
    return (
      <>
        <label>
          <b>选择一个人查看关注: </b>
          <select
            value={this.state.user}
            onChange={(e) => this.setState({ user: e.target.value })}
          >
            <option value="wangkai">王凯</option>
            <option value="zhangsan">张三</option>
            <option value="lisi">李四</option>
          </select>
        </label>
        <h1>欢迎关注 {this.state.user}!</h1>
        <p>
          <FCComponent user={this.state.user} />
          <b> (函数组件)</b>
        </p>
        <p>
          <ClassComponent user={this.state.user} />
          <b> (类组件)</b>
        </p>
        <p>你能看出两者的不同吗</p>
      </>
    );
  }
}
创建ClassComponent类组件
import React from 'react';

class ClassComponent extends React.Component {

  showMessage = () => {
    alert('选择的 ' + this.props.user);
  }

  handleClick = () => {
    setTimeout(this.showMessage, 3000);
  };

  render() {
    return <button onClick={this.handleClick}>尝试</button>;
  }
}

export default ClassComponent;

点击页面后我们发现 初始化的props改变了! user 是通过 props 下发的,props不可改变,那么造成数据改变的原因就一定是 this 指向改变了。
真正的原因也确实如此,虽然props不可改变,但是this是可变的,this.props 的每次调用都会去获取最新的 this 值,这也是React保证数据实时性的重要手段。

那么就很清晰了,当showMessage最终执行时,此时的 this 绑定的是 张三对应的上下文,所以输出为 ‘已关注 张三’

创建FCComponent函数组件
import React from 'react';

function FCComponent(props) {

  const showMessage = () => {
    alert('已关注 ' + props.user);
  }
  const handleClick = () => {
    setTimeout(showMessage, 3000);
  };

  return (
    <button onClick={handleClick}>尝试</button>
  );
}

export default FCComponent;

最终的输出值明显为 ‘选择的 王凯’,props 会在函数执行的瞬间就被捕获,而 props 本身又是不可变值,所以我们可以确保从当前开始读取到的 props 都是最初捕获到的。当父组件传入新的 props 尝试重新渲染函数时,本质是基于新的 props 入参重新调用了一次函数,并不会影响上一次调用。这就是 Dan 所说的函数式组件捕获了渲染所使用的值,并且我们还能进一步意识到:函数组件真正将数据和渲染紧紧的绑定到一起了

注意:

很多人认为在函数组件中延迟输出的 state 是调用时的 state,而不是最新的 state 是一个Bug,恰恰相反,这是一个函数式组件的特性,是真正践行了React设计理念的正确方式。

当然Hooks也给出了获取最新的props和state的方法,就是 useRef,详细用法在我们的课程已经有所介绍,同学们可以自行回顾一下

推荐阅读

大家可以看一下React 团队核心成员和 Redux 作者 Dan的这边文章。进阶理解这部分也借鉴了Dan大佬的这篇文章😄

函数式组件与类组件有何不同?

二、除jsx外,react还可以使用那些方式编写UI

当你不想在构建环境中配置有关 JSX 编译时,不在 React 中使用 JSX 会更加方便。每个 JSX 元素只是调用 React.createElement(component, props, …children) 的语法糖。因此,使用 JSX 可以完成的任何事情都可以通过纯 JavaScript 完成

三、react中样式污染产生的原因

React最终编译打包后都在一个html页面中,如果在两个组件中取一样类名分别引用在自身,那么后者会覆盖前者。默认情况下,只要导入了组件,不管组件有没有显示在页面中,组件的样式就会生效。也就是说并没有自己的局部作用域

四、如何解决react中的样式污染

1、手动处理 (起不同的类名,但是项目一大就会导致类名很乱,不利于团队协作)

2、CSS IN JS : 以js的方式来处理css(推荐)

五、什么是CSS IN JS

CSS IN JS是使用 JavaScript 编写 CSS 的统称,用来解决 CSS 样式冲突、覆盖等问题。

CSS IN JS 的具体实现有 50 多种,比如:React常用(CSS Modules、styled-components)、 Vue常用(

六、CSS module的实现原理

CSS Modules 通过对 CSS 类名重命名,保证每个类名的唯一性,从而避免样式冲突的问题。也就是说:所有类名都具有“局部作用域”,只在当前组件内部生效。在 React 脚手架中:文件名、类名、hash(随机)三部分,只需要指定类名即可 BEM

七、什么是合成事件

React 合成事件(SyntheticEvent)是 React 模拟原生 DOM 事件所有能力的一个事件对象,即浏览器原生事件的跨浏览器包装器。它根据 W3C 规范 来定义合成事件,兼容所有浏览器,拥有与浏览器原生事件相同的接口。

八、合成事件的优势

1、进行浏览器兼容,实现更好的跨平台

React 采用的是顶层事件代理机制,能够保证冒泡一致性,可以跨浏览器执行。React 提供的合成事件用来抹平不同浏览器事件对象之间的差异,将不同平台事件模拟合成事件。

2、避免垃圾回收

事件对象可能会被频繁创建和回收,因此 React 引入事件池,在事件池中获取或释放事件对象。即 React 事件对象不会被释放掉,而是存放进一个数组中,当事件触发,就从这个数组中弹出,避免频繁地去创建和销毁(垃圾回收)。

进阶理解(挑战高薪)

3、方便事件统一管理和事务机制

有兴趣的同学可以看一下源码的实现https://github.com/facebook/react/blob/75ab53b9e1de662121e68dabb010655943d28d11/packages/events/SyntheticEvent.js#L62

九、在类组件定义事件时this指向的问题

一般来说我们都是直接使用箭头函数然后后面跟上一个事件,也可以使用呢call bind apply直接改变事件

这里要注意箭头函数的定义事件的性能问题,call bind apply的坑

十、state同步还是异步的

分版本来讲,在 react17 中,setState 是批量执行的,因为执行前会设置 executionContext。但如果在 setTimeout、事件监听器等函数里,就不会设置 executionContext 了,这时候 setState 会同步执行。可以在外面包一层 batchUpdates 函数,手动设置下 excutionContext 来切换成异步批量执行。

在react 18里都是异步的

进阶理解(挑战高薪)

此处为语雀内容卡片,点击链接查看:https://www.yuque.com/wangkai-jjgja/ef1il2/dgtz6gcsfzfhdige

十一、为什么map的时候要加key

key是react用来追踪哪些列表的元素被修改,被添加或者是被删除的辅助标示。在开发过程中我们需要保证某个元素的key在其同级元素中具有唯一性。

在react的diff算法中react会借助元素的key来判断该元素是最新创建的还是被移动而来的,从而减少不必要的元素渲染。除此之外,react还要根据key来判断元素与本地状态的关联关系。

十二、为什么index不能当做key

当我们用index做下标的时候,点击删除列表中的每一项的下标都会发生变化,如果用下标当做key就会触发dom重新渲染

十三、react组件通讯

父传子

在父组件里的子组件标签上定义属性,在子组件里使用props接收

子传父

父组件给子组件传入一个方法,子组件接收这个方法,在对应的事件里触发接收到的方法,并且可以传参。子传父本质上来说就是通过观察者去触发了一个回调函数。

十三、什么是受控组件

在HTML中,表单元素的标签、、等,这些值改变通常是根据用户输入进行更新。那么在React中,可变状态通常保存在组件的状态属性中,并且只能使用 setState() 进行更新, 如果这个组件里面存放着一些表单控件我们也可以手动的进行组件更新,那么以这种由React控制的输入表单元素而改变其值的方式,称为受控组件

十四、状态提升

在React框架中,当多个组件需要反映相同的变化数据,这时建议将共享状态提升到最近的共同父组件中去。一般我们将该操作称为状态提升。由此我们可以很清楚的明白React状态提升主要就是用来处理父组件和子组件的数据传递的;他可以让我们的数据流动的形式是自顶向下单向流动的,所有组件的数据都是来自于他们的父辈组件,也都是由父辈组件来统一存储和修改,再传入子组件当中。

十五、在项目中封装过哪些组件+如何封装一个弹框组件

使用画一个弹框的UI,使用**固定定位,**定到屏幕中央,然后确定接收的参数,如标题,弹框内容,按钮名等,暴露出两个按钮的点击事件。最后使用ReactDOM.createPortal创建这个组件

十六、ReactDOM.createPorta

Portal 提供了一种将子节点渲染到存在于父组件以外 DOM 节点的方案。在 CSS 中,我们可以使用 position: fixed 等定位方式,让元素从视觉上脱离父元素。在 React 中,Portal 直接改变了组件的挂载方式,不再是挂载到上层父节点上,而是可以让用户指定一个挂载节点。一般用于模态对话框,工具提示、悬浮卡、加载动画等

十七、生命周期

全文背诵,并且回答出执行时间,和使用场景。注意这里不要回答报错后触发的那两个生命周期

十八、为什么react17后删除了那些生命周期

react 打算在17版本推出新的 Async Rendering,提出一种可被打断的生命周期,而可以被打断的阶段正是实际 dom 挂载之前的虚拟 dom 构建阶段,也就是要被去掉的三个生命周期。本身这三个生命周期所表达的含义是没有问题的,但 react 官方认为我们(开发者)也许在这三个函数中编写了有副作用的代码,所以要替换掉这三个生命周期,因为这三个生命周期可能在一次 render 中被反复调用多次

十九、错误边界

是 React 组件,并不是损坏的组件树。错误边界捕捉发生在子组件树中任意地方的 JavaScript 错误,打印错误日志,并且显示回退的用户界面。错误边界捕捉渲染期间、在生命周期方法中和在它们之下整棵树的构造函数中的错误。

二十、什么是fiber

Fiber 是一个基于优先级策略和帧间回调的循环任务调度算法的架构方案。随着应用变得越来越庞大,整个更新渲染的过程开始变得吃力,大量的组件渲染会导致主进程长时间被占用,导致一些动画或高频操作出现卡顿和掉帧的情况。而关键点,便是 同步阻塞。在之前的调度算法中,React 需要实例化每个类组件,生成一棵组件树,使用 同步递归 的方式进行遍历渲染,而这个过程最大的问题就是无法 暂停和恢复。

进阶理解(挑战高薪)

课件全部背过

此处为语雀内容卡片,点击链接查看:https://www.yuque.com/wangkai-jjgja/ef1il2/dgtz6gcsfzfhdige

二十一、什么context

上下文 (Context) 是 React 中一个重要的特性,它允许您在组件树中共享数据,而不必通过每个级别显式地传递参数。这是一种将数据传递到树中任意深度的组件的方法,无论其祖先组件是否知道该数据。

二十二、context的使用场景

1、主题色切换。

2、多国语言切换。也就是国际化

3、祖孙组件之间的传值

二十三、context祖孙传值

1、父组件

import React from "react";
import LearnContext2 from "./LearnContext2";

const MyContext = React.createContext();

export default class LearnContext extends React.Component {
  state = {
    num: 1,
  };

  /**
   * 接收所有层级的子组件返回的消息
   * @param {} msg
   */
  getChildData = (msg) => {
    console.log(msg);
  };

  render() {
    return (
      <div>
        <div>父级组件</div>
        <MyContext.Provider
          /**
           * 给所有的子级组件传一个num的值
           * 并且传一个方法 让他们也可以和父级组件通讯
           */
          value={{ num: this.state.num, getChildData: this.getChildData }}
        >
          <LearnContext2></LearnContext2>
        </MyContext.Provider>
      </div>
    );
  }
}

2、子组件

import React from "react";
import LearnContext3 from "./learn-context3";

export default class LearnContext2 extends React.Component {
  render() {
    return (
      <div>
        <div>子组件</div>
        <LearnContext3></LearnContext3>
      </div>
    );
  }
}

3、孙组件

使用 Consumer 接收传下来的context

import React from "react";
import { MyContext } from "./learn-context";

class LearnContext3 extends React.Component {
  
  render() {
    return (
      <MyContext.Consumer>
        {/* 
          直接使用Consumer
          这种方式获取参数
          但是这种方式 不方便在render意外的地方使用传下来的参数
        */}
        {(context) => (
          <div>
            <div>孙组件</div>
            <div>爷爷——传下来的{context.num}</div>
            <button onClick={() => context.onChildHandle('哈喽爷爷')}>给爷爷问声好</button>
          </div>
        )}
      </MyContext.Consumer>
    );
  }
}

export default LearnContext3;

使用contextType 接收context

import React from "react";
import { MyContext } from "./learn-context";

class LearnContext4 extends React.Component {

  // 可以直接在类里面使用static 声明静态属性
  static contextType = MyContext

  render () {
    return (
      <div>
        <div>孙组件</div>
        {/* 
          当我们把context挂载到当前组件的contextType的时候
          就可以直接使用this.context拿到相关的值
        */}
        <div>爷爷——传下来的{this.context.num}</div>
        <button onClick={() => this.context.onChildHandle('哈喽爷爷')}>给爷爷问声好</button>
      </div>
    )
  }
}

// 也可以直接在这上面进行赋值
LearnContext4.contextType = MyContext

export default LearnContext4

二十四、为什么会出现ref

在react典型的数据流中,props传递是父子组件交互的唯一方式;通过传递一个新的props值来使子组件重新re-render,从而达到父子组件通信。当然,就像react官网所描述的一样,在react典型的数据流之外,某些情况下(例如和第三方的dom库整合,或者某个dom元素focus等)为了修改子组件我们可能需要另一种方式,这就是ref方式。

二十五、当ref值是一个函数,什么时候会被调用,以及参数会有什么表现

React 会在组件挂载时,调用 ref 回调函数并传入 DOM元素(或React实例),当卸载时调用它并传入 null。在 componentDidMount 或 componentDidUpdate 触发前,React 会保证 Refs 一定是最新的。

二十六、什么是高阶组件

高阶组件(Higher-Order Component,HOC)是 React 中一种代码重用的模式,它是一个函数,接受一个组件作为参数,并返回一个新的组件。它的作用是对组件进行抽象,从而在不修改组件代码的情况下,扩展组件的功能

二十六、高阶组件的使用场景

1、数据获取:高阶组件可以在组件挂载时自动获取数据,并将数据通过 props 传递给被包装组件。

2、权限控制:高阶组件可以检查用户是否有访问该组件的权限,从而决定是否渲染该组件。

3、代码重用:高阶组件可以通过封装一些常见的逻辑,来提高代码的复用性。

4、状态管理:高阶组件可以管理一些状态,并通过 props 将状态传递给被包装组件,如redux里的connect。

5、表单处理:高阶组件可以处理表单的数据,包括数据验证、数据提交等,antd里面的Form。

6、设计模式:高阶组件可以作为设计模式的一部分,例如实现观察者模式、策略模式等

二十七、什么是render props

Render props 是一种在 React 中实现代码复用的方法。它允许你在一个组件中定义逻辑,并在另一个组件中渲染这个逻辑。这个技术通过一个特殊的 props,称为 “render props”,来实现复用。

二十八、render props的使用场景

1、实现代码复用:如果多个组件需要实现相似的逻辑,可以使用 render props 实现代码复用。

2、动态渲染内容:通过 render props,可以在不同的场景下动态渲染内容,达到更好的可定制性。

3、访问组件的状态:使用 render props 可以访问组件的状态,从而在不同的场景下呈现不同的内容

二十九、为什么会出现hooks

class component 学习成本高

我们在class component中要学习生命周期,React15、React16.3、React16.4到React17生命周期有了很多变化。生命周期在class组件中非常重要。但是太多的太多的生命周期难记,有些也不知道具体的场景麻烦。还有就是this指向的问题比如我们要写大量的bind函数来改变this的指向,当然也可以通过装饰器等其他方法改变,但是本质上来说还是要跟this打交道

class component 逻辑代码分散

我们在学习代码的第一天,就应该知道高内聚、低耦合这六字箴言。设计组件也是一样的,我们应当考虑代码的高可复用性。然而在class组件中,我们实现一个功能,就不得不把相同业务的一些逻辑分散到各个生命周期中,就显得逻辑分散,比如我们设置一个定时器,就要考虑在合适的生命周期里面初始化定时器,以及销毁定时器等显的逻辑很分散

react hooks 逻辑复用更加便捷

Class组件逻辑复用一般使用的都是HOCRender Props。但这两者虽然都可以实现逻辑复用,但是并没有让组件和代码变得好用和优美起来,这二者都会存在的一个问题是,逻辑的复用会导致嵌套层级过深,形成嵌套地狱。使用class组件,表单组件、国际化、Redux等混在一块形成一长串的高阶组件的形式,就很恶心

三十、useState的实现原理

React 16.8.0 正式增加了 Hooks ,它为函数组件引入了 state 的能力,换句话说就是使函数组件拥有了 Class 组件的功能。

React.useState() 返回的第二个参数 setState 用于更新 state ,并且会触发新的渲染。同时,在后续新的渲染中 React.useState() 返回的第一个 state 值始终是最新的。

为了保证 memoizedState 的顺序与 React.useState() 正确对应,我们需要保证 Hooks 在最顶层调用,也就是不能在循环、条件或嵌套函数中调用。

React.useState() 通过 Object.is() 来判断 memoizedState 是否需要更新

三十一、useEffect如何写在依赖

import React, { useState, useEffect } from 'react';
export default function hook() {

  const [num, setNum] = useState(1)

  /**
   * 第一个参数是回调函数
   * 第二个参数是依赖项
   * 每次num变化时都会变化
   * 
   * 注意初始化的时候,也会调用一次
   */
  useEffect(() => {
    console.log("每次num,改变我才会触发")
    
    return () => {
      /**
       * 这是卸载的回调可以执行某些操作
       * 如果不想执行,则可以什么都不写
       */
      console.log("卸载当前监听")
    }
  }, [num])

  useEffect(() => {
    console.log("每次render页面我就会触发")
    return () => {
      console.log("卸载当前监听")
    }
  })

  return (
    <div>
      <button onClick={() => setNum(num + 1)}>+1</button>
      <div>你好,react hook{num}</div>
    </div>
  );
}

三十一、什么是不可变数据

在编程领域,Immutable Data 是指一种一旦创建就不能更改的数据结构。它的理念是:在赋值时,产生一个与原对象完全一样的新对象,指向不同的内存地址,互不影响

三十二、为什么 React 需要 Immutable Data

调用setState时,React 会以 shallowMerge(浅层合并) 的方式将我们传入的对象与旧的 state 进行合并。shallowMerge 只会合并新旧 state 对象中第一层的内容,如果 state 中对象的引用未变,那么 React 认为这个对象前后没有发生变化。所以如果我们以 mutable 的方式更改了 state 中的某个对象, React 会认为该对象并没有更新,那么相对应的 UI 就不会被重渲染。而以 Immutable 的方式更新 state 就不会出现这个问题

三十三、项目中如何使用不可变数据

一般来说,如果对象不是特别复杂可以直接使用结构赋值的方式,如果对象十分巨大那么可以使用immer这个开源库解决不可变数据的问题

immer的实现原理:原始对象先做了一层 Proxy 代理,得到 draftState 传递给 function。function(带副作用) 直接更改 draftState,最后 produce 返回新的对象

三十四、介绍一下React.memo

**React.memo 为高阶组件。**如果你的组件在相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useState,useReducer 或 useContext 的 Hook,当 state 或 context 发生变化时,它仍会重新渲染。默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现,第二个参数是一个函数,返回true不渲染,false渲染

三十五、React.memo的使用场景

1、展示型组件

如果你有一个仅仅用于展示数据的组件,且数据不需要频繁更新,那么可以使用 React.memo 避免不必要的重新渲染。

2、性能瓶颈

如果某个组件是你应用中性能瓶颈的主要原因,那么可以使用 React.memo 优化它的性能。

3、模拟 PureComponent

如果你想在函数组件中模拟类组件的 PureComponent 行为,那么可以使用 React.memo

三十六、React.PureComponent 和 React.memo的区别

、继承关系

React.PureComponent 是一个 React 组件类,可以被继承;而 React.memo 是一个高阶组件,不能被继承。

2、比较方式

React.PureComponent 使用的是浅层比较(shallow comparison)来决定是否需要重新渲染组件;而 React.memo 是通过比较组件的 props 来决定是否需要重新渲染的。如果需要进行深层比较,则需要使用自定义的比较函数(comparison function)。

3、使用场景

React.PureComponent 适用于状态不多、不需要处理复杂逻辑的组件;而 React.memo 则适用于任何情况下,甚至可以代替 React.PureComponent

三十七、什么是useMemo

它可以帮助你避免在不必要的情况下重新渲染组件。它通过对比当前状态和前一个状态,决定是否重新计算或记忆一个值。

三十八、useMemo的使用场景

1、数据过滤

如果你需要在组件中过滤大量数据,并且数据不需要频繁更新,那么可以使用 useMemo 将过滤结果缓存,避免不必要的重新计算。

2、计算值

如果你需要在组件中计算某些值,并且这些值不需要频繁更新,那么可以使用 useMemo 将这些值缓存,避免不必要的重新计算。

3、预处理

如果你需要在组件中进行复杂的预处理,并且预处理结果不需要频繁更新,那么可以使用 useMemo 将预处理结果缓存,避免不必要的重新计算

三十九、什么是useCallback(这里可以和React.memo关联,和过期闭包关联)

在 React 中,当组件重新渲染时,它会重新执行所有的函数,因此在频繁更新的组件中使用多个函数会导致不必要的性能开销。useCallback 可以解决这个问题,它接受两个参数:一个回调函数和一个依赖项列表。当依赖项列表中的任意一项改变时,useCallback 会重新定义回调函数,否则它会返回一个缓存的函数引用,从而避免不必要的函数重新定义

四十、useCallback 和 useMemo的区别

1、目的不同

useMemo 是用于缓存计算结果,useCallback 是用于缓存函数引用。

2、使用方法不同

useMemo 用于缓存计算结果,并在其依赖项发生变化时进行重新计算;而 useCallback 只是在依赖项发生变化时重新生成一个新的回调函数。

3、返回值不同

useMemo 返回缓存的计算结果,useCallback 返回一个缓存的回调函数

4、总结

总的来说,useMemo 适用于需要缓存计算结果的场景,useCallback 适用于缓存回调函数的场景。

四十一、hooks模仿componentDidMount

  useEffect(() => {
    /**
     * 当它是一个空数组时,回调只会被触发一次,类似于 componentDidMount
     */
    console.log("componentDidmount")
  }, [])

四十二、hooks模仿shouldComponentUpdate

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


const MyComponent =  React.memo((props) => {
  /* 使用 props 渲染 */
  return (
    <div>{props.num}</div>
  )
  /**
   * prevProps 上次的值
   * nextProps 最新的值
   * 
   * 如果传来的值是偶数的话则不更新组件
   */
}, (prevProps, nextProps) => {
  console.log(nextProps, nextProps.num % 2)
  return nextProps.num % 2 === 0
})

export default function hook() {

  const [num, setNum] = useState(1)

  useEffect(() => {
    /**
     * 当它是一个空数组时,回调只会被触发一次,类似于 componentDidMount
     */
    console.log("componentDidmount")
  }, [])

  return (
    <div>
      <button onClick={() => setNum(num + 1)}>+1</button>
      <MyComponent num={num}></MyComponent>
    </div>
  )
}

四十三、hooks模仿componentWillUnmount

  useEffect(() => {
   
    return () => {
      console.log('componentWillUnmount')
    }
  }, [])

四十四、封装hooks 实现componentDidUpdate的部分功能

// 引入我们封装的自定义hooks
import useDidUpdate from './hooks/use-didupdate'

export default () => {
   useDidupdate(() => {
     // 每次初始化的时候不触发,每次render都会触发
    console.log("模仿componentDidUpdate")
  })
}

四十四、什么是过期闭包

过期闭包(stale closure)是指一个闭包在创建之后,所引用的外部作用域内的变量已经被修改,但闭包内仍然保存了旧值。这就导致闭包中的代码与外部作用域内的实际状态不一致,从而造成错误的结果

四十五、react hook中的过时的闭包

在React中,过期闭包问题是指因为闭包的生命周期长于其引用的变量的生命周期而导致的问题。

在React组件的render函数中,如果使用了闭包引用组件的state或props,当state或props发生变化时,闭包将不会自动更新引用的变量。这就可能导致闭包引用了错误的值,从而导致组件的不正确行为。

四十六、react中遇到过那些过期闭包+做项目的时候遇到过那些坑吗

2、useEffect中过期闭包的表现

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

export default function hook() {

  const [count, setCount] = useState(0)
  /**
   * 每次点击都会调用,没切都是原来的值
   */
  useEffect(() => {
    // 是一个过时的闭包
    setInterval(() => {
      console.log(count)
    }, 2000)
  }, [])

  return (
    <div>
      {count}
      <button onClick={() => setCount(count + 1)}> 加1 </button>
    </div>
  )
}

3、useEffect解决方案

让useEffect()知道定时器的方法里面中的闭包依赖于count

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

export default function hook() {

  const [count, setCount] = useState(0)
  /**
   * 每次点击都会调用,没切都是原来的值
   */
  useEffect(() => {
    // 是一个过时的闭包
    const ter = setInterval(() => {
      console.log(count)
    }, 2000)
    
    // 每次调用前先清空定时器,或者说重新创建
    return () => {
      clearInterval(ter)
    }
    
    // 这行是重点,count变化后重新渲染useEffect
  }, [count])

  return (
    <div>
      {count}
      <button onClick={() => setCount(count + 1)}> 加1 </button>
    </div>
  )
}

4、useState过期闭包的表现

点击 +1 然后立即点击 +2,count 只更新到 1。这是因为 delay() 是一个过时的闭包

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

export default function hook() {

  const [count, setCount] = useState(0);

  /**
   *
   * delay() 是一个过时的闭包,它使用在初始渲染期间捕获的过时的 count 变量
   */
  function add() {
    setTimeout(function delay() {
      setCount(count + 1);
    }, 1000);
  }

  const add2 = () => {
    setCount(count + 2)
  }

  return (
    <div>
      {count}
      <button onClick={() => add()}>+1  </button>
      <button onClick={() => add2()}>+2</button>
    </div>
  )
}

5、useState解决方案

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

export default function hook() {

  const [count, setCount] = useState(0);

  /**
   *
   * delay() 是一个过时的闭包,它使用在初始渲染期间捕获的过时的 count 变量
   */
  function add() {
    setTimeout(function delay() {
      setCount((a) => a + 1);
    }, 1000);
  }

  const add2 = () => {
    setCount(count + 2)
  }

  return (
    <div>
      {count}
      <button onClick={() => add()}>+1  </button>
      <button onClick={() => add2()}>+2</button>
    </div>
  )
}

6、useCallback如果依赖项传一个空数组内部也会形成过期闭包

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

export default function hook() {

  const [count, setCount] = useState(0);

  /**
   *
   * delay() 是一个过时的闭包,它使用在初始渲染期间捕获的过时的 count 变量
   */
  function add() {
    setTimeout(function delay() {
      setCount((a) => a + 1);
    }, 1000);
  }

  const add2 = () => {
    setCount(count + 2)
  }

  useCallback(() => {
    // 值永远不会更新
    console.log(count)
  }, [])

  return (
    <div>
      {count}
      <button onClick={() => add()}>+1  </button>
      <button onClick={() => add2()}>+2</button>
    </div>
  )
}

四十六、什么场景下需要使用useReducer

1、当多个 state 需要一起更新时

2、当state 更新逻辑较复杂

3、当下一个 state 依赖于之前的 state,即 编写 setState(prevState => newState)时

包括但不限于以上三种

四十七、useReducer()相对于 useState() 的优势

1、useReducer 相对于 useState 可以更好的描述“如何更新状态”。 比如:useReducer 能够读取相关的状态、同时更新多个状态。

2、组件负责发出 action,reducer 负责更新状态的模式, 使得代码逻辑更加清晰。代码行为更加可以预测(比useEffect 的更新时机更加稳定)

3、通过传递 dispatch ,可以减少状态值的传递。 useReducer 总是返回相同的 dispatch 函数,这是彻底解耦的标志:状态更新逻辑可以任意变化,而发起 action 的渠道始终不变。

四十八、useRef事来解决什么问题的

1、JSX组件转换后对应的真实DOM对象。如何获取input的真实dom

useRef只适合“勾住”小写开头的类似原生标签的组件。如果是自定义的react组件(自定义的组件必须大写字母开头),那么是无法使用useRef(当然也有一些奇淫技巧后面我们后续课程会讲😈)的。比如,我们如何获取这个 真实DOM呢。那么就可以使用useRef

初始化input聚焦
import React,{useEffect,useRef} from 'react'


function Component() {
  //先定义一个inputRef引用变量,用于“勾住”挂载网页后的输入框
  const inputRef = useRef(null);


  useEffect(() => {
    //inputRef.current就是挂载到网页后的那个输入框,一个真实DOM,因此可以调用html中的方法focus()
    inputRef.current.focus();
  },[]);


  return <div>
    {/* 通过 ref 属性将 inputRef与该输入框进行“挂钩” */}
    <input type='text' ref={inputRef} />
  </div>
}
export default Component

2、在useEffect中创建的变量,如何在useEffect 以外的地方,获取并引用

例子

1、组件中有一个变量count,当该组件挂载到网页后,count每秒自动 +1。

2、组件中有一个按钮,点击按钮可以停止count自动+1

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


function Component() {
  const [count,setCount] =  useState(0);
  const timerRef = useRef(null);//先定义一个timerRef引用变量,用于“勾住”useEffect中通过setIntervale创建的计时器


  useEffect(() => {
    //将timerRef.current与setIntervale创建的计时器进行“挂钩”
    timerRef.current = setInterval(() => {
      setCount((prevData) => { return prevData +1});
    }, 1000);
    return () => {
      //通过timerRef.current,清除掉计时器
      clearInterval(timerRef.current);
    }
  },[]);


  const clickHandler = () => {
    //通过timerRef.current,清除掉计时器
    clearInterval(timerRef.current);
  };


  return (
    <div>
      {count}
      <button onClick={clickHandler} >stop</button>
    </div>
  )
}


export default Component

4、父组件调用子组件内的方法

注意:除非情况非常特殊,否则一般情况下都不要采用 父组件调用子组件的函数 这种策略

实现思路

父组件中通过 useRef 定义一个钩子变量,例如 childFunRef

父组件通过参数配置,将 childFunRef 传递给子组件

子组件在自己的 useEffect() 中定义一个函数,例如 doSomting()

划重点:一定要在 useEffect() 中定义 doSomting(),不能直接在子组件内部定义。

因为如果 doSomting() 定义在子组件内部,那么就会造成每一次组件刷新都会重新生成一份 doSomthing()

然后将 doSomting() 赋值到 childFunRef.current 中

这样,当父组件想调用子组件中的 doSomting() 时,可执行 childFunRef.current.doSomting()

父组件
import { useRef } from "react";
import ChildComponent from "./child";


const ParentComponent = () => {
  const childFunRef = useRef();
  const handleOnClick = () => {
    if (childFunRef.current) {
      childFunRef.current.doSomething();
    }
  };
  return (
    <div>
      <ChildComponent funRef={childFunRef} />
      <button onClick={handleOnClick}>执行子项的doSomething()</button>
    </div>
  );
};


export default ParentComponent;
子组件
import { useEffect, useState } from "react";


const ChildComponent = ({ funRef }) => {
  const [num, setNum] = useState(0);
  useEffect(() => {
    const doSomething = () => {
      setNum(Math.floor(Math.random() * 100));
    };
    funRef.current = { doSomething }; //在子组件中修改父组件中定义的childFunRef的值
  }, [funRef]);
  return <div>{num}</div>;
};


export default ChildComponent;

四十九、useRef与createRef的区别

1、useRef是针对函数组件的,如果是类组件则使用React.createRef()。

2、React.createRef()也可以在函数组件中使用。useRef只能在react hooks中使用

3、createRef每次都会返回个新的引用;而useRef不会随着组件的更新而重新创建

五十、介绍一下useImperativeHandle

useImperativeHandle可以让父组件获取并执行子组件内某些自定义函数(方法)。本质上其实是子组件将自己内部的函数(方法)通过useImperativeHandle添加到父组件中useRef定义的对象中。

注意:
1、useRef创建引用变量
2、React.forwardRef将引用变量传递给子组件
3、useImperativeHandle将子组件内定义的函数作为属性,添加到父组件中的ref对象上。

因此,如果想使用useImperativeHandle,那么还要结合useRef、React.forwardRef一起使用。

五十一、什么是React.forwardRef

React.forwardRef 会创建一个React组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。

五十二、useLayoutEffect和useEffect的区别

你可以把useLayoutEffect等同于componentDidMount、componentDidUpdate,因为他们调用阶段是相同的。而useEffect是在componentDidMount、componentDidUpdate调用之后才会触发的。

也就是说,当组件所有DOM都渲染完成后,同步调用useLayoutEffect,然后再调用useEffect。

useLayoutEffect永远要比useEffect先触发完成

五十三、你知道那些调试项目的手段

useDebugValue+React Developer Tools

详细看第36节——useDebugValue+React Developer Tools

五十四、什么是Concurrent React

并发可以理解为同时执行大量任务的能力。并发并不是一个特性。相反,它是一个幕后功能,它允许 React 同时(并发地)准备许多 UI 实例。

并发涉及同时执行多个状态更新,这可以说是 React 18 中最重要的特性。除了并发之外,React 18 还引入了两个新的钩子,称为 useTransition() 和 useDeferredValue() 钩子。

它们都有助于降低状态更新的优先级

五十五、useDeferredValue和useTransition的作用

useDeferredValue 和useTransition这两个钩子可以让我们延迟渲染不紧急的部分,类似于防抖但没有固定的延迟时间,延迟的渲染会在紧急的部分先出现在浏览器屏幕以后才开始,并且可中断不会阻塞用户输入。

简单理解就是如果说某些渲染比较消耗性能,比如存在实时计算和反馈,我们可以使用这个Hook降低其计算的优先级,使得避免整个应用变得卡顿。较多的场景可能就在于用户反馈输入上。比如百度的输入框,用户在输入的时候,页面会发生变化,但是输入框输入并不卡顿

五十六、useDeferredValue vs useTransition

1、相同点

useDeferredValue 本质上和内部实现与 useTransition 一样都是把任务标记成了过渡更新任务。

2、不同点

useTransition 是把 startTransition 内部的更新任务变成了过渡任务transtion,而 useDeferredValue 是把原值通过过渡任务得到新的值,这个值作为延时状态。 也就是说一个是处理一段逻辑,另一个是生产一个新的状态。

useDeferredValue 还有一个不同点就是这个任务,本质上在 useEffect 内部执行,而 useEffect 内部逻辑是异步执行的 ,所以它一定程度上更滞后于 useTransition。可以理解成useDeferredValue = useEffect + transtion

五十七、useTransition或者useDeferredValue和防抖的区别

1、节流防抖本质是 setTimeout ,只不过控制了执行的频率,原理就是让 render 次数减少了。而 transitions 和它相比,并没有减少渲染的次数。

2、节流和防抖需要有效掌握延迟时间,如果时间过长,那么给人一种渲染滞后的感觉,如果时间过短,那么就类似于 setTimeout(fn,0) 还会造成前面的问题。而 startTransition 就不需要考虑这么多

五十八、useId解决什么问题

1、React在服务端渲染,生成随机id(假设为0.1),这一步叫dehydrate(脱水)

2、

哈哈哈
作为HTML传递给客户端,作为首屏内容

3、React在客户端渲染,生成随机id(假设为0.2),这一步叫hydrate(注水)

就会出现客户端、服务端生成的id不匹配!

当然,服务端、客户端无法简单生成稳定、唯一的id是个由来已久的问题,早在15年就有人提过issue:

https://github.com/facebook/react/issues/4000

那么react的useId就是用来解决这个问题的

五十九、useId的实现原理(一般了解)

身份生成算法

身份 id 是 32 进制的字符串,其二进制表示对应树中节点的位置。

每次树分叉成多个子节点时,我们都会在序列的左侧添加额外的位数,表示子节点在当前子节点层级中的位置。

  00101       00010001011010101
    ╰─┬─╯       ╰───────┬───────╯
  Fork 5 of 20       Parent id

这里我们使用了两个前置 0 位。如果只考虑当前的子节点,我们使用 101 就可以了,但是要表达当前层级所有的子节点,三位就不够用。因此需要 5 位。

出于同样的原因,slots 是 1-indexed 而不是 0-indexed 。否则就无法区分该层级第 0 个子节点与父节点。

如果一个节点只有一个子节点,并且没有具体化的 id,声明时没有包含 useId hook。那么我们不需要在序列中分配任何空间。例如这两颗数会产生相同的 id:

<>                      <>
  <Indirection>           <A />
  <A />                   <B />
  </Indirection>        </>
  <B />
</>

为了处理这种情况,每次我们生成一个 id 时,都会分配一个一个新的层级。当然这个层级就只有一个节点「长度为 1 的数组」。

最后,序列的大小可能会超出 32 位,发生这种情况时,我们通过将 id 的右侧部分转换为字符串并将其存储在溢出变量中。之所以使用 32 位字符串,是因为 32 是 toString() 支持的 2 的最大幂数。这样基数足够大就能够得到紧凑的 id 序列,并且我们希望基数是 2 的幂,因为每个 log2(base) 对应一个字符,也就是 log2(32) = 5 bits = 1 ,这样意味着我们可以在不影响最终结果的情况下删除末尾 5 的位。

六十、useInsertionEffect出现的原因

这个钩子与其他钩子略有不同,因为它的唯一目的是对CSS-in-JS库很重要,这些库在运行中生成新的规则并在文档中插入

在某些情况下,

这实际上意味着CSS规则必须在React渲染时针对每一帧的所有DOM节点进行重新计算。虽然你很有可能不会遇到这个问题,但它的扩展性并不好。

为了解决这个问题,React团队引入了useInsertionEffect Hook。它与useLayoutEffect Hook非常相似,但它不能访问DOM节点的引用。

六十一、react-router中有哪些路由模型

1、HashRouter

HashRouter使用URL的哈希部分(即#后面的部分)来匹配路由,它不会向服务器发送请求。例如,URL可以是http://example.com/#/about。HashRouter兼容性比较好,哪怕浏览器不支持HTML5 History API也可以正常使用。

2、BrowserRouter

BrowserRouter使用HTML5 History API来匹配路由,使用 HTML5 的 pushState 和 replaceState API 来实现路由的切换。它可以隐藏URL中的#符号,使URL更加友好。例如,URL可以是http://example.com/about

3、MemoryRouter

MemoryRouter是一个不依赖于浏览器历史记录的路由器。它将URL存储在内存中,而不是浏览器历史记录中,适用于测试或在不支持HTML5 History API的环境中使用

4、StaticRouter

StaticRouter是一个用于服务器端渲染的路由器。它将请求的URL作为参数传递给组件,并将组件的输出发送回客户端。这样就可以在服务器端生成动态HTML,然后将其发送到浏览器。

5、NativeRouter

NativeRouter是用于React Native应用的路由器,它使用Native导航而不是HTML导航来匹配路由

六十二、react中有哪些路由跳转方式

1、普通跳转

import { useNavigate } from "react-router-dom";

export default function APage() {
  /**
   * 使用useNavigate钩子返回一个方法
   * 使用这个方法进行跳转
   */
  const navigate = useNavigate();

  const linlB = () => {
    // 直接跟我们定义的path
    navigate('/b')
  }

  return (
    <div>
      <div>A页面</div>
      <button onClick={() => linlB()}>跳转到B页面</button>
    </div>
  );
}

2、替换当前页面

import { useNavigate } from "react-router-dom";

export default function APage() {
  /**
   * 使用useNavigate钩子返回一个方法
   * 使用这个方法进行跳转
   */
  const navigate = useNavigate();

  const replaceB = () => {
    // 直接跟我们定义的path
    navigate('/b', { replace: true })
  }

  return (
    <div>
      <div>A页面</div>
      <button onClick={() => replaceB()}>把当前页面替换成B页面</button>
    </div>
  );
}

3、前进或后退到浏览器历史记录中的特定页面

function MyComponent() {
  const navigate = useNavigate();

  function handleBack() {
    // 后退几页
    navigate(-1);
  }

  function handleForward() {
    // 前进几页
    navigate(1);
  }

  return (
    <div>
      <button onClick={handleBack}>Back</button>
      <button onClick={handleForward}>Forward</button>
    </div>
  );
}

六十三、react路由传参

1. 路由参数(params形式)

路由参数是将参数嵌入到 URL 中的一种方式。在 React Router 6 中,我们可以通过在路由路径中使用冒号来定义参数

定义
<Route path="/users/:id" element={<UserDetails />} />
获取

我们定义了一个名为 id 的参数,它可以在 URL 中的 /:id 部分找到。当用户访问 /users/123 时,123 将成为路由参数,并可以在组件中通过 useParams 钩子函数访问

function UserDetails() {
  const { id } = useParams();
  // ...
}

2、查询参数(search形式)

查询参数是在 URL 中使用问号传递的一种参数。在 React Router 6 中,我们可以通过在 URL 中添加查询参数来传递参数

定义
<Link to="/users?id=123">User Details</Link>
获取

我们向 /users 页面传递了一个名为 id 的查询参数,并将其设置为 123。我们可以在组件中使用 useLocation 钩子函数获取当前 URL 中的查询参数,并使用 URLSearchParams 对象来解析它们

function UserDetails() {
  const searchParams = useSearchParams();
  // 使用URLSearchParams这个对象解析url的search,然后直接获取id
  const id = searchParams.get('id');
  // ...
}

3. 状态对象

状态对象是一种可以在导航期间传递数据的机制。在 React Router 6 中,我们可以在 navigate 函数中使用第二个参数来传递状态对象

定义
function handleClick() {
  navigate('/users', { state: { id: 123 } });
}
获取

我们在导航到 /users 页面时传递了一个名为 id 的状态对象。我们可以在组件中使用 useLocation 钩子函数获取当前 URL 中的状态对象

function UserDetails() {
  const location = useLocation();
  const { id } = location.state;
  // ...
}
注意

使用状态对象传递数据会将数据存储在浏览器的会话历史中,因此它仅适用于页面之间的相邻导航。如果用户从当前页面返回到其他页面,状态对象中的数据将被清除。如果需要在不同页面之间共享数据,最好使用其他的数据传递方式,如 Redux 或 Context API

六十四、react如何实现嵌套路由

使用Outlet实现

在路由组件里直接定义

六十五、代码分割(路由懒加载)

React Router 在使用时,会把所有路由相关的组件都打包进同一个 JavaScript 文件中,这会导致整个应用的体积变得很大,加载时间变长。为了解决这个问题,我们可以使用代码分割(code splitting)技术,将不同的路由组件分别打包成不同的 JavaScript 文件,实现按需加载。

六十六、路由 (V5) 和路由 (V6) 的区别

react Router v6使用Hooks来实现路由,而v5使用高阶组件(HOCs)来实现路由。这是它们之间最大的区别。

1、路由配置

React Router v5中的路由配置需要将Route组件作为子组件嵌套在Switch组件中。而React Router v6中的路由配置方式发生了变化。现在,我们需要在Routes组件中使用数组来配置路由。

2、嵌套路由

在React Router v6中,嵌套路由的使用方式更加简单直观。在v5中,嵌套路由需要在组件之间进行深度传递props,而在v6中,可以使用嵌套路由。

3、状态管理

React Router v6通过提供useSearchParams、useLocation和useNavigate等Hooks,使得状态管理变得更加方便。这些Hooks可以帮助我们在不同的路由之间共享状态,而在v5中需要使用类似于redux等外部状态管理库来实现。

六十七、如何在类组件中使用最新版的路由v6

编写一个高阶组件

import React from "react";
import { useNavigate } from "react-router-dom";

export default function WithRouter(WarpComponent) {
  const navigate = useNavigate();
  return <WarpComponent {...this.props} navigate={navigate}></WarpComponent>;
}

六十八、什么是redux

Redux 是一种 JavaScript 库,用于管理应用的全局状态。它的目的是帮助开发者管理和同步应用中的数据状态,以实现组件间的数据共享和通信。

Redux 遵循了一种单向数据流的架构模式,将整个应用的状态数据存储在一个全局的状态树(即 store)中,并通过明确的操作,比如 dispatch 一个 action,来修改数据状态。这样可以有效地降低数据状态的耦合度,使得代码更加可维护和可读。

Redux 还支持中间件(middleware)和插件(plugins),允许开发者扩展其功能,以适应不同的业务需求。它也支持热加载(hot reloading),可以在不重启应用的情况下更新代码。

总的来说,Redux 是一个用于简化应用状态管理的工具,广泛应用在 React 和其他前端框架中。

六十九、什么是redux store

Redux 是一个用于管理 JavaScript 应用状态的库。在 Redux 中,整个应用的状态都存储在一个对象中,称为 store。

Store 实际上是一个 JavaScript 对象,它存储了整个应用的状态。它是唯一的,意味着应用中只有一个 store。每当状态发生变化,它会存储最新的状态。

使用 Redux 时,你可以通过调用 store.getState() 来获取当前应用的状态,通过调用 store.dispatch(action) 来更新应用的状态,其中 action 是一个描述发生了什么的对象。

七十、如何分割state或者说分割reducer

使用combineReducers可以对redux进行模块化管理,在 Redux 中,你可以使用多个 Reducer 来处理不同的数据,然后使用 combineReducers 函数将它们合并起来。

七十一、什么是action

在 Redux 中,Action 是一个简单的 JavaScript 对象,用于描述对应应用中的某个事件(例如用户操作)所发生的变化。它包含了一个 type 属性,用于表示事件的类型,以及其他一些可选的数据。

Action 可以被 Redux Store 中的 reducer 函数捕获并处理,从而对应用的状态进行更新。通过使用 Action,可以实现可预测、可追踪和可测试的应用状态管理。

七十二、什么是reducer

Reducer 是 Redux 中的一个概念,它是一个纯函数,用于处理应用的状态变更。Reducer 的输入是当前状态和一个操作(action),输出是下一个状态。

在 Redux 中,所有的状态变更都必须通过发送一个 action 实现。每一个 action 都是一个描述状态变更的对象,包含了一个 type 属性和一些其他属性。当一个 action 被发送到 store,它会触发 store 对应的 reducer,使用当前状态和 action 来生成下一个状态。

七十三、为什么redux中要使用不可变数据

Redux 要求使用不可变数据,是因为它遵循了函数式编程的原则。在函数式编程中,数据不可变是一项重要的原则,这有助于避免状态更改产生的不可预知的副作用。

在 Redux 中,每当 action 被分发,reducer 都会接收到当前的状态和 action,并返回一个新的状态。如果使用的是可变数据类型,并且在 reducer 中直接对状态进行修改,就会造成状态的不可预知的更改。

因此,Redux 要求使用不可变数据,是为了更好地管理应用状态,以及提高代码的可读性和可维护性

七十四、redux中如何使用中间件

applyMiddleware 是 Redux 的一个高阶函数,用于向 Redux Store 应用中间件。

中间件是一个函数,它可以在 dispatch 操作执行前后,对 action 进行拦截、处理、修改等操作。例如:日志记录、错误捕获、异步请求、数据缓存等等。

使用 applyMiddleware,你可以实现额外的功能,并且可以在不修改原始代码的情况下对其进行扩展。

七十五、项目中有封装过哪些中间件

import { produce } from 'immer';

// 定义immerMiddleware中间件

/**
 * 
 * store createStore后返回的store,可以使用他的任何方法
 * next 
 * @returns 
 */
const immerMiddleware = store => next => action => {
  // 使用immer的produce函数生成新的state
  const newState = produce(store.getState(), draft => {
    /**
     * next可以调用对应的action里面的reducer
     * 并且可以拿到reducer返回的结果
     * 我们把返回的结果给draft赋值
     */
    draft = next(action);
  });

  // 返回新的state
  return newState;
};

export default immerMiddleware;

七十六、如何封装封装actions模块

bindActionCreators 是 Redux 中的一个函数,用于将多个 action creators 绑定到 dispatch 函数上,使得可以在 Redux 应用中轻松调用这些 action creators。

七十八、redux中如何编写异步代码

使用redux-thunk,redux-thunk 是一个用于处理 Redux 异步 action 的中间件。它允许我们在 Redux 中编写异步的 action 创建函数(action creator),这些 action 创建函数可以返回一个函数而不是一个 action 对象。这个返回的函数可以接收 dispatch 和 getState 两个参数,并在函数体内进行异步操作后,再使用 dispatch 方法触发一个或多个普通的同步 action

七十九、redux-thunk的工作原理

源码解读

function thunkMiddleware({ dispatch, getState }) {
  return next => action => {
    // 如果 action 是一个函数,那么调用它,并传递 dispatch 和 getState 函数作为参数
    if (typeof action === 'function') {
      return action(dispatch, getState);
    }
    // 否则,调用下一个中间件
    return next(action);
  };
}

八十、flux架构

Flux 是一种由 Facebook 提出的前端应用程序架构,旨在解决传统的 MVC(Model-View-Controller)架构在复杂应用场景下出现的一系列问题。Flux 基于单向数据流的思想,将应用程序拆分为四个主要组件:Action、Dispatcher、Store 和 View。

此处为语雀内容卡片,点击链接查看:https://www.yuque.com/wangkai-jjgja/ef1il2/yu91c1s5c3u3nsst

九十、介绍一下useSyncExternalStore+redux 的实现原理

useSyncExternalStore 是一个新的钩子,它通过强制的同步状态更新,使得外部 store 可以支持并发读取。它实现了对外部数据源订阅时不在需要 useEffect,并且推荐用于任何与 React 外部状态集成的

九十一、介绍一下Redux Toolkit(RTK)

简化Redux的配置

Redux Toolkit提供了一个createSlice函数,可以用来快速创建Redux的action和reducer,不需要手动编写大量的模板代码。

封装常用的Redux函数

Redux Toolkit提供了一些封装过的Redux函数,如createAsyncThunk、createEntityAdapter等,这些函数可以帮助开发者更加容易地处理异步操作、管理实体数据等常见任务。

整合常用的中间件

Redux Toolkit默认集成了常用的中间件,如redux-thunk、redux-logger等,使得开发者可以更加便捷地使用这些中间件,而不需要手动配置。

提供默认的Redux store配置

Redux Toolkit提供了一个configureStore函数,可以用来快速创建一个Redux store,并且默认配置了许多常用的中间件和插件,减少了开发者的配置工作量。

九十二、介绍一下redux-toolkit中的configureStore

configureStore是Redux Toolkit中的一个工厂函数,用于创建Redux store。它的目的是简化Redux store的设置,并提供许多默认设置和内置的中间件,使其更易于使用。

九十三、什么是切片

在 Redux 中,一个 state tree 是由多个 reducer 组成的,每个 reducer 负责管理一个 state 的一部分,这个概念称为“切片”(slice)。使用切片的好处在于,每个 reducer 只需要关心自己管理的部分 state,而不需要关心整个 state tree 的结构。这样可以使 reducer 的代码更加清晰和易于维护。此外,切片还可以让我们更加方便地组织 action creators 和 selectors,从而使代码结构更加清晰

九十四、介绍一下createSlice

createSlice是Redux Toolkit中提供的一个用于快速创建Redux reducer的函数。它基于Redux reducer的标准结构,使得创建和更新Redux state更加方便、简洁。使用createSlice函数创建的slice包含了一个Redux reducer、一组action creators和对应的action types。

九十五、createSlice解决了什么问题

createSlice 解决了传统 Redux 应用中需要手动编写 reducer 和 action creators 的问题,简化了 Redux 应用程序的开发流程,提高了代码的可读性和可维护性。

在传统的 Redux 应用程序中,我们需要手动编写 reducer 函数来处理每个 action type,然后将它们组合成一个大的 rootReducer 函数。这样的代码结构往往比较冗长和繁琐,同时也容易出错。而 createSlice 的出现,让我们可以通过提供一个包含 reducer 和 action creators 的对象来自动生成相应的 reducer 函数和 action creators,从而避免了手动编写 reducer 的过程,使得开发者更加专注于业务逻辑的实现。

九十六、Redux Toolkit中如何编写异步代码

使用createAsyncThunk, createAsyncThunk 是一个由 Redux Toolkit 提供的函数,用于创建处理异步操作的 thunk action creator。使用 createAsyncThunk 可以简化 Redux 中处理异步操作的流程,使代码更加清晰、简洁。

九十七、介绍一下redux-toolkit中的createSelector

在 Redux Toolkit 中,createSelector 是一个函数,它允许我们从 Redux store 中选择部分状态数据,并将其作为参数传递给一个 memoized 函数,以避免在相同的输入下重复计算。此外,Redux Toolkit 中的 createSelector 还支持嵌套选择器和参数选择器,使得选择和组合 Redux store 中的状态更加灵活

九十八、createSelector的使用场景

createSelector函数主要用于优化React应用程序中的性能,特别是在具有大量数据的情况下。它的主要用途是创建输出选择器函数,该函数将redux store中的多个状态组合并到单个值中,并将该值缓存以提高性能

1、过滤和排序数据

通过createSelector函数,可以根据多个条件从Redux store中选择数据,并使用JavaScript函数对其进行过滤、排序等处理。

2、转换数据格式

通过createSelector函数,可以将Redux store中的原始数据转换为更易于处理的格式,如图表数据,饼状图数据等。

3、避免不必要的渲染

使用createSelector函数可以避免不必要的渲染。当createSelector函数的输入参数未更改时,将从缓存中返回结果。只有当输入参数更改时,createSelector函数才会重新计算其输出,并在React组件中触发渲染。

4、避免重复计算

在Redux store中包含大量数据时,使用createSelector函数可以避免不必要的计算。例如,可以通过创建一个选择器函数,该函数选择一个对象数组并返回其长度来避免在每次计算数组长度时进行重复的大量计算

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值