react 面试题

函数组件和类组件

react 组件之间如何通讯

  • ⽗组件向⼦组件通讯: ⽗组件可以向⼦组件通过传 props 的⽅式,向⼦组件进⾏通讯
  • ⼦组件向⽗组件通讯: props+回调的⽅式,⽗组件向⼦组件传递props进⾏通讯,此props为作⽤域为⽗组件⾃身的函 数,⼦组件调⽤该函数,将⼦组件想要传递的信息,作为参数,传递到⽗组件的作⽤域中
  • 兄弟组件通信: 找到这两个兄弟节点共同的⽗节点,结合上⾯两种⽅式由⽗节点转发信息进⾏通信
  • 跨层级通信: Context 设计⽬的是为了共享那些对于⼀个组件树⽽⾔是“全局”的数据,例如当前认证的⽤户、主题或⾸选语⾔,对于跨越多层的全局数据通过 Context 通信再适合不过
  • 发布订阅模式: 发布者发布事件,订阅者监听事件并做出反应,我们可以通过引⼊event模块进⾏通信
  • 全局状态管理⼯具: 借助Redux或者Mobx等全局状态管理⼯具进⾏通信,这种⼯具会维护⼀个全局状态中⼼Store,并根据不同的事件产⽣新的状态

jsx 的本质是什么

JSX仅仅是React.createElement(component, props, …children)函数的语法糖 所有jsx最终都会被转换为React.createElement的函数调用

createElement传递三个参数:
createElement(type, config, children)
1、type:(当前ReactElement的类型,如果是标签元素就使用字符串表示,如 ‘div’,如果是组件元素就使用组件名)
2、config:(所有jsx中的属性都在config中以对象的属性和值得形式存储)
3、children:(存放在标签中,以children数组的方式进行存储,如果是多个参数,react内部有对它们进行处理)

React.createElement最终创建出来一个ReactElement对象
ReactElement对象的作用及为什么要创建它:

React利用ReactElement对象组成一个javaScript对象树
JavaScript的对象树就是虚拟DOM(Virtual DOM)
jsx->createElement函数->ReactElement(对象树)->ReactDOM.render->真实的DOM

为什么采用虚拟DOM,不是直接修改真实的DOM?
很难跟踪状态发生的改变:原有开发模式很难跟踪到状态发生的改变。不方便针对应用程序进行调试
操作真实DOM性能较低:传统开发模式会进行频繁DOM操作,此做法性能非常低
频繁操作DOM的问题
DOM操作性能非常低:

首先,document.createElement本身创建出来的就是一个非常复杂的对象

其次,DOM操作会引起浏览器的回流和重绘,所以开发中应避免频繁的DOM操作
例:ul里包含五个li,如果想要在ul后追另外追加5个li
答:
通过ul.appendChild(li)渲染到DOM上,进行了多次DOM操作,代码性能非常低
对于批量操作,最好的办法不是一次次修改DOM,而是对批量操作进行合并(例如通过 DocumentFragment【片段】进行合并)
 

context 是什么,如何应用

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

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

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

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

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

shouldcomponentupdate 的用途

shouldComponentUpdate是生命周期之一,是不常用的一个方法,能影响组件是否重新渲染。

在更新阶段,当有了new props或者调用setState()方法,在render方法执行前会执行。默认返回false,如果返回false则不刷新组件。

shouldComponentUpdate 允许我们手动地判断是否要进行组件更新

这个函数是re-render是render()函数调用前被调用的,他的两个参数nextProps和nextState,分别表示下一个props和下一个state的值。我们重写这个钩子,当函数返回false时,阻止接下来的render()调用以及组件重新渲染,反之,返回true时,组件向下走render重新渲染。
 

说一下redux 单向数据流机制

严格的单向数据流是 Redux 架构的设计核心。

这意味着应用中所有的数据都遵循相同的生命周期,这样可以让应用变得更加可预测且容易理解。同时也鼓励做数据范式化,这样可以避免使用多个且独立的无法相互引用的重复数据。

Redux 数据的生命周期
Redux 应用中数据的生命周期遵循下面 4 个步骤:

1.调用 store.dispatch(action)
Action 就是一个描述“发生了什么”的普通对象。

