写点什么

百度前端高频 react 面试题(持续更新中)

作者:beifeng1996
  • 2023-02-27
    浙江
  • 本文字数:11577 字

    阅读完需:约 38 分钟

说说你用 react 有什么坑点?

1. JSX 做表达式判断时候,需要强转为 boolean 类型


如果不使用 !!b 进行强转数据类型,会在页面里面输出 0


render() {  const b = 0;  return <div>    {      !!b && <div>这是一段文本</div>    }  </div>}
复制代码


2. 尽量不要在 componentWillReviceProps 里使用 setState,如果一定要使用,那么需要判断结束条件,不然会出现无限重渲染,导致页面崩溃


3. 给组件添加 ref 时候,尽量不要使用匿名函数,因为当组件更新的时候,匿名函数会被当做新的 prop 处理,让 ref 属性接受到新函数的时候,react 内部会先清空 ref,也就是会以 null 为回调参数先执行一次 ref 这个 props,然后在以该组件的实例执行一次 ref,所以用匿名函数做 ref 的时候,有的时候去 ref 赋值后的属性会取到 null


4. 遍历子节点的时候,不要用 index 作为组件的 key 进行传入

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

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


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


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

  • 过程没有副作用。

  • 不依赖外部状态。


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

高阶组件存在的问题

  • 静态方法丢失(必须将静态方法做拷贝)

  • refs 属性不能透传(如果你向一个由高阶组件创建的组件的元素添加ref引用,那么ref指向的是最外层容器组件实例的,而不是被包裹的WrappedComponent组件。)

  • 反向继承不能保证完整的子组件树被解析

  • React 组件有两种形式,分别是 class 类型和 function 类型(无状态组件)。


我们知道反向继承的渲染劫持可以控制 WrappedComponent 的渲染过程,也就是说这个过程中我们可以对 elements tree、 state、 props 或 render() 的结果做各种操作。


但是如果渲染 elements tree 中包含了 function 类型的组件的话,这时候就不能操作组件的子组件了。

对有状态组件和无状态组件的理解及使用场景

(1)有状态组件


特点:


  • 是类组件

  • 有继承

  • 可以使用 this

  • 可以使用 react 的生命周期

  • 使用较多,容易频繁触发生命周期钩子函数,影响性能

  • 内部使用 state,维护自身状态的变化,有状态组件根据外部组件传入的 props 和自身的 state 进行渲染。


使用场景:


  • 需要使用到状态的。

  • 需要使用状态操作组件的(无状态组件的也可以实现新版本 react hooks 也可实现)


总结: 类组件可以维护自身的状态变量,即组件的 state ,类组件还有不同的生命周期方法,可以让开发者能够在组件的不同阶段(挂载、更新、卸载),对组件做更多的控制。类组件则既可以充当无状态组件,也可以充当有状态组件。当一个类组件不需要管理自身状态时,也可称为无状态组件。


(2)无状态组件 特点:


  • 不依赖自身的状态 state

  • 可以是类组件或者函数组件。

  • 可以完全避免使用 this 关键字。(由于使用的是箭头函数事件无需绑定)

  • 有更高的性能。当不需要使用生命周期钩子时,应该首先使用无状态函数组件

  • 组件内部不维护 state ,只根据外部组件传入的 props 进行渲染的组件,当 props 改变时,组件重新渲染。


使用场景:


  • 组件不需要管理 state,纯展示


优点:


  • 简化代码、专注于 render

  • 组件不需要被实例化,无生命周期,提升性能。 输出(渲染)只取决于输入(属性),无副作用

  • 视图和数据的解耦分离


缺点:


  • 无法使用 ref

  • 无生命周期方法

  • 无法控制组件的重渲染,因为无法使用 shouldComponentUpdate 方法,当组件接受到新的属性时则会重渲染


总结: 组件内部状态且与外部无关的组件,可以考虑用状态组件,这样状态树就不会过于复杂,易于理解和管理。当一个组件不需要管理自身状态时,也就是无状态组件,应该优先设计为函数组件。比如自定义的 <Button/><Input /> 等组件。

什么是受控组件和非受控组件

  • 受状态控制的组件,必须要有 onChange 方法,否则不能使用 受控组件可以赋予默认值(官方推荐使用 受控组件) 实现双向数据绑定


class Input extends Component{    constructor(){        super();        this.state = {val:'100'}    }    handleChange = (e) =>{ //e是事件源        let val = e.target.value;        this.setState({val});    };    render(){        return (<div>            <input type="text" value={this.state.val} onChange={this.handleChange}/>            {this.state.val}        </div>)    }}
复制代码


