【精简版】前端面试宝典(React)

575 阅读27分钟

React

React 和 Vue 的异同

共同点

  • 都将注意力集中保持在核心库,而将其他功能如路由和全局状态管理交给相关的库;
  • 都有自己的构建工具,能让你得到一个根据最佳实践设置的项目模板;
  • 都使用了Virtual DOM(虚拟DOM)提高重绘性能;
  • 都有props的概念,允许组件间的数据传递;
  • 都鼓励组件化应用,将应用分拆成一个个功能明确的模块,提高复用性。

不同之处

(1) 数据流向不同:Vue默认是双向数据流,而React一直提倡单向数据流

(2) 虚拟DOM:Vue2.x开始引入"Virtual DOM",消除了和React在这方面的差异,但是在具体的细节还是有各自的特点。

  • Vue宣称可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。
  • 对于React而言,每当应用的状态被改变时,全部子组件都会重新渲染。当然,这可以通过 PureComponent/shouldComponentUpdate这个生命周期方法来进行控制,但Vue将此视为默认的优化。

(3) 组件化:React与Vue最大的不同是模板的编写。

  • Vue鼓励写近似常规HTML的模板。写起来很接近标准 HTML元素,只是多了一些属性。
  • React推荐你所有的模板通用JavaScript的语法扩展——JSX书写。

具体来讲:React中render函数是支持闭包特性的,所以import的组件在render中可以直接调用。但是在Vue中,由于模板中使用的数据都必须挂在 this 上进行一次中转,所以 import 一个组件完了之后,还需要在 components 中再声明下。

(4) 监听数据变化的实现原理不同

  • Vue 通过 getter/setter 以及一些函数的劫持,能精确知道数据变化,不需要特别的优化就能达到很好的性能
  • React 默认是通过比较引用的方式进行的,如果不优化(PureComponent/shouldComponentUpdate)可能导致大量不必要的vDOM的重新渲染。这是因为 Vue 使用的是可变数据,而React更强调数据的不可变。

(5) 高阶组件:react可以通过高阶组件(HOC)来扩展,而Vue需要通过mixins来扩展。

高阶组件就是高阶函数,而React的组件本身就是纯粹的函数,所以高阶函数对React来说易如反掌。相反Vue.js使用HTML模板创建视图组件,这时模板无法有效的编译,因此Vue不能采用HOC来实现。

(6) 构建工具Vue=>vue-cliReact=>Create React App

(7) 跨平台:React => React Native;Vue => Weex

(8) 维护性:Vue 是完整一套由官方维护的框架,核心库主要有由尤雨溪大神独自维护,而React 是FaceBook维护,很多库由社区维护。

React基础

React 是用于构建用户界面的 JavaScript 库,提供了 UI 层面的解决方案,遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效,使用虚拟DOM来有效地操作 DOM,遵循从高阶组件到低阶组件的单向数据流,帮助我们将界面成了各个独立的小块,每一个块就是组件,这些组件之间可以组合、嵌套,构成整体页面.

React理解:React 是一个网页UI框架,通过组件化的方式解决视图层开发复用的问题,本质是一个组件化框架。

它的核心设计思路有三点:

  • 声明式:优势在于直观与组合
  • 组件化:优势在于视图的拆分与模块复用,可以更容易做到高内聚低耦合
  • 通用性:通用性在于一次学习,随处编写

特性:JSX语法、单向数据绑定、虚拟DOM、声明式编程、Component(组件化)

优势

  • 高效灵活
  • 声明式的设计,简单使用
  • 组件式开发,提高代码复用率
  • 单向响应的数据流会比双向绑定的更安全,速度更快

React 事件机制

React 并不是将click事件绑定到了div的真实DOM上,而是在document处监听了所有的事件,当事件发生并且冒泡到document处的时候,React将事件内容封装并交由真正的处理函数运行。这样的方式不仅仅减少了内存的消耗,还能在组件挂在销毁时统一订阅和移除事件。

React 组件的 state 和 props

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

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

区别

  • props 是传递给组件的(类似于函数的形参),而state 是在组件内被组件自己管理的(类似于在一个函数内声明的变量)。
  • props 是不可修改的,所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。
  • state 是在组件中创建的,一般在 constructor中初始化 state。state 是多变的、可以修改,每次setState都异步更新的。

