乐趣区

关于前端:前端一面react面试题持续更新中

React 性能优化

  • shouldCompoentUpdate
  • pureComponent 自带 shouldCompoentUpdate 的浅比拟优化
  • 联合 Immutable.js 达到最优

为什么 useState 要应用数组而不是对象

useState 的用法:

const [count, setCount] = useState(0)

能够看到 useState 返回的是一个数组,那么为什么是返回数组而不是返回对象呢?

这里用到了解构赋值,所以先来看一下 ES6 的解构赋值:

数组的解构赋值
const foo = [1, 2, 3];
const [one, two, three] = foo;
console.log(one);    // 1
console.log(two);    // 2
console.log(three);    // 3
对象的解构赋值
const user = {
  id: 888,
  name: "xiaoxin"
};
const {id, name} = user;
console.log(id);    // 888
console.log(name);    // "xiaoxin"

看完这两个例子,答案应该就进去了:

  • 如果 useState 返回的是数组,那么使用者能够对数组中的元素命名,代码看起来也比拟洁净
  • 如果 useState 返回的是对象,在解构对象的时候必须要和 useState 外部实现返回的对象同名,想要应用屡次的话,必须得设置别名能力应用返回值

上面来看看如果 useState 返回对象的状况:

// 第一次应用
const {state, setState} = useState(false);
// 第二次应用
const {state: counter, setState: setCounter} = useState(0) 

这里能够看到,返回对象的应用形式还是挺麻烦的,更何况理论我的项目中会应用的更频繁。总结:useState 返回的是 array 而不是 object 的起因就是为了 升高应用的复杂度,返回数组的话能够间接依据程序解构,而返回对象的话要想应用屡次就须要定义别名了。

何为 JSX

JSX 是 JavaScript 语法的一种语法扩大,并领有 JavaScript 的全副性能。JSX 生产 React “ 元素 ”,你能够将任何的 JavaScript 表达式封装在花括号里,而后将其嵌入到 JSX 中。在编译实现之后,JSX 表达式就变成了惯例的 JavaScript 对象,这意味着你能够在 if 语句和 for 循环外部应用 JSX,将它赋值给变量,承受它作为参数,并从函数中返回它。

对 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

对虚构 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 等。

hooks 和 class 比拟的劣势?

一、更容易复用代码

二、清新的代码格调 + 代码量更少

毛病

状态不同步
不好用的 useEffect,

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

useEffect 与 useLayoutEffect 的区别

(1)共同点

  • 使用成果: useEffect 与 useLayoutEffect 两者都是用于解决副作用,这些副作用包含扭转 DOM、设置订阅、操作定时器等。在函数组件外部操作副作用是不被容许的,所以须要应用这两个函数去解决。
  • 应用形式: useEffect 与 useLayoutEffect 两者底层的函数签名是完全一致的,都是调用的 mountEffectImpl 办法,在应用上也没什么差别,根本能够间接替换。

(2)不同点

  • 应用场景: useEffect 在 React 的渲染过程中是被异步调用的,用于绝大多数场景;而 useLayoutEffect 会在所有的 DOM 变更之后同步调用,次要用于解决 DOM 操作、调整款式、防止页面闪动等问题。也正因为是同步解决,所以须要防止在 useLayoutEffect 做计算量较大的耗时工作从而造成阻塞。
  • 应用成果: useEffect 是依照程序执行代码的,扭转屏幕像素之后执行(先渲染,后扭转 DOM),当扭转屏幕内容时可能会产生闪动;useLayoutEffect 是扭转屏幕像素之前就执行了(会推延页面显示的事件,先扭转 DOM 后渲染),不会产生闪动。useLayoutEffect 总是比 useEffect 先执行。

在将来的趋势上,两个 API 是会长期共存的,临时没有删减合并的打算,须要开发者依据场景去自行抉择。React 团队的倡议十分实用,如果切实分不清,先用 useEffect,个别问题不大;如果页面有异样,再间接替换为 useLayoutEffect 即可。

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={};}
  }

