写点什么

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

用户头像
it优课
关注
发布于: 5 小时前

React 视频教程系列

React 实战:CNode 视频教程


完整教程目录:点击查看


React 经典教程-从入门到精通


完整教程目录:点击查看


最新最全前端毕设项目(小程序+VUE+Noed+React+uni app+Express+Mongodb)


完整教程目录:点击查看


2021 前端 React 精品教程


完整教程目录:点击查看

1. React 的事件和普通的 HTML 事件有什么不同?

区别:


  • 对于事件名称命名方式,原生事件为全小写,react 事件采用小驼峰;

  • 对于事件函数处理语法,原生事件为字符串,react 事件为函数;

  • react 事件不能采用 return false 的方式来阻止浏览器的默认行为,而必须要地明确地调用preventDefault()来阻止默认行为。


合成事件是 react 模拟原生 DOM 事件所有能力的一个事件对象,其优点如下:


  • 兼容所有浏览器,更好的跨平台;

  • 将事件统一存放在一个数组,避免频繁的新增与删除(垃圾回收)。

  • 方便 react 统一管理和事务机制。


事件的执行顺序为原生事件先执行,合成事件后执行,合成事件会冒泡绑定到 document 上,所以尽量避免原生事件与合成事件混用,如果原生事件阻止冒泡,可能会导致合成事件不执行,因为需要冒泡到 document 上合成事件才会执行。

2. React 组件中怎么做事件代理?它的原理是什么?

React 基于 Virtual DOM 实现了一个 SyntheticEvent 层(合成事件层),定义的事件处理器会接收到一个合成事件对象的实例,它符合 W3C 标准,且与原生的浏览器事件拥有同样的接口,支持冒泡机制,所有的事件都自动绑定在最外层上。


在 React 底层,主要对合成事件做了两件事:


  • 事件委派: React 会把所有的事件绑定到结构的最外层,使用统一的事件监听器,这个事件监听器上维持了一个映射来保存所有组件内部事件监听和处理函数。

  • 自动绑定: React 组件中,每个方法的上下文都会指向该组件的实例,即自动绑定 this 为当前组件。

3. 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 <WrappedComponent data={this.state.data} {...this.props} />;    }  };
// 使用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 ( <div> <p>共享数据组件自己内部的渲染逻辑</p> { this.props.render(this.state) } </div> ); }}
// 调用方式<DataProvider render={data => ( <h1>Hello {data.name}</h1>)}/>
复制代码
复制代码


由此可以看到,render props 的优缺点也很明显∶


  • 优点:数据共享、代码复用,将组件内的 state 作为 props 传递给调用者,将渲染逻辑交给调用者。

  • 缺点:无法在 return 语句外访问数据、嵌套写法不够优雅


(3)Hooks 官方解释∶


Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。通过自定义 hook,可以复用代码逻辑。


// 自定义一个获取订阅数据的hookfunction useSubscription() {  const data = DataSource.getComments();  return [data];}// function CommentList(props) {  const {data} = props;  const [subData] = useSubscription();    ...}// 使用<CommentList data='hello' />复制代码
复制代码


以上可以看出,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 的一些缺点。

4. 对 React-Fiber 的理解,它解决了什么问题?

React V15 在渲染时,会递归比对 VirtualDOM 树,找出需要变动的节点,然后同步更新它们, 一气呵成。这个过程期间, React 会占据浏览器资源,这会导致用户触发的事件得不到响应,并且会导致掉帧,导致用户感觉到卡顿


为了给用户制造一种应用很快的“假象”,不能让一个任务长期霸占着资源。 可以将浏览器的渲染、布局、绘制、资源加载(例如 HTML 解析)、事件响应、脚本执行视作操作系统的“进程”,需要通过某些调度策略合理地分配 CPU 资源,从而提高浏览器的用户响应速率, 同时兼顾任务执行效率。


所以 React 通过 Fiber 架构,让这个执行过程变成可被中断。“适时”地让出 CPU 执行权,除了可以让浏览器及时地响应用户的交互,还有其他好处:


  • 分批延时对 DOM 进行操作,避免一次性操作大量 DOM 节点,可以得到更好的用户体验;

  • 给浏览器一点喘息的机会,它会对代码进行编译优化(JIT)及进行热代码优化,或者对 reflow 进行修正。


核心思想: Fiber 也称协程或者纤程。它和线程并不一样,协程本身是没有并发或者并行能力的(需要配合线程),它只是一种控制流程的让出机制。让出 CPU 的执行权,让 CPU 能在这段时间执行其他的操作。渲染的过程可以被中断,可以将控制权交回浏览器,让位给高优先级的任务,浏览器空闲后再恢复渲染。

5. Component, Element, Instance 之间有什么区别和联系?

  • 元素: 一个元素element是一个普通对象(plain object),描述了对于一个 DOM 节点或者其他组件component,你想让它在屏幕上呈现成什么样子。元素element可以在它的属性props中包含其他元素(译注:用于形成元素树)。创建一个 React 元素element成本很低。元素element创建之后是不可变的。

  • 组件: 一个组件component可以通过多种方式声明。可以是带有一个render()方法的类,简单点也可以定义为一个函数。这两种情况下,它都把属性props作为输入,把返回的一棵元素树作为输出。

  • 实例: 一个实例instance是你在所写的组件类component class中使用关键字this所指向的东西(译注:组件实例)。它用来存储本地状态和响应生命周期事件很有用。


