关于react.js:前端高频react面试题整理

3次阅读

共计 11026 个字符,预计需要花费 28 分钟才能阅读完成。

createElement 和 cloneElement 有什么区别?

createElement 是 JSX 被转载失去的,在 React 中用来创立 React 元素(即虚构 DOM)的内容。cloneElement 用于复制元素并传递新的 props。

辨别状态和 props

条件 State Props
1. 从父组件中接管初始值 Yes Yes
2. 父组件能够扭转值 No Yes
3. 在组件中设置默认值 Yes Yes
4. 在组件的外部变动 Yes No
5. 设置子组件的初始值 Yes Yes
6. 在子组件的外部更改 No Yes

React-Router 4 怎么在路由变动时从新渲染同一个组件?

当路由变动时,即组件的 props 产生了变动,会调用 componentWillReceiveProps 等生命周期钩子。那须要做的只是:当路由扭转时,依据路由,也去申请数据:

class NewsList extends Component {componentDidMount () {this.fetchData(this.props.location);
  }

  fetchData(location) {const type = location.pathname.replace('/', '') ||'top'
    this.props.dispatch(fetchListData(type))
  }
  componentWillReceiveProps(nextProps) {if (nextProps.location.pathname != this.props.location.pathname) {this.fetchData(nextProps.location);
     } 
  }
  render () {...}
}

利用生命周期 componentWillReceiveProps,进行从新 render 的预处理操作。

Redux 申请中间件如何解决并发

应用 redux-Saga redux-saga 是一个治理 redux 利用异步操作的中间件,用于代替 redux-thunk 的。它通过创立 Sagas 将所有异步操作逻辑寄存在一个中央进行集中处理,以此将 react 中的同步操作与异步操作辨别开来,以便于前期的治理与保护。redux-saga 如何解决并发:

  • takeEvery

能够让多个 saga 工作并行被 fork 执行。

import {
    fork,
    take
} from "redux-saga/effects"

const takeEvery = (pattern, saga, ...args) => fork(function*() {while (true) {const action = yield take(pattern)
        yield fork(saga, ...args.concat(action))
    }
})
  • takeLatest

takeLatest 不容许多个 saga 工作并行地执行。一旦接管到新的发动的 action,它就会勾销后面所有 fork 过的工作(如果这些工作还在执行的话)。
在解决 AJAX 申请的时候,如果只心愿获取最初那个申请的响应,takeLatest 就会十分有用。

import {
    cancel,
    fork,
    take
} from "redux-saga/effects"

const takeLatest = (pattern, saga, ...args) => fork(function*() {
    let lastTask
    while (true) {const action = yield take(pattern)
        if (lastTask) {yield cancel(lastTask) // 如果工作曾经完结,则 cancel 为空操作
        }
        lastTask = yield fork(saga, ...args.concat(action))
    }
})

对虚构 DOM 的了解?虚构 DOM 次要做了什么?虚构 DOM 自身是什么?

从实质上来说,Virtual Dom 是一个 JavaScript 对象,通过对象的形式来示意 DOM 构造。将页面的状态形象为 JS 对象的模式,配合不同的渲染工具,使跨平台渲染成为可能。通过事务处理机制,将屡次 DOM 批改的后果一次性的更新到页面上,从而无效的缩小页面渲染的次数,缩小批改 DOM 的重绘重排次数,进步渲染性能。

虚构 DOM 是对 DOM 的形象,这个对象是更加轻量级的对 DOM 的形容。它设计的最后目标,就是更好的跨平台,比方 node.js 就没有 DOM,如果想实现 SSR,那么一个形式就是借助虚构 dom,因为虚构 dom 自身是 js 对象。在代码渲染到页面之前,vue 或者 react 会把代码转换成一个对象(虚构 DOM)。以对象的模式来形容实在 dom 构造,最终渲染到页面。在每次数据发生变化前,虚构 dom 都会缓存一份,变动之时,当初的虚构 dom 会与缓存的虚构 dom 进行比拟。在 vue 或者 react 外部封装了 diff 算法,通过这个算法来进行比拟,渲染时批改扭转的变动,原先没有产生扭转的通过原先的数据进行渲染。

