关于前端:前端经典react面试题及答案

29次阅读

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

为什么 React 元素有一个 $$typeof 属性

目标是为了避免 XSS 攻打。因为 Synbol 无奈被序列化,所以 React 能够通过有没有 $$typeof 属性来断出以后的 element 对象是从数据库来的还是本人生成的。

  • 如果没有 $$typeof 这个属性,react 会回绝解决该元素。
  • 在 React 的古老版本中,上面的写法会呈现 XSS 攻打:
// 服务端容许用户存储 JSON
let expectedTextButGotJSON = {
  type: 'div',
  props: {
    dangerouslySetInnerHTML: {__html: '/* 把你想的搁着 */'},
  },
  // ...
};
let message = {text: expectedTextButGotJSON};

// React 0.13 中有危险
<p>
  {message.text}
</p>

connect 原理

  • 首先 connect 之所以会胜利,是因为 Provider 组件:
  • 在原利用组件上包裹一层,使原来整个利用成为 Provider 的子组件 接管 Reduxstore作为 props,通过context 对象传递给子孙组件上的connect

connect做了些什么。它真正连贯 ReduxReact,它包在咱们的容器组件的外一层,它接管下面 Provider 提供的 store 外面的statedispatch,传给一个构造函数,返回一个对象,以属性模式传给咱们的容器组件

  • connect是一个高阶函数,首先传入 mapStateToPropsmapDispatchToProps,而后返回一个生产Component 的函数 (wrapWithConnect),而后再将真正的Component 作为参数传入 wrapWithConnect,这样就生产出一个通过包裹的Connect 组件,

该组件具备如下特点

  • 通过 props.store 获取先人 Componentstore props包含 statePropsdispatchPropsparentProps, 合并在一起失去nextState,作为props 传给真正的 Component componentDidMount 时,增加事件this.store.subscribe(this.handleChange),实现页面交互
  • shouldComponentUpdate时判断是否有防止进行渲染,晋升页面性能,并失去 nextState componentWillUnmount 时移除注册的事件this.handleChange

因为 connect 的源码过长,咱们只看次要逻辑

export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {return function wrapWithConnect(WrappedComponent) {
    class Connect extends Component {constructor(props, context) {
        // 从先人 Component 处取得 store
        this.store = props.store || context.store
        this.stateProps = computeStateProps(this.store, props)
        this.dispatchProps = computeDispatchProps(this.store, props)
        this.state = {storeState: null}
        // 对 stateProps、dispatchProps、parentProps 进行合并
        this.updateState()}
      shouldComponentUpdate(nextProps, nextState) {
        // 进行判断,当数据产生扭转时,Component 从新渲染
        if (propsChanged || mapStateProducedChange || dispatchPropsChanged) {this.updateState(nextProps)
            return true
          }
        }
        componentDidMount() {
          // 扭转 Component 的 state
          this.store.subscribe(() = {
            this.setState({storeState: this.store.getState()
            })
          })
        }
        render() {
          // 生成包裹组件 Connect
          return (<WrappedComponent {...this.nextState} />
          )
        }
      }
      Connect.contextTypes = {store: storeShape}
      return Connect;
    }
  }

redux 中间件

中间件提供第三方插件的模式,自定义拦挡 action -> reducer 的过程。变为 action -> middlewares -> reducer。这种机制能够让咱们扭转数据流,实现如异步 action,action 过 滤,日志输入,异样报告等性能

常见的中间件:

  • redux-logger: 提供日志输入;
  • redux-thunk: 解决异步操作;
  • redux-promise: 解决异步操作;
  • actionCreator 的返回值是 promise

react-router 里的 <Link> 标签和 <a> 标签有什么区别

比照 <a>,Link 组件防止了不必要的重渲染

setState

在理解 setState 之前,咱们先来简略理解下 React 一个包装构造: Transaction:

事务 (Transaction)

