2021高频前端面试题汇总之React篇

2021高频前端面试题汇总之React篇 React视频教程系列 React 实战:CNode视频教程 完整教程目录:点击查看
1. React 事件机制

点我 复制代码

React并不是将click事件绑定到了div的真实DOM上,而是在document处监听了所有的事件,当事件发生并且冒泡到document处的时候,React将事件内容封装并交由真正的处理函数运行。这样的方式不仅仅减少了内存的消耗,还能在组件挂在销毁时统一订阅和移除事件。
除此之外,冒泡到document上的事件也不是原生的浏览器事件,而是由react自己实现的合成事件(SyntheticEvent)。因此如果不想要是事件冒泡的话应该调用event.preventDefault()方法,而不是调用event.stopProppagation()方法。 2021高频前端面试题汇总之React篇
文章图片
JSX 上写的事件并没有绑定在对应的真实 DOM 上,而是通过事件代理的方式,将所有的事件都统一绑定在了 document 上。这样的方式不仅减少了内存消耗,还能在组件挂载销毁时统一订阅和移除事件。
另外冒泡到 document 上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件(SyntheticEvent)。因此我们如果不想要事件冒泡的话,调用 event.stopPropagation 是无效的,而应该调用 event.preventDefault
实现合成事件的目的如下:
  • 合成事件首先抹平了浏览器之间的兼容问题,另外这是一个跨浏览器原生事件包装器,赋予了跨浏览器开发的能力;
  • 对于原生浏览器事件来说,浏览器会给监听器创建一个事件对象。如果你有很多的事件监听,那么就需要分配很多的事件对象,造成高额的内存分配问题。但是对于合成事件来说,有一个事件池专门来管理它们的创建和销毁,当事件需要被使用时,就会从池子中复用对象,事件回调结束后,就会销毁事件对象上的属性,从而便于下次复用事件对象。
2. React 高阶组件、Render props、hooks 有什么区别,为什么要不断迭代
这三者是目前react解决代码复用的主要方式:
  • 高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。具体而言,高阶组件是参数为组件,返回值为新组件的函数。
  • render props是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术,更具体的说,render prop 是一个用于告知组件需要渲染什么内容的函数 prop。
  • 通常,render props 和高阶组件只渲染一个子节点。让 Hook 来服务这个使用场景更加简单。这两种模式仍有用武之地,(例如,一个虚拟滚动条组件或许会有一个 renderltem 属性,或是一个可见的容器组件或许会有它自己的 DOM 结构)。但在大部分场景下,Hook 足够了,并且能够帮助减少嵌套。
(1)HOC 官方解释∶
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
简言之,HOC是一种组件的设计模式,HOC接受一个组件和额外的参数(如果需要),返回一个新的组件。HOC 是纯函数,没有副作用。
// hoc的定义 function withSubscription(WrappedComponent, selectData) { return class extends React.Component { constructor(props) { super(props); this.state = { data: selectData(DataSource, props) }; } // 一些通用的逻辑处理 render() { // ... 并使用新数据渲染被包装的组件! return ; } }; // 使用 const BlogPostWithSubscription = withSubscription(BlogPost, (DataSource, props) => DataSource.getBlogPost(props.id)); 复制代码

HOC的优缺点∶
  • 优点∶ 逻辑服用、不影响被包裹组件的内部逻辑。
  • 缺点∶ hoc传递给被包裹组件的props容易和被包裹后的组件重名,进而被覆盖
(2)Render props 官方解释∶
"render prop"是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术
具有render prop 的组件接受一个返回React元素的函数,将render的渲染逻辑注入到组件内部。在这里,"render"的命名可以是任何其他有效的标识符。
// DataProvider组件内部的渲染逻辑如下 class DataProvider extends React.Components { state = { name: 'Tom' }render() { return (共享数据组件自己内部的渲染逻辑
{ this.props.render(this.state) }); } }// 调用方式 ( Hello {data.name} )}/>复制代码

由此可以看到,render props的优缺点也很明显∶
  • 优点:数据共享、代码复用,将组件内的state作为props传递给调用者,将渲染逻辑交给调用者。
  • 缺点:无法在 return 语句外访问数据、嵌套写法不够优雅
(3)Hooks 官方解释∶
Hook是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。通过自定义hook,可以复用代码逻辑。
// 自定义一个获取订阅数据的hook function useSubscription() { const data = https://www.it610.com/article/DataSource.getComments(); return [data]; } // function CommentList(props) { const {data} = props; const [subData] = useSubscription(); ... } // 使用 复制代码

以上可以看出,hook解决了hoc的prop覆盖的问题,同时使用的方式解决了render props的嵌套地狱的问题。hook的优点如下∶
  • 使用直观;
  • 解决hoc的prop 重名问题;
  • 解决render props 因共享数据 而出现嵌套地狱的问题;
  • 能在return之外使用数据的问题。