  • 非受控也就意味着我可以不需要设置它的 state 属性,而通过 ref 来操作真实的 DOM


class Sum extends Component{    constructor(){        super();        this.state =  {result:''}    }    //通过ref设置的属性 可以通过this.refs获取到对应的dom元素    handleChange = () =>{        let result = this.refs.a.value + this.b.value;        this.setState({result});    };    render(){        return (            <div onChange={this.handleChange}>                <input type="number" ref="a"/>                {/*x代表的真实的dom,把元素挂载在了当前实例上*/}                <input type="number" ref={(x)=>{                    this.b = x;                }}/>                {this.state.result}            </div>        )    }}
复制代码

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


参考 前端进阶面试题详细解答

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 的控制,称为受控组件。

Redux 中异步的请求怎么处理

可以在 componentDidmount 中直接进⾏请求⽆须借助 redux。但是在⼀定规模的项⽬中,上述⽅法很难进⾏异步流的管理,通常情况下我们会借助 redux 的异步中间件进⾏异步处理。redux 异步流中间件其实有很多,当下主流的异步中间件有两种 redux-thunk、redux-saga。


(1)使用 react-thunk 中间件


redux-thunk 优点:


  • 体积⼩: redux-thunk 的实现⽅式很简单,只有不到 20⾏代码

  • 使⽤简单: redux-thunk 没有引⼊像 redux-saga 或者 redux-observable 额外的范式,上⼿简单


redux-thunk 缺陷:


  • 样板代码过多: 与 redux 本身⼀样,通常⼀个请求需要⼤量的代码,⽽且很多都是重复性质的

  • 耦合严重: 异步操作与 redux 的 action 偶合在⼀起,不⽅便管理

  • 功能孱弱: 有⼀些实际开发中常⽤的功能需要⾃⼰进⾏封装


使用步骤:


  • 配置中间件,在 store 的创建中配置


import {createStore, applyMiddleware, compose} from 'redux';import reducer from './reducer';import thunk from 'redux-thunk'
// 设置调试工具const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;// 设置中间件const enhancer = composeEnhancers( applyMiddleware(thunk));
const store = createStore(reducer, enhancer);
export default store;
复制代码


  • 添加一个返回函数的 actionCreator,将异步请求逻辑放在里面


/**  发送get请求,并生成相应action,更新store的函数  @param url {string} 请求地址  @param func {function} 真正需要生成的action对应的actionCreator  @return {function} */// dispatch为自动接收的store.dispatch函数 export const getHttpAction = (url, func) => (dispatch) => {    axios.get(url).then(function(res){        const action = func(res.data)        dispatch(action)    })}
复制代码


