写点什么

我的 react 面试题笔记整理(附答案)

作者:beifeng1996
  • 2022-11-15
    浙江
  • 本文字数:9103 字

    阅读完需:约 30 分钟

shouldComponentUpdate 有什么用?为什么它很重要?

组件状态数据或者属性数据发生更新的时候,组件会进入存在期,视图会渲染更新。在生命周期方法 should ComponentUpdate 中,允许选择退出某些组件(和它们的子组件)的和解过程。和解的最终目标是根据新的状态,以最有效的方式更新用户界面。如果我们知道用户界面的某一部分不会改变,那么没有理由让 React 弄清楚它是否应该更新渲染。通过在 shouldComponentUpdate 方法中返回 false, React 将让当前组件及其所有子组件保持与当前组件状态相同。

react 最新版本解决了什么问题,增加了哪些东西

React 16.x 的三大新特性 Time Slicing、Suspense、 hooks


  • Time Slicing(解决 CPU 速度问题)使得在执行任务的期间可以随时暂停,跑去干别的事情,这个特性使得 react 能在性能极其差的机器跑时,仍然保持有良好的性能

  • Suspense (解决网络 IO 问题) 和 lazy 配合,实现异步加载组件。 能暂停当前组件的渲染, 当完成某件事以后再继续渲染,解决从 react 出生到现在都存在的「异步副作用」的问题,而且解决得非的优雅,使用的是 T 异步但是同步的写法,这是最好的解决异步问题的方式

  • 提供了一个内置函数 componentDidCatch,当有错误发生时,可以友好地展示 fallback 组件; 可以捕捉到它的子元素(包括嵌套子元素)抛出的异常; 可以复用错误组件。


(1)React16.8 加入 hooks,让 React 函数式组件更加灵活,hooks 之前,React 存在很多问题:


  • 在组件间复用状态逻辑很难

  • 复杂组件变得难以理解,高阶组件和函数组件的嵌套过深。

  • class 组件的 this 指向问题

  • 难以记忆的生命周期


hooks 很好的解决了上述问题,hooks 提供了很多方法


  • useState 返回有状态值,以及更新这个状态值的函数

  • useEffect 接受包含命令式,可能有副作用代码的函数。

  • useContext 接受上下文对象(从 React.createContext 返回的值)并返回当前上下文值,

  • useReducer useState 的替代方案。接受类型为 (state,action)=> newState 的 reducer,并返回与 dispatch 方法配对的当前状态。

  • useCalLback 返回一个回忆的 memoized 版本,该版本仅在其中一个输入发生更改时才会更改。纯函数的输入输出确定性 o useMemo 纯的一个记忆函数 o useRef 返回一个可变的 ref 对象,其 Current 属性被初始化为传递的参数,返回的 ref 对象在组件的整个生命周期内保持不变。

  • useImperativeMethods 自定义使用 ref 时公开给父组件的实例值

  • useMutationEffect 更新兄弟组件之前,它在 React 执行其 DOM 改变的同一阶段同步触发

  • useLayoutEffect DOM 改变后同步触发。使用它来从 DOM 读取布局并同步重新渲染


(2)React16.9


  • 重命名 Unsafe 的生命周期方法。新的 UNSAFE_前缀将有助于在代码 review 和 debug 期间,使这些有问题的字样更突出

  • 废弃 javascrip:形式的 URL。以 javascript:开头的 URL 非常容易遭受攻击,造成安全漏洞。

  • 废弃"Factory"组件。 工厂组件会导致 React 变大且变慢。

  • act()也支持异步函数,并且你可以在调用它时使用 await。

  • 使用 <React.ProfiLer> 进行性能评估。在较大的应用中追踪性能回归可能会很方便


(3)React16.13.0


  • 支持在渲染期间调用 setState,但仅适用于同一组件

  • 可检测冲突的样式规则并记录警告

  • 废弃 unstable_createPortal,使用 CreatePortal

  • 将组件堆栈添加到其开发警告中,使开发人员能够隔离 bug 并调试其程序,这可以清楚地说明问题所在,并更快地定位和修复错误。

对 React 中 Fragment 的理解,它的使用场景是什么?

