REACT篇章高频面试题【2023】

react篇章

  1. 说一说组件化或模块化开发是什么?
    组件化或模块化开发是将复杂的程序根据一定的规范封装成几个模块并进行组合开发的方式 。
    组件化或模块化开发有利于团队协作开发,有利于组件复用,提高开发效率,方便后期维护,减少页面中冗余代码。

  2. react项目中,默认会安装什么?
    react、react-dom、react-native、react-scripts、dependencies、eslintConfig、browserlist、production、development
    scripts:start为开发环境,进行本地启动web服务器;build为生产环境,打包部署;test单元测试;eject暴露webpack配置规则,默认打包规则的修改;
    eslintConfig:对webpack中eslint检测相关配置;
    public 页面模版;
    node_modules 文件打包的文件;
    package.json 提供node包的版本下载记录;
    @babel/core es6转化为es5语法
    @pmmmwh/react-refrech-webpack-plugin 浏览器刷新功能
    @babel-loader 对babel的处理
    @babel-preset-react-app 对@babel/preset-env语法包的重写并将es6转化为es5语法
    @camelcase 语言包处理
    @html-webpack-plugin 将打包后的css、js自动打包到html
    npm eject 是不可逆的,无法还原。

  3. 项目初始化后会修改的配置有哪些?
    修改预加载器:在webpack.config.js文件中配置less
    修改域名和端口号:在script目录下的start.js文件中配置HOST、DEFAULT_PORT,也基于环境变量修改cross-env
    修改浏览器兼容:在package.json文件中配置browerslist,可以对postcss-loader、babel-loader生效,如果处理ES6内置api兼容需要@babel/polyfill对内置api的重写
    修改跨域处理:在setupProxy.js文件中配置实现跨域代理,基于http-proxy-middleware来实现

  4. react的原理是什么?解释下MVC体系?
    react声明式的方式并基于jsx语法构建来描述用户界面的状态变化,使用虚拟DOM来表示UI的状态变化,尽可能地复用现有的DOM节点,以提高性能。
    mvc为model数据层,view视图层,controller控制层,构建数据层在视图层中需要动态处理,对应数据模型;控制层视图中进行某些操作时,去修改相关数据,然后react会按照最新的数据重新渲染视图,数据驱动视图的渲染。

  5. jsx底层原理以及应用?
    基于babel-preset-react-app将编写的jsx语法编译为虚拟dom对象,将构建的虚拟dom渲染为真实dom,真实dom为浏览器页面中,最后渲染出来让用户看见的元素。第一次渲染页面是直接从虚拟dom转化编译为虚拟dom,后期更新视图时,需要经过dom-diff的对比计算补丁包patch,两次视图差异部分并将patch补丁包渲染。
    a. 编译为虚拟dom对象,基于babel-preset-react-app语法包将jsx编译为React.createElement()格式,执行createElement()创建虚拟dom。
    b. 构建真实dom对象,调用render方法,v16版本中React.render()方法,v18版本中需要先创建root根节点,然后调用render()方法。
    createElement(e, props, …children):在createElement函数中创建virtualDom对象,定义属性$$typeof的初始值为Symbol(‘react.element’),定义属性key的初始值为null,定义属性ref的初始值为null,定义属性type的初始值为null,定义props的初始值为空对象;创建变量为len并将children.length赋值给len,将e赋值给virtualDom.type,判断props是否为null,如果不为null则将props展开并赋值给virtualDom.props,判断len是否为1,如果等于1将children[0]赋值给virtualDom.props.children,判断len是否大于1,如果大于1将children赋值给virtualDom.props.children,最后返回virtualDom。
    render(virtualDom, container):先解构出virtualDom中的type、props,判断type是否为string类型,如果是则动态创建标签并存储标签名,为标签设置相关属性或子节点;遍历props对象,判断对象中key值是否为className,如果是则给动态创建的标签添加类名即value值;判断对象中key值是否为style,如果是则继续遍历value并动态添加style样式;判断key值是否为children,如果是则遍历children的value值继续判断,如果子节点是virtualDom并进行递归处理。

  6. 密集数组和稀疏数组?
    密集数组:每项都有值,哪怕值为null。
    稀疏数组:不能直接使用数组迭代方法,forEach、map,如果想遍历,通过fill进行填充变为密集数组。
    new Array(5) 只传一个值,是创建长度为n每一项是empty的稀疏数组
    其余情况是创建数组,传递什么,将其作为数组中某一项的值
    new Array(5,10)
    new Array(‘5’)

  7. react中函数组件渲染机制?
    基于babel-preset-react-app将调用的组件转化为React.createElement格式,
    执行React.createElement并创建虚拟dom,
    基于render()方法将虚拟dom编译为真实dom,此时type的值为函数,执行组件函数把virtualDom中的props作为实参传递给函数,接收函数执行的返回结果,当前组件的virtualDom基于render把组件返回的虚拟dom变为真实dom,并插入到root容器中。

  8. 函数组件中属性props的作用以及属性校验规则?
    函数组件中传递进来的属性props是只读性,此属性涉及对象规则设置。
    冻结:object.freeze(str) 不可修改 不可删除 不可添加 ,object.isFrozen(str) 检测是否冻结
    密封:object.seal(str) 不可添加 不可删除 但是可以修改,object.isSealed(str) 检测是否密封
    不可扩展:object.preventExtensions(str) 不可新增 但可以修改 可以删除,object.isExtensible(str) 检测是否可扩展
    被冻结和被密封的对象为不可劫持对象,不可扩展的对象为可劫持对象。
    props的作用:赋组件调用子组件可以基于属性,将不同的信息传递给子组件,子组件接收相应属性,程序安不同渲染效果。
    属性校验规则:将函数组件当作对象,设置静态私有属性方法,此时传递到子组件的属性会先经历规则校验,但是无论是否校验成功都会将属性传递给props,只不过如果不符合校验规则控制台会抛出异常信息,打不会影响使用。

  9. react中插槽处理机制?
    插槽处理机制:基于props.children获取传递的子节点信息,进行渲染,为插槽机制。封装组件时预留插槽内容不需要写,调用组件时基于双闭合调用方式将子节点信息传递给子组件,在组件内部渲染。
    插槽的作用:让组件具备更强的复用性,传递html结构使用插槽;属性传递的是数据,插槽传递的是html结构。
    作用域名插槽:在父组件给传递的插槽信息设置名字+slot,子组件中传递进来插槽信息使用React.children.toArray(children)将传入的 React 元素列表转换为一个数组,然后进行遍历渲染。
    具名插槽:在调用组件传递插槽信息时,可以不考虑渲染顺序,直接设置对应的名字。

  10. 函数组件 类组件 hooks组件的区别?
    函数组件:为静态组件,第一次渲染组件将函数执行,产生一个私有的上下文,将解析出来的props传递进来,并对函数返回jsx元素进行渲染,当点击时,会执行绑定的函数,修改上下文中的变量,此时私有的变量值会发生改变,但是视图不会更新。除非父组件重新调用子组件才可重新渲染。因为函数组件每执行一次会产生一个闭包。
    类组件:为动态组件,第一次渲染后,可通过类组件中的点击事件等改变私有数据,并且视图效果,类组件基于构造函数进行封装,必须继承React.Component或者React.PureComponent。
    类组件基于extends实现继承,Component函数类似于将此函数看作普通函数来执行,this为创建的实例,给实录设置私有属性props、context、refs、updater,然后基于原型继承,除了具备原型上所提供的方法外,还提供了isReactComponent、setState、forceUpdate。自己设置constructor,一定需要写super,等价于React.Component.call(this)。
    hooks组件:具备了函数组件和类组件各自的优势,在函数组件的基础上基于hooks让函数组件拥有状态,周期函数,实现修改状态视图更新的效果。

  11. 类组件第一次渲染逻辑?
    校验规则;初始化静态私有属性;执行相关周期函数,componentWillMount在组件第一次渲染之前执行,但是此钩子函数目前已经不建议使用,因为不安全并且在开启严格模式use strict控制台会报错;触发渲染函数render() 将返回的jsx元素编译为virtual-dom;触发componentDidMount钩子函数,此时第一次渲染完毕,并且页面已经存在真实dom,可以获取dom元素。

  12. 类组件更新以及卸载的逻辑?
    类组件更新即修改了相关状态;会触发shouldComponentUpdate钩子函数判断是否允许被更新,此钩子函数中传入两个参数修改后的状态和修改前的状态进行对比,如果状态有变化则返回true,如果状态没有变化则返回false;允许更新后触发ComponentWillUpdate钩子函数,此阶段状态还没有被修改,此钩子函数目前不建议使用,不安全但如果非要使用前边添加UNSAFE;然后修改状态值,属性值让this.state.xxx改为最新值;触发render函数,并且按照最新的状态将返回的virtual-dom和上一次渲染的virtual-dom进行对比,使用diff算法将差异的部分进行渲染为真实dom;触发ComponentDidUpdate,组件渲染完毕。当组件卸载会触发ComponentWillUnmount周期函数,此时为组件销毁之前,然后触发销毁。
    this.farceUpdate() 强制更新视图,会直接跳过shouldComponentUpdate钩子函数校验,直接从ComponentWillUpdate开始进行更新,此时视图一定更新。

  13. 类组件中PureComponent和Component的区别?
    PureComponent:会给类组件默认添加shouldComponentUpdate钩子函数,在此周期函数中会对新旧状态进行浅比较,如果状态没有发生变化,则返回false,如果状态有变化则返回true。
    Component:需要自己添加shouldComponentUpdate钩子函数进行深比较,然后减少渲染次数。

  14. 受控组件与非受控组件的区别?
    受控组件:基于修改数据、状态,让视图更新。
    非受控组件:基于ref获取dom元素,操作dom实现需求。

  15. 类组件和函数组件如何基于ref获取元素?
    类组件中基于ref可以做的事:赋值给一个标签可以获取dom元素,赋值给一个类子组件可以获取子组件实例并且可基于实例调用子组件属性和方法,赋值给一个函数组件需要配合React.forwardRef()实现ref的转发获取子组件中某个dom元素。
    函数组件中基于ref可以做的事:基于ref={函数}可以创建dom元素或将子组件的实例赋值给变量,基于React.createRef()创建ref对象并且使用box.current获取dom元素,基于useRef创建对象并且可用box.current获取dom元素。
    useRef与React.createRef的区别:
    useRef:第一次渲染和更新是创建的ref一致,因为在每一次更新组件时,执行ref不会在创建新的ref对象。因此在函数组件中使用useRef性能会更好。
    React.createRef:每一次组件更新都会创建一个新的ref对象,因为函数组件每次更新都会把函数重新执行,这样会重新创建一个新的ref对象。但是在类组件中更新时不会重新创建,只是重新render,不是重新执行整个类组件,因此只创建一次ref对象。

  16. 类组件中this.setState方法详细说明?
    this.setState([partialState],[callback])
    [partialState]:支持部分状态改变
    [callback]:状态更改,在视图完毕后触发执行。callback函数在componentDidUpdate钩子函数之后执行,componentDidUpdate钩子函数是在状态更改后触发,而callback可以在指定状态更新后执行,如果shouldComponentUpdate阻止了状态的更新,componentDidUpdate不会执行,但callback会执行。
    setState中的状态任务需要执行,首先将任务放入消息队列中,因为函数自上而下执行,会将此操作放入更新队列中,只对当前上下文、同步做处理,同步事件完成后会让更新队列中的任务统一渲染,并更新一次(批处理)。
    执行顺序:触发setState -> shouldComponentUpdate -> componentWillUpdate ->状态更改 ->修改状态 -> 执行render渲染函数 -> componentDidUpdate -> 执行setState中callback函数。
    例:函数中使用三个定时器,时间错开间隔大,渲染三次会有三次数据更新。如果间隔时间小于1ms或更小时,可能三次渲染的数据没有变化。
    批处理操作:当前相同时间段内,遇到setState会立即放在更新队列update中,当所有代码操作结束会刷新队列,将所有放入的setState合并在一起执行,只触发一次视图更新。
    react-dom中的flushSync会强制刷新更新队列,让修改状态任务立即批处理一次。
    例:定义初始值state = 0,将this.setState 放入for循环中修改状态并循环20次,默认渲染1次,修改后的state值为1,因为每次循环state状态没有更新,只是将修改的任务放入update队列中,所以每一轮取的state都是0,放入update队列中,在最后一轮中将state修改为1。如果改为渲染结果值是20,更新20次,使用flushSync可以做到。如果改为渲染结果值是20,更新1次,使用setState中的回调函数进行修改,将回调函数放入更新队列中依次执行函数。
    React18:setState都是异步操作,合成事件、周期函数、定时器等,目的是实现批处理操作,减少视图更新次数,降低消耗性能。让更新逻辑和流程更清晰。原理是利用更新队列[update]机制来处理的。
    React16:setState在合成事件、周期函数中操作是异步操作,但是在定时器、手动获取dom元素事件绑定中setState会变为同步操作,立即更新状态和视图渲染。

  17. react中的合成事件原理是什么?
    React中合成事件synthetic:目的是为了浏览器的兼容性,在react内部处理给合成事件绑定普通函数,当事件行为触发,此方法中的this为undefined,想让this指向实例,使用bind预先处理函数中的this和实例。
    React中合成事件对象syntheticBaseEvent:react中经过内部处理,将各个浏览器事件统一化后,构建一个对象,合成事件中包含浏览器内部常用属性和方法,clientX、clientY、target、pageX、pageY、preventDefault 可获取浏览器原生事件对象。
    bind在react事件中绑定的应用:事件为普通函数,需要改变函数中的this,将this指向实例用到bind;给箭头函数传递指定实参,可基于bind预先处理,bind会把事件对象以最后一个实参传递给函数,合成事件对象是最后一个实参。
    事件和事件委托:window -> document ->html -> body -> root -> outer -> inner ,事件具备传播机制,点击子元素触发行为,从window子元素查找,捕获阶段分析路径,触发子元素事件源的点击行为目标阶段,按照捕获阶段分析出来的路径从里到外,将每个元素的点击行为也触发为冒泡阶段。
    事件和事件绑定:事件是浏览器赋予元素的默认行为,事件绑定给这个行为绑定的一定方法。即使没有给body的点击事件绑定方法,当点击时其点击行为也会被触发,只不过是不做什么而已。e.stopPropagation() 可阻止冒泡,可阻止捕获,即可阻止事件传播;e.stopImmediatePropagation() 阻止事件传播,但它可以将当前元素阻止,不让其执行,还没执行的方法。
    事件委托:利用事件的传播机制,实现的一套事件绑定处理方案。
    事件委托优势:提高了js运行的能,并且将处理逻辑集中在一起。某些需求必须基于事件委托。动态绑定的标签做事件绑定。
    事件合成原理:‘绝对不是’给当前元素基于addEventListener单独做事件绑定,react中合成事件都是基于事件委托处理的。react17版本后,都是委托给#root容器,冒泡和捕获。对于没有传播机制的事件,单独做事件绑定,onMouseEnter,onMouseLeave。
    源码解说:
    a. 视图渲染中,遇到合成事件,并没有给元素做事件绑定,而是给元素设置对应的属性,合成事件属性。
    b. 给root事件绑定,绑定方法在所有路径中,有合成事件都执行,捕获+冒泡。
    所谓合成事件,其实并没有给元素本身做事件绑定,而是给元素设置onXXX、onXXCapture合成事件属性,当事件行为触发,根据原生事件传播机制,都会传播到root容器上,react内部给root容器做了事件绑定,当react内部绑定方法执行时,会根据e,path中分析的路径,一次会触发执行对应阶段的onXX、onCapture等合成事件,合成事件是利用事件委托完成的。

  18. 原生事件绑定?
    React18中
    在react18中合成事件处理机制是将事件委托给root元素。
    在点击inner之后,window捕获 - > document捕获 -> html捕获 -> body捕获 -> root捕获 -> root(react内部绑定)【outer捕获 -> inner捕获】-> root原生捕获 -> outer捕获 -> inner捕获 -> inner冒泡 -> outer冒泡 -> root(react内部绑定)【inner冒泡 -> outer冒泡】-> root原生冒泡 -> body冒泡 -> html冒泡 -> document冒泡 -> window冒泡。
    e.stopPropagation() 在合成事件对象中的阻止原生事件和合成事件传播。
    例:inner中onClick中添加e.stopPropagation()
    inner 原生冒泡 -> outer 原生冒泡 -> inner 合成冒泡 -> root 冒泡,root冒泡是因为单独给它绑定了点击事件。root 原生冒泡与inner原生冒泡为同级,因此会执行。
    e.nativeEvent.stopPropagation() 在合成事件中阻止原生事件传播。
    例:inner中onClick添加e.nativeEvent.stopPropagation()
    inner 原生冒泡 -> outer 原生冒泡 -> inner 合成冒泡 -> outer 合成冒泡 -> root 冒泡,因为绑定在root上,所以需要冒泡到root才停止执行。
    e.nativeEvent.stopImmediatePropagation() 在合成事件中只阻止原生事件的传播,不可以阻止合成事件的传播。
    例:inner中onClick添加e.nativeEvent.stopImmediatePropagation()
    inner 原生冒泡 -> outer 原生冒泡 -> inner 合成冒泡 -> outer 合成冒泡,不会执行root绑定事件,因为阻止的是原生事件传播,阻止了root上其他绑定的方法执行。
    e.stopPropagation() 在原生事件中阻止inner,只到inner原生冒泡即刻停止。
    React16中
    在react16中合成事件处理机制是将事件委托给document,所有的事件只在冒泡阶段做委托。
    在点击inner之后,window捕获 - > document捕获 -> html捕获 -> body捕获 -> root捕获 -> outer捕获 -> inner捕获 -> inner冒泡 -> outer冒泡 -> root冒泡 ->body冒泡 -> html冒泡 -> document合成事件【outer合成捕获 -> inner合成捕获 -> outer合成冒泡 -> inner合成捕获】-> document原生事件 -> window冒泡。先完成内部所有捕获阶段,然后完成内部所有冒泡阶段。

  19. react18版本和react16版本的区别?
    渲染机制不同:v16中ReactDOM.render() 直接渲染;v18中使用ReactDOM.createRoot()获取root元素然后使用render渲染。
    setState不同:v16中除了合成事件、周期函数外,在定时器、手动函数绑定中为同步事件;v18中无论什么事件都是异步处理机制。
    合成事件不同:v16中将所有合成事件操作包括捕获和冒泡,全部放在document的冒泡阶段,进行执行并在一个事件中执行;v18中将事件委托给root,在root捕获阶段做合成事件捕获,在冒泡阶段做合成事件冒泡,分开执行。
    合成对象处理不同:v16中为了防止每次重新创建新的合成事件对象,设置了事件对象池,每次事件触发,如果传播到了document,在委托方法中首先会对内置事件对象做统一处理,生成合成事件对象,在操作结束后会清空合成事件对象中的成员信息,为null;v18中去除缓存机制,没有事件对象池,不存在信息清空问题。

  20. react中事件的其他细节知识?
    移动端onClick 点击事件会存在300ms的延迟。
    pc端click点击事件不存在延迟。
    双击事件:pc端会触发2次click,触发1次dbClick;移动端不触发click,触发1次dbClick。
    单击事件:第一次点击后,检测300ms是否有第二次点击操作,如果没有就是点击有的话就是双击。
    单指事件:touch:touchStart、touchMove、touchEnd

  21. pm2是什么?
    pm2进行服务持久化管理,pm2在终端关掉,服务器也在。

  22. Antd中form表单处理机制?
    a. 自动收集表单中输入的信息:Form-Item中的name是指定手机信息的字段,默认在内容onChange事件中进行收集,可以基于trigger改变手机的时机。
    b. 基于initialValue:可以在Form中设置表单初始值。
    c. 基于rules设定表单校验规则:自动校验,提交按钮在Form标签中,并且htmlType需要submit,点击按钮会自动触发表单校验,成功会执行Form中设置的onFinish方法,不成功执行onFinishFailed方法;手动校验,获取Form组件的实例,通过实例相关方法执行,实现校验。getFieldValue获取收集的信息,resetFields将表单信息重置到initialValues中,validateFields表单校验返回promise实例。

  23. 如何优化组件渲染次数?
    使用 shouldComponentUpdate 方法 或 PureComponent:
    shouldComponentUpdate 方法可以用于自定义组件的渲染逻辑,避免不必要的组件重新渲染,如果组件的 props 和 state 没有发生变化,可以通过返回 false 来阻止组件重新渲染。
    PureComponent 是 React 中的一个基础组件类,它可以实现浅比较 props 和 state,只有当 props 或 state 发生变化时才会重新渲染。
    使用 React.memo 或 useMemo:
    React.memo 是 React 中的一个高阶组件,它可以将组件包装起来,只有当 props 发生变化时才会重新渲染。
    useMemo 是 React 的一个 Hook,它可以用于计算一些不经常改变的变量,并将其缓存起来,避免在每次渲染时都重新计算。
    避免在 render 方法中调用某些方法:
    在 render 方法中调用某些方法会导致组件重新渲染,可以将这些方法提取到组件的实例方法中,并在组件挂载后调用。
    使用 React.lazy 和 Suspense:
    React.lazy 可以用于动态加载组件,只有当组件需要渲染时才会加载,避免了不必要的渲染。Suspense 可以用于在组件加载时显示一些回退内容。

  24. react中使用过的hook函数有哪些,都有什么作用?
    useState:用于在函数组件中添加及修改状态。
    useEffect:用于在函数组件中使用生命周期函数。
    useLayoutEffect:用于在函数组件中使用生命周期函数,并在第一次真实dom还没有渲染,回调函数中的状态更新,视图立即更新,创建新的virtual-dom与上一次的virtual-dom进行合并并渲染为真实dom。
    useContext:用于在函数组件中获取上下文对象信息,可以基于上下文方案实现祖先元素和后代元素的通信。
    useReducer:用于在函数组件中实现对状态的统一管理,对useState的升级处理。
    useCallback:用于在函数组件中获取稳定的引用。
    useMemo:用于在函数组件中计算不经常改变的变量。
    useRef:用于在函数组件中获取稳定的引用。

  25. useState的底层机制是什么,useState优化机制,react v18和react v16的区别?
    底层机制:函数组件的每次渲染或更新,都是把函数重新执行,产生一个全新的‘私有上下文’,那它最大的特点就是函数内部的代码都需要重新执行,每次执行函数都会产生闭包。执行useState中设置的初始值只有第一次会生效,以后再执行获取的状态都是最新的状态值,而不是初始值,返回的修改状态方法,每一次都是返回新的值。
    useState自带优化机制:每次修改状态时,去拿最新修改的值和之前的状态值基于Obiect.is()做浅对比,如果发现两次的值一样则不会修改状态,不会更新视图。类似于PureComponent,在shouldComponentUpdate中执行。
    react18中基于useState创建的修改状态的方法,执行是异步的,原理与类组件中的this.setState相同。
    react16中useState和this.setState一样,在合成事件中是异步操作,但是在定时器、手动绑定时间等异步操作中是同步的。

  26. useEffect的底层机制、useLayoutEffect的底层机制,以及它们的区别?
    useEffect(()=>{}):第一次渲染完毕执行回调函数callback,等价于componentDidMount;在组件每次更新完毕后,执行回调函数callback,等价于componentDidUpdate。
    useEffect(()=>{},[]):只有第一次渲染完毕,会执行回调函数callback,之后更新视图回调函数不会执行,等价于componentDidMount。
    useEffect(()=>{},[num]):第一次渲染完毕执行回调函数callback,当依赖值改变也会触发回调函数callback执行,依赖值不改变则回调函数不执行。
    useEffect(()=>{ return ()=>{} }:返回小函数会在组件释放时执行,获取上一次释放的状态值,组件更新会执行上一次返回的函数。
    useEffect必须在函数的最外层上下文调用,不可将其嵌套在条件判断循环等操作;如果设置返回值,则返回值必须时函数,代表组件销毁时触发;使用async await需要使用函数包一层。
    useEffect和useLayoutEffect的区别:
    组件第一次渲染先基于react-app 编译,然后创建v-dom,最后将v-dom渲染为真实dom,在effect链表中,useEffect加入链表中的方法是在渲染为真实dom之后通知执行,而useLayoutEffect加入链表中方法会在浏览器渲染真实dom之前通知执行。
    useEffect第一次真实dom已经渲染,组件更新会重新渲染真实dom,频繁切换会出现样式内容闪烁。useLayoutEffect第一次dom没有渲染,遇到callback修改状态,创建新的v-dom和旧的v-dom合并在一起渲染为真实dom,这样不会出现内容样式闪烁。

  27. 视图更新的步骤是什么?
    基于babel-preset-react-app将jsx编译为createElement格式;
    将createElement执行创建出virtualDom;
    基于root.render方法,将virtualDom变为真实dom,useLayoutEffect会阻塞浏览器渲染,先去执行effect链表中的callback方法,而useEffect浏览器渲染和effect链表中方法同步执行。
    浏览器渲染和绘制真实dom对象。

  28. useRef与React.createRef对象创建的区别?
    useRef:第一次渲染和更新时创建的ref一致,每次更新函数组件时,执行useRef不会在创建新的ref对象。
    React.createRef:每次函数组件更新,会创建新的ref对象,因为函数组件每次更新都会重新执行函数,这样会重新创建一个ref对象。类组件中不会创建新的ref对象,只是重新render,不会重新执行整个类组件。
    类组件中基于ref获取子组件的实例,可以基于实例调用子组件中挂载到实例上的属性、方法、状态。
    函数组件中基于React.forward实现ref转发,只可以获取子组件内部的某个元素标签,想要获取函数组件内部的属性和方法,需要配合useImperativeHandle函数实现。

  29. useMemo、useCallback、自定义hook函数
    useMemo:第一次渲染组件时,useMemo中的回调函数会执行;后期只有依赖的状态值发生改变,回调函数才会执行;useMemo具备计算缓存,在依赖的状态值没有发生改变,回调函数没有触发执行时,useMemo的返回结果获取的是上一次计算出来的结果。当组件中有消耗性能的计算操作使用useMemo缓存起来,设置对应依赖,可以保证依赖值不改变时不会处理此回调函数中的操作,提高组件更新速度。
    useCallback:第一次渲染组件,useCallback中的回调函数会执行,后期组件更新判断依赖的状态值是否改变,如果改变则重新创建新的函数堆内存空间,如果没有改变或者没有设置依赖,则获取的一直是第一次创建的函数堆内存,不会创建新的函数堆内存空间,基于useCallback可以始终获取第一次创建的函数的堆内存引用。useCallback使用场景:父子组件嵌套,父组件要把一个内部的函数,基于属性传递给子组件,此时使用useCallback更好。
    例:父组件更新时,传递给子组件的属性仅仅是一个函数,子组件基本不会有变化,使用useCallback,子组件内部处理验证父组件传递的属性是否会发生改变,如果没有变化则不更新组件。
    React.PureComponent:类子组件中在shouldComponentUpdate中对新老属性做浅比较。
    React.memo:函数组件对新老属性做比较,如果不一致才会将函数组件执行。
    自定义Hook函数:目的是可将某些逻辑提取到可重用的函数中,创建useXXX名称,返回值可以自定义。

  30. react中组件之间的通信方案(类组件和函数组件)?
    父子组件通信(类组件):以父组件为主导,基于属性实现通信。
    父组件传递信息给子组件:基于属性,子组件基于props接收;
    子组件修改父组件数据:父组件将修改自己数据的方法,基于属性传递给子组件,子组件调用并执行此方法即可;
    父组件传递HTML结构给子元素:基于属性中的children插槽来实现,子组件props.children接收;
    父组件调用子组件:可以给子组件设置ref,用来获取子组件的方法、属性、数据。
    父子组件通信(函数组件):以父组件为主导,基于属性实现通信。
    父组件传递信息给子组件:基于属性,子组件基于props接收;
    祖先组件传递信息给后代组件:通过创建上下文,让子组件在需要的时候通过上下文获取父组件的数据,从而实现通信;
    子组件获取父组件数据:基于React.forward并配合useImperativeHandle函数将其ref暴露给父组件实现ref转发,可获取子组件内部的某个元素标签以及函数组件内部的属性和方法;

  31. react中样式私有化处理的方法?
    行内样式:不适用于普通样式、主流样式的编写;
    css样式表:人为有规范的保证组件最外层样式唯一性;
    CSS Module:本质是写样式表,编写的样式为静态样式;
    CSS - IN - JS:将css写入JS中,动态管理样式:
    React - JSS 类组件使用HOC高阶函数,styled-components更加简单。

  32. 什么是单项数据流?
    组件渲染顺序依赖于深度优先原则。
    父组件第一次渲染:父componentWillMount -> 父render -> 子componentWillMount -> 子render -> 子componentDidMount -> 父componentDidMount
    父组件更新:父shouldComponentUpdate -> 父componentWillUpdate -> 父render -> 子componentWillReceiveProps -> 子shouldComponentUpdate -> 子componentWillUpdate -> 子render -> 子componentDidUpdate -> 父componentDidUpdate
    父组件释放:父componentWillUnmount -> 子componentWillUnmount -> 父组件释放
    属性的传递是单向的,父组件可基于属性把信息传递给子组件,子组件无法基于属性给父组件传递信息,但可以将父组件传递的方法执行,从而实现子改父。生命周期为深度优先原则即为单项数据流。

  33. HOC高级组件了解吗?
    利用js中的闭包 [柯理化函数]实现组件代理,在代理组件中经过业务逻辑处理,获取信息,最后基于属性等方案传递最终要渲染的组件。


  1. redux以及redux的处理流程?
    react公共管理状态方案:redux / react-redux
    redux主要是解决了react中复合组件通信,无论什么组件都可以实现组件之间的信息共享。
    处理流程:
    利用createStrore创建一个store公共容器,用来存储个组件用到状态信息,以及让组件更新的方法事件池;在组件使用时可通过store.getState()进行获取公共状态信息;公共状态信息改变可以保证使用到此公共状态信息的组件可以更新,需要在组件中将让组件更新的方法放在事件池中使用store.subscribe(函数),公共状态改变会触发事件池执行,事件池中方法执行,组件就会更新,组件更新就会从公共状态中拿到最新状态来渲染;修改公共状态不可以直接修改,需要使用reducer通过接收dispatch派发过来的行为对象action中的type值,匹配reducer函数中的逻辑。

  2. redux中store容器的特点?
    state公共状态(undefined)、事件池
    dispatch派发每次都会执行reducer函数,第一次派发state没有值,会将initial初始值赋值给state,第一次派发是在redux内部派发的dispatch,第一次派发传递的action.type不会和任何逻辑匹配,reducer中返回的state会整体替换公共状态,目的是为了复制初始值。
    dispatch第二次派发,基于业务逻辑实现手动派发,执行reducer函数,action.type是派发过来与reducer函数中逻辑进行匹配的属性,在reducer函数中需要将获取store克隆一份state,这时不会对公共状态直接修改。

  3. redux中reducer函数和dispatch的关系?
    每次dispatch派发都会执行reducer函数,第一次派发在redux内部完成,赋值给state并返回,第二次派发执行reducer函数传一个action对象,action中的type值与reducer函数中的第一个逻辑匹配,需要在reducer函数中克隆一份state,这时不会对公共状态直接修改。
    为了可以在各组件中都可以将store获取到,可以基于上下文方案将store存储在Provider中,进而进行获取。

  4. redux模块化开发的问题?
    事件池中存放的让组件更新的方法,每次状态更新都要执行一遍,会造成性能问题。如果想解决只能修改源码redux。

  5. react-redux中reducer合并combineReducers的原理?
    combineReducers合并多个reducer,在combineReducers函数中返回一个reducer函数,在此函数中传入总状态state和action派发时传入的对象,在每一次dispatch派发时都是执行总的reducer,而在总的reducer中会遍历执行每一个模块的reducer,此时会返回新的状态state,也会传入action行为对象,这样可以将每个模块reducer执行。

  6. react-redux的特点有哪些?
    特点:使redux操作在react项目更简单。
    react-redux内部创建了上下文对象,并且可将store存放在redux内部的Provider上下文中。
    在组件中获取公共状态信息进行绑定等,不需要基于getState获取公共状态,直接基于react-redux中提供的connct函数直接处理。

  7. react-redux中connect的原理?
    connect实际上是一个高阶函数,接收React组件作为参数,返回一个新的组件。具体来说,connect通过将组件和Store容器连接起来,让组件能够获得并使用Store容器中的数据和方法。
    connect中有两个参数,mapStateToProps可以获取redux中的公共状态,把需要的信息作为属性,传递给组件。mapDispatchToProps可以基于dispatch派发任务执行,返回此方法作为属性传递给组件。在mapDispatchToProps有两种写法,一种是使用函数的形式返回dispatch执行派发任务;另一种是直接使用如action.vote,此时在connect内部有将此对象actionCreator(action.vote)变为action.vote,在redux源码中使用bindActionCreators方法来返回此对象方法,并在此函数中会执行返回dispatch派发action对象。

  8. react-redux处理流程?
    将reducer状态按照模块划分和管理,将所有模块利用combineReducers进行合并,每次派发会将所有模块的reducer依次执行。
    创建actionCreator对象,按照模块管理需要派发对象。
    在store文件中创建store容器,使用createStore创建。
    使用react-redux内部提供的Provider组件,可以在自己内部创建上下文,将store放在根组件的上下文中。
    使用react-redux内部提供的connect函数,在组件中获取公共状态或者将需要派发的操作基于属性的方式传递给组件,组件中以props的形式接收。

  9. redux的常用中间件及处理机制?
    redux-logger:每次派发,在控制台输出派发日志,方便对redux的操作进行调试,输出内容:派发前状态,派发行为,派发后的状态。
    redux-thunk:用于处理异步请求,派发两次,第一次派发用的是重写后的dispatch,此方法不会验证对象是否有属性,此次派发仅仅为第二次派发做准备;第二次将返回的函数执行,把真正的dispatch传递给函数。需要手动将dispatch方法返回。
    redux-promise:用于处理异步请求,派发两次,第一次派发用的是重写后的dispatch,此方法不会验证对象是否有属性,此次派发仅仅为第二次派发做准备;监听返回的promise实例,在实例成功后,自动基于真正的dispatch将成功结果派发给reducer函数。自动处理。
    redux-toolkit:基于切片机制,将reducer和actionCreator混合在一起,使用configStore创建store容器。createSlice用于简化 Redux 中 reducer 的编写,里边包含切片名称name、初始值initialState、reducers不同状态的reducer函数。在组件中使用可使用hook函数useSelector、useDispatch方法来获取公共状态以及dispatch派发。

  10. redux设计上的不足之处?
    redux公共管理状态方案,用来实现组件之间的信息共享。
    不足之处:
    公共状态地址:基于getState获取公共状态信息,它是直接和redux公共状态共用堆内存地址,会导致直接修改公共状态信息。我在源码中也了解了getState函数并没有对此作处理,而是直接返回公共状态信息。如果想对此优化可以在函数中返回状态信息时做深拷贝处理,让两个状态信息不指向一个堆内存地址。
    事件池中方法执行:redux中会让组件更新的方法放入事件池中,在公共状态改变会触发事件池中所有方法执行,这个操作在后期不论哪个组件中dispatch派发使得公共状态信息更改,事件池所有方法都要执行这样比较消耗性能。我在源码中也了解了此函数中有段代码nextlisteners.push(),这段代码没有做任何依赖判断,而是将listener函数之间push进去,然后在dispatch派发时直接遍历nextlisteners并执行里边的每个listener函数,因此无论哪个状态信息更新,事件池中所有的方法都会执行。如果想优化可以向事件池中传入与让组件更新的方法相对应的依赖信息,在每次reducer时获取修改前后的state状态信息,在事件池中方法执行时进行判断修改前后状态,如果状态没有变化则不执行此方法。
    合并后reducer函数的执行:react-redux中使用combineReducers将所有模块reducer合并,并不是将所有代码合并,而视会创建一个总的reducer,在每次dispatch派发都会执行总的reducer,而总的reducer函数中会遍历每个小模块的reducer函数并进行执行,即便在循环过程中发现有匹配的action.type,也会继续执行完其他模块,然后退出循环。如果想优化,可以在模块reducer中派发行为标识进行匹配。


  1. 对Object.defineProperty的了解?
    Object.getOwnPropertyDescriptor(对象, 成员) 获取某个成员规则
    Object.getOwnPropertyDescriptors(对象) 获取对象所有成员规则
    对象成员设置规则:
    configurable:是否可以删除
    writable:是否可以更改
    enumerable:是否可枚举[可以被for/in或Object.keys]
    value:成员值
    Object.defineProperty(obj,key,descriptors)
    设置对象中某个成员规则;
    如果成员已经存在,则修改其规则;
    如果成员不存在,则新增此成员并设置规则;
    数据劫持:get()、set()
    get() 后期获取obj.x成员信息时会触发GET函数执行
    set() 设置成员值的时候会触发setter函数,val为设置的值

  2. 对JS装饰器 decorator的了解?
    对类、类属性、类方法之类的一种装饰。
    在vsCode中支持装饰器:settings -> decorators -> user -> Extension -> js/ts设置选中。
    webpack中babel支持装饰器:
    @babel/plugin-proposal-class-properties;@babel/plugin-proposal-decorators;
    装饰器语法以遗留版本为主:[ “@babel/plugin-proposal-class-properties”,{ “legacy”:true } ]
    编译class插件如果为true直接设置:[ “@babel/plugin-proposal-decorators”,{ “loose”:true } ]
    类装饰器语法:
    类的装饰器在声明之前被声明,可以用来监视或替换类的定义;@test
    装饰器函数中,给类设置私有属性方法或者设置原型上的属性方法;
    同一个装饰器可以作用在多个类上[基于class创建类] @xxx class AA{}
    同一个类可以使用多个装饰器,处理顺序从下到上,编译后的代码从里向外执行,动态处理向装饰器传值。

  3. 同一个类使用多个装饰器,如何处理的?
    const test = () =>{
    console.log(1)
    return () =>{ console.log(2) }
    }
    const handle = () =>{
    console.log(3)
    return () =>{ console.log(4) }
    }
    @test
    @handle
    class Demo { }
    打印结果:1 3 4 2
    因为刚进来执行test,才可以让返回的函数对demo类进行装饰,因此先打印1,然后执行handle,装饰器从下到上的顺序执行。

  4. 基于mobx的公共状态管理方案的原理?
    基于MobX的公共状态管理方案的原理是通过观察者模式来实现状态管理,基于proxy对当前某个对象进行数据劫持和代理,这样可以操作对象成员的时候触发get、set等劫持函数,做一些特殊处理。
    @observable 装饰器,将公共状态变为可监测的,之后可以基于autorun或observer等监测机制才可以生效,基于es6 proxy做过数据劫持,这样后期修改状态值,可以在setter函数中做一个特殊处理。
    @action 装饰器,修饰函数的装饰器,它让函数中的状态更改为"异步批处理"。
    @action.bound:保证函数无论如何执行,函数中的this都是store。
    @computed:装饰器,创建一个具备计算缓存的计算属性。
    reaction:监听器,提供更细粒化的状态检测,默认不会执行,要指定需要检测的状态,第一个参数函数返回数组。
    autorun:监听器,自己通过用到的状态,自动监测变化。
    observer:监听器,函数组件中无法使用装饰器方法,但可以执行observer()方法,将函数组件传递进去。
    configure:此配置是mobx的全局配置,强制使用action方法的模式去修改状态,不允许单独基于实例修改状态。

  5. mobx5中的装饰器、监听器?
    装饰器:主要是用来修饰类、属性、方法等
    @observable:修饰一个或多个响应式数据,这些数据的变化将会自动触发渲染。
    @computed:修饰一个或多个派生值,这些派生值是基于其他响应式数据计算得出的,并且会自动更新。
    @action:修饰一个或多个修改响应式数据的函数,这些函数只有通过action装饰器才能被触发。
    监听器:主要是用来响应响应式数据的变化,包括autorun、reaction等。
    autorun:当响应式数据变化时自动执行,通常用于在数据变化后需要执行的操作。
    reaction:用于创建一个函数,该函数会在其依赖项更改时运行,与autorun类似,但是提供了更多的配置选项。

  6. mobx6中处理方式?
    基于makeObservable给状态和方法设置;
    基于makeAutoObservable自动设置属性和方法;
    MobX 6的工作流程与MobX 5基本相同,采用观察者模式进行状态管理,通过装饰器语法修饰状态和方法,通过监听器监听状态变化并执行相应的操作。


  1. react-router-dom 前端路由机制中路由设计模式有哪些以及设计原理?
    路由设计模式:
    hash:每一次路由跳转都是改变页面的hash值,并且监听hashChange事件,渲染不同内容,改变页面哈希值主页面不刷新,根据不同哈希值让容器中渲染不同组件。
    hash原理:构建路由匹配表,每当重新加载页面或切换哈希值都先到这个路由表中进行匹配,根据哈希值匹配渲染内容。监测hash值的变化重新进行路由匹配 window.onHashChange。
    history:利用H5中historyAPI来实现页面地址切换,可以不刷新页,根据不同地址,到路由表中进行匹配,让容器渲染不同内容。
    history原理:使用history.pushState() 地址跳转新增历史记录,监听window.onPopState。history存在问题,切换地址在页面不刷新情况下是没有问题的,但是如果刷新页面,此地址不存在会报404错误,此时需要服务器配合在地址不存在的情况下也可以将主页面内容返回。

  2. 谈谈在v5和v6版本中router路由机制?
    v5路由机制:
    基于HashRouter或BrowserRouter将所有要渲染的内容渲染出来;
    路由匹配规则:路由地址为route中path字段指定的地址,页面地址为浏览器url后边的哈希值。路由匹配分为非精准匹配和精准匹配,默认非精准匹配。需要设置精准匹配使用exact。必须在各组件中配置渲染机制。
    v6路由机制:
    移除了switch、redirect、withRouter;使用Navigate代替Redirect,withRouter需要自己去实现。所有路由匹配规则放入Routes中,基于Route匹配路由规则,不再基于component、render控制渲染组件,而基于element语法格式进行渲染。默认为精准匹配,不需要设置exact。所有的路由不在分散到各组件中编写,而统一写在一起处理。Outlet路由容器,用来渲染二级或多级路由匹配内容。

  3. 路由优化处理方案?(路由懒加载)
    a. 将最开始要展示的内容、组件打包到主js中[bundle.js],其余组件打包成独立的js或者记忆组件一起打包。
    b. 当页面加载的时候,首先只把主js[bundle.js]请求回来进行渲染,其余的js先不加载。这样可以减少白屏等待时间。
    c. 路由切换时进行路由匹配,想渲染哪个组件,就将哪个组件所在的文件动态导入渲染,分割打包js按需异步加载,路由懒加载;
    先把要渲染的组件导入bundle.js文件;
    借助React.lazy函数和es6中的import实现,分割打包并按需导入;
    在最后渲染的组件使用suspense进行包裹,可以在suspense标签中添加属性fallback可在异步加载组件没有处理完成前,展示loading效果。
    component: lazy( ()=> import(/webpackchunkName:‘aChild’/ ‘./A’)) 基于/*webpackchunkName:'aChild'*/ 可以告诉webpack打包后文件的名字,名字相同的组件会合并到一个JS中打包。

  4. v5和v6版本中路由跳转以及传参的方案?
    v5路由跳转以及传惨方式:
    跳转方式:Link路由跳转,history.push()编程式导航跳转。
    传参方式:
    a. 问号传参:传递的信息在url上,不安全,在目标路由刷新信息也在,search存储的是问号信息,必须为urlencoded字符串;接收参数使用useLocation()获取location.search.substring(),基于new URLSearchParams处理问号传参信息。
    b. 路径传参:传递的信息在url中,不安全,在目标组件中刷新信息也在,将需要传递的值作为路由路径中的一部分,path: ‘/c/:id/:name’,每次匹配路由地址是基于path-to- regexp处理,只有/c/100/wd才可以匹配;接收参数使用useRouteMatch()获取params,或者使用useParams来获取id、name。
    c. 隐式传参:传递的信息不在url中,安全,没有限制,在目标组件中刷新信息会丢失,使用对象中state进行传递;接收参数使用useLocation()获取state,组件刷新信息丢失。
    v6路由跳转以及传惨方式:
    跳转方式:Link路由跳转,NavLink路由跳转,Navigate遇到此组件会跳转也可称重定向,navigate()编程式导航,取消了基于history传参。
    传参方式:
    a. 问号传参:navigate( { pathname:‘/a’,search: qs.stringify({}) } );接收传参使用useLocation,useSearchParams来获取问号传参。
    b. 路径传参:Route属性path=“/c/:id/:name”,使用navigate(‘/c/100/wd’)进行传递参数;接收参数使用useLocation、useMatch、useParams接收,使用useMatch必须传递一个地址,对地址进行解析。
    c. 隐式传参:在v6版本中刷新页面参数会被保留;接收参数useLocation(),location.state为传递过来的状态信息。

  5. NavLink和Link的区别是什么?
    两者都是实现路由跳转,语法几乎一致。
    NavLink: 每次加载或者路由切换完毕都会拿最新的路由地址,与NavLink中to指定的地址或pathname地址进行匹配,匹配上会默认active选中样式表,也可以基于activeClassName重新设置选中的样式雷鸣也可精准匹配,基于这种机制,可以给选中的导航设置相关选中样式。
    Link: 主要用于在页面间切换时,避免页面重新加载,to属性,可以描述需要定位的页面。

  6. 在react-router-dom v6版本中常用的hook有哪些?
    useNavigate:代替useHistory,实现编程式导航。
    useLocation:在v5、v6版本中都有,获取location对象信息。
    useSearchParams:在v6版本新增,获取问号传参信息,获取的结果是一个URLSearchParams对象。
    useParams:在v5、v6版本都有,获取路径参数匹配信息。
    useMatch(pathname):代替useRouteMatch,v5中此hook函数可以基于params获取路径参数匹配信息,但是v6中需要自己传递地址。

  7. 函数组件和类组件基于route匹配渲染如何获取路由信息?
    函数组件:基于props获取路由信息,也可以使用hook函数获取。
    类组件:基于属性获取,或者使用高阶组件withRouter获取。[自己写的]

  8. 函数组件和类组件没有基于route匹配渲染如何获取路由信息?
    函数组件:基于hook处理,也可以使用withRouter获取。[自己写的]
    类组件:使用withRouter获取。[自己写的]

  9. useReducer如何实现对状态统一管理?
    useReducer 是对useState的升级处理,普通处理需求,基本都是useState直接处理,不用useReducer;如果一个组件的逻辑很复杂,需要大量状态、修改状态的逻辑,使用useReducer管理状态更好。多有状态修改逻辑全部统一处理。

  10. rem的原理?移动端响应式开发设置步骤?
    rem是相对于html根元素font-size计算值的倍数的css单位,当根元素html元素的字体大小改变时,rem的长度也会随之按比例改变。
    移动端开发步骤:(原生代码)
    找参照物的比例,fontSize初始值为100px,1rem = 100px,设计稿测量值转化为rem单位。
    根据当前设备,让rem的转化比例跟着缩放rem和px的换算比例一致。计算此设备,rem和px换算比例。获取document.documentElement.clientWidth,设置设备宽度,进行计算。使用window.addEventListener()监测设备的resize尺寸变化。
    在react中使用lib-flexible设置rem和px换算比,postcss-pxtorem可以将我们写的px单位按照当时换算的比例自动转换为rem,不用自己换算。

  11. 真实项目组件分为哪几种,分别有什么作用?
    普通业务组件:页面组件
    通用业务组件:多个页面复用
    通用功能组件:ui组件库等

  12. jsx视图中可以直接导入静态资源图片吗?
    jsx不可以直接导入静态资源图片。因为经过webpack打包处理,项目的结构目录会改变,在css样式中,使用图片可以使用相对地址,webpack打包时会处理css中图片导入,将需要的图片进行打包,经打包后的地址重新覆盖css中写的地址。
    解决图片处理:使用绝对地址,基于ES6 Module模块方式导入图片。

  13. react-dom中flushSync的作用?
    flushSync用于强制React立即执行所有挂起的更新,并同步更新DOM。
    flushSync函数返回的是undefined,并且可能强制显示任何挂起的Suspense边界的fallback状态。

  14. 组件释放时,react内部的情况?
    移除虚拟dom,真实dom,合成事件绑定。
    不会移除设置的定时器,监听器,基于addEventListener的事件绑定。
    为了性能优化,需要手动移除该事件。
    dangerouslySetInnerHTML :可以基于{}语法绑定的内容全部作为普通文本进行渲染。

  15. react中路由跳转push与replace的区别?
    push方法:会改变浏览器的历史记录堆栈。当调用push方法并传入一个路径时,浏览器历史记录堆栈中将会添加一个新的记录,新的路由会被设置为当前路由,可以点击“后退”按钮返回到前一个路由。
    replace方法:不会改变浏览器的历史记录堆栈。当调用replace方法并传入一个路径时,当前的路由将会被替换为新的路由,但浏览器历史记录的堆栈顶部不会改变,用户点击“后退”按钮时,将没有任何变化,因为历史记录堆栈中没有新的路由记录。

  16. React-Router中组件有几种类型?
    路由器组件:React Router提供了BrowserRouter和HashRouter两种路由器组件,它们会为应用创建一个专用的history对象。
    路由匹配组件:用来匹配一个由Route的path到当前位置的pathname。
    导航组件:用来进行路由之间的转换,React Router提供了Link、NavLink和Redirect三种导航组件。


  1. 首页进入详情页面,然后从详情页面返回首页,停留在刚进来的位置如何做?
    react中:使用keepAlive缓存机制,下载插件keepalive-react-component,在根组件引入keepAliveProvider,在首页路由组件中引入withkeepAlive,,可以支持懒加载,并且配置滚动位置scroll为true。
    在A组件路由跳转时,把A组件中需要的数据存储到redux中,A释放,B加载。当B回到A,A开始加载,首先判断redux中是否存储了数据,如果没有存储则是第一次加载,如果存储了把存储数据拿出来渲染。
    将A组件的真实dom信息缓存,从B到A,将A之前的缓存信息拿出来用。

  2. 假如从列表页第4页某条数据进入它的详情页,修改某操作之后然后返回还停留在此页的此条数据的位置,如何做?

  3. 删除当前页的最后一条数据并自动返回到前一页如何做?

  4. DOM-DIFF算法的原理以及目的?
    目的:为了尽可能保证最少的更新和渲染,尽可能使用旧节点进行渲染。
    原理:前提是组件更新时所有标签没有修改。
    第一轮循环,遍历Fiber链表,链表中旧节点去虚拟dom中相同位置找新节点,进行对比,按照位置进行比较,比较时先看key,key相同看标签和内容,如果标签和内容都一样则复用旧节点,如果标签名不一样则删除旧节点新增新节点,如果内容不一样标记更新;key不一样直接跳出第一轮循环。
    第二轮循环,遍历虚拟dom,遍历之前会根据Fiber链表中创建出的Map查找映射表并将key值作为属性名,旧节点作为属性值,从虚拟dom的第一个节点开始遍历,处理没有第一轮循环被处理过的旧节点,在循环中找到相同key的旧节点进行比较,找到相同key,对比标签和内容,然后拿旧节点的索引值和全局的最高权重值比较,用来决定位置是否挪动,如果找不到key的旧节点,则说明为新增节点,第二轮结束后将没有比较过的旧节点进行删除。
    如果组件更新,节点的key值和节点所在的顺序没有变化,只需要第一轮遍历即可。

  5. 循环创建的元素需要key值,为什么尽可能不用索引值当key值?
    循环创建元素需要添加key值,尽可能不用索引值作为key值,使用不会因为位置或索引改变而改变的值做key。
    索引值做key,如果遇到数据的位置移动,或者在标签中间删除或新增内容,之前的元素索引值key都会变化很难实现‘旧地复用’,基本上都是新内容替换旧内容,实现更新。


  1. 淘系解决方案,有哪些?
    dva、umi4

  2. Iterator迭代器了解吗?
    Iterator迭代器为各种不同数据结构提供的统一访问机制,任何数据结构只要部署Iterator接口,就可以完成遍历操作for/of循环,依次处理数据所有成员。Iterator迭代器拥有next方法,依次处理数据所有成员,每次遍历返回结果为对象{done,value},done记录是否遍历完成,value当前遍历的结果。
    JS中具备迭代器的数据结构:
    主要看数据结构是否具备Symbol.iterator属性,有该属性具备迭代器,没有则不具备,具备迭代器的可以使用for/of循环来迭代每一项值。
    数组:Array.prototype[Symbol(symbol.iterator)] = function(){}
    部分类数组:arguments[Symbol(symbol.iterator)]、nodeList.prototype[Symbol(symbol.iterator)]
    字符串:String.prototype[Symbol(symbol.iterator)]
    Set/Map …
    纯粹对象或自己构建的类数组对象,默认不具备symbol.iterator这个属性,因此不具备迭代器的规范,不可以使用for/of。

  3. for/of的原理?
    原理:迭代执行,先执行数组中的symbol.iterator方法,获取一个具备迭代器规范对象itor;开始迭代,每一次迭代都是把itor.next执行,获取对象中value属性值,赋值给val变量,然后看对象中done这个属性的值,如果是false,继续迭代;如果是true,结束迭代。

  4. Generator生成器对象了解吗?(yield)
    创建一个Generator生成器,给创建的普通函数function后边添加*,箭头函数是无法变为生成器的;每一个Generator生成器函数都是GeneratorFunction这个类的实例。
    Generator生成器作用:可以基于返回的迭代对象itor,基于next方法控制函数体中代码一步步执行。每一次执行next,控制函数题中代码开始执行或者从上一次暂停的位置继续执行,遇到yield则暂停;遇到函数体中return或者已经执行到函数末尾,函数中返回的value为undefined。
    生成器嵌套:yield 支持让我们进入另外一个函数中进行一步步执行。

  5. 需要三个接口串行,有哪些方案?
    利用promise.then进行执行。
    利用await async 进行执行。
    利用生成器函数yield,Generator函数执行。

  6. async和await的原理是什么?
    利用promise+Generator的语法糖将异步操作转换成同步操作进而实现async和await。
    Generator函数控制异步数据操作,利用递归方法执行Generator函数,并使用promise来获取成功后的value。

  7. redux-sage有了解吗?
    用于管理异步数据获取的redux中间价,目标是让副作用管理更容易,执行更高效,测试更简单。
    redux中action为纯粹对象,reducer为纯函数,只能处理同步操作,因此如果想要处理异步操作,使用组件间redux-sage,组件派发后,在store文件中执行sagaMiddleware.run(saga)方法,在saga.js文件中创建监听器,监听每次派发任务,当某个任务被监听后通知指定工作者去处理异步操作,异步结束后通知reducer执行,使用effectAPI,创建执行函数并且执行。
    每一次派发都要先走redux-sage中间件,识别当前派发的任务是否被监听,被监听则创建监听器,当某个任务被监听后通知去处理异步操作,结束后通知reducer执行;没有被监听,直接会通知reducer执行。

  8. redux-saga中effectsAPI有哪些?
    yield take(异步标识):监听派发的异步任务,创建监听器监听指定标识的异步操作,只会监听一次。单次处理。
    yield takeEvery(异步标识, workingCount):可以实现一直监听的操作,被监听到执行要执行的方法。重复处理。
    yield takeLatest(异步标识, workingCount):和takeEvery一样每次异步派发都会被监测,会执行workingCount函数,在执行workingCount前会把正在执行的操作结束掉,只保留当前的操作,即对异步派发任务的防抖处理。
    yield throttle(ms, 异步标识, workingCount):对异步派发做节流处理,组件中频繁操作,控制一定的触发频率,依然会做多次触发,不过做了降频处理。它不是对执行的方法做节流,而是对异步任务的监测做节流,第一次异步任务被监测到派发后,下一次监测需要过ms这么长时间。
    yield debounce(ms, 异步标识, workingCount):和takeLatest一样,做防抖处理,只识别一次,原理与throttle类似,对异步任务的监测做防抖处理。
    以上用来做任务监测的方法
    yield delay(2000):设置延迟操作,延迟时间到达后会执行代码。
    yield put(action):派发任务到reducer,等价于dispatch。
    yield select(state => state.demo):基于mapState函数,返回需要使用的公共状态。
    yield call(方法, 实参1, 实参2):基于call方法,可以把指定的函数执行,把实参一项项传递给方法。
    yield apply(this, 方法, [实参1, 实参2])。
    yield fork(方法, 实参1, 实参2):以非阻塞形式执行函数。

  9. react性能优化有哪些?
    使用生产环境构建。确保在部署到生产环境时使用了React的生产版本,它会进行代码压缩和性能优化。
    减少不必要的渲染。React使用虚拟DOM进行渲染,但每次组件重新渲染都会有一定的性能开销。因此,优化组件shouldComponentUpdate或PureComponent,避免不必要的重新渲染。
    列表渲染性能优化。在列表中使用唯一的key属性来标识每个子元素。这样可以帮助React识别每个元素的身份,减少重复渲染和操作的开销。
    使用React.memo。对于那些不依赖于父组件传入的props的函数组件,可以使用React.memo进行包裹,减少不必要的重渲染。
    避免多层级的嵌套组件。当组件层级嵌套过深时,会增加diff算法的复杂度和渲染时间。尽量保持组件的层级扁平和简洁。


  1. dva以及它的原理是什么?
    基于redux和redux-sage的数据流方案,为了简化开发体验,dva额外内置了react-router和fetch。dva的特点是它对redux用户友好;通过reducers、effects和subscriptions组织model;插件机制如dva-loading,可以自动处理loading;支持HWR,基于babel-plugin-dva-hmr实现。
    原理:组件进行派发触发reducer方法执行,如果是同步修改会直接触发执行reducer函数;如果是异步派发,dispatch派发后会调用model层中effect API,执行完后通知reducer函数执行,最后修改state。

  2. roadhog脚手架的作用?
    roadhog是一个cli工具,提供了server、build、test三个命令,分别用于本地调试和构建,并提供了mock功能模拟接口数据,默认开启css modules,提供了json格式配置方式。

  3. 从0-1创建项目需要考虑什么?
    UI组件库按需导入,在dva中利用extraBabelPlugins中配置;
    配置跨域代理,使用proxy进行配置;
    如果是移动端,需要配置响应式,利用插件lib-flexible,获取设备大小,计算rem值;
    设置浏览器兼容性,在开发环境和生产环境的浏览器兼容器,如果需要处理ie兼容性问题,可以安装插件,@babel/polyfill并在入口导入,在package.json文件中设置browersLists配置;
    进行一些webpack配置,环境变量使用插件cross-env。

  4. dva中实现路由的方式?
    Link/NavLink、编程式导航,所有经过路由匹配的组件都有一个props属性,包含history、location、match三个对象信息。
    路由跳转:history.push(‘/personal’)
    问号传参:history.push({ pathname: ‘/personal’, search:‘?lx=0&name=wd’ })
    隐式传参:history.push( ‘/personal’, state: { lx:0, name:‘wd’ }) 刷新页面数据会丢失
    路径传参:history.push( ‘/personal/0/wd’ ) 基于: ? 设置匹配规则
    当组件不是经过理由匹配,使用withRouter,可以将三个对象传递给组件。
    routerRedux:react-router-redux所提高的对象,routerRedux可以在组件中实现路由跳转,而且可以在redux操作中实现路由跳转。
    redux内部跳转:yield put( routerRedux.push() )
    redux外部跳转:dispatch( routerRedux.push() )
    组件中使用dispatch,引入connect。

  5. dva中model层处理流程?
    每个模块具备五部分信息:
    namespace:模块名,后期获取状态和进行派发标识
    state:公共状态管理
    reducers:reducer中派发行为标识判断以及状态更改,同步+纯函数+dispatch派发
    effects:基于redux-sage实现异步操作
    subscriptions:在此订阅的方法,页面加载会一定执行
    组件中的使用:
    基于dva中提供的connect高阶函数,使用公共状态以及dispatch方法;dispatch同步派发,直接触发reducers中的方法修改状态;dispatch异步派发先在effects中完成异步获取数据,然后执行reducers函数。

  6. 手写延时函数?
    const delay = ( interval = 1000 ) =>{
    return new Promise( resolve => {
    setTimeout(() =>{
    resolve()
    },interval)
    } )
    }

  7. dva中subscriptions的作用?
    subscriptions模块下,页面加载方法会立刻执行,此方法为普通函数,不可以是Generator函数。只有访问了组件,组件被注册此板块订阅的方法才会被执行,而且只会执行一次,后来路由切换也不会执行,如果想要在路由切换执行需要使用history.listen进行路由监听。
    history.listen():在model没有懒加载的情况下,可以让setup函数在页面第一次加载过程中,就订阅到事件池中国,并且通知执行,在setup中基于history.listen创建路由跳转监听器,每一次会执行,因此需要在第一次执行完毕移除路由跳转监听器。unlisten。

  8. dva中dva-loading如何使用?
    dva中默认在redux公共状态中会添加一个loading状态,loading返回一个对象,在组件中将loading基于属性为传递给组件。

  9. dva中redux-logger如何使用?
    import createLogger from ‘redux-logger’
    const app = dva( { onAction: createLogger() } )


  1. umi4的原理?
    Umi4的原理是通过配置在不同模式之间切换,并尽可能保证功能的一致性;使用ESBuild进行JS压缩、CSS压缩、依赖编译、jest编译、配置文件和MOCK文件的编译。

  2. 三大框架的区别?
    create-react-app:配置脚手架需要使用yarn eject暴露出配置项,并在webpack源码中直接修改,也可在package.json文件中修改;配置路由管理,可用routerV6预定式路由模式统一,那处理权限校验等需要自己实现;数据管理基于redux/react-redux/mobx实现状态管理需要自己搭建一系列操作。
    dva框架:配置脚手架在.webpackrc.js文件中基于roadhog进行webpack配置;路由管理使用的是v4,react版本低v16,默认为约定式路由,但是统一处理、权限校验需要自己封装;数据管理,那dva本身就是对redux、redux-saga的封装,因此操作数据管理很方便。
    umi框架:配置脚手架在config目录下config.js文件中按照umi所提供的方式修改配置项;对于路由管理umi中使用的是动态路由管理机制,操作起来非常方便;数据管理继承了dva封装的model机制,因此操作数据也很方便。

webpack篇章

  1. webpack解决了什么问题?
    代码转换,将es6转化为es5,less编译为css;文件优化,压缩js、css文件;代码分割,提取多个页面公共代码,提取首屏不需要执行的部分代码;模块合并,将模块合并为一个文件;自动刷新,创建本地服务器,监听本地源代码的变化;自动发布,自动构建线上发布代码并传输给发布环境;处理跨域代理。

  2. webpack的原理是什么?
    webpack是一个js应用程序的打包构建工具,在应用程序打包时我们会安装配置各种打包规则,比如压缩文件、base64图片转换等,那首先使用mode进行配置打包模式,分为生产模式和开发模式;然后使用entry进行配置打包入口,可以配置多入口也可以配置单入口,单入口直接以字符串的形式写入即可,那多入口文件需要使用对象的形式进行配置;output配置出口文件,其中文件名可以哈希的形式进行输出;loader为加载器,一般用于实现代码编译,可在module中进行配置如js兼容性处理方式等;那在plugin中可配置一些插件,如js、css压缩的插件等;那在optimization可配置一些优化项;在devServer中可配合webpack-dev-server在本地启动web服务器,实现本地预览项目以及跨域处理。

  3. 说说webpack的模块化处理方式?
    利用export、module.exports导出自定义模块,import或者require引入模块。

  4. 如何提高webpack打包速度?
    设置编译时忽略node_modules文件中的配置
    使用js、css等压缩插件进行压缩文件从而提高打包速度
    利用chunk配置动态文件名以便于配置缓存

  5. 对模块化开发的了解?
    优点:
    便于多人协作开发,每个部分开发不会干扰其它地方。
    便于调试修改,因为模块独立,发现问题比较容易,修改一处,也不影响别处。
    利于代码复用,小块的代码可以更方便拿到别的项目中不加或者稍加修改使用,提高可维护性。
    便于功能的扩充,因为软件各个部分是独立的,不需要理解整个软件就可以添加功能,特别适合二次开发。
    进化历史:
    单例设计模式 -> AMN(require)-> commonJS -> CMD(sea)-> ES6Module

  6. 前端性能优化?
    提高首屏优化速度:使用骨架屏并在真实内容渲染出来之前,添加loading效果,给用户更好的感官体验;减少http请求次数,可以将css和js做压缩处理,使用base64格式加快图片渲染;较少页面渲染次数,将script标签放在页面尾部,因为此标签会阻碍GUI渲染,使用link进行异步加载css资源,在页面头部引入此文件,而使用import导入css会阻碍GUI渲染,尽量减少import的使用。
    提高页面运行时的性能:减少循环嵌套,降低时间复杂度;利用事件委托优化绑定事件并减少dom操作;使用函数防抖和节流处理频繁触发点击;合理使用闭包,手动释放无用内存;尽可能不使用for/in循环,因为性能消耗大。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值