写点什么

2022 前端二面 react 面试题

作者:Geek_07a724
  • 2022 年 9 月 14 日
    浙江
  • 本文字数:6411 字

    阅读完需:约 21 分钟

可以使用 TypeScript 写 React 应用吗?怎么操作?

(1)如果还未创建 Create React App 项目


  • 直接创建一个具有 typescript 的 Create React App 项目:


 npx create-react-app demo --typescript
复制代码


(2)如果已经创建了 Create React App 项目,需要将 typescript 引入到已有项目中


  • 通过命令将 typescript 引入项目:


npm install --save typescript @types/node @types/react @types/react-dom @types/jest
复制代码


  • 将项目中任何 后缀名为 ‘.js’ 的 JavaScript 文件重命名为 TypeScript 文件即后缀名为 ‘.tsx’(例如 src/index.js 重命名为 src/index.tsx )

react 性能优化是在哪个生命周期函数中

在 shouldComponentUpdate 这个方法中,这个方法主要用来判断是否需要调用 render 方法重绘 DOM 因为 DOM 的描绘非常消耗性能,如果能够在 shouldComponentUpdate 方法中能写出更优化的 diff 算法,极大的提高性能

React.Children.map 和 js 的 map 有什么区别?

JavaScript 中的 map 不会对为 null 或者 undefined 的数据进行处理,而 React.Children.map 中的 map 可以处理 React.Children 为 null 或者 undefined 的情况。

react-redux 的实现原理?

通过 redux 和 react context 配合使用,并借助高阶函数,实现了 react-redux

setState 到底是异步还是同步?

先给出答案: 有时表现出异步,有时表现出同步


  • setState只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout 中都是同步的

  • setState 的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数setState(partialState, callback)中的callback拿到更新后的结果

  • setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次setStatesetState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新

类组件(Class component)和函数式组件(Functional component)之间有何不同

  • 类组件不仅允许你使用更多额外的功能,如组件自身的状态和生命周期钩子,也能使组件直接访问 store 并维持状态

  • 当组件仅是接收 props,并将组件自身渲染到页面时,该组件就是一个 '无状态组件(stateless component)',可以使用一个纯函数来创建这样的组件。这种组件也被称为哑组件(dumb components)或展示组件

虚拟 DOM 的引入与直接操作原生 DOM 相比,哪一个效率更高,为什么

虚拟 DOM 相对原生的 DOM 不一定是效率更高,如果只修改一个按钮的文案,那么虚拟 DOM 的操作无论如何都不可能比真实的 DOM 操作更快。在首次渲染大量 DOM 时,由于多了一层虚拟 DOM 的计算,虚拟 DOM 也会比 innerHTML 插入慢。它能保证性能下限,在真实 DOM 操作的时候进行针对性的优化时,还是更快的。所以要根据具体的场景进行探讨。


在整个 DOM 操作的演化过程中,其实主要矛盾并不在于性能,而在于开发者写得爽不爽,在于研发体验/研发效率。虚拟 DOM 不是别的,正是前端开发们为了追求更好的研发体验和研发效率而创造出来的高阶产物。虚拟 DOM 并不一定会带来更好的性能,React 官方也从来没有把虚拟 DOM 作为性能层面的卖点对外输出过。**虚拟 DOM 的优越之处在于,它能够在提供更爽、更高效的研发模式(也就是函数式的 UI 编程方式)的同时,仍然保持一个还不错的性能。

Redux 内部原理 内部怎么实现 dispstch 一个函数的

redux-thunk中间件作为例子,下面就是thunkMiddleware函数的代码


// 部分转为ES5代码,运行middleware函数会返回一个新的函数,如下:return ({ dispatch, getState }) => {    // next实际就是传入的dispatch    return function (next) {        return function (action) {            // redux-thunk核心            if (typeof action === 'function') {                 return action(dispatch, getState, extraArgument);            }            return next(action);        };    };}
复制代码


redux-thunk库内部源码非常的简单,允许action是一个函数,同时支持参数传递,否则调用方法不变


  • redux创建Store:通过combineReducers函数合并reducer函数,返回一个新的函数combination(这个函数负责循环遍历运行reducer函数,返回全部state)。将这个新函数作为参数传入createStore函数,函数内部通过 dispatch,初始化运行传入的combination,state 生成,返回 store 对象

  • redux中间件:applyMiddleware函数中间件的主要目的就是修改dispatch函数,返回经过中间件处理的新的dispatch函数

  • redux使用:实际就是再次调用循环遍历调用reducer函数,更新state

React 高阶组件是什么,和普通组件有什么区别,适用什么场景

官方解释∶


高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。


高阶组件(HOC)就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件,它只是一种组件的设计模式,这种设计模式是由 react 自身的组合性质必然产生的。我们将它们称为纯组件,因为它们可以接受任何动态提供的子组件,但它们不会修改或复制其输入组件中的任何行为。