2.Redux store 调用传入的 reducer 函数
Store 会把两个参数传入 reducer: 当前的 state 树和 action。然后返回一个新的state

3.根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树
根 reducer 的结构完全由你决定。Redux 原生提供combineReducers()辅助函数,来把根 reducer 拆

分成多个函数,用于分别处理 state 树的一个分支。

4.Redux store 保存了根 reducer 返回的完整 state 树。
这个新的树就是应用的下一个 state!所有订阅 store.subscribe(listener) 的监听器都将被调用;监听器里可以调用 store.getState() 获得当前 state。

现在,可以应用新的 state 来更新 UI。如果你使用了 React Redux 这类的绑定库,这时就应该调用 component.setState(newState) 来更新。


 

react 类组件的 setstate 是同步操作还是异步操作

  • 假如所有setState是同步的,意味着每执行一次setState时(有可能一个同步代码中,多次setState),都重新vnode diff + dom修改,这对性能来说是极为不好的。如果是异步,则可以把一个同步代码中的多个setState合并成一次组件更新。所以默认是异步的,但是在一些情况下是同步的。

  • setState 并不是单纯同步/异步的,它的表现会因调用场景的不同而不同。在源码中,通过 isBatchingUpdates 来判断setState 是先存进 state 队列还是直接更新,如果值为 true 则执行异步操作,为 false 则直接更新。
  • 异步: 在 React 可以控制的地方,就为 true,比如在 React 生命周期事件和合成事件中,都会走合并操作,延迟更新的策略。
  • 同步: 在 React 无法控制的地方,比如原生事件,具体就是在 addEventListener 、setTimeout、setInterval 等事件中,就只能同步更新。
  • 一般认为,做异步设计是为了性能优化、减少渲染次数:
  • setState设计为异步,可以显著的提升性能。如果每次调用 setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的;最好的办法应该是获取到多个更新,之后进行批量更新;
  • 如果同步更新了state,但是还没有执行render函数,那么stateprops不能保持同步。stateprops不能保持一致性,会在开发中产生很多的问题;

什么是纯函数

纯函数是对给定的输入返还相同的输出的函数,并且要求所有的数据都是不可变的

特性

函数内部传入指定的值,就会返回唯一确定的值

不会造成超出作用域的变化,例如修改全局变量或引用传递的参数

优势

通过纯函数可以产生可测试的代码

不依赖外部环境计算,不会产生副作用,复用性高

可读性高,不管是不是纯函数,都会有一个语义化的名称,便于阅读

符合模块化概念及单一职责原则

介绍react组件的生命周期

  • 装载阶段(Mount),组件第一次在DOM树中被渲染的过程;
  • 更新过程(Update),组件状态发生变化,重新更新渲染的过程;
  • 卸载过程(Unmount),组件从DOM树中被移除的过程;

react 发起 Ajax 应该在哪个生命周期

对于同步的状态改变,是可以放在componentWillMount,对于异步的,最好好放在componentDidMount。但如果此时有若干细节需要处理,比如你的组件需要渲染子组件,而且子组件取决于父组件的某个属性,那么在子组件的componentDidMount中进行处理会有问题:因为此时父组件中对应的属性可能还没有完整获取,因此就让其在子组件的componentDidUpdate中处理。

至于为什么,先看看react的生命周期:
constructor() —> componentWillMount() —> render() -----> componentDidMount()

上面这些方法的调用是有次序的,由上而下,也就是当说如果你要获取外部数据并加载到组件上,只能在组件"已经"挂载到真实的网页上才能作这事情,其它情况你是加载不到组件的。

constructor被调用是在组件准备要挂载的最一开始,所以此时组件尚未挂载到网页上。
constructor()中获取数据的话,如果时间太长,或者出错,组件就渲染不出来,你整个页面都没法渲染了。

componentWillMount方法的调用在constructor之后,在render之前,在这方法里的代码调用setState方法不会触发重渲染,所以它一般不会用来作加载数据之用,它也很少被使用到。

componentDidMount方法中的代码,是在组件已经完全挂载到网页上才会调用被执行,所以可以保证数据的加载。此外,在这方法中调用setState方法,会触发重渲染。所以,官方设计这个方法就是用来加载外部数据用的,或处理其他的副作用代码

componentDidMount()中能保证你的组件已经正确渲染。