  • 生成 action,并发送 action


componentDidMount(){    var action = getHttpAction('/getData', getInitTodoItemAction)    // 发送函数类型的action时,该action的函数体会自动执行    store.dispatch(action)}
复制代码


(2)使用 redux-saga 中间件


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 缺陷:


  • 额外的学习成本: redux-saga 不仅在使⽤难以理解的 generator function,⽽且有数⼗个 API,学习成本远超 redux-thunk,最重要的是你的额外学习成本是只服务于这个库的,与 redux-observable 不同,redux-observable 虽然也有额外学习成本但是背后是 rxjs 和⼀整套思想

  • 体积庞⼤: 体积略⼤,代码近 2000⾏,min 版 25KB 左右

  • 功能过剩: 实际上并发控制等功能很难⽤到,但是我们依然需要引⼊这些代码

  • ts⽀持不友好: yield⽆法返回 TS 类型


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


  • 配置中间件


import {createStore, applyMiddleware, compose} from 'redux';import reducer from './reducer';import createSagaMiddleware from 'redux-saga'import TodoListSaga from './sagas'
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;const sagaMiddleware = createSagaMiddleware()
const enhancer = composeEnhancers( applyMiddleware(sagaMiddleware));
const store = createStore(reducer, enhancer);sagaMiddleware.run(TodoListSaga)
export default store;
复制代码


  • 将异步请求放在 sagas.js 中


import {takeEvery, put} from 'redux-saga/effects'import {initTodoList} from './actionCreator'import {GET_INIT_ITEM} from './actionTypes'import axios from 'axios'
function* func(){ try{ // 可以获取异步返回数据 const res = yield axios.get('/getData') const action = initTodoList(res.data) // 将action发送到reducer yield put(action) }catch(e){ console.log('网络请求失败') }}
function* mySaga(){ // 自动捕获GET_INIT_ITEM类型的action,并执行func yield takeEvery(GET_INIT_ITEM, func)}
export default mySaga
复制代码


  • 发送 action


componentDidMount(){  const action = getInitTodoItemAction()  store.dispatch(action)}
复制代码

对 React-Intl 的理解,它的工作原理?

React-intl 是雅虎的语言国际化开源项目 FormatJS 的一部分,通过其提供的组件和 API 可以与 ReactJS 绑定。


React-intl 提供了两种使用方法,一种是引用 React 组件,另一种是直接调取 API,官方更加推荐在 React 项目中使用前者,只有在无法使用 React 组件的地方,才应该调用框架提供的 API。它提供了一系列的 React 组件,包括数字格式化、字符串格式化、日期格式化等。


在 React-intl 中,可以配置不同的语言包,他的工作原理就是根据需要,在语言包之间进行切换。

对 React context 的理解

在 React 中,数据传递一般使用 props 传递数据,维持单向数据流,这样可以让组件之间的关系变得简单且可预测,但是单项数据流在某些场景中并不适用。单纯一对的父子组件传递并无问题,但要是组件之间层层依赖深入,props 就需要层层传递显然,这样做太繁琐了。


Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。


可以把 context 当做是特定一个组件树内共享的 store,用来做数据传递。简单说就是,当你不想在组件树中通过逐层传递 props 或者 state 的方式来传递数据时,可以使用 Context 来实现跨层级的组件数据传递。


JS 的代码块在执行期间,会创建一个相应的作用域链,这个作用域链记录着运行时 JS 代码块执行期间所能访问的活动对象,包括变量和函数,JS 程序通过作用域链访问到代码块内部或者外部的变量和函数。


假如以 JS 的作用域链作为类比,React 组件提供的 Context 对象其实就好比一个提供给子组件访问的作用域,而 Context 对象的属性可以看成作用域上的活动对象。由于组件 的 Context 由其父节点链上所有组件通 过 getChildContext()返回的 Context 对象组合而成,所以,组件通过 Context 是可以访问到其父组件链上所有节点组件提供的 Context 的属性。

为什么 React 并不推荐优先考虑使用 Context?

  • Context 目前还处于实验阶段,可能会在后面的发行版本中有很大的变化,事实上这种情况已经发生了,所以为了避免给今后升级带来大的影响和麻烦,不建议在 app 中使用 context。

  • 尽管不建议在 app 中使用 context,但是独有组件而言,由于影响范围小于 app,如果可以做到高内聚,不破坏组件树之间的依赖关系,可以考虑使用 context

  • 对于组件之间的数据通信或者状态管理,有效使用 props 或者 state 解决,然后再考虑使用第三方的成熟库进行解决,以上的方法都不是最佳的方案的时候,在考虑 context。

  • context 的更新需要通过 setState()触发,但是这并不是很可靠的,Context 支持跨组件的访问,但是如果中间的子组件通过一些方法不影响更新,比如 shouldComponentUpdate() 返回 false 那么不能保证 Context 的更新一定可以使用 Context 的子组件,因此,Context 的可靠性需要关注

React 中 refs 的作用是什么?有哪些应用场景?

Refs 提供了一种方式,用于访问在 render 方法中创建的 React 元素或 DOM 节点。Refs 应该谨慎使用,如下场景使用 Refs 比较适合:


  • 处理焦点、文本选择或者媒体的控制

  • 触发必要的动画

  • 集成第三方 DOM 库


Refs 是使用 React.createRef() 方法创建的,他通过 ref 属性附加到 React 元素上。要在整个组件中使用 Refs,需要将 ref 在构造函数中分配给其实例属性:


class MyComponent extends React.Component {  constructor(props) {    super(props)    this.myRef = React.createRef()  }  render() {    return <div ref={this.myRef} />  }}
复制代码


由于函数组件没有实例,因此不能在函数组件上直接使用 ref


function MyFunctionalComponent() {  return <input />;}class Parent extends React.Component {  constructor(props) {    super(props);    this.textInput = React.createRef();  }  render() {    // 这将不会工作!    return (      <MyFunctionalComponent ref={this.textInput} />    );  }}
复制代码


但可以通过闭合的帮助在函数组件内部进行使用 Refs:


function CustomTextInput(props) {  // 这里必须声明 textInput,这样 ref 回调才可以引用它  let textInput = null;  function handleClick() {    textInput.focus();  }  return (    <div>      <input        type="text"        ref={(input) => { textInput = input; }} />      <input        type="button"        value="Focus the text input"        onClick={handleClick}      />    </div>  );  }
复制代码


注意:


  • 不应该过度的使用 Refs

  • ref 的返回值取决于节点的类型:

  • ref 属性被用于一个普通的 HTML 元素时,React.createRef() 将接收底层 DOM 元素作为他的 current 属性以创建 ref

  • ref 属性被用于一个自定义的类组件时,ref 对象将接收该组件已挂载的实例作为他的 current

  • 当在父组件中需要访问子组件中的 ref 时可使用传递 Refs 或回调 Refs。

React 声明组件有哪几种方法,有什么不同?

React 声明组件的三种方式:


  • 函数式定义的无状态组件

  • ES5 原生方式React.createClass定义的组件

  • ES6 形式的extends React.Component定义的组件


(1)无状态函数式组件 它是为了创建纯展示组件,这种组件只负责根据传入的 props 来展示,不涉及到 state 状态的操作组件不会被实例化,整体渲染性能得到提升,不能访问 this 对象,不能访问生命周期的方法


(2)ES5 原生方式 React.createClass // RFC React.createClass 会自绑定函数方法,导致不必要的性能开销,增加代码过时的可能性。


(3)E6 继承形式 React.Component // RCC 目前极为推荐的创建有状态组件的方式,最终会取代 React.createClass 形式;相对于 React.createClass 可以更好实现代码复用。


无状态组件相对于于后者的区别: 与无状态组件相比,React.createClass 和 React.Component 都是创建有状态的组件,这些组件是要被实例化的,并且可以访问组件的生命周期方法。


React.createClass 与 React.Component 区别:


① 函数 this 自绑定


  • React.createClass 创建的组件,其每一个成员函数的 this 都有 React 自动绑定,函数中的 this 会被正确设置。

  • React.Component 创建的组件,其成员函数不会自动绑定 this,需要开发者手动绑定,否则 this 不能获取当前组件实例对象。


② 组件属性类型 propTypes 及其默认 props 属性 defaultProps 配置不同


  • React.createClass 在创建组件时,有关组件 props 的属性类型及组件默认的属性会作为组件实例的属性来配置,其中 defaultProps 是使用 getDefaultProps 的方法来获取默认组件属性的

  • React.Component 在创建组件时配置这两个对应信息时,他们是作为组件类的属性,不是组件实例的属性,也就是所谓的类的静态属性来配置的。


③ 组件初始状态 state 的配置不同


  • React.createClass 创建的组件,其状态 state 是通过 getInitialState 方法来配置组件相关的状态;

  • React.Component 创建的组件,其状态 state 是在 constructor 中像初始化组件属性一样声明的。

React 中发起网络请求应该在哪个生命周期中进行?为什么?

对于异步请求,最好放在 componentDidMount 中去操作,对于同步的状态改变,可以放在 componentWillMount 中,一般用的比较少。


如果认为在 componentWillMount 里发起请求能提早获得结果,这种想法其实是错误的,通常 componentWillMount 比 componentDidMount 早不了多少微秒,网络上任何一点延迟,这一点差异都可忽略不计。


react 的生命周期: constructor() -> componentWillMount() -> render() -> componentDidMount()


上面这些方法的调用是有次序的,由上而下依次调用。


  • constructor 被调用是在组件准备要挂载的最开始,此时组件尚未挂载到网页上。

  • componentWillMount 方法的调用在 constructor 之后,在 render 之前,在这方法里的代码调用 setState 方法不会触发重新 render,所以它一般不会用来作加载数据之用。

  • componentDidMount 方法中的代码,是在组件已经完全挂载到网页上才会调用被执行,所以可以保证数据的加载。此外,在这方法中调用 setState 方法,会触发重新渲染。所以,官方设计这个方法就是用来加载外部数据用的,或处理其他的副作用代码。与组件上的数据无关的加载,也可以在 constructor 里做,但 constructor 是做组件 state 初绐化工作,并不是做加载数据这工作的,constructor 里也不能 setState,还有加载的时间太长或者出错,页面就无法加载出来。所以有副作用的代码都会集中在 componentDidMount 方法里。


总结:


  • 跟服务器端渲染(同构)有关系,如果在 componentWillMount 里面获取数据,fetch data 会执行两次,一次在服务器端一次在客户端。在 componentDidMount 中可以解决这个问题,componentWillMount 同样也会 render 两次。

  • 在 componentWillMount 中 fetch data,数据一定在 render 后才能到达,如果忘记了设置初始状态,用户体验不好。

  • react16.0 以后,componentWillMount 可能会被执行多次。

在 React 中组件的 props 改变时更新组件的有哪些方法?

在一个组件传入的 props 更新时重新渲染该组件常用的方法是在componentWillReceiveProps中将新的 props 更新到组件的 state 中(这种 state 被成为派生状态(Derived State)),从而实现重新渲染。React 16.3 中还引入了一个新的钩子函数getDerivedStateFromProps来专门实现这一需求。


(1)componentWillReceiveProps(已废弃)


在 react 的 componentWillReceiveProps(nextProps)生命周期中,可以在子组件的 render 函数执行前,通过 this.props 获取旧的属性,通过 nextProps 获取新的 props,对比两次 props 是否相同,从而更新子组件自己的 state。


这样的好处是,可以将数据请求放在这里进行执行,需要传的参数则从 componentWillReceiveProps(nextProps)中获取。而不必将所有的请求都放在父组件中。于是该请求只会在该组件渲染时才会发出,从而减轻请求负担。


(2)getDerivedStateFromProps(16.3 引入)


这个生命周期函数是为了替代componentWillReceiveProps存在的,所以在需要使用componentWillReceiveProps时,就可以考虑使用getDerivedStateFromProps来进行替代。


两者的参数是不相同的,而getDerivedStateFromProps是一个静态函数,也就是这个函数不能通过 this 访问到 class 的属性,也并不推荐直接访问属性。而是应该通过参数提供的 nextProps 以及 prevState 来进行判断,根据新传入的 props 来映射到 state。


需要注意的是,如果 props 传入的内容不需要影响到你的 state,那么就需要返回一个 null,这个返回值是必须的,所以尽量将其写到函数的末尾:


static getDerivedStateFromProps(nextProps, prevState) {    const {type} = nextProps;    // 当传入的type发生变化的时候,更新state    if (type !== prevState.type) {        return {            type,        };    }    // 否则,对于state不进行任何操作    return null;}
复制代码

解释 React 中 render() 的目的。

每个 React 组件强制要求必须有一个 **render()**。它返回一个 React 元素,是原生 DOM 组件的表示。如果需要渲染多个 HTML 元素,则必须将它们组合在一个封闭标记内,例如 <form><group><div> 等。此函数必须保持纯净,即必须每次调用时都返回相同的结果。

哪些方法会触发 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.Children.map 和 js 的 map 有什么区别?

JavaScript 中的 map 不会对为 null 或者 undefined 的数据进行处理,而 React.Children.map 中的 map 可以处理 React.Children 为 null 或者 undefined 的情况。

useEffect 和 useLayoutEffect 的区别

useEffect


基本上 90%的情况下,都应该用这个,这个是在 render 结束后,你的 callback 函数执行,但是不会 block browser painting,算是某种异步的方式吧,但是 class 的 componentDidMount 和 componentDidUpdate 是同步的,在 render 结束后就运行,useEffect 在大部分场景下都比 class 的方式性能更好.


useLayoutEffect


这个是用在处理 DOM 的时候,当你的 useEffect 里面的操作需要处理 DOM,并且会改变页面的样式,就需要用这个,否则可能会出现出现闪屏问题, useLayoutEffect 里面的 callback 函数会在**DOM 更新完成后立即执行,但是会在浏览器进行任何绘制之前运行完成,**阻塞了浏览器的绘制.

React 中 constructor 和 getInitialState 的区别?

两者都是用来初始化 state 的。前者是 ES6 中的语法,后者是 ES5 中的语法,新版本的 React 中已经废弃了该方法。


getInitialState 是 ES5 中的方法,如果使用 createClass 方法创建一个 Component 组件,可以自动调用它的 getInitialState 方法来获取初始化的 State 对象,


var APP = React.creatClass ({  getInitialState() {    return {         userName: 'hi',        userId: 0     }; }})
复制代码


React 在 ES6 的实现中去掉了 getInitialState 这个 hook 函数,规定 state 在 constructor 中实现,如下:


Class App extends React.Component{    constructor(props){      super(props);      this.state={};    }  }
复制代码


用户头像

beifeng1996

关注

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

还未添加个人简介

评论

发布
暂无评论
百度前端高频react面试题(持续更新中)_前端_beifeng1996_InfoQ写作社区