是 React 中的一个调用构造,用于包装一个办法,构造为: initialize – perform(method) – close。通过事务,能够对立治理一个办法的开始与完结;处于事务流中,示意过程正在执行一些操作

  • setState: React 中用于批改状态,更新视图。它具备以下特点:

异步与同步: setState 并不是单纯的异步或同步,这其实与调用时的环境相干:

  • 合成事件 生命周期钩子 (除 componentDidUpdate) 中,setState 是 ” 异步 ” 的;

    • 起因: 因为在 setState 的实现中,有一个判断: 当更新策略正在事务流的执行中时,该组件更新会被推入 dirtyComponents 队列中期待执行;否则,开始执行 batchedUpdates 队列更新;

      • 在生命周期钩子调用中,更新策略都处于更新之前,组件仍处于事务流中,而 componentDidUpdate 是在更新之后,此时组件曾经不在事务流中了,因而则会同步执行;
      • 在合成事件中,React 是基于 事务流实现的事件委托机制 实现,也是处于事务流中;
    • 问题: 无奈在 setState 后马上从 this.state 上获取更新后的值。
    • 解决: 如果须要马上同步去获取新值,setState 其实是能够传入第二个参数的。setState(updater, callback),在回调中即可获取最新值;
  • 原生事件 和 setTimeout 中,setState 是同步的,能够马上获取更新后的值;

    • 起因: 原生事件是浏览器自身的实现,与事务流无关,天然是同步;而 setTimeout 是搁置于定时器线程中延后执行,此时事务流已完结,因而也是同步;
  • 批量更新 : 在 合成事件 和 生命周期钩子 中,setState 更新队列时,存储的是 合并状态(Object.assign)。因而后面设置的 key 值会被前面所笼罩,最终只会执行一次更新;
  • 函数式 : 因为 Fiber 及 合并 的问题,官网举荐能够传入 函数 的模式。setState(fn),在 fn 中返回新的 state 对象即可,例如 this.setState((state, props) => newState);

    • 应用函数式,能够用于防止 setState 的批量更新的逻辑,传入的函数将会被 顺序调用;

注意事项:

  • setState 合并,在 合成事件 和 生命周期钩子 中屡次间断调用会被优化为一次;
  • 当组件已被销毁,如果再次调用 setState,React 会报错正告,通常有两种解决办法

    • 将数据挂载到内部,通过 props 传入,如放到 Redux 或 父级中;
    • 在组件外部保护一个状态量 (isUnmounted),componentWillUnmount 中标记为 true,在 setState 前进行判断;

Redux 实现原理解析

为什么要用 redux

React 中,数据在组件中是单向流动的,数据从一个方向父组件流向子组件(通过 props), 所以,两个非父子组件之间通信就绝对麻烦,redux 的呈现就是为了解决 state 外面的数据问题

Redux 设计理念

Redux是将整个利用状态存储到一个中央上称为 store, 外面保留着一个状态树store tree, 组件能够派发(dispatch) 行为 (action) 给store, 而不是间接告诉其余组件,组件外部通过订阅 store 中的状态 state 来刷新本人的视图

Redux 三大准则

  • 惟一数据源

整个利用的 state 都被存储到一个状态树外面,并且这个状态树,只存在于惟一的 store 中

  • 放弃只读状态

state是只读的,惟一扭转 state 的办法就是触发 actionaction 是一个用于形容以产生工夫的一般对象

  • 数据扭转只能通过纯函数来执行

应用纯函数来执行批改,为了形容 action 如何扭转 state 的,你须要编写reducers

Redux 源码

let createStore = (reducer) => {
    let state;
    // 获取状态对象
    // 寄存所有的监听函数
    let listeners = [];
    let getState = () => state;
    // 提供一个办法供内部调用派发 action
    let dispath = (action) => {
        // 调用管理员 reducer 失去新的 state
        state = reducer(state, action);
        // 执行所有的监听函数
        listeners.forEach((l) => l())
    }
    // 订阅状态变动事件,当状态扭转产生之后执行监听函数
    let subscribe = (listener) => {listeners.push(listener);
    }
    dispath();
    return {
        getState,
        dispath,
        subscribe
    }
}
let combineReducers=(renducers)=>{
    // 传入一个 renducers 治理组,返回的是一个 renducer
    return function(state={},action={}){let newState={};
        for(var attr in renducers){newState[attr]=renducers[attr](state[attr],action)

        }
        return newState;
    }
}
export {createStore,combineReducers};

