搭建dva的typescript开发环境(一)
阅读本文你需要了解(pre-knowledge)
- dvajs框架
- 基本了解less
- css模块化的概念
如有需要, 可以阅读上一篇文章《搭建react的typescript开发环境》.
目录
由于本文较长,您可以根据目录有选择性地阅读。
前言
这半年来一直在使用dvajs开发项目,也尝试过各种“姿势”,最近想要把使用dvajs的一些经验以及一些相对比较好的解决方案记录下来。因为使用typescript能让我们更好地了解框架的逻辑,所以我们还是用typescript来构建这个项目。
在我查阅资料的过程中,发现一件有趣的事情,有很多前辈都已经尝试过用typescript搭建dvajs的环境了(踩坑),可以说这帮我省下了很多事哈。
创建项目
这一部分的内容在上一篇文章中有详细的介绍,所以在这里只是简略带过。您也可以直接跳过本节,阅读下一节使用dvajs。
本来尝试使用dvajs的脚手架创建项目,然后更改配置使用typescript,但是发现dvajs的脚手架封装了太多东西,修改配置的时候非常隐晦。所以最终还是决定从零开始手动搭建dvajs的开发环境。
- 首先创建一个项目,做一些初始化工作.
mkdir dva-ts cd dva-ts npm init -y
- 在
package.json
中加入一些基础依赖如下,然后运行npm install
安装依赖。// ...other config "devDependencies": { "@types/dva": "^1.1.0", "@types/react": "^16.7.7", "@types/react-dom": "^16.0.10", "@types/redux-saga": "^0.10.5", "@types/node": "^10.12.11", "babel-core": "^6.26.3", "babel-loader": "^7.1.5", "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", "css-loader": "^1.0.1", "style-loader": "^0.23.1", "react": "^16.6.3", "react-dom": "^16.6.3", "webpack": "^4.26.0", "webpack-cli": "^3.1.2", "webpack-dev-server": "^3.1.10", "dva": "^2.4.1", "typescript": "^3.1.6", "ts-loader": "^5.3.0" }, // ...other config
- 接下来配置webpack、babel和typescript
// webpack.config.js const path = require('path') module.exports = { mode: 'development', entry: [ './src/index.tsx' ], output: { path: path.resolve(__dirname, '/dist'), publicPath: '/', filename: 'bundle.js' }, devServer: { contentBase: './dist', port: 80, }, module: { rules: [ { test: /\.tsx?$/, exclude: /node_modules/, use: [ { loader: 'babel-loader' }, { loader: 'ts-loader', } ] }, { test: /\.css$/, exclude: /^node_modules$/, use: [ { loader: 'style-loader' }, { loader: 'css-loader', }, ] }, ] }, resolve: { extensions: ['*', '.js', '.jsx', '.ts', '.tsx'] }, };
// .babelrc { "presets": [ "es2015", "react" ] }
// tsconfig.json { "compilerOptions": { "jsx": "react", "lib": [ "es6", "dom" ], "rootDir": "src", "module": "commonjs", "target": "es6", "sourceMap": true, "moduleResolution": "node", "noImplicitReturns": true, "noImplicitThis": true, "noImplicitAny": true, "strictNullChecks": true, "allowSyntheticDefaultImports": true, "experimentalDecorators": true, }, "include": [ "./src" ], "exclude": [ "node_modules" ] }
- 随便写一点代码
// src/routes/Index.tsx import * as React from 'react'; import * as ReactDOM from 'react-dom'; export default class Index extends React.Component { render() { return <div>react</div> } }
// src/index.tsx import Index from './routes/Index'; ReactDOM.render(<Index />, document.getElementById('root'));
<!-- ./dist/index.html --> <!DOCTYPE html> <html> <head> <title>React Webpack Babel Setup</title> </head> <body> <div id="root"></div> <script src="/bundle.js"></script> </body> </html>
为了方便下文中引用dvajs, 这里将组件和入口文件分成了
src/routes/Inedx.tsx
和src/index.tsx
两个. - 在
package.json
中加入start的script:webpack-dev-server --progress --colors --hot --history-api-fallback --config ./webpack.config.js
- 运行
npm start
启动webpack dev server. 这时打开 http://localhost/ 就可以看到我们的组件渲染成功了。如果对以上的配置有什么疑问,可以阅读上一篇文章。
使用dvajs
接下来进入今天的正题了,我们要用上dvajs这个框架。
- 首先安装依赖
npm install --save-dev dva
. - 修改
src/index.tsx
,并添加一个router.tsx
文件来定义路由。// src/index.tsx import dva from 'dva'; import createHistory from 'history/createBrowserHistory'; const app = dva({ history: createHistory(), onError: (e) => { console.error(e.message); } }); app.router(require('./router').default); app.start("#root");
// src/router.tsx import * as React from 'react'; import { Router, Route, Switch, Redirect } from 'dva/router'; import * as H from 'history'; import Index from './routes/Index'; function RouterConfig({ history }: { history: H.History }): JSX.Element { return ( <Router history={history}> <Switch> <Route path="/" component={Index} /> </Switch> </Router> ); } export default RouterConfig;
- 到这里为止,我们就能用dvajs把项目运行起来啦。小伙伴们可以定义不同的路由、组件来实现一些简单的功能了。
使用less和启用css模块化
使用less
下面为我们的项目增加一点样式。我们选择使用less作为css预编译处理器。
- 在ts项目中使用less其实非常容易, 只要为less文件配置一个loader就可以了。我们先通过
npm i --save-dev less less-loader
安装less
和less-loader
,然后在webpack.config.js
文件的module.rules
数组中加入以下一项配置:// ... other config { test: /\.less$/, exclude: /^node_modules$/, use: [ { loader: 'style-loader' }, { loader: 'css-loader', }, { loader: 'less-loader', } ] }, // ... other config
- 新建文件
routes/Index.less
.root { font-size: 20px; }
- 然后修改文件
routes/Index.tsx
- 加入一行
import './Index.less';
- 修改render函数的返回值
return <div className="root">react</div>
- 加入一行
- 重启我们的dev server, 就可以看到我们的less文件已经成功应用到项目上拉.
css模块化
对于高度组件化的react项目来说,css模块化是一个重要的需求,可以有效地避免样式污染。接下来我们在项目中加入css模块化的配置。
- 我们改写
routes/Index.tsx
文件的样式import方式为import * as styles from './Index.less';
, 然后修改jsx中className的引用为<div className={styles.root}>react</div>
。不过这个时候会看到ts报了一个找不到模块./Index.less
的错误。这是因为在ts中,import一个东西(变量、类等等)都需要有该内容的声明文件,否则就过不了编译。 - 这时候我们就需要一个工具typed-css-modules来生成less文件的定义文件(.d.ts)。使用
npm install --global typed-css-modules
来全局安装这个package(因为我们是当作cli来使用的,所以全局安装;也可以不使用全局安装,在package.json中定义script来执行这个脚本)。 - 安装完成之后, 执行
tcm -p src/**/*.less
,这时候我们可以看到在src/routes
目录下生成了一个Index.less.d.ts
的文件,里面包含了一个名为root的字符串定义。css模块化本质上就是通过变量引用(上述从
./Index.less
文件导入的styles变量),在编译时生成hash值覆盖原有的类名(生成一个带hash的字符串代替原本为root的className)来实现的。
我们可以在package.json中增加上述命令的脚本"less": "tcm -p src/**/*.less"
,并且将typed-css-modules
这个依赖加入到项目的devDependencies中去(npm install --save-dev typed-css-modules
),这样下次使用的时候执行npm run less
就可以了。 - 接下来我们要配置css模块化的规则(css模块化是在css-loader中实现的),修改
webpack.config.js
中的css-loader
的配置信息。其中的localIdentName
的规则可以参考css-loader配置说明// webpack.config.js // ... { loader: 'css-loader', options: { modules: true, importLoaders: 1, localIdentName: "[name]__[local]___[hash:base64:5]", sourceMap: true, minimize: true } }, // ...
- 配置完成后,可以发现class已经是一个编译后的名字了。
注意: 每次修改webpack或者babel的配置后, 都需要重新启动web server才能使配置生效.
使用antd
- 接下来为我们的项目引入antd组件库。
npm install --save-dev antd
. - 然后在代码中使用antd
import * as React from 'react'; import { Button } from 'antd'; import * as styles from './Index.less'; export default class Index extends React.Component { render() { return ( <div className={styles.root}> <h1>react</h1> <Button type="primary">示例按钮</Button> </div> ) } }
- 参照antd官方文档, 我们引入
ts-import-plugin
这个插件(npm install --save-dev ts-import-plugin
), 并修改webpack.config.js
中ts-loader
的配置.const tsImportPluginFactory = require('ts-import-plugin') // ... { loader: 'ts-loader', options: { transpileOnly: true, getCustomTransformers: () => ({ before: [tsImportPluginFactory({ libraryDirectory: 'es', libraryName: 'antd', style: 'css', })] }), compilerOptions: { module: 'es2015' } } } // ...
需要注意, 这里要把css文件的css模块化关掉(保留less文件的css模块化), 否则antd的样式就会失效.
antd的某些组件需要启用less-loader的javascript选项.javascriptEnabled: true
IconFont解决方案
我们知道Ant Design中有一个Icon组件用起来非常方便, 他具备有很多png切图不具备的优势:
- 矢量, 不会因为变大而失真(svg也可以).
- 引用方便, 只要一个type属性就可以, 不需要引入文件(图片)到项目中.
- 可以自由设置颜色(跟文字一样).
但是我们项目中的图标往往并不会全部出现在AntD的Icon组件库中(什么?你说可以去别人网站上找一个适用的图标库.这个想法很不错.), 这时候我们就需要自己实现一个IconFont组件了.
在这里推荐一个免费的字体合成网站IcoMoon. 这个网站支持自主创建项目, 导入svg图标, 然后生成字体文件, 打包下载后还有使用示例, 可以说非常好用了.
不过需要注意, IcoMoon的项目是存储在本地的, 所以一旦清除浏览器数据, 可能会导致你的项目丢失, 需要谨慎操作(当然也可以开通vip实现云同步, 土豪请随意~).