一般的从后台(服务器)获取的数据,都会与组件上要用的数据加载有关,所以都在componentDidMount方法里面作。虽然与组件上的数据无关的加载,也可以在constructor里做,但constructor是做组件state初绐化工作,并不是设计来作加载数据这工作的,所以所有有副作用的代码都会集中在componentDidMount方法里

总结下:

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

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

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

我们应当将AJAX 请求放到 componentDidMount 函数中执行,主要原因有下:

1.React 下一代调和算法 Fiber 会通过开始或停止渲染的方式优化应用性能,其会影响到 componenТWillMount 的触发次数。对于 componenТWillMount 这个生命周期函数的调用次数会变得不确定,React 可能会多次频繁调用 componenТWillMount。如果我们将 AJAX 请求放到 componenТWillMount 函数中,那么显而易见其会被触发多次,自然也就不是好的选择。
  2.如果我们将 AJAX 请求放置在生命周期的其他函数中,我们并不能保证请求仅在组件挂载完毕后才会要求响应。如果我们的数据请求在组件挂载之前就完成,并且调用了setState函数将数据添加到组件状态中,对于未挂载的组件则会报错。而在 componentDidMount 函数中进行 AJAX 请求则能有效避免这个问题。

在componentDidMount()函数中发送ajax请求,拿到数据,通过setState()保存在state中,供给组件使用。当组件要卸载时,在componentWillUnmount()函数中,通过this.serverRequest.abort();将还没有完成的ajax请求停止。

渲染列表,为什么使用key

在回答这个问题之前,我们先简单了解一下 React 的 DOM Diff 算法原理。

React 会在状态发生变化时,对真实 DOM 树按需批量更新,产生新的 UI。

为此底层做的工作是:将新旧两棵虚拟 DOM 树进行 diff 对比,计算出 patch 补丁,打到真实 DOM 树上。

为了高效,React 的 diff 算法做了限制:

只做同层级的节点对比,不跨层级比较;

如果元素的类型不同(如从 p 变成 div),那它们就是不相同的,会销毁整个旧子树,并调用其下组件的卸载钩子,然后再创建全新的树,相当消耗性能。

如果类型相同,会进行打补丁操作(如更新 className 和标签下的文本内容)

但这样做会有一个问题,如果同级的多节点 只是位置发生了变化,但因为相同索引位置对不上,又发现不能复用,就要销毁一棵树并创建一棵新树,实在是太过于低效了。

于是 React 给开发者提供 key 来标记节点,来优化 React diff 算法,告知 React 某个节点其实没有被

移除或不能被原地复用,只是换了位置而已,让 React 更新一下位置。

列表渲染不提供 key 会怎样?
不提供 key,React 就无法确定某个节点是否移动了。

React 就只会对比相同位置的两个节点,如果它们类型相同(比如都是 li 元素),就会对比 props 的不同,进行 props 的打补丁。

因为 列表渲染通常都是相同的类型,所以位置变动时,多半是会触发节点原地复用效果,倒是不用担心树的销毁重建发生。

原地复用在不提供 key 的时候有时候也是能正确渲染的。

对于列表的渲染,我们有必要提供 key,来对节点进行区分,React 的 DOM Diff 算法会基于 key 进行节点位置的调整,确保一些涉及到内部状态的节点的渲染状态。

通常来说,key 值应该是唯一的,通常来自后端返回的数据。在你确认列表不会发生位置变更时,可以使用数组索引作为 key,以去掉恼人的警告提示。

有一个点需要说明的是,key 并不是列表渲染的专属,普通的节点也可以用 key。
 

