第一部分:函数组件和类组件
1.React 组件之间如何通讯
用props进行父子组件通信。父组件向子组件传递数据时,只需在子组件的标签内传递参数,子组件通过props属性就能接收到父组件传递过来的参数。
用回调函数进行子父组件通信。子组件向父组件通信的基本思路是,父组件向子组件传一个函数,然后通过这个函数的回调,拿到子组件传过来的值。
用Context进行跨组件通信。使用Context提供了组件之间通信的一种方式,可以共享数据,其他数据都能读取对应的数据。
2.JSX 本质是什么
JSX 本质是 React.createElement()**函数的语法糖。
React.createElement() 函数有三个参数,第一个参数是 type,也就是标签或者组件名、第二个参数是 props,是标签属性(例如 id、class、样式或者向子组件传递的数据等)、第三个参数是 children,是标签或者组件内的内容。
3.Context 是什么,如何应用?
Context 是一个访问系统资源和进行应用程序级操作的抽象接口,称为上下文。
Context 的主要功能有:
- 启动 Activity。
- 启动和停止 Service。
- 发送广播消息(Intent)。
- 注册广播消息(Intent)接收者。
- 访问 APK 中的各种资源,如 Resources 和 AssetManager 等。
- 访问 Package 的相关信息。
- APK 的各种权限管理。
在 Android 中,每个组件(Activity、Service、BroadcastReceiver)都有一个与之关联的 Context,它提供了许多应用程序操作的入口。
4.说一下 shouldComponentUpdata 的用途
shouldComponentUpdate是React生命周期方法之一,它的作用是控制组件是否需要重新渲染。当组件的props或state发生变化时,React会默认重新渲染组件,但是有时候我们希望避免不必要的渲染,以提高性能。
通过在shouldComponentUpdate中返回一个布尔值,我们可以告诉React是否需要重新渲染组件。如果返回false,React将不会重新渲染组件,否则将会重新渲染。
通常情况下,我们可以在shouldComponentUpdate中比较当前props和state与下一次更新的props和state是否相同,如果相同则返回false,否则返回true。这样可以避免不必要的渲染,提高组件的性能。
5.说一下 redux 单向数据流的机制
指的是应用中所有的数据都遵循相同的生命周期,从而使应用变得更加可预测且容易理解。
Redux数据流遵循以下4个步骤:
- 调用store.dispatch(action)。Action就是一个描述“发生了什么”的普通对象。
- Redux store调用传入的reducer函数。Store会把两个参数传入reducer:当前的state树和action。然后返回一个新的state。
- 根reducer应该把多个子reducer输出合并成一个单一的state树。根reducer的结构完全由你决定。这个新的树就是应用的下一个state。
Redux数据流是单向的,也就是说,一旦数据从action通过reducer被派发出去,就不能再更改。因此,Redux鼓励做数据范式化,这样可以避免使用多个且独立的无法相互引用的重复数据。
6.React 类组件的 setState 是同步操作还是异步操作?
通常情况下,setState()方法是异步执行的,也就是说,React不会立即更新组件state的值,而是将这个更新动作放到一个队列中,等待合适的时机再去执行。这样可以避免频繁的重新渲染,提高性能。但在某些情况下,setState()也可能是同步执行的,如在生命周期函数componentDidUpdate()和回调函数中调用setState()。此时,React会立即更新组件state的值,并且会触发重渲染
7.什么是纯函数
纯函数是指不依赖于且不改变它作用域之外的变量状态的函数
也就是说,纯函数的返回值只由它调用时的参数决定
8.介绍React 组件生命周期
React组件生命周期分为三个阶段:挂载阶段、更新阶段、卸载阶段。
-
挂载阶段:这个阶段组件被创建,执行初始化并挂载到DOM中,完成组件的第一次渲染。依次调用的生命周期方法如下:
- constructor:组件被创建时,会首先调用组建的构造方法。这个构造方法接受一个props参数,props是从父组件中传入的属性对象,若父组件没有传入这个参数则props指向的是组件的默认属性。必须在这方法中首先调用super(props)才能保证props被传入组件中。但是,不论props是否改变,父组件render方法每一次调用都会导致组件更新。
- componentWillMount:在组件render之前执行该方法,可用来修改state。
-
更新阶段:此时该组件已经进入了稳定运行阶段,这个阶段组件可以处理用户交互,或者接收事件更新界面。
-
卸载阶段:当组件卸载时,会依次执行相应的生命周期函数,如
componentWillUnmount()
等。
9.React 发起 ajax 应该在那个生命周期
在React中,发起ajax请求应该在componentDidMount生命周期事件中
10.渲染列表,为何使用key?
在React中,使用key主要是为了帮助React识别哪些元素是被添加、移除或修改了,并优化DOM操作和重新渲染的性能1。
如果不给列表项加上key,React仍然可以正常渲染列表,但可能会有一些不可预料的问题。例如,如果在列表中添加或删除项,React可能会使用错误的元素来更新DOM,导致UI显示错误。
为了确保列表渲染的正确性和性能,建议在使用map渲染列表时,始终添加一个唯一的key属性。通常可以使用数据中的唯一标识符或索引作为key值
11.函数组件和class组件的区别
- 语法不同:函数组件是一个纯函数,它需要接受props参数并且返回一个React元素。class组件需要继承React.Component,而且class组件需要创建render并且返回React元素,语法上来讲更复杂。
- 调用方式不同:函数式组件可以直接调用,返回一个新的React元素。class组件在调用时是需要创建一个实例的,然后通过调用实例里的render方法来返回一个React元素。
- 状态管理(state)不同:函数式组件没有状态管理,class组件有状态管理。
- 使用场景不同:函数式组件一般是用在大型项目中来分割大组件(函数式组件不用创建实例,所有更高效),一般情况下能用函数式组件就不用class组件,提升效率
12.什么是受控组件、什么是非受控组件?
受控组件依赖于状态,受控组件的修改会实时映射到状态值上,此时可以对输入的内容进行校验。受控组件只有继承React.Component才会有状态,受控组件必须要在表单上使用onChange事件来绑定对应的事件。例如,一个输入框就是一个受控组件,用户输入的内容会受到状态的影响,每次状态发生改变,输入框的内容也会跟着改变。
非受控组件不受状态的控制,非受控组件获取数据就是相当于操作DOM。非受控组件可以很容易和第三方组件结合,更容易同时集成React和非React代码。例如一个不与表单绑定的输入框就是一个非受控组件,用户输入的内容不会受到状态的影响,不管状态怎么改变,输入框的内容都不会发生改变
13.何时使用异步组件?
- 项目过大,希望通过异步的方式加载组件,对其进行分包处理。
- 优化性能,提高页面加载速度。例如,在路由中使用懒加载就是使用了异步组件的原理。
14.多个组件有公共逻辑,如何抽离?
- 高阶组件
高阶组件是接受一个组件并返回一个新组件的函数。可以使用高阶组件封装公共逻辑,使得每个组件都可以复用该逻辑。
- mixin
mixin可以全局混入功能,给每个组件合并抽离出来的逻辑。但要注意过于简单粗暴,应写相应的逻辑限制混入实际的作用范围。
- 公共方法
公共方法可以抽离成一个util.js文件单独保存,某个组件需要时再导入。
- Vue原型
大量使用的公共方法可以直接定义在Vue.prototype上,在入口文件进行操作。
15.Redux 如何进行异步请求?
- Redux Thunk:Redux Thunk是一种Redux middleware,用于处理异步请求。它能够让异步调用看起来像同步调用,使得代码更加简洁易懂。
- Redux Saga:Redux Saga是一种更高级的Redux middleware,用于处理更复杂的异步请求,比如多个请求之间的数据依赖关系等。
- API Gateways:API Gateways是一种将前端和后端分开的解决方案,前端通过API Gateway发出请求,后端通过API Gateway接收请求并处理。
- GraphQL:GraphQL是一种查询语言和运行时,可以让前端只发送需要的数据,减少不必要的数据传输。它也可以和Redux配合使用。
16.React-router 如何配置懒加载?
React Router可以配合Webpack进行懒加载配置
使用Lazy进行懒加载
17.什么是 PureComponent ?
PureComponent 是React中的一个纯组件,用于优化React程序,减少render函数的渲染次数,提高性能
PureComponent进行的是浅比较,也就是说如果是引用数据类型的数据,只会比较是不是同一个地址,而不会比较这个地址里面的数据是否一致。浅比较会忽略属性和状态的突变情况,也就是数据引用指针没有变化,而数据发生改变的时候render是不会执行的。如果需要重新渲染,那么就需要重新开辟空间引用数据。当组件更新时,如果组件的props或者state都没有改变,render函数就不会触发。省去虚拟DOM的生成和对比过程,达到提升性能的目的(切记props和state不能使用同一个引用)。
18.React 事件和 DOM 事件有什么区别?
- 事件处理函数不同:React事件处理函数是通过属性的方式直接绑定到组件元素上,而DOM事件处理函数是添加到元素的事件监听器中1。
- 事件触发时机不同:React事件在所有挂载的组件上都会触发,而DOM事件只在用户与网页交互时才会触发。
- 事件分发机制不同:React事件在分发前,首先会对事件进行包装,把原生DOM事件包装成合成事件,而DOM事件直接分发。
- 事件冒泡机制不同:在React中,事件不会冒泡,而是通过事件监听器统一绑定在页面的document上,当事件在具体的DOM节点上被触发后,最终都会冒泡到document上,由document上所绑定的统一事件处理程序将事件分发到具体的组件实例;而在浏览器环境中,事件会冒泡1
19.React 性能优化方式有哪些?
- 使用 React.memo() 或 PureComponent 来减少不必要的组件渲染。可以减少组件更新的次数,提高性能。
- 使用 React.lazy() 或 React.Suspense 来进行组件的懒加载。这样可以减少初次加载时的资源消耗。
- 使用 shouldComponentUpdate() 或 React.meno() 来手动控制组件的更新。通过对 props 或 state 的浅比较,避免无用的渲染。
- 使用React.Fragment或空标签(<>…</>)来替代不需要添加额外DOM节点的父元素,减少DOM数量。
- 使用key属性给列表元素添加唯一标识,这样React可以更高效地重新渲染列表。
- 使用虚拟化技术,例如 React-virtualized 或 react-window 来处理大型列表或表格,只渲染可视区域的内容,减少渲染数量。
- 对于计算密集型的操作,可以考虑使用 Web Workers 来将运算任务放在后台线程中,避免阻塞主线程。
20. React 和 Vue 的区别
- 模板语法不同:Vue使用模板语法来描述UI,这是一种类似于HTML的语法,可以直接在浏览器中运行;而React使用JSX语法来描述UI,这是一种在JavaScript中编写HTML的语法,需要经过编译才能在浏览器中运行。
- 框架本质不同:Vue本质是MVVM框架,由MVC发展而来;而React是前端组件化框架,由后端组件化发展而来。
- 数据流不同:Vue是响应式的数据双向绑定系统;而React是单向数据流,没有双向绑定。
- 监听数据变化的实现原理不同:Vue通过getter/setter以及一些函数的劫持,能精确知道数据变化;而React通过shouldComponentUpdate这个生命周期方法可以进行控制。
- 渲染过程不同:Vue可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树;而React在应用的状态被改变时,全部子组件都会重新渲染。
第二部分:react hooks
1.列举是个常用的 React 内置 Hooks
- useState:让你在函数组件中添加状态(state)。
- useEffect:让你在函数组件中执行副作用操作。
- useContext:让你在函数组件中访问 React 的 Context API。
- useReducer:让你在函数组件中使用 Redux 的 reducer。
- useCallback:返回一个 memoized 回调函数。
- useMemo:返回一个 memoized 值。
- useRef:返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。
- useImperativeHandle:允许你在类组件中设置 ref 的值。
- useLayoutEffect:其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。
- useDebugValue:这个 Hook 用于调试 React 组件,可以让你在开发者工具中看到更详细的组件堆栈信息。
2.为什么会有React Hooks,它解决了那些问题?
React Hooks是React 16.8版本中引入的新特性
- 状态管理:在函数组件中,我们无法使用类组件中的
this.state
方式来进行状态管理。而Hooks通过将state和生命周期函数封装到函数组件中,让函数组件也能拥有类组件的状态管理功能。 - 副作用:在函数组件中,我们无法使用副作用(side effects)来控制数据的输出,而Hooks通过
useEffect
的方式让我们在函数组件中添加副作用。 - 条件渲染和列表渲染:在函数组件中,我们无法使用条件渲染和列表渲染,而Hooks通过
useState
和useContext
的方式让我们在函数组件中实现条件渲染和列表渲染。 - 依赖注入:在函数组件中,我们无法使用依赖注入来注入一些参数,而Hooks通过
useCallback
和useMemo
的方式让我们在函数组件中实现依赖注入。
3.React Hooks 如何模拟组件的生命周期?
- useState:可以模拟
state
和componentDidMount
、componentDidUpdate
生命周期。在函数组件中,使用useState
可以创建一个新的 state,并且每次该 state 更新时,都会触发渲染。 - useEffect:可以模拟
componentDidMount
、componentDidUpdate
和componentWillUnmount
生命周期。在函数组件中,使用useEffect
可以创建一个副作用函数,在组件挂载后执行,当组件卸载时也会执行。 - useContext:可以模拟
componentDidMount
和componentDidUpdate
生命周期。在函数组件中,使用useContext
可以创建一个新的 context,并且每次该 context 更新时,都会触发渲染。
总之,React Hooks 提供了更灵活的方式来模拟组件的生命周期。
4.如何自定义Hooks?
自定义Hook是使用了内置Hook的普通函数,是对函数组件可复用的部分逻辑的做了一层封装,然后再被函数组件使用1。
自定义Hook需要按照Hook的规则实现,函数名必须以use开头,调用自定义Hook函数时,应该放到顶层,例如:很多组件都需要在第一次加载完成后,获取所有学生数据,就可以使用自定义Hook实现1
5.说一下React Hooks 性能优化
- 减少不必要的渲染次数。React Hooks 使得组件的渲染次数增多,因此,应该尽可能减少不必要的渲染次数。例如,使用 useCallback 来减少重复渲染的次数。
- 减少重复计算。React Hooks 中,每次组件重新渲染时,都会重新计算所有副作用函数,因此,应该尽可能减少重复计算。例如,使用 useMemo 来缓存计算结果,减少重复计算。
- 合理拆分组件。React Hooks 可以将组件拆分成更小的函数,这样可以使得组件更加简洁,同时也可以减少不必要的渲染次数和重复计算。
- 使用 React.memo。对于一些不必要的渲染,可以使用 React.memo 进行优化,从而减少不必要的渲染次数。
- 优化 useState 和 useEffect。useState 和 useEffect 是 React Hooks 中常用的两个函数,因此,应该尽可能优化它们的性能。例如,在 useEffect 中使用 useRef 来避免不必要的渲染次数
6.使用React Hooks 遇到那些坑?
- 不要在循环、条件语句或嵌套函数中使用Hook。React Hooks应该在组件的顶部使用,不能在循环、条件语句或嵌套函数中使用。
- 自定义Hook的命名。自定义Hook的名称必须以use开头,否则会导致ESLint的错误提示。
- 不要直接在事件处理程序中使用useState。由于事件处理程序通常在主线程中执行,而useState是在React渲染过程中执行的,因此,在事件处理程序中直接使用useState可能会导致不正确的结果。
- 注意Hook的顺序。如果在一个组件中多次使用同一个Hook,那么每次调用时应该保证使用的顺序一致,否则会导致状态错乱等问题。
- 小心useEffect的依赖数组。useEffect的依赖数组中的值,会在每次渲染时进行检查。如果这个数组中的值发生了改变,那么useEffect将会重新运行。因此,应该仔细考虑这个数组中的值,以避免不必要的渲染。
- 不要直接修改状态。React Hooks中的状态应该始终被视为不可变的,不应该直接修改状态。应该使用函数式的方式更新状态,例如,使用useState的setter函数来更新状态。
7.Hooks 相比 HOC 和 Render Prop 有那些优点?
- 易用性:Hooks是React 16.8以后新增的API,目的就是增加代码的可复用性和逻辑性,弥补函数组件没有状态以及生命周期的问题。由于是函数组件,不需要实例化,性能上得到了大大的提升,hooks的设计初衷就是将组件单元化、独立化,形成自己的独立渲染环境1。
- 嵌套性:HOC和Render Prop都容易嵌套过多,过度包装,而Hooks很好的避免了这个问题1。
- 可访问性:虽然Render Prop解决了数据共享代码复用的问题,但在return之外无法访问数据;而Hooks则可以在return之外访问到数据1。
第三部分:webpack 面试真题
1.前端为何要进行打包和构建?
- 体积更小,加载更快:通过打包和构建,可以将多个文件和模块合并为一个或几个文件,从而减少文件数量和体积,提高加载速度。
- 编译高级语言和语法:前端使用的高级语言(如TypeScript、ES6+)和语法(如模块化、SCSS)需要编译为浏览器可识别的语言和语法。
- 兼容性和错误检查:通过打包和构建,可以进行代码的错误检查、代码风格规范、安全漏洞检查等,提高代码的质量和兼容性。
- 统一、高效的开发环境:通过打包和构建,可以统一代码规范、构建流程和产出标准,提高开发效率和代码质量。
- 集成公司构建规范:前端打包和构建可以集成公司内部的构建规范,如提测、上线等流程。
2.module chunk bundle 的区别
- Module:Module是Webpack中最基本的概念,代表着一个单独的文件(或一组相关的文件),可以是JavaScript、CSS、图片、JSON等任何类型的文件。
- Chunk:Chunk是Webpack打包过程中的中间产物,代表着一组被合并在一起的Modules。通常情况下,一个应用程序会生成一个或多个Chunk。
- Bundle:Bundle是Webpack最终输出的文件,是由一组已经经过加载和编译的Chunk组成的。一个应用程序通常会生成一个Bundle,其中包含了所有的JavaScript、CSS、图片等资源,可以被直接加载到浏览器中运行。
3.loader 和 plugin 的区别
- 作用:loader主要是用来扩展webpack的功能,专注于转化文件(transform)这一领域,完成压缩,打包,语言编译等,而plugin不仅只局限在打包,资源的加载上,还可以打包优化和压缩,重新定义环境变量等。
- 运行时期:loader运行在打包文件之前,而plugins在整个编译周期都起作用。
- 设计目标:一个loader的职责是单一的,只需要完成一种转换,一个loader其实就是一个Node.js模块。 当需要调用多个loader去转换一个文件时,每个loader会链式打的顺序执行。在webpack运行的生命周期中会广播出许多事件,plugin会监听这些事件,在核实的时机通过webpack提供的API改变输出结果。
4.常见的 loader 和 plugin 有那些?
常见的loader有:
- file-loader:把文件输出到一个文件夹中,在代码中通过相对URL去引用输出的文件。
- url-loader:和file-loader类似,但是能在文件很小的情况下以base64的方式把文件内容注入到代码中去。
- source-map-loader:加载额外的Source Map文件,以方便断点调试。
- image-loader:加载并且压缩图片文件。
- babel-loader:把ES6+的代码转换成ES5的代码。
- css-loader:解析CSS文件,并处理CSS中的依赖关系。
- style-loader:把CSS代码注入到HTML文档中。
- eslint-loader:通过ESLint检查JavaScript代码。
常见的plugin有:
- HtmlWebpackPlugin:生成HTML文件,并自动将打包后的JavaScript和CSS文件引入到HTML文件中。
- CleanWebpackPlugin:清除输出目录。
- ExtractTextWebpackPlugin:将CSS代码提取到单独的CSS文件中。
- DefinePlugin:定义全局变量
5.Babel 和 webpack 的区别
- 转换机制不同:Babel是一个JavaScript编译器,主要负责将新版本的JavaScript代码转换为向后兼容的版本,以便在不支持最新语法和特性的浏览器中运行。Webpack则是一个模块打包工具,将项目中的各个模块打包成最终的静态资源文件12。
- 主要功能不同:Babel主要做的是语法转换,确保最新的JavaScript特性能够以最快的速度传递到开发者手中,但不负责模块的组合。Webpack更多的是将输入的各个模块用自己内部的一套逻辑将代码“链接”起来,起胶水的作用,目标是输出可以直接在浏览器中执行的代码1。
在实际项目中,Webpack和Babel通常一起使用,通过Webpack的配置来集成和配置Babel,以便对JavaScript代码进行转换和优化2。
6.Webpack 如何产出一个lib 库?
- 安装Webpack和相关插件:首先,确保已经安装了Node.js和npm。然后,在项目根目录下运行以下命令安装Webpack和相关插件:
npm install webpack webpack-cli --save-dev
- 创建Webpack配置文件:在项目根目录下创建一个名为
webpack.config.js
的文件,并添加以下内容:const path = require('path'); module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, 'lib'), filename: 'my-library.js', library: 'MyLibrary', libraryTarget: 'umd', globalObject: 'this' }, mode: 'production' };
这个配置文件告诉Webpack读取
src/index.js
作为入口文件,并将输出文件命名为lib/my-library.js
。library
属性指定了库的名称,libraryTarget
属性设置为umd
表示生成一个Universal Module Definition (UMD) 兼容的库,可以在CommonJS和AMD模块中使用。globalObject
属性设置为this
表示在浏览器环境中使用全局变量来访问库。 - 编写库代码:在
src
目录下创建一个名为index.js
的文件,并编写库的代码。例如:const myFunction = () => { console.log('Hello from my library!'); }; export default myFunction;
- 构建库:在终端中运行以下命令,Webpack将根据配置文件中的设置构建库:
npx webpack --config webpack.config.js
Webpack将编译
src/index.js
并将其打包为UMD兼容的库文件lib/my-library.js
。现在你已经成功地使用Webpack生成了一个库文件。你可以在其他项目中使用这个库文件,并在浏览器中通过全局变量
MyLibrary
来访问它。
7.说一下Babel-polyfill 和 babel-runtime 的区别
- 功能不同。Babel-polyfill的作用是在运行环境中模拟一个ES6+的环境,支持代码中使用新增的API特性。也就是说,如果你的代码中使用了ES6+的特性,而当前运行环境并不支持,那么使用Babel-polyfill就可以让你的代码在旧环境中也能正常运行。而Babel-runtime则是将ES6代码编译成ES5代码去执行。也就是说,不管浏览器是否支持ES6,只要是ES6的语法,它都会进行转码成ES5。因此,使用Babel-runtime可以减少因浏览器不兼容而导致的代码错误1。
- 实现方式不同。Babel-polyfill是通过向全局对象和内置对象的prototype上添加方法来实现的,会造成全局空间污染。而Babel-runtime则不会污染全局对象和内置对象的原型,只需引入所需的方法即可,更加灵活1。
8.Webpack 如何实现懒加载?
Webpack 可以通过使用动态导入(dynamic imports)的方式来实现懒加载。动态导入语法是在 import 语句后面添加一个参数,这个参数是一个字符串或者一个表达式,用来指定需要动态加载的模块。Webpack 会自动将这个参数当作一个独立的 chunk 来处理,从而实现懒加载。
9.为何Proxy 不能被 Polyfill?
Proxy 不能被 Polyfill 的原因是其实现需要依赖底层语言特性和运行环境的支持,因此不易被模拟。
Polyfill 是一种技术,在不支持某个特性的浏览器中,通过 JavaScript
代码来模拟该特性,从而达到在这些浏览器中同样可以使用该特性的目的。然而,Proxy
在实现上需要依赖底层语言特性和运行环境的支持,因此并不是所有的浏览器都支持
Proxy。在某些浏览器,例如IE
浏览器中,由于其JavaScript 引擎的限制以及较老的 ECMAScript 标准支持程度,无法直接支持 Proxy,因此需要使用 polyfill 来实现兼容1
10.Webpack 如何优化构建速度?
- 使用更快的硬件:例如使用 SSD 硬盘而不是 HDD 硬盘,使用更快的 CPU。这个方法可能比较直观,但也可能需要一些额外的投资。
- 升级 Node.js 和 Webpack 到最新版本:新版本的 Node.js 和 Webpack 通常会有性能提升和新的特性。
- 使用 HappyPack 或者 thread-loader:这两个插件可以让 Webpack 使用多个子进程进行构建,这样可以充分利用多核 CPU 的性能。
- 使用缓存:Webpack 有很多缓存的选项。例如,使用
cache-loader
、hard-source-webpack-plugin
或者thread-loader
可以缓存模块和提高模块的复用率。 - 减少构建文件的数量:减少
require
和import
的数量,可以减少 Webpack 解析和编译的模块数量,从而提高构建速度。如果可能的话,你可以尽量使用大的、集成的库,而不是许多小库。 - 优化 loaders:loaders 是 Webpack 中最容易影响构建速度的部分。尽可能减少 loader 的数量,因为每个 loader 都会增加构建时间。此外,尽可能在 loader 中做更多的工作,以减少 loader 的数量和复杂性。
- 使用 DLL:DLL 可以将一些稳定的依赖库预编译成静态资源,这样在构建主程序时就可以避免重复编译这些库。
- 优化 resolve.modules:将模块解析的范围限定在项目目录中或特定的模块目录中,避免全局范围的模块查找,从而提高模块解析速度。
- 分离 entry points:如果一个大型的单入口文件(entry point)需要大量时间来编译,那么你可以尝试将它分离成多个小的入口文件。每个小的入口文件对应一个特定的功能或者页面,这样就可以并行地编译这些入口文件,而不是串行地编译一个大的入口文件。
- Externals:将那些不常改动且体积较大的第三方库,通过在 webpack 配置文件中配置 externals,让 webpack 不去处理这些文件,而是直接通过 script 标签引入。
11.Webpack 如何优化产出代码?
- 压缩代码:使用
TerserWebpackPlugin
插件可以压缩 JavaScript 代码,从而减小输出文件的大小。在webpack.config.js
文件中添加以下配置即可启用:const TerserPlugin = require('terser-webpack-plugin'); module.exports = { optimization: { minimizer: [new TerserPlugin()], }, };
- 树摇(Tree Shaking):Tree Shaking 是一个通过去除未使用的代码来减少最终包大小的优化技术。在 Webpack 中,使用 ES6 模块语法(即
import
和export
)可以让 Webpack 自动去除未使用的代码。确保在源代码中使用import
和export
,而不是require
和module.exports
。 - 代码分割(Code Splitting):通过将代码分割成多个小块,可以并行下载和解析这些小块,从而加快页面加载速度。使用
SplitChunksPlugin
插件可以进行代码分割。以下是一个示例配置:const { SplitChunksPlugin } = require('webpack').optimize; module.exports = { optimization: { splitChunks: { chunks: 'all', }, }, plugins: [ new SplitChunksPlugin(), ], };
- 提取公共模块:使用
optimization.runtimeChunk
或optimization.splitChunks
可以将公共模块提取出来,多个入口点可以共享这个公共模块,减少总体代码大小。 - 排除不必要的模块:在配置文件中的
resolve.modules
里,可以排除不需要的文件夹,减少不必要的解析。 - 使用 DLL:通过 DllPlugin 和 DllReferencePlugin 可以将稳定的依赖库预编译成静态资源,减少主配置的编译时间。在主配置中只需要引入 DLL 即可。
- 优化图片和其他资源:对于图片和其他资源,可以使用 Webpack 的
file-loader
或url-loader
进行优化。例如,使用url-loader
可以将小图片转化为 Base64 编码,以减少 HTTP 请求的数量。 - 使用 Scope Hoisting:Scope Hoisting 通过减少函数声明和内存占用来提升 JavaScript 的执行速度。在生产模式下启用 Scope Hoisting 可以提高代码运行速度。以下是一个示例配置:
module.exports = { optimization: { concatenateModules: true, }, };
- 使用 Source Map:虽然 Source Map 对开发者有帮助,但它们会增加产出的文件大小。在生产环境中,建议使用
'cheap-module-source-map'
替代'source-map'
来降低 Source Map 的体积和创建时间。 - 优化 loaders:尽可能减少 loader 的数量,因为每个 loader 都会增加构建时间。在 loader 中做更多的工作,以减少 loader 的数量和复杂性。
- Externals:将那些不常改动且体积较大的第三方库,通过在 webpack 配置文件中配置 externals,让 webpack 不去处理这些文件,而是直接通过 script 标签引入。这样可以有效减小产出文件的大小。
- 利用缓存提高构建速度:Webpack 有很多缓存的选项,例如使用
cache-loader
、hard-source-webpack-plugin
或者thread-loader
可以缓存模块和提高模块的复用率。同时,利用 Node.js 的文件缓存也可以提高构建速度。 - 多线程/多进程构建:使用 HappyPack 或 thread-loader 可以使 Webpack 使用多个子进程进行构建,这样可以充分利用多核 CPU 的性能。
- 优化 resolve.modules:将模块解析的范围限定在项目目录中或特定的模块目录中,避免全局范围的模块查找,从而提高模块解析速度。
- 减少构建文件的数量:减少 import 和 require 的数量,可以减少 webpack 解析和编译的模块数量,从而提高构建速度。如果可能的话,你可以尽量使用大的、集成的库,而不是许多小库。
说一下什么是 redux、 react-redux、 redux-thunk、 redux-saga、redux-toolkit,它们之间的 区别是什么?
Redux、React-Redux、Redux-Thunk、Redux-Saga和Redux-Toolkit是Redux生态系统中的不同组件或中间件,用于帮助管理应用程序的状态。
- Redux:是一个JavaScript的状态容器,提供了可预测的状态管理。Redux可以与React和其他界面库一起使用,其核心理念是Store和Action。
- React-Redux:是一个官方支持的库,它为React应用程序提供了Redux的封装,可以更方便地管理和使用Redux。
- Redux-Thunk:是Redux的中间件,用于将异步操作的代码拆分到action中。
- Redux-Saga:也是Redux的中间件,用于将异步操作的代码拆分到单独的文件中管理。
- Redux-Toolkit:是Redux的现代化封装,用于快速构建现代化Redux应用。它包含了许多常用的功能和最佳实践,使得创建和管理Redux状态更加简单和直观。
这些组件或中间件之间的主要区别在于它们的功能和用途。例如,Redux用于存储和管理状态,React-Redux用于在React应用程序中使用Redux,Redux-Thunk和Redux-Saga是处理异步操作的中间件,而Redux-Toolkit则是一个现代化的封装库。