在 React 中,组件返回的元素只能有一个根元素。为了不添加多余的 DOM 节点,我们可以使用 Fragment 标签来包裹所有的元素,Fragment 标签不会渲染出任何元素。React 官方对 Fragment 的解释:


React 中的一个常见模式是一个组件返回多个元素。Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。


import React, { Component, Fragment } from 'react'
// 一般形式render() { return ( <React.Fragment> <ChildA /> <ChildB /> <ChildC /> </React.Fragment> );}// 也可以写成以下形式render() { return ( <> <ChildA /> <ChildB /> <ChildC /> </> );}
复制代码

hooks 常用的

useEffct使用:如果不传参数:相当于render之后就会执行传参数为空数组:相当于componentDidMount如果传数组:相当于componentDidUpdate如果里面返回:相当于componentWillUnmount会在组件卸载的时候执行清除操作。effect 在每次渲染的时候都会执行。React 会在执行当前 effect 之前对上一个 effect 进行清除。
useLayoutEffect:useLayoutEffect在浏览器渲染前执行useEffect在浏览器渲染之后执行
当父组件引入子组件以及在更新某一个值的状态的时候,往往会造成一些不必要的浪费,而useMemo和useCallback的出现就是为了减少这种浪费,提高组件的性能,不同点是:useMemo返回的是一个缓存的值,即memoized 值,而useCallback返回的是一个memoized 回调函数。

useCallback父组件更新子组件会渲染,针对方法不重复执行,包装函数返回函数;
useMemo:const memoizedValue =useMemo(callback,array)callback是一个函数用于处理逻辑array 控制useMemo重新执⾏行的数组,array改变时才会 重新执行useMemo不传数组,每次更新都会重新计算空数组,只会计算一次依赖对应的值,当对应的值发生变化时,才会重新计算(可以依赖另外一个 useMemo 返回的值)不能在useMemo⾥面写副作⽤逻辑处理,副作用的逻辑处理放在 useEffect内进行处理
自定义hook自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook,自定义 Hook 是一种自然遵循 Hook 设计的约定,而并不是 React 的特性在我看来,自定义hook就是把一块业务逻辑单独拿出去写。
const [counter, setCounter] = useState(0); const counterRef = useRef(counter); // 可以保存上一次的变量
useRef 获取节点function App() { const inputRef = useRef(null);
return <div> <input type="text" ref={inputRef}/> <button onClick={() => inputRef.current.focus()}>focus</button> </div>}
复制代码


参考:前端react面试题详细解答

在 React 中,refs 的作用是什么

Refs 可以用于获取一个 DOM 节点或者 React 组件的引用。何时使用 refs 的好的示例有管理焦点/文本选择,触发命令动画,或者和第三方 DOM 库集成。你应该避免使用 String 类型的 Refs 和内联的 ref 回调。Refs 回调是 React 所推荐的。

React-Router 4 的 Switch 有什么用?

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


假如不加 <Switch>


import { Route } from 'react-router-dom'
<Route path="/" component={Home}></Route><Route path="/login" component={Login}></Route>
复制代码


Route 组件的 path 属性用于匹配路径,因为需要匹配 /Home,匹配 /loginLogin,所以需要两个 Route,但是不能这么写。这样写的话,当 URL 的 path 为 “/login” 时,<Route path="/" /><Route path="/login" /> 都会被匹配,因此页面会展示 Home 和 Login 两个组件。这时就需要借助 <Switch> 来做到只显示一个匹配组件:


import { Switch, Route} from 'react-router-dom'
<Switch> <Route path="/" component={Home}></Route> <Route path="/login" component={Login}></Route></Switch>
复制代码


此时,再访问 “/login” 路径时,却只显示了 Home 组件。这是就用到了 exact 属性,它的作用就是精确匹配路径,经常与<Switch> 联合使用。只有当 URL 和该 <Route> 的 path 属性完全一致的情况下才能匹配上:


import { Switch, Route} from 'react-router-dom'
<Switch> <Route exact path="/" component={Home}></Route> <Route exact path="/login" component={Login}></Route></Switch>
复制代码

Redux Thunk 的作用是什么