函数组件和类组件的区别

  • 相同点:
  • 组件是 React 可复用的最小代码片段,它们会返回要在页面中渲染的 React 元素。也正因为组件是 React 的最小编码单位,所以无论是函数组件还是类组件,在使用方式和最终呈现效果上都是完全一致的。

  • 我们甚至可以将一个类组件改写成函数组件,或者把函数组件改写成一个类组件(虽然并不推荐这种重构行为)。从使用者的角度而言,很难从使用体验上区分两者,而且在现代浏览器中,闭包和类的性能只在极端场景下才会有明显的差别。所以,基本可认为两者作为组件是完全一致的。

  • 不同点:
  • 它们在开发时的心智模型上却存在巨大的差异。类组件是基于面向对象编程的,它主打的是继承、生命周期等核心概念;而函数组件内核是函数式编程,主打的是 immutable、没有副作用、引用透明等特点。
  • 之前,在使用场景上,如果存在需要使用生命周期的组件,那么主推类组件;设计模式上,如果需要使用继承,那么主推类组件。但现在由于 React Hooks 的推出,生命周期概念的淡出,函数组件可以完全取代类组件。其次继承并不是组件最佳的设计模式,官方更推崇“组合优于继承”的设计概念,所以类组件在这方面的优势也在淡出。
  • 性能优化上,类组件主要依靠 shouldComponentUpdate 阻断渲染来提升性能,而函数组件依靠 React.memo 缓存渲染结果来提升性能。
  • 从上手程度而言,类组件更容易上手,从未来趋势上看,由于React Hooks 的推出,函数组件成了社区未来主推的方案。
  • 类组件在未来时间切片与并发模式中,由于生命周期带来的复杂度,并不易于优化。而函数组件本身轻量简单,且在 Hooks 的基础上提供了比原先更细粒度的逻辑组织与复用,更能适应 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 代码。如果你不介意代码美观性,并且希望快速编写代码,使用非受控组件往往可以减少你的代码量。否则,你应该使用受控组件。

何时使用异步组件

从 React 中引入 lazy 方法和 Suspense 组件,然后用 lazy 方法处理我们的组件,lazy 会返回一个新的React 组件,我们可以直接在 Suspense 标签内使用,这样组件就会在匹配的时候才加载。

lazy 接受一个函数作为参数,函数内部使用 import() 方法异步加载组件,加载的结果返回。

Suspense 组件的 fallback 属性是必填属性,它接受一个组件,在内部的异步组件还未加载完成时显示,所以我们通常传递一个 Loading 组件给它,如果没有传递的话,就会报错。

多个公共组件有公共逻辑,如何抽离

函数式编程和组件式编程思想某舟意义上是一致的,它们都是’组合‘的艺术,一个大的函数可以有多个职责单一函数组合而成。组件也是如此。我们将一个大的组件拆分为子组件,对组件做更细粒度的控制,保持它们的纯净性,让它们的职责更加单一,更加独立,这带来的好处就是可以复用性,可测性和可预测性。

我们可以很容易地保证一个底层组件的纯净性,因为它本来就很简单。但是对于一个复杂的组件树,则需要花心思进行构建,所以就有了’状态管理‘的需求。这些状态管理器通常都在组件树的外部维护一个或者多个状态库,然后通过依赖注入形式,将局部的状态注入到子树中,通过视图和逻辑分离的原则,来维持组件树的纯净性。

Redux就是一个典型的解决方案,在Redux的世界里可以认为一个复杂的组件树就是一颗状态树的映射,只要状态树不变,组件树就不变,Redux建议保持组件的纯净性,将组件状态交给Redux和配套的异步处理工具来维护,这样就将整个应用抽象成一个"单向数据流",这是一种简单的“输入/输出”关系。
 

redux 如何进行异步请求

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

使用react-thunk中间件

redux-thunk优点:

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

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

react-router 如何配置懒加载

原理
webpack代码分割
React利用 React.lazy与import()实现了渲染时的动态加载
利用Suspense来处理异步加载资源时页面应该如何显示的问题
1.React.lazy
通过lazy() api来动态import需要懒加载的组件
import的组件目前只支持export default的形式导出
Suspense来包裹懒加载的组件进行加载,可以设置fallback现实加载中效果
React.lazy可以结合Router来对模块进行懒加载。

2.react-loadable

react-loadable是以组件级别来分割代码的,这意味着,我们不仅可以根据路由按需加载,还可以根据组件按需加载,使用方式和路由分割一样,只用修改组件的引入方式即可

3.webpack配置中使用lazyload-loader

4.import() webpack v2+

符合ECMAScript提议的import()语法,该提案与普通 import 语句或 require 函数的类似,但返回一个 Promise 对象

5.requre.ensure webpack v1 v2

什么是purecomponent

一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,我们就把这个函数叫做纯函数

  • 函数的返回结果只依赖于它的参数
  • 函数执行过程里面没有副作用

