跳到主要内容前端开发指南:如何优雅管理 Import 语句避免满屏导入 | 极客日志JavaScript大前端
前端开发指南:如何优雅管理 Import 语句避免满屏导入
综述由AI生成探讨了前端开发中 import 语句过多导致代码混乱的问题,并提供了多种优化方案。主要方法包括使用模块重导简化路径、利用 require.context 动态管理路由、采用动态 import 实现按需加载、谨慎使用 ProvidePlugin 注入全局变量、以及利用 TypeScript 命名空间消除类型导入。此外,还介绍了 Webpack 别名、Prettier 配置、全局组件加载及 babel-plugin-import 等辅助手段。文章强调在提升开发效率的同时,需注意包体积控制和代码可维护性,帮助开发者构建更整洁的代码结构。
WenxuanMa25 浏览 序言
如果打开一个文件,看到的全是满屏幕的 import 语句,这将是一种怎样的体验?
密密麻麻的 import 语句不仅仅是一种视觉上的冲击,更是对代码组织结构的一种考验。我们是如何做到让 import'占领满屏'的了,又该如何优雅地管理这些 import 语句呢?本文将从产生大量 import 语句的原因、可能带来的问题以及如何优化和管理 import 语句几个角度来进行探讨。
import 是如何'占领满屏'的?
1. 拒绝使用模块重导(Re-export)
模块重导是一种通用的技术。在腾讯、字节、阿里等各大厂的组件库中都有大量使用。
如:字节的 arco-design 组件库中的组件。通过重导在 components/index.tsx 文件暴露所有组件,在使用时一个 import 就可以使用 N 个组件了。
import Modal from '@arco-design/web-react/es/Modal'
import Checkbox from '@arco-design/web-react/es/Checkbox'
import Message from '@arco-design/web-react/es/Message'
...
import { Modal, Checkbox, Message} from '@arco-design/web-react'
Re-export 一般用于收拢同类型的模块,一般都是以文件夹为单位,如 components、routes、utils、hooks、stories 等都通过各自的 index.tsx 暴露,这样就能极大程度的简化导入路径、提升代码可读性、可维护性。
Re-export 的几种形式
1. 直接重导出
直接从另一个模块重导出特定的成员。
export { foo, bar } from './moduleA';
2. 重命名并重导出(含默认导出)
从另一个模块导入成员,可能会重命名它们,然后再导出。
默认导出也可以重命名并重导出。
export { foo as newFoo, bar newBar } ;
{ } ;
as
from
'./moduleA'
export
default
as
ModuleDDefault
from
'./moduleD'
3. 重导出整个模块(不含默认导出)
将另一个模块的所有导出成员作为单个对象重导出。(注意:整个导出不会包含 export default)
export * from './moduleA';
4. 收拢、结合导入与重导出
首先导入模块中的成员,然后使用它们,最后将其重导出。
import { foo, bar } from './moduleA';
export { foo, bar };
通过这些形式,我们可以灵活地组织和管理代码模块。每种形式都有其适用场景,选择合适的方式可以帮助我们构建出更清晰、更高效的代码结构。
2. 从不使用 require.context
require.context 是一个非常有用的功能,它允许我们动态地导入一组模块,而不需要显式地一个接一个地导入。
只需一段代码让你只管增加文件、组件,将自动收拢重导。
在项目路由、状态管理等固定场景下极其好使(能提效、尽可能避免了增加一个配置要动 N 个文件的情况)
尤其是在配置路由时、产生大批量的 import(多少个页面就得导入多少个 import😅)
import A from '@/pages/A'
import B from '@/pages/B'
...
const routesContext = require.context('./routes', false, /.ts$/);
const routes = [];
routesContext.keys().forEach(modulePath => {
const route = routesContext(modulePath);
routes.push(route.default || route)
});
export default routes;
在大项目、多路由的情况下,使用 require.context 在处理路由导入上大有可为。
3. 使用 import 动态导入
动态 import 也能实现类似 require.context 的功能、动态收拢模块。
const Component = await import('./Component');
4. 对 ProvidePlugin 不感兴趣
webpack.ProvidePlugin 是个好东西,但也不能滥用。
项目中用到的变量/函数/库或工具,只要配置后就可以在任何地方使用了。
相信我–看完这个示例,如果你没用过、那你肯定会迫不及待的想要尝试了🤗
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.ProvidePlugin({
React: 'react',
_: 'lodash',
dayjs: 'dayjs',
Utils: path.resolve(__dirname, 'src/utils.js')
})
]
};
现在你可以在任何地方使用 dayjs、lodash、Utils 等,而不需要导入它
- webpack.ProvidePlugin 是一个强大的工具,它可以帮助我们减少重复的导入语句,使代码更加干净整洁。但是,它不会减少构建大小,因为这些库仍然会被包含在你的最终打包文件中。正确使用这个插件可以提高开发效率,但需要谨慎使用,以避免隐藏依赖关系,导致代码难以理解和维护。
- 对于需要按需加载的模块或组件,考虑使用动态 import() 语法,这样可以更有效地控制代码的加载时机和减小打包体积。
- 谨慎使用 ProvidePlugin,只为那些确实需要在多个地方使用的模块配置全局变量,以避免不必要的代码打包。
另外,如果是 Vite 项目可以使用 vite-plugin-inject 代替 ProvidePlugin 的功能。
import inject from 'vite-plugin-inject';
...
plugins: [
inject({
dayjs: 'dayjs',
}),
]
...
import dayjs from 'dayjs';
declare global {
const dayjs: typeof dayjs;
}
{
"compilerOptions": {
},
"include": [ "src/**/*", "globals.d.ts" ]
}
5. 大量使用 Typescript 导入类型
在 TS 项目中,满屏 import 肯定少不了 TS 的份。但如果合理配置,必定能急剧减少 import 的导入。
这里介绍下自己在项目中使用最多的方法:TS 命名空间。有了它既能让类型模块化,更过分的是在使用时可以直接不导入类型😅。
同样,它和 ProvidePlugin 一样炸裂,可以直接灭掉 import 导入。
declare namespace IAccount {
type IList<T = IItem> = {
count: number
list: T[]
}
interface IUser {
id: number;
name: string;
avatar: string;
}
}
const [list, setList] = useState<IAccount.IList|undefined>();
const [user, setUser] = useState<IAccount.IUser|undefined>();
注意⚠️eslint 可能需要配置下开启🔛使用命名空间。
6. 不去充分利用 babel 特性
React 似乎也意识到不妥:在 17 版本之前,由于 jsx 的特性每个组件都需要明文引入 import React from 'react',但在这之后由编译器自行转换,无需引入 React。如果你使用的React17之前的版本也可以通过修改 babel 达到这个目的,有非常详细的说明。(也提供了自动去除引入的脚本)
7. 其它优化手段
1. 设置 webpack、ts 别名
resolve: {
alias: {
"@src": path.resolve(__dirname, 'src/'),
"@components": path.resolve(__dirname, 'src/components/'),
"@utils": path.resolve(__dirname, 'src/utils/')
}
}
import MyComponent from '../../../../components/MyComponent';
import MyComponent from '@components/MyComponent';
2. 设置格式化 prettier.printWidth
值设置的太小可能会导致频繁换行、给够难以阅读。其值在 120 较为合适吧(看团队实际的使用情况)。
{
"printWidth": 120,
...
}
3. 按条件动态全局加载组件
在入口文件引入全局组件,使用 require.ensure 或 import 根据条件动态加载组件,既能便于维护、减少引用、也能减少性能开销。
Vue.component('IMessage', function (resolve) {
if (/^\/pagea|pageb/.test(location.pathname)) {
require.ensure(['./components/message/index.vue'], function() {
resolve(require('./components/message/index.vue'));
});
}
});
4. babel-plugin-import 的使用
babel-plugin-import 不是直接减少 import 的数量,而是通过优化 import 语句来减少打包体积,提高项目的加载性能。这对于使用了大型第三方库的项目来说是一个非常有价值的优化手段。
{
"plugins": [
["import", {
"libraryName": "@arco-design/web-react",
"libraryDirectory": "es",
"style": true
}, "@arco-design/web-react"]
]
}
import { Button } from '@arco-design/web-react';
import Button from '@arco-design/web-react/es/button';
import '@arco-design/web-react/es/button/style/css.js';
总结与最佳实践建议
导致 import 占满全屏的原因有很多。但不用 模块重导、require.context、import 动态导入、webpack.ProvidePlugin 等手段,一定会让我们写出满屏的 import😂🤣😅😇。
只有想不到的,没有做不到的。只要你想、相信就一定能如愿以偿。
- 明确依赖边界:尽量保持模块间的低耦合,避免过度使用全局变量(如 ProvidePlugin),这会增加调试难度。
- 按需加载优先:对于大型 UI 库或重型逻辑,优先使用
babel-plugin-import 或动态 import,以减少首屏资源体积。
- 类型安全与便利性的平衡:TS 命名空间虽然方便,但在超大型项目中可能导致类型污染,需配合 ESLint 规则限制使用范围。
- 路径语义化:利用 Webpack 别名和 Prettier 配置,统一团队的代码风格,减少因路径过长导致的视觉疲劳。
通过上述组合拳,可以有效解决 import 语句泛滥的问题,提升代码的可读性和项目的可维护性。
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online