需要注意的是:hook只能在组件顶层使用,不可在分支语句中使用。
总结∶ Hoc、render props和hook都是为了解决代码复用的问题,但是hoc和render props都有特定的使用场景和明显的缺点。hook是react16.8更新的新的API,让组件逻辑复用更简洁明了,同时也解决了hoc和render props的一些缺点。
3. 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自动做了一层浅比较。
4. Redux 中异步的请求怎么处理
可以在 componentDidmount 中直接进?请求?须借助redux。但是在?定规模的项?中,上述?法很难进?异步流的管理,通常情况下我们会借助redux的异步中间件进?异步处理。redux异步流中间件其实有很多,当下主流的异步中间件有两种redux-thunk、redux-saga。
(1)使用react-thunk中间件
redux-thunk优点:
  • 体积?: redux-thunk的实现?式很简单,只有不到20?代码
  • 使?简单: redux-thunk没有引?像redux-saga或者redux-observable额外的范式,上?简单
redux-thunk缺陷:
  • 样板代码过多: 与redux本身?样,通常?个请求需要?量的代码,?且很多都是重复性质的
  • 耦合严重: 异步操作与redux的action偶合在?起,不?便管理
  • 功能孱弱: 有?些实际开发中常?的功能需要??进?封装
使用步骤:
  • 配置中间件,在store的创建中配置
import {createStore, applyMiddleware, compose} from 'redux'; import reducer from './reducer'; import thunk from 'redux-thunk'// 设置调试工具 const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose; // 设置中间件 const enhancer = composeEnhancers( applyMiddleware(thunk) ); const store = createStore(reducer, enhancer); export default store; 复制代码

  • 添加一个返回函数的actionCreator,将异步请求逻辑放在里面
/** 发送get请求,并生成相应action,更新store的函数 @param url {string} 请求地址 @param func {function} 真正需要生成的action对应的actionCreator @return {function} */ // dispatch为自动接收的store.dispatch函数 export const getHttpAction = (url, func) => (dispatch) => { axios.get(url).then(function(res){ const action = func(res.data) dispatch(action) }) } 复制代码

  • 生成action,并发送action