React 中 setState 的第二个参数作用

setState 的第二个参数是一个可选的回调函数。这个回调函数将在组件重新渲染后执行。等价于在 componentDidUpdate 生命周期内执行。通常建议使用 componentDidUpdate 来代替此方式。在这个回调函数中你可以拿到更新后 state 的值

纯函数

特点

  • 给定相同的输入,总是返回相同的输出。
  • 过程没有副作用。
  • 不依赖外部状态。

React.Fragment

相关资料:一文让你彻底理解 React Fragment

React Fragment 是 React 中的一个特性,它允许你对一组子元素进行分组,而无需向 DOM 添加额外的节点,从而允许你从 React 组件中返回多个元素。

组件基础

组件本质上就是类和函数,但是与常规的类和函数不同的是,组件承载了渲染视图的 UI 和更新视图的 setState 、 useState 等方法

React高阶组件

官方解释:高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

高阶组件(HOC)就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件,它只是一种组件的设计模式,这种设计模式是由react自身的组合性质必然产生的。我们将它们称为纯组件,因为它们可以接受任何动态提供的子组件,但它们不会修改或复制其输入组件中的任何行为。

高阶组件运用了装饰模式,,装饰模式的特点是不需要改变被装饰对象本身,而只是在外面套一个外壳接口。

优点:逻辑服用、不影响被包裹组件的内部逻辑。

缺点:hoc传递给被包裹组件的props容易和被包裹后的组件重名,进而被覆盖

适用场景

  • 代码复用,逻辑抽象
  • 渲染劫持
  • State 抽象和更改
  • Props 更改

有状态组件和无状态组件

有状态组件:  是一个class类,继承componet(用于需要一些状态去存储和修改数据)

无状态组件:  是一个es6写的箭头函数函数,并不继承 componet(用于一些简单的逻辑,比如,父组件向子组件传属性值)

区别

  • 最大的区别是无状态组件,无法使用state,因为state是继承 componet
  • 无状态组件,没有生命周期函数,生命周期函数是基于state的

受控组件 和 非受控组件

受控组件:是 React 控制中的组件,并且是表单数据真实的唯一来源。

非受控组件:由 DOM 处理表单数据的地方,而不是在 React 组件中。

生命周期

React的生命周期

三个阶段

  • 挂载阶段(Mount):组件第一次在DOM树中被渲染的过程;
  • 更新过程(Update):组件状态发生变化,重新更新渲染的过程;
  • 卸载过程(Unmount):组件从DOM树中被移除的过程;

image.png React常见生命周期的过程大致如下:

  • 挂载阶段,首先执行 constructor 构造方法,来创建组件
  • 创建完成后会执行 render 方法,该方法会返回需要渲染的内容
  • 随后,React 会将需要渲染的内容挂载到 DOM 树上
  • 挂载完成之后就会执行 componentDidMount 生命周期函数
  • 如果我们给组件创建一个 props(用于组件通信)、调用 setState(更改state中的数据)、调用 forceUpdate(强制更新组件)时,都会重新调用render函数
  • render 函数重新执行之后,就会重新进行DOM树的挂载
  • 挂载完成之后就会执行 componentDidUpdate 生命周期函数
  • 当移除组件时,就会执行 componentWillUnmount 生命周期函数

组件挂载阶段

挂载阶段组件被创建,然后组件实例插入到 DOM 中,完成组件的第一次渲染,该过程只会发生一次,在此阶段会依次调用以下这些方法:

  • constructor():在 React 组件挂载之前,会调用它的构造函数。
  • getDerivedStateFromProps:在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用
  • render:class 组件中唯一必须实现的方法
  • componentDidMount:在组件挂在后(插入 DOM 树中)立即调用

constructor

特点

  • React.Component 子类实现构造函数时,应在其他语句之前前调用 super(props)
  • 不要调用 setState() 方法。如果组件需要使用内部 state,请直接在构造函数中为 this.state 赋值初始 state