参考 前端进阶面试题具体解答

在哪个生命周期中你会收回 Ajax 申请?为什么?

Ajax 申请应该写在组件创立期的第五个阶段,即 componentDidMount 生命周期办法中。起因如下。
在创立期的其余阶段,组件尚未渲染实现。而在存在期的 5 个阶段,又不能确保生命周期办法肯定会执行(如通过 shouldComponentUpdate 办法优化更新等)。在销毀期,组件行将被销毁,申请数据变得无意义。因而在这些阶段发岀 Ajax 申请显然不是最好的抉择。
在组件尚未挂载之前,Ajax 申请将无奈执行结束,如果此时发出请求,将意味着在组件挂载之前更新状态(如执行 setState),这通常是不起作用的。
在 componentDidMount 办法中,执行 Ajax 即可保障组件曾经挂载,并且可能失常更新组件。

Redux 中间件原理

  • 指的是 action 和 store 之间,沟通的桥梁就是 dispatch,action 就是个对象。比方你用了 redux-thunk,action 也能够是个函数,怎么实现这个过程,就是通过中间件这个桥梁帮你实现的。action 达到 store 之前会走中间件,这个中间件会把函数式的 action 转化为一个对象,在传递给 store

调用 setState 之后产生了什么

在代码中调用 setState 函数之后,React 会将传入的参数与之前的状态进行合并,而后触发所谓的和谐过程(Reconciliation)。通过和谐过程,React 会以绝对高效的形式依据新的状态构建 React 元素树并且着手从新渲染整个 UI 界面。在 React 失去元素树之后,React 会计算出新的树和老的树之间的差别,而后依据差别对界面进行最小化从新渲染。通过 diff 算法,React 可能准确制导哪些地位产生了扭转以及应该如何扭转,这就保障了按需更新,而不是全副从新渲染。

  • 在 setState 的时候,React 会为以后节点创立一个 updateQueue 的更新列队。
  • 而后会触发 reconciliation 过程,在这个过程中,会应用名为 Fiber 的调度算法,开始生成新的 Fiber 树,Fiber 算法的最大特点是能够做到异步可中断的执行。
  • 而后 React Scheduler 会依据优先级高下,先执行优先级高的节点,具体是执行 doWork 办法。
  • 在 doWork 办法中,React 会执行一遍 updateQueue 中的办法,以取得新的节点。而后比照新旧节点,为老节点打上 更新、插入、替换 等 Tag。
  • 以后节点 doWork 实现后,会执行 performUnitOfWork 办法取得新节点,而后再反复下面的过程。
  • 当所有节点都 doWork 实现后,会触发 commitRoot 办法,React 进入 commit 阶段。
  • 在 commit 阶段中,React 会依据后面为各个节点打的 Tag,一次性更新整个 dom 元素

createElement 与 cloneElement 的区别是什么

createElement 函数是 JSX 编译之后应用的创立 React Element 的函数,而 cloneElement 则是用于复制某个元素并传入新的 Props

React 中 keys 的作用是什么?

KeysReact 用于追踪哪些列表中元素被批改、被增加或者被移除的辅助标识

  • 在开发过程中,咱们须要保障某个元素的 key 在其同级元素中具备唯一性。在 React Diff 算法中React 会借助元素的 Key 值来判断该元素是早先创立的还是被挪动而来的元素,从而缩小不必要的元素重渲染。此外,React 还须要借助 Key 值来判断元素与本地状态的关联关系,因而咱们绝不可漠视转换函数中 Key 的重要性

HOC 相比 mixins 有什么长处?