react 事件和dom 事件有什么区别

在浏览器中,我们通过事件监听来实现 JS 和 HTML 之间的交互。一个页面往往会被绑定许许多多的事件,而页面接收事件的顺序,就是事件流

W3C 标准约定了一个事件的传播过程要经过以下 3 个阶段:

  1. 事件捕获阶段

  2. 目标阶段

  3. 事件冒泡阶段

当事件被触发时,首先经历的是一个捕获过程:事件会从最外层的元素开始“穿梭”,逐层“穿梭”到最内层元素,这个过程会持续到事件抵达它目标的元素(也就是真正触发这个事件的元素)为止;此时事件流就切换到了“目标阶段”——事件被目标元素所接收;然后事件会被“回弹”,进入到冒泡阶段——它会沿着来时的路“逆流而上”,一层一层再走回去。被绑定在页面的 document 上。当事件在具体的 DOM 节点上被触发后,最终都会冒泡到 document 上,document 上所绑定的统一事件处理程序会将事件分发到具体的组件实例。

在分发事件之前,React 首

这个过程很像我们小时候玩的蹦床:从高处下落,触达蹦床后再弹起、回到高处,整个过程呈一个对称的“V”字形。

这里再强调一下 e.target 这个属性,它指的是触发事件的具体目标,它记录着事件的源头。所以说,不管咱们的监听函数在哪一层执行,只要我拿到这个 e.target,就相当于拿到了真正触发事件的那个元素。拿到这个元素后,我们完全可以模拟出它的行为,实现无差别的监听效果。

像这样利用事件的冒泡特性,把多个子元素的同一类型的监听逻辑,合并到父元素上通过一个监听函数来管理的行为,就是事件委托。通过事件委托,我们可以减少内存开销、简化注册步骤,大大提高开发效率。

这绝妙的事件委托,正是 React合成事件的灵感源泉。

React 事件系统是如何工作的
React 的事件系统沿袭了事件委托的思想。在 React 中,除了少数特殊的不可冒泡的事件(比如媒体类型的事件)无法被事件系统处理外,绝大部分的事件都不会被绑定在具体的元素上,而是统一

先会对事件进行包装,把原生 DOM 事件包装成合成事件。

认识 React 合成事件
合成事件是 React 自定义的事件对象,它符合W3C规范,在底层抹平了不同浏览器的差异,在上层面向开发者暴露统一的、稳定的、与 DOM 原生事件相同的事件接口。开发者们由此便不必再关注烦琐的兼容性问题,可以专注于业务逻辑的开发。

虽然合成事件并不是原生 DOM 事件,但它保存了原生 DOM 事件的引用。当你需要访问原生 DOM 事件对象时,可以通过合成事件对象的 e.nativeEvent 属性获取到它

React 事件系统工作流拆解
既然是事件系统,那就逃不出“事件绑定”和“事件触发”这两个关键动作。首先让我们一起来看看事件的绑定是如何实现的。

事件的绑定
事件的绑定是在组件的挂载过程中完成的,具体来说,是在 completeWork 中完成的。关于 completeWork,我们已经在第 15 讲中学习过它的工作原理,这里需要你回忆起来的是 completeWork 中的以下三个动作:

completeWork 内部有三个关键动作:创建 DOM 节点(createInstance)、将 DOM 节点插入到 DOM 树中(appendAllChildren)、为 DOM 节点设置属性(finalizeInitialChildren)。

其中“为 DOM 节点**设置属性”**这个环节,会遍历 FiberNode 的 props key。当遍历到事件相关的 props 时,就会触发事件的注册链路

react 性能优化方式有哪些

   1.render里面尽量减少新建变量和bind函数,传递参数是尽量减少传递参数的数量。

 2.定制shouldcomponentupdate函数

3.使用react.purecomponent

4.使用React.memo来缓存组件
React.memo是一个高阶组件,高阶组件(HOC)是参数为组件,返回值为新组件的函数React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useState,useReducer 或 useContext 的 Hook,当 state 或 context 发生变化时,它仍会重新渲染。默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现

5.延迟加载不是立即需要的组件
 

说一下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

react hooks

1.列举10个常用的react内置hooks