组件的构造函数,第一个被执行,若没有显式定义它,会有一个默认的构造函数,但是若显式定义了构造函数,必须在构造函数中执行 super(props),否则无法在构造函数中拿到this。

如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数Constructor。

constructor 中通常只做两件事:

  • 初始化组件的 state
  • 给事件处理方法绑定 this

render

render 是 React 中最核心的方法,一个组件中必须要有这个方法,它会根据状态 state 和属性 props 渲染组件。

这个函数只做一件事,就是返回需要渲染的内容,所以不要在这个函数内做其他业务逻辑,通常调用该方法会返回以下类型中一个:

  • React 元素:这里包括原生的 DOM 以及 React 组件;
  • 数组和 Fragment(片段) :可以返回多个元素;
  • Portals(插槽) :可以将子元素渲染到不同的 DOM 子树种;
  • 字符串和数字:被渲染成 DOM 中的 text 节点;
  • 布尔值或 null:不渲染任何内容。

componentDidMount()

componentDidMount()会在组件挂载后(插入 DOM 树中)立即调用。
该阶段通常进行以下操作

  • 执行依赖于DOM的操作;
  • 发送网络请求;(官方建议)
  • 添加订阅消息(会在componentWillUnmount取消订阅);

注意:如果在 componentDidMount 中调用 setState ,就会触发一次额外的渲染,多调用了一次 render 函数,由于它是在浏览器刷新屏幕前执行的,所以用户对此是没有感知的,但是应当避免这样使用,这样会带来一定的性能问题,尽量是在 constructor 中初始化 state 对象。

组件更新阶段

当组件的 props 改变了,或组件内部调用了setState / forceUpdate,会触发更新重新渲染,这个过程可能会发生多次,调用顺序如下:

  • getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • getSnapshotBeforeUpdate
  • componentDidUpdate

getDerivedStateFromProps()

getDerivedStateFromProps() 会在调用 render 方法之前调用,即在渲染 DOM 元素之前会调用,并且在初始挂载及后续更新时都会被调用。该方法返回一个对象用于更新 state,如果返回 null 则不更新任何内容。
目的:让组件在 props 变化时更新 state。

shouldComponentUpdate()

shouldComponentUpdate() 方法会返回一个布尔值,指定 React 是否应该继续渲染,默认值是 true, 即 state 每次发生变化组件都会重新渲染。
它的返回值用于判断 React 组件的输出是否受当前 state 或 props 更改的影响,当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。

getSnapshotBeforeUpdate()

getSnapshotBeforeUpdate() 方法在最近一次渲染输出(提交到 DOM 节点)之前调用。
在 getSnapshotBeforeUpdate() 方法中,可以访问更新前的 props 和 state。
它需要与 componentDidUpdate() 方法一起使用,否则会出现错误

组件卸载阶段

componentWillUnmount() 方法在组件卸载及销毁之前直接调用。
该方法中不应调用 setState(),因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。

props 相关

componentWillReceiveProps:

props发生变化时执行componentWillReceiveProps
特点

  • 组件初次渲染时不会执行componentWillReceiveProps;
  • 当 props 发生变化时执行componentWillReceiveProps;
  • 在这个函数里面,旧的属性仍可以通过this.props来获取;
  • 此函数可以作为 react 在 prop 传入之后, render() 渲染之前更新 state 的机会。即可以根据属性的变化,通过调用this.setState()来更新你的组件状态,在该函数中调用 this.setState() 将不会引起第二次渲染。
  • 也可在此函数内根据需要调用自己的自定义函数,来对prop的改变做出一些响应。

注意:当父组件向子组件传递引用类型(或复合类型,比如对象、数组等)的属性时,要注意打印的this.props和nextProps的内容是一致的,因为引用类型在内存中只有一份,传值时是浅拷贝

组件通信

React的5种主流通信方式

  1. props 和 callback 方式
  2. ref 方式。
  3. React-redux 或 React-mobx 状态管理方式。
  4. context 上下文方式。
  5. event bus 事件总线

组件间常见的通信情况:父组件向子组件通信、子组件向父组件通信、跨级组件通信、非嵌套关系的组件通信