HOC 和 Vue 中的 mixins 作用是统一的,并且在晚期 React 也是应用 mixins 的形式。然而在应用 class 的形式创立组件当前,mixins 的形式就不能应用了,并且其实 mixins 也是存在一些问题的,比方:

  • 隐含了一些依赖,比方我在组件中写了某个 state 并且在 mixin 中应用了,就这存在了一个依赖关系。万一下次他人要移除它,就得去 mixin 中查找依赖
  • 多个 mixin 中可能存在雷同命名的函数,同时代码组件中也不能呈现雷同命名的函数,否则就是重写了,其实我始终感觉命名真的是一件麻烦事。。
  • 雪球效应,尽管我一个组件还是应用着同一个 mixin,然而一个 mixin 会被多个组件应用,可能会存在需要使得 mixin 批改本来的函数或者新增更多的函数,这样可能就会产生一个保护老本

HOC 解决了这些问题,并且它们达成的成果也是统一的,同时也更加的政治正确(毕竟更加函数式了)。

react-router 里的 Link 标签和 a 标签的区别

从最终渲染的 DOM 来看,这两者都是链接,都是 标签,区别是∶ <Link>是 react-router 里实现路由跳转的链接,个别配合 <Route> 应用,react-router 接管了其默认的链接跳转行为,区别于传统的页面跳转,<Link> 的“跳转”行为只会触发相匹配的<Route> 对应的页面内容更新,而不会刷新整个页面。

<Link>做了 3 件事件:

  • 有 onclick 那就执行 onclick
  • click 的时候阻止 a 标签默认事件
  • 依据跳转 href(即是 to),用 history (web 前端路由两种形式之一,history & hash)跳转,此时只是链接变了,并没有刷新页面而 <a> 标签就是一般的超链接了,用于从以后页面跳转到 href 指向的另一 个页面(非锚点状况)。

a 标签默认事件禁掉之后做了什么才实现了跳转?

let domArr = document.getElementsByTagName('a')
[...domArr].forEach(item=>{item.addEventListener('click',function () {location.href = this.href})
})

React.createClass 和 extends Component 的区别有哪些?

React.createClass 和 extends Component 的 bai 区别次要在于:

(1)语法区别

  • createClass 实质上是一个工厂函数,extends 的形式更加靠近最新的 ES6 标准的 class 写法。两种形式在语法上的差异次要体现在办法的定义和动态属性的申明上。
  • createClass 形式的办法定义应用逗号,隔开,因为 creatClass 实质上是一个函数,传递给它的是一个 Object;而 class 的形式定义方法时务必谨记不要应用逗号隔开,这是 ES6 class 的语法标准。

(2)propType 和 getDefaultProps

  • React.createClass:通过 proTypes 对象和 getDefaultProps()办法来设置和获取 props.
  • React.Component:通过设置两个属性 propTypes 和 defaultProps

(3)状态的区别

  • React.createClass:通过 getInitialState()办法返回一个蕴含初始值的对象
  • React.Component:通过 constructor 设置初始状态

(4)this 区别

  • React.createClass:会正确绑定 this
  • React.Component:因为应用了 ES6,这里会有些微不同,属性并不会主动绑定到 React 类的实例上。

(5)Mixins

  • React.createClass:应用 React.createClass 的话,能够在创立组件时增加一个叫做 mixins 的属性,并将可供混合的类的汇合以数组的模式赋给 mixins。
  • 如果应用 ES6 的形式来创立组件,那么 React mixins 的个性将不能被应用了。

React-Router 怎么设置重定向?

应用 <Redirect> 组件实现路由的重定向:

<Switch>
  <Redirect from='/users/:id' to='/users/profile/:id'/>
  <Route path='/users/profile/:id' component={Profile}/>
</Switch>

当申请 /users/:id 被重定向去 '/users/profile/:id'

  • 属性 from: string:须要匹配的将要被重定向门路。
  • 属性 to: string:重定向的 URL 字符串
  • 属性 to: object:重定向的 location 对象
  • 属性 push: bool:若为真,重定向操作将会把新地址退出到拜访历史记录外面,并且无奈回退到后面的页面。