useState    设置和改变state,代替原来的state和setState
useEffect   代替原来的生命周期,componentDidMount,componentDidUpdate 和 componentWillUnmount 的合并版
useLayoutEffect 与 useEffect 作用相同,但它会同步调用 effect
useMemo 控制组件更新条件,可根据状态变化控制方法执行,优化传值
useCallback useMemo优化传值,usecallback优化传的方法,是否更新
useRef  跟以前的ref,一样,只是更简洁了
useContext  上下文爷孙及更深组件传值
useReducer  代替原来redux里的reducer,配合useContext一起使用
useDebugValue   在 React 开发者工具中显示自定义 hook 的标签,调试使用。
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。

2.为什么会有react hooks 它解决了哪些问题

一、是什么
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性

至于为什么引入hook,官方给出的动机是解决长时间使用和维护react过程中常遇到的问题,例如:

难以重用和共享组件中的与状态相关的逻辑

逻辑复杂的组件难以开发与维护,当我们的组件需要处理多个互不相关的 local state 时,每个生命周期函数中可能会包含着各种互不相关的逻辑在里面

类组件中的this增加学习成本,类组件在基于现有工具的优化上存在些许问题

由于业务变动,函数组件不得不改为类组件等等

在以前,函数组件也被称为无状态的组件,只负责渲染的一些工作

因此,现在的函数组件也可以是有状态的组件,内部也可以维护自身的状态以及做一些逻辑方面的处理

二、有哪些

上面讲到,Hooks让我们的函数组件拥有了类组件的特性,例如组件内的状态、生命周期

最常见的hooks有如下:

useState

useEffect

其它 hooks

在组件通信过程中可以使用useContextrefs学习中我们也用到了useRef获取DOM结构......

还有很多额外的hooks,如:

useReducer

useCallback

useMemo

useRef

三、解决什么

可以看到hooks能够更容易解决状态相关的重用的问题:

每调用useHook一次都会生成一份独立的状态

通过自定义hook能够更好的封装我们的功能

编写hooks为函数式编程,每个功能都包裹在函数中,整体风格更清爽,更优雅

hooks的出现,使函数组件的功能得到了扩充,拥有了类组件相似的功能,在我们日常使用中,使用hooks能够解决大多数问题,并且还拥有代码复用机制,因此优先考虑hooks


3.react hooks如何模拟组件的生命周期

constructor

class 组件

class Example extends Component {
    constructor() {
        super();
        this.state = {
            count: 0
        }
    }
    render() {
      return null;
  }
}

函数组件不需要构造函数,可以通过调用 useState 来初始化 state

function Example() {
  const [count, setCount] = useState(0);
  return null;
}
componentDidMount

class 组件访问 componentDidMount

class Example extends React.Component {
  componentDidMount() {
    console.log('I am mounted!');
  }
  render() {
    return null;
  }
}

使用 hooks 模拟 componentDidMount

function Example() {
  useEffect(() => console.log('mounted'), []);
  return null;
}

useEffect 拥有两个参数,第一个参数作为回调函数会在浏览器布局和绘制完成后调用,因此它不会阻碍浏览器的渲染进程。
第二个参数是一个数组

当数组存在并有值时,如果数组中的任何值发生更改,则每次渲染后都会触发回调。
当它不存在时,每次渲染后都会触发回调。
当它是一个空列表时,回调只会被触发一次,类似于 componentDidMount。


shouldComponentUpdate


class 组件访问 shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState){
  console.log('shouldComponentUpdate')
  // return true 更新组件
  // return false 则不更新组件
}
hooks 模拟 shouldComponentUpdate

const MyComponent = React.memo(
    _MyComponent, 
    (prevProps, nextProps) => nextProps.count !== prevProps.count
)

React.memo 包裹一个组件来对它的 props 进行浅比较,但这不是一个 hooks,因为它的写法和 hooks 不同,其实React.memo 等效于 PureComponent,但它只比较 props。

componentDidUpdate


class 组件访问 componentDidUpdate
componentDidMount() {
  console.log('mounted or updated');
}
 
componentDidUpdate() {
  console.log('mounted or updated');
}

使用 hooks 模拟 componentDidUpdate

useEffect(() => console.log('mounted or updated'));
值得注意的是,这里的回调函数会在每次渲染后调用,因此不仅可以访问 componentDidUpdate,还可以访问componentDidMount,如果只想模拟 componentDidUpdate,我们可以这样来实现。