组件通信的方式有哪些

  • 父组件向子组件通讯:传 props 的⽅式
  • 子组件向父组件通讯:props+回调的方式,父组件向⼦组件传递props进行通讯,此props为作⽤域为父组件⾃身的函数,子组件调用该函数,将子组件想要传递的信息,作为参数,传递到父组件的作⽤域中
  • 兄弟组件通信:找到这两个兄弟节点共同的父节点,结合上面两种方式由父节点转发信息进行通信
  • 跨层级通信:Context 设计目的是为了共享那些对于⼀个组件树而言是“全局”的数据,例如当前认证的⽤户、主题或首选语⾔,对于跨越多层的全局数据通过 Context 通信再适合不过
  • 发布订阅模式: 发布者发布事件,订阅者监听事件并做出反应,可以通过引⼊event模块进⾏通信
  • 全局状态管理工具: 借助Redux或者Mobx等全局状态管理⼯具进行通信,这种工具会维护⼀个全局状态中⼼Store,并根据不同的事件产⽣新的状态

跨级组件的通信方式

父组件向子组件的子组件通信,向更深层子组件通信:

  • 使用props,利用中间组件层层传递,但是如果父组件结构较深,那么中间每一层组件都要去传递props,增加了复杂度,并且这些props并不是中间组件自己需要的。
  • 使用context,context相当于一个大容器,可以把要通信的内容放在这个容器中,这样不管嵌套多深,都可以随意取用,对于跨越多层的全局数据可以使用context实现。

非嵌套关系组件的通信方式

即没有任何包含关系的组件,包括兄弟组件以及不在同一个父级中的非兄弟组件。

  • 自定义事件通信(发布订阅模式)
  • 通过redux等进行全局状态管理
  • 如果是兄弟组件通信,可以找到这两个兄弟节点共同的父节点, 结合父子间通信方式进行通信。

如何解决 props 层级过深的问题

  • 使用Context API:提供一种组件之间的状态共享,而不必通过显式组件树逐层传递props;
  • 使用Redux等状态库。

路由

React-Router

实现思想

  • 基于 history 库来实现上述不同的客户端路由实现思想,并且能够保存历史记录等,磨平浏览器差异,上层无感知
  • 通过维护的列表,在每次 URL 发生变化的回收,通过配置的 路由路径,匹配到对应的 Component,并且 render

如何配置实现路由切换

  • 使用<Route> 组件
  • 结合使用 <Switch> 组件和 <Route> 组件
  • 使用 <Link>、 <NavLink>、<Redirect> 组件

怎么设置重定向:使用<Redirect>组件实现路由的重定向:

react-router 里的 Link 标签和 a 标签的区别<a>标签就是普通的超链接,用于从当前页面跳转到href指向的另一 个页面(非锚点情况); <Link>是 react-router 里实现路由跳转的链接,一般配合<Route> 使用,react-router 接管了其默认的链接跳转行为,区别于传统的页面跳转,<Link> 的“跳转”行为只会触发相匹配的<Route>对应的页面内容更新,而不会刷新整个页面。 <Link>做了3件事情:

  • 有onclick那就执行onclick
  • click的时候阻止a标签默认事件
  • 根据跳转href(即是to),用history (web前端路由两种方式之一,history & hash)跳转,此时只是链接变了,并没有刷新页面。

获取URL的参数

  • get传值
  • 动态路由传值
  • 通过query或state传值

获取历史对象

  • 如果React >= 16.8 时可以使用 React Router中提供的Hooks
  • 使用this.props.history获取历史对象

路由有几种模式:支持使用 hash(对应 HashRouter)和 browser(对应 BrowserRouter) 两种路由规则, react-router-dom 提供了 BrowserRouter 和 HashRouter 两个组件来实现应用的 UI 和 URL 同步

  • BrowserRouter:使用 HTML5 提供的 history API(pushState、replaceState 和 popstate 事件)来保持 UI 和 URL 的同步。
  • HashRouter:使用 URL 的 hash 部分(即 window.location.hash)来保持 UI 和 URL 的同步。

React-Router 4 的 Switch有什么用:Switch 通常被用来包裹 Route,用于渲染与路径匹配的第一个子 <Route><Redirect>,它里面不能放其他元素。

