阅读本文你需要了解(pre-knowledge)

  1. dvajs框架
  2. 基本了解less
  3. css模块化的概念

如有需要, 可以阅读上一篇文章《搭建react的typescript开发环境》.

目录

由于本文较长,您可以根据目录有选择性地阅读。

  1. 前言
  2. 创建项目
  3. 使用dvajs
  4. 使用less和启用css模块化
  5. 使用antd
  6. IconFont解决方案

前言

这半年来一直在使用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.tsxsrc/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安装lessless-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.jsts-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实现云同步, 土豪请随意~).