函数式组件(Functional component)根本没有实例instance。类组件(Class component)有实例instance,但是永远也不需要直接创建一个组件的实例,因为 React 帮我们做了这些。

6. 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 的特性将不能被使用了。

7. 对 React 的插槽(Portals)的理解,如何使用,有哪些使用场景

React 官方对 Portals 的定义:


Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案


Portals 是 React 16 提供的官方解决方案,使得组件可以脱离父组件层级挂载在 DOM 树的任何位置。通俗来讲,就是我们 render 一个组件,但这个组件的 DOM 结构并不在本组件内。


Portals 语法如下:


ReactDOM.createPortal(child, container);复制代
复制代码


  • 第一个参数 child 是可渲染的 React 子项,比如元素,字符串或者片段等;

  • 第二个参数 container 是一个 DOM 元素。


一般情况下,组件的 render 函数返回的元素会被挂载在它的父级组件上:


import DemoComponent from './DemoComponent';render() {  // DemoComponent元素会被挂载在id为parent的div的元素上  return (    <div id="parent">        <DemoComponent />    </div>  );}
复制代码

8. Redux 原理及工作流程

(1)原理 Redux 源码主要分为以下几个模块文件


  • compose.js 提供从右到左进行函数式编程

  • createStore.js 提供作为生成唯一 store 的函数

  • combineReducers.js 提供合并多个 reducer 的函数,保证 store 的唯一性

  • bindActionCreators.js 可以让开发者在不直接接触 dispacth 的前提下进行更改 state 的操作

  • applyMiddleware.js 这个方法通过中间件来增强 dispatch 的功能


const actionTypes = {    ADD: 'ADD',    CHANGEINFO: 'CHANGEINFO',}
const initState = { info: '初始化',}
export default function initReducer(state=initState, action) { switch(action.type) { case actionTypes.CHANGEINFO: return { ...state, info: action.preload.info || '', } default: return { ...state }; }}
export default function createStore(reducer, initialState, middleFunc) {
if (initialState && typeof initialState === 'function') { middleFunc = initialState; initialState = undefined; }
let currentState = initialState;
const listeners = [];
if (middleFunc && typeof middleFunc === 'function') { // 封装dispatch return middleFunc(createStore)(reducer, initialState); }
const getState = () => { return currentState; }
const dispatch = (action) => { currentState = reducer(currentState, action);
listeners.forEach(listener => { listener(); }) }
const subscribe = (listener) => { listeners.push(listener); }
return { getState, dispatch, subscribe }}复制代码
复制代码


(2)工作流程


  • const store= createStore(fn)生成数据;

  • action: {type: Symble('action01), payload:'payload' }定义行为;

  • dispatch 发起 action:store.dispatch(doSomething('action001'));

  • reducer:处理 action,返回新的 state;


通俗点解释:


  • 首先,用户(通过 View)发出 Action,发出方式就用到了 dispatch 方法

  • 然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action,Reducer 会返回新的 State

  • State—旦有变化,Store 就会调用监听函数,来更新 View


以 store 为核心,可以把它看成数据存储中心,但是他要更改数据的时候不能直接修改,数据修改更新的角色由 Reducers 来担任,store 只做存储,中间人,当 Reducers 的更新完成以后会通过 store 的订阅来通知 react component,组件把新的状态重新获取渲染,组件中也能主动发送 action,创建 action 后这个动作是不会执行的,所以要 dispatch 这个 action,让 store 通过 reducers 去做更新 React Component 就是 react 的每个组件。

9. 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)}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)}
复制代码

10. Redux 状态管理器和变量挂载到 window 中有什么区别

两者都是存储数据以供后期使用。但是 Redux 状态更改可回溯——Time travel,数据多了的时候可以很清晰的知道改动在哪里发生,完整的提供了一套状态管理模式。


随着 JavaScript 单页应用开发日趋复杂,JavaScript 需要管理比任何时候都要多的 state (状态)。 这些 state 可能包括服务器响应、缓存数据、本地生成尚未持久化到服务器的数据,也包括 UI 状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器等等。


管理不断变化的 state 非常困难。如果一个 model 的变化会引起另一个 model 变化,那么当 view 变化时,就可能引起对应 model 以及另一个 model 的变化,依次地,可能会引起另一个 view 的变化。直至你搞不清楚到底发生了什么。state 在什么时候,由于什么原因,如何变化已然不受控制。 当系统变得错综复杂的时候,想重现问题或者添加新功能就会变得举步维艰。 如果这还不够糟糕,考虑一些来自前端开发领域的新需求,如更新调优、服务端渲染、路由跳转前请求数据等等。前端开发者正在经受前所未有的复杂性,难道就这么放弃了吗?当然不是。


这里的复杂性很大程度上来自于:我们总是将两个难以理清的概念混淆在一起:变化和异步。 可以称它们为曼妥思和可乐。如果把二者分开,能做的很好,但混到一起,就变得一团糟。一些库如 React 视图在视图层禁止异步和直接操作 DOM 来解决这个问题。美中不足的是,React 依旧把处理 state 中数据的问题留给了你。Redux 就是为了帮你解决这个问题。

用户头像

it优课

关注

还未添加个人签名 2021.06.26 加入

还未添加个人简介

评论

发布
暂无评论
2021高频前端面试题汇总之React篇