Redux thunk 是一个允许你编写返回一个函数而不是一个 action 的 actions creators 的中间件。如果满足某个条件,thunk 则可以用来延迟 action 的派发(dispatch),这可以处理异步 action 的派发(dispatch)。

react 生命周期

初始化阶段:


  • getDefaultProps:获取实例的默认属性

  • getInitialState:获取每个实例的初始化状态

  • componentWillMount:组件即将被装载、渲染到页面上

  • render:组件在这里生成虚拟的 DOM 节点

  • componentDidMount:组件真正在被装载之后


运行中状态:


  • componentWillReceiveProps:组件将要接收到属性的时候调用

  • shouldComponentUpdate:组件接受到新属性或者新状态的时候(可以返回 false,接收数据后不更新,阻止 render 调用,后面的函数不会被继续执行了)

  • componentWillUpdate:组件即将更新不能修改属性和状态

  • render:组件重新描绘

  • componentDidUpdate:组件已经更新


销毁阶段:


  • componentWillUnmount:组件即将销毁


shouldComponentUpdate 是做什么的,(react 性能优化是哪个周期函数?)


shouldComponentUpdate 这个方法用来判断是否需要调用 render 方法重新描绘 dom。因为 dom 的描绘非常消耗性能,如果我们能在 shouldComponentUpdate 方法中能够写出更优化的 dom diff 算法,可以极大的提高性能。


在 react17 会删除以下三个生命周期


componentWillMount,componentWillReceiveProps , componentWillUpdate

constructor 为什么不先渲染?

由 ES6 的继承规则得知,不管子类写不写 constructor,在 new 实例的过程都会给补上 constructor。


所以:constructor 钩子函数并不是不可缺少的,子组件可以在一些情况略去。比如不自己的 state,从 props 中获取的情况

React 中有使用过 getDefaultProps 吗?它有什么作用?

通过实现组件的 getDefaultProps,对属性设置默认值(ES5 的写法):


var ShowTitle = React.createClass({  getDefaultProps:function(){    return{      title : "React"    }  },  render : function(){    return <h1>{this.props.title}</h1>  }});
复制代码

React 中的 setState 和 replaceState 的区别是什么?

(1)setState() setState()用于设置状态对象,其语法如下:


setState(object nextState[, function callback])
复制代码


  • nextState,将要设置的新状态,该状态会和当前的 state 合并

  • callback,可选参数,回调函数。该函数会在 setState 设置成功,且组件重新渲染后调用。


合并 nextState 和当前 state,并重新渲染组件。setState 是 React 事件处理函数中和请求回调函数中触发 UI 更新的主要方法。


(2)replaceState() replaceState()方法与 setState()类似,但是方法只会保留 nextState 中状态,原 state 不在 nextState 中的状态都会被删除。其语法如下:


replaceState(object nextState[, function callback])
复制代码


  • nextState,将要设置的新状态,该状态会替换当前的 state。

  • callback,可选参数,回调函数。该函数会在 replaceState 设置成功,且组件重新渲染后调用。


总结: setState 是修改其中的部分状态,相当于 Object.assign,只是覆盖,不会减少原来的状态。而 replaceState 是完全替换原来的状态,相当于赋值,将原来的 state 替换为另一个对象,如果新状态属性减少,那么 state 中就没有这个状态了。

React 中的 props 为什么是只读的?

this.props是组件之间沟通的一个接口,原则上来讲,它只能从父组件流向子组件。React 具有浓重的函数式编程的思想。


提到函数式编程就要提一个概念:纯函数。它有几个特点:


  • 给定相同的输入,总是返回相同的输出。

  • 过程没有副作用。

  • 不依赖外部状态。


this.props就是汲取了纯函数的思想。props 的不可以变性就保证的相同的输入,页面显示的内容是一样的,并且不会产生副作用

哪些方法会触发 React 重新渲染?重新渲染 render 会做些什么?

(1)哪些方法会触发 react 重新渲染?


  • setState()方法被调用


setState 是 React 中最常用的命令,通常情况下,执行 setState 会触发 render。但是这里有个点值得关注,执行 setState 的时候不一定会重新渲染。当 setState 传入 null 时,并不会触发 render。