const mounted = useRef();
useEffect(() => {
  if (!mounted.current) {
    mounted.current = true;
  } else {
   console.log('I am didUpdate')
  }
});

useRef 在组件中创建“实例变量”。它作为一个标志来指示组件是否处于挂载或更新阶段。当组件更新完成后在会执行 else 里面的内容,以此来单独模拟 componentDidUpdate。

componentWillUnmount


class 组件访问 componentWillUnmount
componentWillUnmount() {
  console.log('will unmount');
}
hooks 模拟 componentWillUnmount

useEffect(() => {
  return () => {
    console.log('will unmount');
  }
}, []);

总结


引入 hooks 的函数组件功能越来越完善,在多数情况下,我们完全可以使用 hook 来替代 class 组件。并且使用函数组件也有以下几点好处。

纯函数概念,同样的 props 会得到同样的渲染结果。
可以使用函数组合,嵌套,实现功能更加强大的组件。
组件不会被实例化,整体渲染性能得到提升。
但是 hooks 模拟的生命周期与class中的生命周期不尽相同,我们在使用时,还是需要思考业务场景下那种方式最适合。

4.如何自定义hooks

要知道自定义hooks的目的是什么,区别于自定义组件

自定义hooks最大的好处就是可以 逻辑复用

1. 自定义 Hooks 是一个函数,约定函数名称必须以 use 开头,React 就是通过函数名称是否以 use 开头来判断是不是 Hooks

2. Hooks 只能在函数组件中或其他自定义 Hooks 中使用,否则,会报错!

3. 自定义 Hooks 用来提取组件的状态逻辑,根据不同功能可以有不同的参数和返回值(就像使用普通函数一样)

下面这是一个自定义倒计时的 hooks

① 首先定义一个函数并导出

② 先明白需求,我需要函数来干什么

第一:我可以传递一个定时器的初始时间(initCount) ,如果不传默认10秒

第二: 当时间结束时,我需要做什么, callBack=()=>{}  结束之后的回调函数

第三: 我可以获取到剩余的时间吗?

第四:  我可以自己控制什么时候执行这个定时器吗

③ 明白需求后对着下面格式书写代码

④ 该hooks 可以传递一个时间,并使用结束时候的回调函数

⑤  该hooks 可以实时返回:剩余时间( count ),什么时候开始执行倒计时( start( ) )
export default function useCountDown (initCount = 10, callBack = () => {}) {
  const timeId = useRef(null)
 
  const [count, setCount] = useState(initCount)
 
  const start = () => {
    setCount(initCount)
    timeId.current = setInterval(() => {
      setCount((count) => count - 1)
    }, 1000)
  }
  useEffect(() => {
    return () => {
      console.log('..........')
      clearInterval(timeId.current)
    }
  }, [])
 
  useEffect(
    () => {
      console.log(count, timeId.current)
      if (count === 0) {
        clearInterval(timeId.current)
        callBack()
      }
    },
    [count]
  )
 
  return { count, start }
}

5.说一下react hooks 性能优化

React 性能优化思路
我觉得 React 性能优化的理念的主要方向就是这两个:

减少重新 render 的次数。因为在 React 里最重(花时间最长)的一块就是 reconction(简单的可以理解为 diff),如果不 render,就不会 reconction。

减少计算的量。主要是减少重复计算,对于函数式组件来说,每次 render 都会重新从头开始执行函数调用。

在使用类组件的时候,使用的 React 优化 API 主要是:shouldComponentUpdate和 PureComponent,这两个 API 所提供的解决思路都是为了减少重新 render 的次数,主要是减少父组件更新而子组件也更新的情况,虽然也可以在 state 更新的时候阻止当前组件渲染,如果要这么做的话,证明你这个属性不适合作为 state,而应该作为静态属性或者放在 class 外面作为一个简单的变量 。

1,React.memo(component,fun(pre,next))
        使用React.memo包裹组件,在组件不发生数据更新的时候,不会重新渲染

2,useCallback(fun, [a, b])

        传递子组件方法的时候,使用useCallback包裹

3,useMemo缓存函数运行结果

        useMemo(fun, [a, b]);
 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值