Redux

为什么需要Redux

  • JS开发的应用程序需要管理的状态越来越多,越来越复杂,这些状态包括服务器返回的数据、缓存数据、用户操作产生的数据等等,也包括一些UI的状态,比如某些元素是否被选中,是否显示加载动效,当前分页;

  • 管理不断变化的state是非常困难的:

  • React是在视图层帮助我们解决了DOM的渲染过程,但是State依然是留给我们自己来管理:

  • Redux就是一个帮助我们管理State的容器:Redux是JavaScript的状态容器,提供了可预测的状态管理;

  • Redux除了和React一起使用之外,它也可以和其他界面库一起来使用(比如Vue),并且它非常小(包括依赖在内,只有2kb)

概念:Redux是专门用于集中式管理状态的javascript库,它基于flux,并不是React的插件库。 Redux简化了React中的单向数据流。 Redux将状态管理完全从React中抽象出来。

主要解决的问题:单纯的Redux只是一个状态机,是没有UI呈现的,react-redux作用是将Redux的状态机和React的UI呈现绑定在一起,当你dispatch action改变state的时候,会自动更新页面。

Redux的核心理念

  • Store

  • action:Redux要求我们通过action来更新数据: 所有数据的变化,必须通过派发(dispatch)action来更新; action是一个普通的JavaScript对象,用来描述这次更新的type和content;

  • reducer:reducer是一个纯函数; reducer做的事情就是将传入的state和action结合起来生成一个新的state;

Redux三大原则

  1. 单一数据源
  2. state是只读的,修改state唯一方法就是触发action
  3. 使用纯函数 reducer来修改state,返回一个新的state给store

Redux工作流程

  • View在redux中会派发action方法
  • action通过store的dispatch方法会派发给store
  • store接收action,连同之前的state,一起传递给reducer
  • reducer返回新的数据给store
  • store去改变自己的state\

Redux 中异步的请求怎么处理

可以在 componentDidmount 中直接进⾏请求⽆须借助redux,但是一定规模的项目,会借助redux的异步中间件进⾏异步处理

主流的异步中间件有两种:redux-thunk、redux-saga

react-thunk中间件

优点:

  • 体积⼩: redux-thunk的实现⽅式很简单,只有不到20⾏代码
  • 使⽤简单: redux-thunk没有引⼊像redux-saga或者redux-observable额外的范式,上⼿简单

缺陷:

  • 样板代码过多: 与redux本身⼀样,通常⼀个请求需要⼤量的代码,⽽且很多都是重复性质的
  • 耦合严重: 异步操作与redux的action偶合在⼀起,不⽅便管理
  • 功能孱弱: 有⼀些实际开发中常⽤的功能需要⾃⼰进⾏封装

使用步骤

  • 配置中间件,在store的创建中配置
  • 添加一个返回函数的actionCreator,将异步请求逻辑放在里面
  • 生成action,并发送action

使用 redux-saga 中间件

优点:

  • 异步解耦: 异步操作被被转移到单独 saga.js 中,不再是掺杂在 action.js 或 component.js 中
  • action摆脱thunk function: dispatch 的参数依然是⼀个纯粹的 action (FSA),⽽不是充满 “⿊魔法” thunk function
  • 异常处理: 受益于 generator function 的 saga 实现,代码异常/请求失败 都可以直接通过 try/catch 语法直接捕获处理
  • 功能强⼤: redux-saga提供了⼤量的Saga 辅助函数和Effect 创建器供开发者使⽤,开发者⽆须封装或者简单封装即可使⽤
  • 灵活: redux-saga可以将多个Saga可以串⾏/并⾏组合起来,形成⼀个⾮常实⽤的异步flow
  • 易测试,提供了各种case的测试⽅案,包括mock task,分⽀覆盖等等

缺陷:

  • 额外的学习成本: redux-saga不仅在使⽤难以理解的 generator function,⽽且有数⼗个API,学习成本远超redux-thunk,最重要的是额外学习成本是只服务于这个库的,与redux-observable不同,redux-observable虽然也有额外学习成本但是背后是RxJS和⼀整套思想
  • 体积庞⼤: 体积略⼤,代码近2000⾏,min版25KB左右
  • 功能过剩: 实际上并发控制等功能很难⽤到,但是我们依然需要引⼊这些代码
  • ts⽀持不友好: yield⽆法返回TS类型