当渲染一个列表时,何为 key?设置 key 的目标是什么

Keys 会有助于 React 辨认哪些 items 扭转了,被增加了或者被移除了。Keys 应该被赋予数组内的元素以赋予 (DOM) 元素一个稳固的标识,抉择一个 key 的最佳办法是应用一个字符串,该字符串能惟一地标识一个列表项。很多时候你会应用数据中的 IDs 作为 keys,当你没有稳固的 IDs 用于被渲染的 items 时,能够应用我的项目索引作为渲染项的 key,但这种形式并不举荐,如果 items 能够从新排序,就会导致 re-render 变慢。

何为纯函数(pure function)

一个纯函数是一个不依赖于且不扭转其作用域之外的变量状态的函数,这也意味着一个纯函数对于同样的参数总是返回同样的后果。

为何 React 事件要本人绑定 this

在 React 源码中,当具体到某一事件处理函数将要调用时,将调用 invokeGuardedCallback 办法。

function invokeGuardedCallback(name, func, a) {
  try {func(a);
  } catch (x) {if (caughtError === null) {caughtError = x;}
  }
}

事件处理函数是间接调用的,并没有指定调用的组件,所以不进行手动绑定的状况下间接获取到的 this 是不精确的,所以咱们须要手动将以后组件绑定到 this 上

useEffect 和 useLayoutEffect 的区别

useEffect
基本上 90% 的状况下, 都应该用这个, 这个是在 render 完结后, 你的 callback 函数执行, 然而不会 block browser painting, 算是某种异步的形式吧, 然而 class 的 componentDidMount 和 componentDidUpdate 是同步的, 在 render 完结后就运行,useEffect 在大部分场景下都比 class 的形式性能更好.
useLayoutEffect
这个是用在解决 DOM 的时候, 当你的 useEffect 外面的操作须要解决 DOM, 并且会扭转页面的款式, 就须要用这个, 否则可能会呈现呈现闪屏问题, useLayoutEffect 外面的 callback 函数会在 DOM 更新实现后立刻执行, 然而会在浏览器进行任何绘制之前运行实现, 阻塞了浏览器的绘制.

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 封装的状态管理工具。

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

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

父子组件的通信形式?

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

// 子组件: Child
const Child = props =>{return <p>{props.name}</p>
}
// 父组件 Parent
const Parent = ()=>{return <Child name="react"></Child>}

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

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

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})
})

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))
    }
})

setState 是同步异步?为什么?实现原理?

1. setState 是同步执行的

setState 是同步执行的,然而 state 并不一定会同步更新

2. setState 在 React 生命周期和合成事件中批量笼罩执行

在 React 的生命周期钩子和合成事件中,屡次执行 setState,会批量执行

具体表现为,屡次同步执行的 setState,会进行合并,相似于 Object.assign,雷同的 key,前面的会笼罩后面的

当遇到多个 setState 调用时候,会提取单次传递 setState 的对象,把他们合并在一起造成一个新的
繁多对象,并用这个繁多的对象去做 setState 的事件,就像 Object.assign 的对象合并,后一个
key 值会笼罩后面的 key 值

通过 React 解决的事件是不会同步更新 this.state 的. 通过 addEventListener || setTimeout/setInterval 的形式解决的则会同步更新。
为了合并 setState,咱们须要一个队列来保留每次 setState 的数据,而后在一段时间后执行合并操作和更新 state,并清空这个队列,而后渲染组件。

在 React 中怎么应用 async/await?

async/await 是 ES7 规范中的新个性。如果是应用 React 官网的脚手架创立的我的项目,就能够间接应用。如果是在本人搭建的 webpack 配置的我的项目中应用,可能会遇到 regeneratorRuntime is not defined 的异样谬误。那么咱们就须要引入 babel,并在 babel 中配置应用 async/await。能够利用 babel 的 transform-async-to-module-method 插件来转换其成为浏览器反对的语法,尽管没有性能的晋升,但对于代码编写体验要更好。

调用 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 元素
退出移动版