另外古代前端框架的一个根本要求就是毋庸手动操作 DOM,一方面是因为手动操作 DOM 无奈保障程序性能,多人合作的我的项目中如果 review 不严格,可能会有开发者写出性能较低的代码,另一方面更重要的是省略手动 DOM 操作能够大大提高开发效率。

为什么要用 Virtual DOM:

(1)保障性能上限,在不进行手动优化的状况下,提供过得去的性能

上面比照一下批改 DOM 时实在 DOM 操作和 Virtual DOM 的过程,来看一下它们重排重绘的性能耗费∶

  • 实在 DOM∶ 生成 HTML 字符串+ 重建所有的 DOM 元素
  • Virtual DOM∶ 生成 vNode+ DOMDiff+必要的 DOM 更新

Virtual DOM 的更新 DOM 的筹备工作消耗更多的工夫,也就是 JS 层面,相比于更多的 DOM 操作它的生产是极其便宜的。尤雨溪在社区论坛中说道∶ 框架给你的保障是,你不须要手动优化的状况下,我仍然能够给你提供过得去的性能。(2)跨平台 Virtual DOM 实质上是 JavaScript 的对象,它能够很不便的跨平台操作,比方服务端渲染、uniapp 等。

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 中,一切都是组件”这句话。

组件是 React 利用 UI 的构建块。这些组件将整个 UI 分成小的独立并可重用的局部。每个组件彼此独立,而不会影响 UI 的其余部分。

React Hooks 在平时开发中须要留神的问题和起因

(1)不要在循环,条件或嵌套函数中调用 Hook,必须始终在 React 函数的顶层应用 Hook

这是因为 React 须要利用调用程序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用 Hook,就容易导致调用程序的不一致性,从而产生难以预料到的结果。

(2)应用 useState 时候,应用 push,pop,splice 等间接更改数组对象的坑

应用 push 间接更改数组无奈获取到新值,应该采纳析构形式,然而在 class 外面不会有这个问题。代码示例:

function Indicatorfilter() {let [num,setNums] = useState([0,1,2,3])
  const test = () => {
    // 这里坑是间接采纳 push 去更新 num
    // setNums(num)是无奈更新 num 的
    // 必须应用 num = [...num ,1]
    num.push(1)
    // num = [...num ,1]
    setNums(num)
  }
return (
    <div className='filter'>
      <div onClick={test}> 测试 </div>
        <div>
          {num.map((item,index) => (<div key={index}>{item}</div>
          ))}      </div>
    </div>
  )
}

class Indicatorfilter extends React.Component<any,any>{constructor(props:any){super(props)
      this.state = {nums:[1,2,3]
      }
      this.test = this.test.bind(this)
  }

  test(){
      // class 采纳同样的形式是没有问题的
      this.state.nums.push(1)
      this.setState({nums: this.state.nums})
  }

  render(){let {nums} = this.state
      return(
          <div>
              <div onClick={this.test}> 测试 </div>
                  <div>
                      {nums.map((item:any,index:number) => (<div key={index}>{item}</div>
                      ))}                  </div>
          </div>

      )
  }
}

(3)useState 设置状态的时候,只有第一次失效,前期须要更新状态,必须通过 useEffect

TableDeail 是一个公共组件,在调用它的父组件外面,咱们通过 set 扭转 columns 的值,认为传递给 TableDeail 的 columns 是最新的值,所以 tabColumn 每次也是最新的值,然而理论 tabColumn 是最开始的值,不会随着 columns 的更新而更新:

const TableDeail = ({columns,}:TableData) => {const [tabColumn, setTabColumn] = useState(columns) 
}

// 正确的做法是通过 useEffect 扭转这个值
const TableDeail = ({columns,}:TableData) => {const [tabColumn, setTabColumn] = useState(columns) 
    useEffect(() =>{setTabColumn(columns)},[columns])
}

(4)善用 useCallback

父组件传递给子组件事件句柄时,如果咱们没有任何参数变动可能会选用 useMemo。然而每一次父组件渲染子组件即便没变动也会跟着渲染一次。