redux-saga可以捕获action,然后执行一个函数,那么可以把异步代码放在这个函数中,步骤

  • 配置中间件
  • 将异步请求放在sagas.js中
  • 发送action

Redux 怎么实现属性传递,介绍下原理

react-redux 数据传输∶ view-->action-->reducer-->store-->view。

Redux 中间件是怎么拿到 store 和 action?然后怎么处理?

redux中间件本质就是一个函数柯里化。redux applyMiddleware Api 源码中每个middleware接收2个参数,Store的getState函数和dispatch函数,分别获得store和action,最终返回一个函数。该函数会被传入next的下一个midllewaredispatch方法,并返回一个接收action的新函数,这个函数可以直接调用next(action),或者在其他需要的时刻调用,甚至根本不去调用它。调用链中最后middleware会接受真实的store的dispatch方法作为next参数,并借此结束调用链。所以,midlleware的函数签名是({getState,dispatch})=>next=>action

Redux 中的 connect 有什么作用

connect 负责连接 React和Redux

(1) 获取state:connect通过context 获取Provider中的store,通过store.getState()获取整个store tree上所有state

(2) 包装原组件:将state和action通过props的方式传入到原组件内部 wrapWithConnect返回一个ReactComponent对象Connect,Connect重新render外部传入的原组件WrappedComponent,并把connect中传入的mapStateToProps,mapDispatchToProps与组件上原有的props合并后,通过属性的方式传给WrappedComponent

(3) 监听store tree变化:connect缓存了store tree的中state,通过当前state状态 和变更前state状态进行比较,从而确实是否调用this.setState()方法触发Connect及其子组件的重新渲染

Redux 和 Vuex

本质:redux与vuex都是对mvvm思想的服务,将数据从视图中抽离的一种方案

共同思想

  • 单—的数据源
  • 变化可以预测

区别

  • Vuex改进了Redux中的Action和Reducer函数,以mutations变化函数取代Reducer,无需switch,只需在对应的mutation函数里改变state值即可
  • Vuex由于Vue自动重新渲染的特性,无需订阅重新渲染函数,只要生成新的State即可
  • Vuex数据流的顺序是∶View调用store.commit提交对应的请求到Store中对应的mutation函数->store改变(vue检测到数据变化自动渲染)

Hooks

Hook 是 React 16.8 的新增特性。它可以在不编写 class 的情况下使用 state 以及其他的 React 特性。

React Hooks

解决了哪些问题

  • 在组件之间复用状态逻辑很难
  • 复杂组件变得难以理解
  • 难以理解的 class

API 索引

基础 Hook

(1) useStateuseState可以弥补函数组件没有state的缺陷。useState可以接受一个初识值,也可以是一个函数actionaction返回值作为新的state。返回一个数组,第一个值为state读取值,第二个值为改变statedispatchAction函数。

(2) useEffect:可以弥补函数组件没有生命周期的缺点,用作数据交互、事件监听,还有一些基于dom的操作。可以在useEffect第一个参数回调函数中,做一些请求数据,事件监听等操作,第二个参数作为dep依赖项,当依赖项发生变化,重新执行第一个函数。注意:在useEffect第一个参数回调函数,返一个函数用于清除事件监听等操作。

(3) useContext:可以使用 useContext ,来获取父级组件传递过来的 context 值,这个当前值就是最近的父级组件 Provider 设置的 value 值,useContext 参数一般是由 createContext 方式引入 ,也可以父级上下文 context 传递 ( 参数为 context )。useContext 可以代替 context.Consumer 来获取 Provider 中保存的 value

额外的 Hook

(1) useReduceruseState底层就是一个简单版的useReduceruseReducer 接受的第一个参数是一个函数,可以认为它就是一个 reducer , reducer 的参数就是常规 reducer 里面的 stateaction ,返回改变后的 state , useReducer 第二个参数为 state 的初始值 返回一个数组,数组的第一项就是更新之后 state 的值 ,第二个参数是派发更新的 dispatch 函数。