componentDidMount(){ var action = getHttpAction('/getData', getInitTodoItemAction) // 发送函数类型的action时,该action的函数体会自动执行 store.dispatch(action) } 复制代码

(2)使用redux-saga中间件
redux-saga优点:
  • 异步解耦: 异步操作被被转移到单独 saga.js 中,不再是掺杂在 action.js 或 component.js 中
  • action摆脱thunk function: dispatch 的参数依然是?个纯粹的 action (FSA),?不是充满 “?魔法” thunk function
  • 异常处理: 受益于 generator function 的 saga 实现,代码异常/请求失败 都可以直接通过 try/catch 语法直接捕获处理
  • 功能强?: redux-saga提供了?量的Saga 辅助函数和Effect 创建器供开发者使?,开发者?须封装或者简单封装即可使?
  • 灵活: redux-saga可以将多个Saga可以串?/并?组合起来,形成?个?常实?的异步flow
  • 易测试,提供了各种case的测试?案,包括mock task,分?覆盖等等
redux-saga缺陷:
  • 额外的学习成本: redux-saga不仅在使?难以理解的 generator function,?且有数?个API,学习成本远超redux-thunk,最重要的是你的额外学习成本是只服务于这个库的,与redux-observable不同,redux-observable虽然也有额外学习成本但是背后是rxjs和?整套思想
  • 体积庞?: 体积略?,代码近2000?,min版25KB左右
  • 功能过剩: 实际上并发控制等功能很难?到,但是我们依然需要引?这些代码
  • ts?持不友好: yield?法返回TS类型
redux-saga可以捕获action,然后执行一个函数,那么可以把异步代码放在这个函数中,使用步骤如下:
  • 配置中间件
import {createStore, applyMiddleware, compose} from 'redux'; import reducer from './reducer'; import createSagaMiddleware from 'redux-saga' import TodoListSaga from './sagas'const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose; const sagaMiddleware = createSagaMiddleware()const enhancer = composeEnhancers( applyMiddleware(sagaMiddleware) ); const store = createStore(reducer, enhancer); sagaMiddleware.run(TodoListSaga)export default store; 复制代码

  • 将异步请求放在sagas.js中
import {takeEvery, put} from 'redux-saga/effects' import {initTodoList} from './actionCreator' import {GET_INIT_ITEM} from './actionTypes' import axios from 'axios'function* func(){ try{ // 可以获取异步返回数据 const res = yield axios.get('/getData') const action = initTodoList(res.data) // 将action发送到reducer yield put(action) }catch(e){ console.log('网络请求失败') } }function* mySaga(){ // 自动捕获GET_INIT_ITEM类型的action,并执行func yield takeEvery(GET_INIT_ITEM, func) }export default mySaga 复制代码

  • 发送action
componentDidMount(){ const action = getInitTodoItemAction() store.dispatch(action) }

5. Redux 中间件是什么?接受几个参数?柯里化函数两端的参数具体是什么?
Redux 的中间件提供的是位于 action 被发起之后,到达 reducer 之前的扩展点,换而言之,原本 view -→> action -> reducer -> store 的数据流加上中间件后变成了 view -> action -> middleware -> reducer -> store ,在这一环节可以做一些"副作用"的操作,如异步请求、打印日志等。
applyMiddleware源码:
export default function applyMiddleware(...middlewares) { return createStore => (...args) => { // 利用传入的createStore和reducer和创建一个store const store = createStore(...args) let dispatch = () => { throw new Error() } const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) } // 让每个 middleware 带着 middlewareAPI 这个参数分别执行一遍 const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 接着 compose 将 chain 中的所有匿名函数,组装成一个新的函数,即新的 dispatch dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } } 复制代码

从applyMiddleware中可以看出∶
  • redux中间件接受一个对象作为参数,对象的参数上有两个字段 dispatch 和 getState,分别代表着 Redux Store 上的两个同名函数。
  • 柯里化函数两端一个是 middewares,一个是store.dispatch
6. 对 React Hook 的理解,它的实现原理是什么
React-Hooks 是 React 团队在 React 组件开发实践中,逐渐认知到的一个改进点,这背后其实涉及对类组件和函数组件两种组件形式的思考和侧重。
(1)类组件: 所谓类组件,就是基于 ES6 Class 这种写法,通过继承 React.Component 得来的 React 组件。以下是一个类组件:
class DemoClass extends React.Component { state = { text: "" }; componentDidMount() { //... } changeText = (newText) => { this.setState({ text: newText }); }; render() { return ({this.state.text}
); } }复制代码

可以看出,React 类组件内部预置了相当多的“现成的东西”等着我们去调度/定制,state 和生命周期就是这些“现成东西”中的典型。要想得到这些东西,难度也不大,只需要继承一个 React.Component 即可。
当然,这也是类组件的一个不便,它太繁杂了,对于解决许多问题来说,编写一个类组件实在是一个过于复杂的姿势。复杂的姿势必然带来高昂的理解成本,这也是我们所不想看到的。除此之外,由于开发者编写的逻辑在封装后是和组件粘在一起的,这就使得类组件内部的逻辑难以实现拆分和复用。
(2)函数组件:函数组件就是以函数的形态存在的 React 组件。早期并没有 React-Hooks,函数组件内部无法定义和维护 state,因此它还有一个别名叫“无状态组件”。以下是一个函数组件:
function DemoFunction(props) { const { text } = props return ({`函数组件接收的内容:[${text}]`}
); } 复制代码

相比于类组件,函数组件肉眼可见的特质自然包括轻量、灵活、易于组织和维护、较低的学习成本等。
通过对比,从形态上可以对两种组件做区分,它们之间的区别如下:
  • 类组件需要继承 class,函数组件不需要;
  • 类组件可以访问生命周期方法,函数组件不能;
  • 类组件中可以获取到实例化后的 this,并基于这个 this 做各种各样的事情,而函数组件不可以;
  • 类组件中可以定义并维护 state(状态),而函数组件不可以;
除此之外,还有一些其他的不同。通过上面的区别,我们不能说谁好谁坏,它们各有自己的优势。在 React-Hooks 出现之前,类组件的能力边界明显强于函数组件。
实际上,类组件和函数组件之间,是面向对象和函数式编程这两套不同的设计思想之间的差异。而函数组件更加契合 React 框架的设计理念: 2021高频前端面试题汇总之React篇
文章图片
React 组件本身的定位就是函数,一个输入数据、输出 UI 的函数。作为开发者,我们编写的是声明式的代码,而 React 框架的主要工作,就是及时地把声明式的代码转换为命令式的 DOM 操作,把数据层面的描述映射到用户可见的 UI 变化中去。这就意味着从原则上来讲,React 的数据应该总是紧紧地和渲染绑定在一起的,而类组件做不到这一点。函数组件就真正地将数据和渲染绑定到了一起。函数组件是一个更加匹配其设计理念、也更有利于逻辑拆分与重用的组件表达形式。
为了能让开发者更好的的去编写函数式组件。于是,React-Hooks 便应运而生。
React-Hooks 是一套能够使函数组件更强大、更灵活的“钩子”。
函数组件比起类组件少了很多东西,比如生命周期、对 state 的管理等。这就给函数组件的使用带来了非常多的局限性,导致我们并不能使用函数这种形式,写出一个真正的全功能的组件。而React-Hooks 的出现,就是为了帮助函数组件补齐这些(相对于类组件来说)缺失的能力。
【2021高频前端面试题汇总之React篇】如果说函数组件是一台轻巧的快艇,那么 React-Hooks 就是一个内容丰富的零部件箱。“重装战舰”所预置的那些设备,这个箱子里基本全都有,同时它还不强制你全都要,而是允许你自由地选择和使用你需要的那些能力,然后将这些能力以 Hook(钩子)的形式“钩”进你的组件里,从而定制出一个最适合你的“专属战舰”。

    推荐阅读