// 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));
复制代码


1)HOC 的优缺点


  • 优点∶ 逻辑服用、不影响被包裹组件的内部逻辑。

  • 缺点∶hoc 传递给被包裹组件的 props 容易和被包裹后的组件重名,进而被覆盖


2)适用场景


  • 代码复用,逻辑抽象

  • 渲染劫持

  • State 抽象和更改

  • Props 更改


3)具体应用例子


  • 权限控制: 利用高阶组件的 条件渲染 特性可以对页面进行权限控制,权限控制一般分为两个维度:页面级别和 页面元素级别


// HOC.jsfunction withAdminAuth(WrappedComponent) {    return class extends React.Component {        state = {            isAdmin: false,        }        async UNSAFE_componentWillMount() {            const currentRole = await getCurrentUserRole();            this.setState({                isAdmin: currentRole === 'Admin',            });        }        render() {            if (this.state.isAdmin) {                return <WrappedComponent {...this.props} />;            } else {                return (<div>您没有权限查看该页面,请联系管理员!</div>);            }        }    };}
// pages/page-a.jsclass PageA extends React.Component { constructor(props) { super(props); // something here... } UNSAFE_componentWillMount() { // fetching data } render() { // render page with data }}export default withAdminAuth(PageA);

// pages/page-b.jsclass PageB extends React.Component { constructor(props) { super(props); // something here... } UNSAFE_componentWillMount() { // fetching data } render() { // render page with data }}export default withAdminAuth(PageB);
复制代码


  • 组件渲染性能追踪: 借助父组件子组件生命周期规则捕获子组件的生命周期,可以方便的对某个组件的渲染时间进行记录∶


class Home extends React.Component {        render() {            return (<h1>Hello World.</h1>);        }    }    function withTiming(WrappedComponent) {        return class extends WrappedComponent {            constructor(props) {                super(props);                this.start = 0;                this.end = 0;            }            UNSAFE_componentWillMount() {                super.componentWillMount && super.componentWillMount();                this.start = Date.now();            }            componentDidMount() {                super.componentDidMount && super.componentDidMount();                this.end = Date.now();                console.log(`${WrappedComponent.name} 组件渲染时间为 ${this.end - this.start} ms`);            }            render() {                return super.render();            }        };    }
export default withTiming(Home);
复制代码


注意:withTiming 是利用 反向继承 实现的一个高阶组件,功能是计算被包裹组件(这里是 Home 组件)的渲染时间。


  • 页面复用


const withFetching = fetching => WrappedComponent => {    return class extends React.Component {        state = {            data: [],        }        async UNSAFE_componentWillMount() {            const data = await fetching();            this.setState({                data,            });        }        render() {            return <WrappedComponent data={this.state.data} {...this.props} />;        }    }}
// pages/page-a.jsexport default withFetching(fetching('science-fiction'))(MovieList);// pages/page-b.jsexport default withFetching(fetching('action'))(MovieList);// pages/page-other.jsexport default withFetching(fetching('some-other-type'))(MovieList);
复制代码

什么是 JSX

jsx 是 JavaScriptXML 的简写,是 react 使用的一种文件,它利用 JavaScript 的表现力和类似 HTML 的模板语法,这使得 HTML 文件非常容易理解。此文件能使应用非常可靠,并能够提高其性能 jsx 的语法规则


  • 定义虚拟 DOM 的时候 不需要写引号

  • 标签中要混入 js 表达式的时候需要用 {}

  • 在 jsx 中写标签的类名的时候 用 className 代替 class

  • 内联样式的时候 ,需要 style={{key:value}}

  • 标签必须要闭合

  • 标签首字母的约定

  • 若为小写字母,则将 jsx 转换为 html 中同名元素,若 html 中无该标签明对应的同名元素 则报错

  • 若为大写字母,react 就去渲染对应的组件,若没有定义组件 则报错

  • 当根据数据遍历生成的标签,一定要给标签设置单独的 key 否则会报错

对 componentWillReceiveProps 的理解

该方法当props发生变化时执行,初始化render时不执行,在这个回调函数里面,你可以根据属性的变化,通过调用this.setState()来更新你的组件状态,旧的属性还是可以通过this.props来获取,这里调用更新状态是安全的,并不会触发额外的render调用。


使用好处: 在这个生命周期中,可以在子组件的 render 函数执行前获取新的 props,从而更新子组件自己的 state。 可以将数据请求放在这里进行执行,需要传的参数则从 componentWillReceiveProps(nextProps)中获取。而不必将所有的请求都放在父组件中。于是该请求只会在该组件渲染时才会发出,从而减轻请求负担。