redux 有什么毛病

  • 一个组件所须要的数据,必须由父组件传过来,而不能像 flux 中间接从 store 取
  • 当一个组件相干数据更新时,即便父组件不须要用到这个组件,父组件还是会从新 render,可能会有效率影响,或者须要写简单的 shouldComponentUpdate 进行判断

React 如何获取组件对应的 DOM 元素?

能够用 ref 来获取某个子节点的实例,而后通过以后 class 组件实例的一些特定属性来间接获取子节点实例。ref 有三种实现办法:

  • 字符串格局:字符串格局,这是 React16 版本之前用得最多的,例如:<p ref="info">span</p>
  • 函数格局:ref 对应一个办法,该办法有一个参数,也就是对应的节点实例,例如:<p ref={ele => this.info = ele}></p>
  • createRef 办法 :React 16 提供的一个 API,应用 React.createRef() 来实现

state 和 props 触发更新的生命周期别离有什么区别?

state 更新流程: 这个过程当中波及的函数:

  1. shouldComponentUpdate: 当组件的 state 或 props 产生扭转时,都会首先触发这个生命周期函数。它会接管两个参数:nextProps, nextState——它们别离代表传入的新 props 和新的 state 值。拿到这两个值之后,咱们就能够通过一些比照逻辑来决定是否有 re-render(重渲染)的必要了。如果该函数的返回值为 false,则生命周期终止,反之持续;

留神:此办法仅作为 性能优化的形式 而存在。不要希图依附此办法来“阻止”渲染,因为这可能会产生 bug。应该 思考应用内置的 PureComponent 组件,而不是手动编写 shouldComponentUpdate()

  1. componentWillUpdate:当组件的 state 或 props 产生扭转时,会在渲染之前调用 componentWillUpdate。componentWillUpdate 是 React16 废除的三个生命周期之一。过来,咱们可能心愿能在这个阶段去收集一些必要的信息(比方更新前的 DOM 信息等等),当初咱们齐全能够在 React16 的 getSnapshotBeforeUpdate 中去做这些事;
  2. componentDidUpdate:componentDidUpdate() 会在 UI 更新后会被立刻调用。它接管 prevProps(上一次的 props 值)作为入参,也就是说在此处咱们依然能够进行 props 值比照(再次阐明 componentWillUpdate 的确鸡肋哈)。

props 更新流程: 绝对于 state 更新,props 更新后惟一的区别是减少了对 componentWillReceiveProps 的调用。对于 componentWillReceiveProps,须要晓得这些事件:

  • componentWillReceiveProps:它在 Component 承受到新的 props 时被触发。componentWillReceiveProps 会接管一个名为 nextProps 的参数(对应新的 props 值)。该生命周期是 React16 废除掉的三个生命周期之一。在它被废除前,能够用它来比拟 this.props 和 nextProps 来从新 setState。在 React16 中,用一个相似的新生命周期 getDerivedStateFromProps 来代替它。

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

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

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

react 的虚构 dom 是怎么实现的

首先说说为什么要应用 Virturl DOM,因为操作实在DOM 的消耗的性能代价太高,所以 react 外部应用 js 实现了一套 dom 构造,在每次操作在和实在 dom 之前,应用实现好的 diff 算法,对虚构 dom 进行比拟,递归找出有变动的 dom 节点,而后对其进行更新操作。为了实现虚构 DOM,咱们须要把每一种节点类型形象成对象,每一种节点类型有本人的属性,也就是 prop,每次进行diff 的时候,react会先比拟该节点类型,如果节点类型不一样,那么 react 会间接删除该节点,而后间接创立新的节点插入到其中,如果节点类型一样,那么会比拟 prop 是否有更新,如果有 prop 不一样,那么 react 会断定该节点有更新,那么重渲染该节点,而后在对其子节点进行比拟,一层一层往下,直到没有子节点

正文完
 0