(5)不要滥用 useContext

能够应用基于 useContext 封装的状态管理工具。

React.Component 和 React.PureComponent 的区别

PureComponent 示意一个纯组件,能够用来优化 React 程序,缩小 render 函数执行的次数,从而进步组件的性能。

在 React 中,当 prop 或者 state 发生变化时,能够通过在 shouldComponentUpdate 生命周期函数中执行 return false 来阻止页面的更新,从而缩小不必要的 render 执行。React.PureComponent 会主动执行 shouldComponentUpdate。

不过,pureComponent 中的 shouldComponentUpdate() 进行的是 浅比拟,也就是说如果是援用数据类型的数据,只会比拟不是同一个地址,而不会比拟这个地址外面的数据是否统一。浅比拟会疏忽属性和或状态渐变状况,其实也就是数据援用指针没有变动,而数据产生扭转的时候 render 是不会执行的。如果须要从新渲染那么就须要从新开拓空间援用数据。PureComponent 个别会用在一些纯展现组件上。

应用 pureComponent 的 益处:当组件更新时,如果组件的 props 或者 state 都没有扭转,render 函数就不会触发。省去虚构 DOM 的生成和比照过程,达到晋升性能的目标。这是因为 react 主动做了一层浅比拟。

React 事件机制

<div onClick={this.handleClick.bind(this)}> 点我 </div>

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

除此之外,冒泡到 document 上的事件也不是原生的浏览器事件,而是由 react 本人实现的合成事件(SyntheticEvent)。因而如果不想要是事件冒泡的话应该调用 event.preventDefault()办法,而不是调用 event.stopProppagation()办法。JSX 上写的事件并没有绑定在对应的实在 DOM 上,而是通过事件代理的形式,将所有的事件都对立绑定在了 document 上。这样的形式不仅缩小了内存耗费,还能在组件挂载销毁时对立订阅和移除事件。

另外冒泡到 document 上的事件也不是原生浏览器事件,而是 React 本人实现的合成事件(SyntheticEvent)。因而咱们如果不想要事件冒泡的话,调用 event.stopPropagation 是有效的,而应该调用 event.preventDefault

实现合成事件的目标如下:

  • 合成事件首先抹平了浏览器之间的兼容问题,另外这是一个跨浏览器原生事件包装器,赋予了跨浏览器开发的能力;
  • 对于原生浏览器事件来说,浏览器会给监听器创立一个事件对象。如果你有很多的事件监听,那么就须要调配很多的事件对象,造成高额的内存调配问题。然而对于合成事件来说,有一个事件池专门来治理它们的创立和销毁,当事件须要被应用时,就会从池子中复用对象,事件回调完结后,就会销毁事件对象上的属性,从而便于下次复用事件对象。

Redux 和 Vuex 有什么区别,它们的独特思维

(1)Redux 和 Vuex 区别

  • Vuex 改良了 Redux 中的 Action 和 Reducer 函数,以 mutations 变动函数取代 Reducer,无需 switch,只需在对应的 mutation 函数里扭转 state 值即可
  • Vuex 因为 Vue 主动从新渲染的个性,无需订阅从新渲染函数,只有生成新的 State 即可
  • Vuex 数据流的程序是∶View 调用 store.commit 提交对应的申请到 Store 中对应的 mutation 函数 ->store 扭转(vue 检测到数据变动主动渲染)

艰深点了解就是,vuex 弱化 dispatch,通过 commit 进行 store 状态的一次更变;勾销了 action 概念,不用传入特定的 action 模式进行指定变更;弱化 reducer,基于 commit 参数间接对数据进行转变,使得框架更加繁难;

(2)独特思维

  • 单—的数据源
  • 变动能够预测

实质上∶ redux 与 vuex 都是对 mvvm 思维的服务,将数据从视图中抽离的一种计划。

React Hooks 解决了哪些问题?

React Hooks 次要解决了以下问题:

(1)在组件之间复用状态逻辑很难

