1. 请描述出webpack核心的几个部分,及作用
参考答案
webpack其实是一个平台,在平台中,我们会安装/融入/配置各种打包规则
+ mode:打包模式「开发环境development、生产环境production」
+ entry:入口「webpack就是从入口开始,根据CommonJS/ES6Module模块规范,分析出模块之间的依赖,从而按照相关的依赖关系,进行打包的」
+ output:出口
+ loader:加载器「一般都是用于实现代码编译的」
+ plugin:插件「处理的需求比较多了,例如:压缩、编译HTML、清空打包...」
+ resolve:解析器
+ optimization:优化项
+ devServer:配合webpack-dev-server,在本地启动Web服务,实现项目预览以及跨域处理...
+ ......
2.介绍一下,webpack中你知道的加载器、插件及其作用
参考答案
常用的插件:
+ html-webpack-plugin:编译HTML
+ clean-webpack-plugin:清空打包内容
+ css-minimizer-webpack-plugin 压缩CSS
+ terser-webpack-plugin 压缩JS
+ mini-css-extract-plugin 抽离CSS样式
常用的加载器:
+ css-loader、style-loader、less-loader、postcss-loader 处理CSS的
+ babel-loader 编译JS的
+ file-loader、url-loader 处理图片的
+ ESLint-loader 代码检测的
3.create-react-app是如何处理IE兼容的
tips:需要处理兼容的部分包含:CSS3样式属性、ES6+语法、ES6+内置API
1. 设置browserslist「浏览器兼容列表」
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
2. 考虑CSS3样式的兼容问题
+ webpack内部的postcss-loader & autoprefixer,根据browserslist,自动给CSS3加相关的前缀
3. 考虑ES6+语法兼容
+ webpack内部的babel & babel-loader & @babel/preset-env & @babel/core,根据browserslist,自动把ES6语法转换为ES5语法!
4. 考虑ES6+内置API的兼容:主要是重写常用的API {@babel/polyfill}
import 'react-app-polyfill/ie9';
import 'react-app-polyfill/ie11';
import 'react-app-polyfill/stable';
4.React项目的“开发环境”下「基于create-react-app创建的项目」,我们需要基于proxy实现跨域代理;目前已知需要代理的服务器地址是:
公司测试服务器:http://192.168.1.123:8081/
开源平台服务器地址:https://open.weixin.com/v2/applet/
请写跨域代理配置的代码!
/* src/setupProxy.js */
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function (app) {
// 参考答案
app.use(
createProxyMiddleware("/api", {
target: "http://192.168.1.123:8081",
changeOrigin: true,
ws: true,
pathRewrite: { "^/api": "" }
})
);
app.use(
createProxyMiddleware("/wx", {
target: "https://open.weixin.com/v2/applet",
changeOrigin: true,
ws: true,
pathRewrite: { "^/wx": "" }
})
);
};
5. 有A/B两个模块,A模块中需要调用B模块提供的方法,请你基于ES6Module模块规范,实现模块的导出和导入「至少两种方案」
方案一:
/* B模块 */
let listeners = [];
const sum=function sum(...params){};
const query=function query(...params){};
export default {
listeners,
sum,
query
};
/* A模块 */
import B from './B';
B.listeners;
B.sum();
B.query();
方案二:
/* B模块 */
export let listeners = [];
export const sum=function sum(...params){};
export const query=function query(...params){};
/* A模块 */
import {listeners,sum,query} from './B';
6. 简单描述:MVC和MVVM两种框架模式的区别
React框架采用的是MVC体系;Vue框架采用的是MVVM体系;
MVC:model数据层 + view视图层 + controller控制层
- 实现了数据驱动视图的渲染!!
- 视图中的表单内容改变,想要修改数据,需要开发者自己去写代码实现!!
- MVC是“单向数据驱动”
MVVM:model数据层 + view视图层 + viewModel数据/视图监听层
- 实现了数据驱动视图的渲染:监听数据的更新,让视图重新渲染
- 实现了视图驱动数据的更改:监听页面中表单元素内容改变,自动去修改相关的数据
- MVVM是“双向数据驱动”
7.简单描述:JSX语法渲染的步骤
- 首先:基于babel-preset-react-app把JSX编译为React.createElement格式
- 其次:把createElement方法执行,创建出virtualDOM虚拟DOM对象
- 最后:基于root.render方法把virtualDOM变为真实DOM「如果是组件更新,是先进行DOM-DIFF,最后把差异部分渲染为真实DOM即可」
8. 根据需求,实现相关的代码
/*
根据arr数组,循环动态创建出对应的li元素;
li元素的样式是:字体14px,文字黑色;
em元素的样式是:前三行,字体16px,文字红色;后面和li保持一致;
*/
let arr = [{
id:1,
title:'标题一'
},{
id:2,
title:'标题二'
},...];
const NewsList=function NewsList(){
return <ul className="news-box">
{arr.map((item,index)=>{
let {id,title}=item;
return <li key={id} style={{
fontSize:'14px',
color:'#000'
}}>
{index<=2?<em style={{
fontSize:'16px',
color:'red'
}}>{id}</em>:<em>{id}</em>}
{title}
</li>;
})}
</ul>;
};
9. 简单描述:函数组件和类组件的区别?
函数组件
- 不具备“状态、ref、周期函数”等内容,第一次渲染完毕后,无法基于组件内部的操作来控制其更新,因此称之为静态组件!
- 但是具备属性及插槽,父组件可以控制其重新渲染!
- 渲染流程简单,渲染速度较快!
- 基于FP(函数式编程)思想设计,提供更细粒度的逻辑组织和复用!
类组件
- 具备“状态、ref、周期函数、属性、插槽”等内容,可以灵活的控制组件更新,基于钩子函数也可灵活掌控不同阶段处理不同的事情!
- 渲染流程繁琐,渲染速度相对较慢!
- 基于OOP(面向对象编程)思想设计,更方便实现继承等!
React Hooks 组件,就是基于 React 中新提供的 Hook 函数,可以让函数组件动态化!
10. state 和 props 区别是什么?
props是指组件间传递的一种方式,props自然也可以传递state。由于React的数据流是自上而下的,所以是从父组件向子组件进行传递;另外组件内部的this.props属性是只读的不可修改!
state是组件内部的状态(数据),不能够直接修改,必须要通过setState来改变值的状态,从而达到更新组件内部数据的作用。
参考:state和props的区别
11. 如何实现props的规则校验?
import React from "react";
import PropTypes from 'prop-types';
const Demo = function Demo(props) {
....
};
// 赋值属性默认值
Demo.defaultProps = {
x: 0
};
// 设置属性规则
Demo.propTypes = {
x: PropTypes.number,
title: PropTypes.string.isRequired
};
export default Demo;
12. 简述:如果让你封装一个公共的组件,你会从哪些角度去提高组件的复用性
我们可以基于这样几种方式:
- 数据信息:可以基于props(属性)传递进来
- HTML结构信息:可以基于props.children(插槽)传递进来
还可以基于ref等操作,获取子组件的实例,从而调用其实例上的属性和方法;或者配合React.fowardRef实现ref转发,从而获取子组件内部的元素等!!
13. React 类组件中,常用的生命周期函数有哪些?及意义!
14. React.Component 和 React.PureComponent 的区别?
参考答案
PureComponent相比较于Component,自动增加shouldComponentUpdate周期函数,并对原始属性/状态和最新属性/状态进行浅比较!
+ 当属性和状态没有任何改变的情况下,是不会进行组件更新的,起到一个优化的作用!
+ forceUpdate强制更新时,会跳过内部设置的shouldComponentUpdate函数
*/
// 源码可以不写
const shallowEqual = function shallowEqual(objA, objB){
if (Object.is(objA, objB)) return true;
if (typeof objA !== 'object' || objA === null ||
typeof objB !== 'object' || objB === null) {
return false;
}
const keysA = Reflect.ownKeys(objA),
keysB = Reflect.ownKeys(objB);
if (keysA.length !== keysB.length) return false;
for (let i = 0; i < keysA.length; i++) {
let key = keysA[i];
if (
!objB.hasOwnProperty(key) ||
!Object.is(objA[key], objB[key])
) {
return false;
}
}
return true;
};
// 测试使用
export default class Demo extends React.Component {
...
shouldComponentUpdate(nextProps, nextState) {
return !shallowEqual(this.props, nextProps) ||
!shallowEqual(this.state, nextState);
}
...
};
15. 阐述一下,setState方法是同步还是异步?为哈要这样设计?forceUpdate和setState的区别是什么?
React18中,setState的操作是异步的「在任何地方都是」,基于updater更新队列机制,实现了状态的批处理!这样做的好处有:
- 统一更新,提高视图更新的性能
- 处理流程更加稳健
- …
在React18之前,我们只在 React合成事件/周期函数期间 批量更新;默认情况下,React中不会对 promise、setTimeout、原生事件处理(native event handlers)等操作中的setState进行批处理「也就是在这些场景中,setState是同步操作!」
基于ReactDOM提供的flushSync函数,可以让更新队列立即刷新(渲染),模拟出同步操作的效果!!
forceUpdate是强制更新,会跳过shouldComponentUpdate的校验,平日开发中,尽可能减少对他的使用!!
16. 什么是React Hooks组件,解决了什么问题?
为函数组件提供状态、生命周期等原本 class 组件中才有的功能,可以理解为通过 Hooks 为函数式组件钩入了 class 组件的特性。
解决问题
1、从组件中提取状态逻辑,解决了在组件之间复用状态逻辑很难的问题;
2、将组件中相互关联的部分拆分成更小的函数,解决了复杂组件的问题;
3、在非class的情况下使用更多的React特性,解决了class组件与函数组件有差异的问题。
17. 列举常见的React Hook函数,及作用「不少于5个」
基础 Hook
- useState 使用状态管理
- useEffect 使用周期函数
- useContext 使用上下文信息
额外的 Hook
- useReducer useState的替代方案,借鉴redux处理思想,管理更复杂的状态和逻辑
- useCallback 构建缓存优化方案
- useMemo 构建缓存优化方案
- useRef 使用ref获取DOM
- useImperativeHandle 配合forwardRef(ref转发)一起使用
- useLayoutEffect 与useEffect相同,但会在所有的DOM变更之后同步调用effect
部分钩子信息的解释参考
18. useEffect和useLayoutEffect的区别?
useLayoutEffect
- 其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。
- 可以使用它来读取 DOM 布局并同步触发重渲染。
- 在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。
- 尽可能使用标准的 useEffect 以避免阻塞视觉更新。
19.简述useMemo和useCallback的作用?
在前端开发的过程中,我们需要缓存一些内容,以避免在需渲染过程中,因大量不必要的耗时计算而导致的性能问题。useMemo可以帮助我们去实现数据的计算缓存!
在父组件调用子组件的时候,如果需要基于属性给子组件传递一个函数,为了让每一次传递给子组件的函数,都是相同的引用地址,我们可以把函数基于useCallback处理一下「useCallback 用于得到一个固定引用值的函数」!这样在子组件中,再基于PureComponent或者React.memo的配合,可以减少子组件非必要的更新操作!!
20. 看代码,分别写出:组件第一次渲染 & 一分钟后点击按钮 各自输出结果
import React, { useState, useEffect, useLayoutEffect } from "react";
const Demo = function Demo() {
let [x, setX] = useState(10),
[y, setY] = useState(0);
useEffect(() => {
console.log('@1:', x, y);
return () => {
console.log('@2:', x, y);
};
});
useEffect(() => {
console.log('@3:', x, y);
setTimeout(() => {
console.log('@4:', x, y);
}, 10);
}, []);
useEffect(() => {
console.log('@5:', x, y);
}, [x]);
useEffect(() => {
console.log('@6:', x, y);
}, [y]);
useLayoutEffect(() => {
console.log('@7:', x, y);
}, [x]);
return <div className="demo-box">
<span>0</span>
<button onClick={() => setX(x + 1)}>按钮</button>
</div>;
};
export default Demo;
//输出结果
第一次渲染:
@7: 10 0
@1: 10 0
@3: 10 0
@5: 10 0
@6: 10 0
@4: 10 0
点击按钮:
@7: 11 0
@2: 10 0
@1: 11 0
@5: 11 0
21. 简述ref在React组件中的作用
- 给HTML标签设置ref,可以获取对应的DOM元素
- 给子组件(类组件)设置ref,可以获取子组件的实例「这样就可以获取子组件中的相关信息了」
- 给子组件(函数组件)设置ref,需要配合forwardRef和useImperativeHandle,获取子组件内部的元素或者数据/方法等!!
22. 想在Demo「父组件」中,获取Child「子组件」中的:x状态和change方法,该如何编写代码?
import React, { useState, useImperativeHandle, forwardRef, useRef, useEffect } from "react";
const Child = forwardRef(function Child(props, ref) {
let [x, setX] = useState(0);
const change = () => {
setX(x + 1);
};
useImperativeHandle(ref, () => {
return {
x,
change
};
});
return <div className="child-box">
...
</div>;
});
const Demo = function Demo() {
let child = useRef(null);
useEffect(() => {
console.log(child.current); //包含需要的信息
}, []);
return <div className="demo-box">
<Child ref={child} />
</div>;
};
export default Demo;
23. 简述什么是合成事件?以及React中为啥要使用合成事件?
合成事件是围绕浏览器原生事件,充当跨浏览器包装器的对象;它们将不同浏览器的行为合并为一个API,这样做是为了确保事件在不同浏览器中显示一致的属性!
React中的合成事件是基于事件委托实现的;React17及以后,是委托给#root元素;React17以前,是委托给document元素,并且没有实现捕获阶段的派发;
24. 假如ev是合成事件对象,那么请说出:ev.stopPropagation & ev.nativeEvent.stopPropagation & ev.nativeEvent.stopImmediatePropagation 三者的区别?
ev.stopPropagation()
//合成事件对象中的“阻止事件传播”:阻止原生的事件传播 & 阻止合成事件中的事件传播
ev.nativeEvent.stopPropagation();
//原生事件对象中的“阻止事件传播”:只能阻止原生事件的传播
ev.nativeEvent.stopImmediatePropagation();
//原生事件对象的阻止事件传播,相比较于stopPropagation,只不过还可以阻止元素上其它绑定的方法执行
25. 看代码,分析出在React16/18两个版本中,点击inner元素,分别会输出啥?
import React from "react";
class Demo extends React.Component {
render() {
return <div className="outer"
onClick={() => { console.log('outer 冒泡「合成」'); }}
onClickCapture={() => { console.log('outer 捕获「合成」'); }}>
<div className="inner"
onClick={() => { console.log('inner 冒泡「合成」'); }}
onClickCapture={() => { console.log('inner 捕获「合成」'); }}
></div>
</div>;
}
componentDidMount() {
document.addEventListener('click', () => { console.log('document 捕获'); }, true);
document.addEventListener('click', () => { console.log('document 冒泡'); }, false);
document.body.addEventListener('click', () => { console.log('body 捕获'); }, true);
document.body.addEventListener('click', () => { console.log('body 冒泡'); }, false);
let root = document.querySelector('#root');
root.addEventListener('click', () => { console.log('root 捕获'); }, true);
root.addEventListener('click', () => { console.log('root 冒泡'); }, false);
let outer = document.querySelector('.outer');
outer.addEventListener('click', () => { console.log('outer 捕获「原生」'); }, true);
outer.addEventListener('click', () => { console.log('outer 冒泡「原生」'); }, false);
let inner = document.querySelector('.inner');
inner.addEventListener('click', () => { console.log('inner 捕获「原生」'); }, true);
inner.addEventListener('click', () => { console.log('inner 冒泡「原生」'); }, false);
}
}
export default Demo;
输出
React18版本中:
document 捕获
body 捕获
outer 捕获「合成」
inner 捕获「合成」
root 捕获
outer 捕获「原生」
inner 捕获「原生」
inner 冒泡「原生」
outer 冒泡「原生」
inner 冒泡「合成」
outer 冒泡「合成」
root 冒泡
body 冒泡
document 冒泡
React16版本中:
document 捕获
body 捕获
root 捕获
outer 捕获「原生」
inner 捕获「原生」
inner 冒泡「原生」
outer 冒泡「原生」
root 冒泡
body 冒泡
outer 捕获「合成」
inner 捕获「合成」
inner 冒泡「合成」
outer 冒泡「合成」
document 冒泡