componentWillReceiveProps 在初始化 render 的时候不会执行,它会在 Component 接受到新的状态(Props)时被触发,一般用于父组件状态更新时子组件的重新渲染。

React 中 keys 的作用是什么?

render () {  return (    <ul>      {this.state.todoItems.map(({item,i}) => {        return <li key={i}>{item}</li>      })}    </ul>  )}
复制代码


在 React Diff 算法中 React 会借助元素的 Key 值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染。

diff 算法?

  • 把树形结构按照层级分解,只比较同级元素

  • 给列表结构的每个单元添加唯一的 key 属性,方便比较

  • React 只会匹配相同 class 的 component(这里面的 class 指的是组件的名字)

  • 合并操作,调用 component 的 setState 方法的时候, React 将其标记为 dirty.到每一个 事件循环结束, React 检查所有标记 dirty 的 component 重新绘制.

  • 选择性子树渲染。开发人员可以重写 shouldComponentUpdate 提高 diff 的性能。

useEffect 和 useLayoutEffect 的区别

useEffect


基本上 90%的情况下,都应该用这个,这个是在 render 结束后,你的 callback 函数执行,但是不会 block browser painting,算是某种异步的方式吧,但是 class 的 componentDidMount 和 componentDidUpdate 是同步的,在 render 结束后就运行,useEffect 在大部分场景下都比 class 的方式性能更好.


useLayoutEffect


这个是用在处理 DOM 的时候,当你的 useEffect 里面的操作需要处理 DOM,并且会改变页面的样式,就需要用这个,否则可能会出现出现闪屏问题, useLayoutEffect 里面的 callback 函数会在**DOM 更新完成后立即执行,但是会在浏览器进行任何绘制之前运行完成,**阻塞了浏览器的绘制.

react 的虚拟 dom 是怎么实现的


首先说说为什么要使用Virturl DOM,因为操作真实DOM的耗费的性能代价太高,所以react内部使用js实现了一套 dom 结构,在每次操作在和真实 dom 之前,使用实现好的 diff 算法,对虚拟 dom 进行比较,递归找出有变化的 dom 节点,然后对其进行更新操作。为了实现虚拟DOM,我们需要把每一种节点类型抽象成对象,每一种节点类型有自己的属性,也就是 prop,每次进行diff的时候,react会先比较该节点类型,假如节点类型不一样,那么react会直接删除该节点,然后直接创建新的节点插入到其中,假如节点类型一样,那么会比较prop是否有更新,假如有prop不一样,那么react会判定该节点有更新,那么重渲染该节点,然后在对其子节点进行比较,一层一层往下,直到没有子节点

React 中怎么检验 props?验证 props 的目的是什么?

React 为我们提供了 PropTypes 以供验证使用。当我们向 Props 传入的数据无效(向 Props 传入的数据类型和验证的数据类型不符)就会在控制台发出警告信息。它可以避免随着应用越来越复杂从而出现的问题。并且,它还可以让程序变得更易读。


import PropTypes from 'prop-types';
class Greeting extends React.Component { render() { return ( <h1>Hello, {this.props.name}</h1> ); }}
Greeting.propTypes = { name: PropTypes.string};
复制代码


当然,如果项目汇中使用了 TypeScript,那么就可以不用 PropTypes 来校验,而使用 TypeScript 定义接口来校验 props。

指出(组件)生命周期方法的不同

  • componentWillMount -- 多用于根组件中的应用程序配置

  • componentDidMount -- 在这可以完成所有没有 DOM 就不能做的所有配置,并开始获取所有你需要的数据;如果需要设置事件监听,也可以在这完成

  • componentWillReceiveProps -- 这个周期函数作用于特定的 prop 改变导致的 state 转换

  • shouldComponentUpdate -- 如果你担心组件过度渲染,shouldComponentUpdate 是一个改善性能的地方,因为如果组件接收了新的 prop, 它可以阻止(组件)重新渲染。shouldComponentUpdate 应该返回一个布尔值来决定组件是否要重新渲染

  • componentWillUpdate -- 很少使用。它可以用于代替组件的 componentWillReceivePropsshouldComponentUpdate(但不能访问之前的 props)

  • componentDidUpdate -- 常用于更新 DOM,响应 prop 或 state 的改变

  • componentWillUnmount -- 在这你可以取消网络请求,或者移除所有与组件相关的事件监听器

state 和 props 共同点和区别

共同点


  • state 和 props 的改变都会触发 render 函数(界面会发生改变)


不同点


  • props 是 readonly(只读),但是 state 是可读可写

  • props 来自父组件,state 是组件内部的数据对象

用户头像

Geek_07a724

关注

还未添加个人签名 2022.09.14 加入

还未添加个人简介

评论

发布
暂无评论
2022前端二面react面试题_前端_Geek_07a724_InfoQ写作社区