React 没有提供将可复用性行为“附加”到组件的路径(例如,把组件连贯到 store)解决此类问题能够应用 render props 和 高阶组件。然而这类计划须要从新组织组件构造,这可能会很麻烦,并且会使代码难以了解。由 providers,consumers,高阶组件,render props 等其余形象层组成的组件会造成“嵌套天堂”。只管能够在 DevTools 过滤掉它们,但这阐明了一个更深层次的问题:React 须要为共享状态逻辑提供更好的原生路径。

能够应用 Hook 从组件中提取状态逻辑,使得这些逻辑能够独自测试并复用。Hook 使咱们在无需批改组件构造的状况下复用状态逻辑。这使得在组件间或社区内共享 Hook 变得更便捷。

(2)简单组件变得难以了解

在组件中,每个生命周期经常蕴含一些不相干的逻辑。例如,组件经常在 componentDidMount 和 componentDidUpdate 中获取数据。然而,同一个 componentDidMount 中可能也蕴含很多其它的逻辑,如设置事件监听,而之后需在 componentWillUnmount 中革除。互相关联且须要对照批改的代码被进行了拆分,而齐全不相干的代码却在同一个办法中组合在一起。如此很容易产生 bug,并且导致逻辑不统一。

在少数状况下,不可能将组件拆分为更小的粒度,因为状态逻辑无处不在。这也给测试带来了肯定挑战。同时,这也是很多人将 React 与状态治理库联合应用的起因之一。然而,这往往会引入了很多抽象概念,须要你在不同的文件之间来回切换,使得复用变得更加艰难。

为了解决这个问题,Hook 将组件中互相关联的局部拆分成更小的函数(比方设置订阅或申请数据),而并非强制依照生命周期划分。你还能够应用 reducer 来治理组件的外部状态,使其更加可预测。

(3)难以了解的 class

除了代码复用和代码治理会遇到困难外,class 是学习 React 的一大屏障。咱们必须去了解 JavaScript 中 this 的工作形式,这与其余语言存在微小差别。还不能遗记绑定事件处理器。没有稳固的语法提案,这些代码十分冗余。大家能够很好地了解 props,state 和自顶向下的数据流,但对 class 却束手无策。即使在有教训的 React 开发者之间,对于函数组件与 class 组件的差别也存在一致,甚至还要辨别两种组件的应用场景。

为了解决这些问题,Hook 使你在非 class 的状况下能够应用更多的 React 个性。从概念上讲,React 组件始终更像是函数。而 Hook 则拥抱了函数,同时也没有就义 React 的精力准则。Hook 提供了问题的解决方案,无需学习简单的函数式或响应式编程技术

react16 版本的 reconciliation 阶段和 commit 阶段是什么

  • reconciliation 阶段蕴含的次要工作是对 current tree 和 new tree 做 diff 计算,找出变动局部。进行遍历、比照等是能够中断,歇一会儿接着再来。
  • commit 阶段是对上一阶段获取到的变动局部利用到实在的 DOM 树中,是一系列的 DOM 操作。不仅要保护更简单的 DOM 状态,而且中断后再持续,会对用户体验造成影响。在广泛的利用场景下,此阶段的耗时比 diff 计算等耗时绝对短。

react 实现一个全局的 dialog

import React, {Component} from 'react';
import {is, fromJS} from 'immutable';
import ReactDOM from 'react-dom';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import './dialog.css';
let defaultState = {
  alertStatus:false,
  alertTip:"提醒",
  closeDialog:function(){},
  childs:''
}
class Dialog extends Component{
  state = {...defaultState};
  // css 动画组件设置为指标组件
  FirstChild = props => {const childrenArray = React.Children.toArray(props.children);
    return childrenArray[0] || null;
  }
  // 关上弹窗
  open =(options)=>{options = options || {};
    options.alertStatus = true;
    var props = options.props || {};
    var childs = this.renderChildren(props,options.childrens) || '';
    console.log(childs);
    this.setState({
      ...defaultState,
      ...options,
      childs
    })
  }
  // 敞开弹窗
  close(){this.state.closeDialog();
    this.setState({...defaultState})
  }
  renderChildren(props,childrens) {
    // 遍历所有子组件
    var childs = [];
    childrens = childrens || [];
    var ps = {
        ...props,  // 给子组件绑定 props
        _close:this.close  // 给子组件也绑定一个敞开弹窗的事件    
       };
    childrens.forEach((currentItem,index) => {
        childs.push(React.createElement(
            currentItem,
            {
                ...ps,
                key:index
            }
        ));
    })
    return childs;
  }
  shouldComponentUpdate(nextProps, nextState){return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
  }
   
