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);    // 1console.log(two);    // 2console.log(three);    // 3
对象的解构赋值
const user = {  id: 888,  name: "xiaoxin"};const { id, name } = user;console.log(id);    // 888console.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 向子组件传递须要的信息。

// 子组件: 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>        }}

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 元素