(2) useCallbackuseMemouseCallback 接收的参数都是一样,都是在其依赖项发生变化后才执行,都是返回缓存的值,区别在于 useMemo 返回的是函数运行的结果, useCallback 返回的是函数。 返回的callback可以作为props回调函数传递给子组件。

(3) useMemouseMemo接受两个参数,第一个参数是一个函数,返回值用于产生保存值。 第二个参数是一个数组,作为dep依赖项,数组里面的依赖项发生变化,重新执行第一个函数,产生新的值。

应用场景

  • 缓存一些值,避免重新执行上下文
  • 减少不必要的dom循环
  • 减少子组件渲染

(4) useRef

useRef作用

  • 可以用来获取dom元素,或者class组件实例 。
  • react-hooks原理文章中讲过,创建useRef时候,会创建一个原始对象,只要函数组件不被销毁,原始对象就会一直存在,那么我们可以利用这个特性,来通过useRef保存一些数据。

useImperativeHandle useLayoutEffect useDebugValue

在平时开发中需要注意的问题

  • 不要在循环、条件或嵌套函数中调用Hook,必须始终在React函数的顶层使用Hook
  • 使用useState时候,使用push,pop,splice等直接更改数组对象的坑
  • useState设置状态的时候,只有第一次生效,后期需要更新状态,必须通过useEffect
  • 善用useCallback
  • 不要滥用useContext:可以使用基于 useContext 封装的状态管理工具。

虚拟DOM

虚拟DOM是对DOM的抽象,这个对象是更加轻量级的对DOM的描述。它设计的最初目的,就是更好的跨平台

React diff 算法的原理是什么

diff 算法探讨的就是虚拟 DOM 树发生变化后,生成 DOM 树更新补丁的方式。它通过对比新旧两株虚拟 DOM 树的变更差异,将更新补丁作用于真实 DOM,以最小成本完成视图更新。

其他

在 React 中遍历的方法有哪些

  • 遍历数组:map && forEach
  • 遍历对象:map && for in

在React中页面重新加载时怎样保留数据

数据持久化方式

  • Redux: 将页面的数据存储在redux中,在重新加载页面时,获取Redux中的数据;
  • data.js:使用webpack构建的项目,可以建一个文件,data.js,将数据保存data.js中,跳转页面后获取;
  • sessionStorge: 在进入选择地址页面之前,componentWillUnMount的时候,将数据存储到sessionStorage中,每次进入页面判断sessionStorage中有没有存储的那个值,有,则读取渲染数据;没有,则说明数据是初始化的状态。返回或进入除了选择地址以外的页面,清掉存储的sessionStorage,保证下次进入是初始化的数据
  • history API: History API 的 pushState 函数可以给历史记录关联一个任意的可序列化 state,所以可以在路由 push 的时候将当前页面的一些信息存到 state 中,下次返回到这个页面的时候就能从 state 里面取出离开前的数据重新渲染。react-router 直接可以支持。这个方法适合一些需要临时存储的场景。

React 有哪些性能优化的手段?

  1. 使用纯组件;
  2. 使用 React.memo 进行组件记忆(React.memo 是一个高阶组件),对于相同的输入,不重复执行;
  3. 如果是类组件,使用 shouldComponentUpdate(这是在重新渲染组件之前触发的其中一个生命周期事件)生命周期事件,可以利用此事件来决定何时需要重新渲染组件;
  4. 路由懒加载;
  5. 使用 React Fragments 避免额外标记;
  6. 不要使用内联函数定义(如果我们使用内联函数,则每次调用“render”函数时都会创建一个新的函数实例);
  7. 避免在Willxxx系列的生命周期中进行异步请求,操作dom等;
  8. 如果是类组件,事件函数在Constructor中绑定bind改变this指向;
  9. 避免使用内联样式属性;
  10. 优化 React 中的条件渲染;
  11. 不要在 render 方法中导出数据;
  12. 列表渲染的时候加key;
  13. 在函数组件中使用useCallback和useMemo来进行组件优化,依赖没有变化的话,不重复执行;
  14. 类组件中使用immutable对象;