class App extends React.Component {  state = {    a: 1  };
render() { console.log("render"); return ( <React.Fragement> <p>{this.state.a}</p> <button onClick={() => { this.setState({ a: 1 }); // 这里并没有改变 a 的值 }} > Click me </button> <button onClick={() => this.setState(null)}>setState null</button> <Child /> </React.Fragement> ); }}
复制代码


  • 父组件重新渲染


只要父组件重新渲染了,即使传入子组件的 props 未发生变化,那么子组件也会重新渲染,进而触发 render


(2)重新渲染 render 会做些什么?


  • 会对新旧 VNode 进行对比,也就是我们所说的 Diff 算法。

  • 对新旧两棵树进行一个深度优先遍历,这样每一个节点都会一个标记,在到深度遍历的时候,每遍历到一和个节点,就把该节点和新的节点树进行对比,如果有差异就放到一个对象里面

  • 遍历差异对象,根据差异的类型,根据对应对规则更新 VNode


React 的处理 render 的基本思维模式是每次一有变动就会去重新渲染整个应用。在 Virtual DOM 没有出现之前,最简单的方法就是直接调用 innerHTML。Virtual DOM 厉害的地方并不是说它比直接操作 DOM 快,而是说不管数据怎么变,都会尽量以最小的代价去更新 DOM。React 将 render 函数返回的虚拟 DOM 树与老的进行比较,从而确定 DOM 要不要更新、怎么更新。当 DOM 树很大时,遍历两棵树进行各种比对还是相当耗性能的,特别是在顶层 setState 一个微小的修改,默认会去遍历整棵树。尽管 React 使用高度优化的 Diff 算法,但是这个过程仍然会损耗性能.

React 中什么是受控组件和非控组件?

(1)受控组件 在使用表单来收集用户输入时,例如<input><select><textearea>等元素都要绑定一个 change 事件,当表单的状态发生变化,就会触发 onChange 事件,更新组件的 state。这种组件在 React 中被称为受控组件,在受控组件中,组件渲染出的状态与它的 value 或 checked 属性相对应,react 通过这种方式消除了组件的局部状态,使整个状态可控。react 官方推荐使用受控表单组件。


受控组件更新 state 的流程:


  • 可以通过初始 state 中设置表单的默认值

  • 每当表单的值发生变化时,调用 onChange 事件处理器

  • 事件处理器通过事件对象 e 拿到改变后的状态,并更新组件的 state

  • 一旦通过 setState 方法更新 state,就会触发视图的重新渲染,完成表单组件的更新


受控组件缺陷: 表单元素的值都是由 React 组件进行管理,当有多个输入框,或者多个这种组件时,如果想同时获取到全部的值就必须每个都要编写事件处理函数,这会让代码看着很臃肿,所以为了解决这种情况,出现了非受控组件。


(2)非受控组件 如果一个表单组件没有 value props(单选和复选按钮对应的是 checked props)时,就可以称为非受控组件。在非受控组件中,可以使用一个 ref 来从 DOM 获得表单值。而不是为每个状态更新编写一个事件处理程序。


React 官方的解释:


要编写一个非受控组件,而不是为每个状态更新都编写数据处理函数,你可以使用 ref 来从 DOM 节点中获取表单数据。因为非受控组件将真实数据储存在 DOM 节点中,所以在使用非受控组件时,有时候反而更容易同时集成 React 和非 React 代码。如果你不介意代码美观性,并且希望快速编写代码,使用非受控组件往往可以减少你的代码量。否则,你应该使用受控组件。


例如,下面的代码在非受控组件中接收单个属性:


class NameForm extends React.Component {  constructor(props) {    super(props);    this.handleSubmit = this.handleSubmit.bind(this);  }  handleSubmit(event) {    alert('A name was submitted: ' + this.input.value);    event.preventDefault();  }  render() {    return (      <form onSubmit={this.handleSubmit}>        <label>          Name:          <input type="text" ref={(input) => this.input = input} />        </label>        <input type="submit" value="Submit" />      </form>    );  }}
复制代码


总结: 页面中所有输入类的 DOM 如果是现用现取的称为非受控组件,而通过 setState 将输入的值维护到了 state 中,需要时再从 state 中取出,这里的数据就受到了 state 的控制,称为受控组件。

对 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 可以通过高阶组件(Higher Order Components-- HOC)来扩展,而 vue 需要通过 mixins 来扩展。


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


6)构建工具


两者都有自己的构建工具


  • React ==> Create React APP

  • Vue ==> vue-cli


7)跨平台


  • React ==> React Native

  • Vue ==> Weex

fetch 封装

npm install whatwg-fetch --save  // 适配其他浏览器npm install es6-promise
export const handleResponse = (response) => { if (response.status === 403 || response.status === 401) { const oauthurl = response.headers.get('locationUrl'); if (!_.isEmpty(oauthUrl)) { window.location.href = oauthurl; return; } } if (!response.ok) { return getErrorMessage(response).then(errorMessage => apiError(response.status, errorMessage)); } if (isJson(response)) { return response.json(); } if (isText(response)) { return response.text(); }
return response.blob();};
const httpRequest = { request: ({ method, headers, body, path, query, }) => { const options = {}; let url = path; if (method) { options.method = method; } if (headers) { options.headers = {...options.headers,...headers}; } if (body) { options.body = body; } if (query) { const params = Object.keys(query) .map(k => `${k}=${query[k]}`) .join('&'); url = url.concat(`?${params}`); } return fetch(url, Object.assign({}, options, { credentials: 'same-origin' })).then(handleResponse); },};
export default httpRequest;
复制代码

除了在构造函数中绑定 this,还有其它方式吗

你可以使用属性初始值设定项(property initializers)来正确绑定回调,create-react-app 也是默认支持的。在回调中你可以使用箭头函数,但问题是每次组件渲染时都会创建一个新的回调。

为什么使用 jsx 的组件中没有看到使用 react 却需要引入 react?

本质上来说 JSX 是React.createElement(component, props, ...children)方法的语法糖。在 React 17 之前,如果使用了 JSX,其实就是在使用 React, babel 会把组件转换为 CreateElement 形式。在 React 17 之后,就不再需要引入,因为 babel 已经可以帮我们自动引入 react。

React 16 中新生命周期有哪些

关于 React16 开始应用的新生命周期: 可以看出,React16 自上而下地对生命周期做了另一种维度的解读:


  • Render 阶段:用于计算一些必要的状态信息。这个阶段可能会被 React 暂停,这一点和 React16 引入的 Fiber 架构(我们后面会重点讲解)是有关的;

  • Pre-commit 阶段:所谓“commit”,这里指的是“更新真正的 DOM 节点”这个动作。所谓 Pre-commit,就是说我在这个阶段其实还并没有去更新真实的 DOM,不过 DOM 信息已经是可以读取的了;

  • Commit 阶段:在这一步,React 会完成真实 DOM 的更新工作。Commit 阶段,我们可以拿到真实 DOM(包括 refs)。


与此同时,新的生命周期在流程方面,仍然遵循“挂载”、“更新”、“卸载”这三个广义的划分方式。它们分别对应到:


  • 挂载过程:

  • constructor

  • getDerivedStateFromProps

  • render

  • componentDidMount

  • 更新过程:

  • getDerivedStateFromProps

  • shouldComponentUpdate

  • render

  • getSnapshotBeforeUpdate

  • componentDidUpdate

  • 卸载过程:

  • componentWillUnmount

父子组件的通信方式?

父组件向子组件通信:父组件通过 props 向子组件传递需要的信息。


// 子组件: Childconst Child = props =>{  return <p>{props.name}</p>}// 父组件 Parentconst Parent = ()=>{    return <Child name="react"></Child>}
复制代码


子组件向父组件通信:: props+回调的方式。


// 子组件: Childconst Child = props =>{  const cb = msg =>{      return ()=>{          props.callback(msg)      }  }  return (      <button onClick={cb("你好!")}>你好</button>  )}// 父组件 Parentclass Parent extends Component {    callback(msg){        console.log(msg)    }    render(){        return <Child callback={this.callback.bind(this)}></Child>        }}
复制代码


用户头像

beifeng1996

关注

还未添加个人签名 2022-09-01 加入

还未添加个人简介

评论

发布
暂无评论
我的react面试题笔记整理(附答案)_React_beifeng1996_InfoQ写作社区