  render(){
    return (
      <ReactCSSTransitionGroup
        component={this.FirstChild}
        transitionName='hide'
        transitionEnterTimeout={300}
        transitionLeaveTimeout={300}>
        <div className="dialog-con" style={this.state.alertStatus? {display:'block'}:{display:'none'}}>
            {this.state.childs}        </div>
      </ReactCSSTransitionGroup>
    );
  }
}
let div = document.createElement('div');
let props = { };
document.body.appendChild(div);
let Box = ReactD

子类:

// 子类 jsx
import React, {Component} from 'react';
class Child extends Component {constructor(props){super(props);
        this.state = {date: new Date()};
  }
  showValue=()=>{this.props.showValue && this.props.showValue()
  }
  render() {
    return (
      <div className="Child">
        <div className="content">
           Child           <button onClick={this.showValue}> 调用父的办法 </button>
        </div>
      </div>
    );
  }
}
export default Child;

css:

.dialog-con{
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.3);
}

我当初有一个 button,要用 react 在下面绑定点击事件,要怎么做?

class Demo {render() {return <button onClick={(e) => {alert('我点击了按钮')
    }}>
      按钮
    </button>
  }
}

你感觉你这样设置点击事件会有什么问题吗?

因为 onClick 应用的是匿名函数,所有每次重渲染的时候,会把该 onClick 当做一个新的 prop 来解决,会将外部缓存的 onClick 事件进行从新赋值,所以绝对间接应用函数来说,可能有一点的性能降落

批改

class Demo {onClick = (e) => {alert('我点击了按钮')
  }

  render() {return <button onClick={this.onClick}>
      按钮
    </button>
  }

你对 Time Slice 的了解?

工夫分片

  • React 在渲染(render)的时候,不会阻塞当初的线程
  • 如果你的设施足够快,你会感觉渲染是同步的
  • 如果你设施十分慢,你会感觉还算是灵活的
  • 尽管是异步渲染,然而你将会看到残缺的渲染,而不是一个组件一行行的渲染进去
  • 同样书写组件的形式

也就是说,这是 React 背地在做的事件,对于咱们开发者来说,是通明的,具体是什么样的成果呢?

组件是什么?类是什么?类变编译成什么

  • 组件指的是页面的一部分,实质就是一个类,最实质就是一个构造函数
  • 类编译成构造函数

state 和 props 共同点和区别

共同点

  • state 和 props 的扭转都会触发 render 函数(界面会产生扭转)

不同点

  • props 是 readonly(只读),然而 state 是可读可写
  • props 来自父组件,state 是组件外部的数据对象

为什么不间接更新 state 呢 ?

如果试图间接更新 state,则不会从新渲染组件。

// 谬误
This.state.message = 'Hello world';

须要应用 setState() 办法来更新 state。它调度对组件 state 对象的更新。当 state 扭转时,组件通过从新渲染来响应:

// 正确做法
This.setState({message:‘Hello World’});

这三个点 (…) 在 React 干嘛用的?

... 在 React(应用 JSX)代码中做什么?它叫什么?

<Modal {...this.props} title='Modal heading' animation={false}/>

这个叫扩大操作符号或者开展操作符,例如,如果 this.props 蕴含 a:1b:2,则

<Modal {...this.props} title='Modal heading' animation={false}>

等价于上面内容:

<Modal a={this.props.a} b={this.props.b} title='Modal heading' animation={false}>

扩大符号不仅实用于该用例,而且对于创立具备现有对象的大多数(或全副)属性的新对象十分不便,在更新state 咱们就常常这么做:

this.setState((prevState) => {return { foo: { ...prevState.foo, a: "updated"} };
});
正文完
 0