• 从零开始配置TypeScript + React + React-Router + Redux + Webpack开发环境


    转载请注明出处!

    说在前面的话:

    1、为什么不使用现成的脚手架?脚手架配置的东西太多太重了,一股脑全塞给你,我只想先用一些我能懂的库和插件,然后慢慢的添加其他的。而且自己从零开始配置也能学到更多的东西不是么。

    2、教程只配置了开发环境,并没有配置生产环境。

    3、教程针对人群是有过React + Redux经验,并且想在新项目中使用TypeScript的人(或者是想自己从零开始配置开发环境的)

    4、因为前端发展日新月异,今天能用的配置到明天可能就不能用了(比如React-Router就有V4了,而且官方说是完全重写的),所以本文中安装的包都是指定版本的。

    5、教程遵循最小可用原则,所以能不用的库和插件就没用(主要是会的就没多少,怕用出问题,逃~~)。

    6、基于5,所以教程不会一开始就把所有东西全装上,会一步一步慢慢来。

    6、教程在macOS下完成,win环境系可能会有一些其他的问题。

    初始环境

    node版本为6.9.0

    初始化项目

     创建并进入项目

    mkdir demo && cd demo

    初始化项目

    npm init

    安装初始依赖

    首先是安装webpack和webpack-dev-server(全局安装过的请忽略)

    npm i -D webpack@3.6.0

    然后安装React和Types中React的声明文件

    npm i --S react@15.5.4 react-dom@15.5.4 @types/react@15.6.0 @types/react-dom@15.5.0

    上面@types开头的包都是typeScript的声明文件,你可以进入node_modules/@types/XX/index.d.ts进行查看。

    关于声明文件的具体介绍可以在github上的DefinitelyTyped库看到。

    接下来安装TypeScript,ts-loader和source-map-loader

    npm i -D typescript@2.5.3 ts-loader@2.3.7 source-map-loader@0.2.2

    ts-loader可以让Webpack使用TypeScript的标准配置文件tsconfig.json编译TypeScript代码。

    source-map-loader使用任意来自Typescript的sourcemap输出,以此通知webpack何时生成自己的sourcemaps。 这让你在调试最终生成的文件时就好像在调试TypeScript源码一样。

    添加TypeScript配置文件 

    我们需要一个tsconfig.json文件来告诉ts-loader如何编译代码TypeScript代码。

    在当前根目录下创建tsconfig.json文件,并添加如下内容:

    {
      "compilerOptions": {
        "outDir": "./dist/",
        "sourceMap": true,
        "noImplicitAny": true,
        "module": "commonjs",
        "target": "es5",
        "jsx": "react"
      },
      "include": [
        "./src/**/*"
      ]
    }

    outDir:输出目录。

    sourceMap:把 ts 文件编译成 js 文件的时候,同时生成对应的sourceMap文件。

    noImplicitAny:如果为true的话,TypeScript 编译器无法推断出类型时,它仍然会生成 JavaScript 文件,但是它也会报告一个错误。为了找到错误还是设置为true比较好。

    module:代码规范,也可以选amd。

    target:转换成es5

    jsx:TypeScript具有三种JSX模式:preservereactreact-native。 这些模式只在代码生成阶段起作用 - 类型检查并不受影响。 在preserve模式下生成代码中会保留JSX以供后续的转换操作使用(比如:Babel)。 另外,输出文件会带有.jsx扩展名。 react模式会生成React.createElement,在使用前不需要再进行转换操作了,输出文件的扩展名为.js。 react-native相当于preserve,它也保留了所有的JSX,但是输出文件的扩展名是.js。我们这里因为不会用babel再转,所以用react就行。

    include:需要编译的目录。

    写些代码

    首先创建目录

    mkdir src && cd src
    mkdir components && cd components

    在此文件夹下添加一个Hello.tsx文件,代码如下:

    import * as React from 'react';
    
    export interface Props {
      name: string;
      enthusiasmLevel?: number;
    }
    
    export default class Hello extends React.Component<Props, object> {
      render() {
        const { name, enthusiasmLevel = 1 } = this.props;
    
        if (enthusiasmLevel <= 0) {
          throw new Error('You could be a little more enthusiastic. :D');
        }
    
        return (
          <div className="hello">
            <div className="greeting">
              Hello {name + getExclamationMarks(enthusiasmLevel)}
            </div>
          </div>
        );
      }
    }
    
    function getExclamationMarks(numChars: number) {
      return Array(numChars + 1).join('!');
    }

    接下来,在src下创建index.tsx文件,代码如下:

    import * as React from "react";
    import * as ReactDOM from "react-dom";
    
    import Hello from "./components/Hello";
    
    ReactDOM.render(
      <Hello name="TypeScript" enthusiasmLevel={10} />,
      document.getElementById('root') as HTMLElement
    );

    我们还需要一个页面来显示Hello组件。 在根目录创建一个名为index.html的文件,如下:

    <!DOCTYPE html>
    <html>
    
    <head>
      <meta charset="UTF-8" />
      <title>demo</title>
    </head>
    
    <body>
      <div id="root"></div>
      <script src="./dist/bundle.js"></script>
    </body>
    
    </html>

    编写webpack配置文件

    在根目录下创建一个名为webpack.common.config.js文件,并添加一下内容:

    module.exports = {
      entry: "./src/index.tsx",
      output: {
        filename: "bundle.js",
        path: __dirname + "/dist"
      },
    
      devtool: "source-map",
    
      resolve: {
        extensions: [".ts", ".tsx", ".js", ".json"]
      },
    
      module: {
        rules: [
          { test: /.tsx?$/, loader: "ts-loader" },
    
          { enforce: "pre", test: /.js$/, loader: "source-map-loader" }
        ]
      },
    
      plugins: [
      ],
    };

    这里不做过多的解释了。基本上有webpack经验的都看得懂。至于为什么是webpack.common.config.js而不是webpack.config.js。是因为我们现在要配置的是开发环境,以后需要配置生产环境,所以我们就需要多个配置文件,并且将这两个的通用部分放入到webpack.common.config.js

    在根目录下运行一下命令:

    webpack --config webpack.common.config.js

    然后打开index.html就能看到我们写的页面了。

    编写webpack开发环境配置文件

    如果是正式做开发,上面的代码肯定是不够的,我们需要webpacl-dev-server提供的最基本也是最好用的热更新功能。

    npm i -D webpack-dev-server@2.9.1

    在根目录下创建webpack.dev.config.js,并添加以下配置:

    const webpack = require('webpack');
    const config = require('./webpack.common.config');
    config.devServer = {
      hot: true,
      publicPath: '/dist/'
    }
    config.plugins.push(new webpack.HotModuleReplacementPlugin());
    module.exports = config;

    首先需要引入公共的配置,然后在基础之上进行修改。

    devServer就是webpack-dev-server的配置项。

    hot:开启热更新,开启热更新之后,我们需要在plugins中加入webpack.HotModuleReplacementPlugin来完全启用。同时官方文档中指出,如果在命令中使用--hot来启动webpack-dev-server的话,就会自动加载这个插件,不再需要在config.js中进行引入。

    关于HMR的相关部分可以点击webpack HMR查看。

    publicPath:资源目录,因为webpack-dev-server启动之后会把编译后的资源放在内存当中,那这些资源在哪呢?就是在publicPath指定的目录里,因为我们在webpack.common.config.js中配置的output.path是当前目录的/dist目录下,为了不再去更改根目录下的index.html文件,所以我们这里也设置成/dist/。 这部分内容具体可以参照详解Webpack2的那些路径

     运行命令:

    webpack-dev-server --config webpack.dev.config.js

    打开网页,进入localhots:8080就可以看到我们的页面了。打开浏览器的开发者工具,在console部分能看到以下两句提示就说明热更新启动成功了,

    然后把这部分很长的命令加入到npm scripts。在package.json的scripts下添加 "start": "webpack-dev-server --config webpack.dev.config.js" 

    输入npm start 就可以开启我们的服务了。

    添加一个简单的redux(非新手向)

    安装redux的依赖

    npm i -S redux@3.7.2 react-redux@5.0.5 @types/react-redux@5.0.6

    为了能体现redux,我们接下来给我们的网页添加两个按钮来增加/删除文字后面的感叹号。

    首先,我们来创建一个文件来存放store的接口声明,放入src/types/index.tsx中,代码如下:

    export interface StoreState {
        languageName: string;
        enthusiasmLevel?: number;
    }

    定义一些常量供action和reducer使用,放入src/constants/index.tsx

    export const INCREMENT_ENTHUSIASM = 'INCREMENT_ENTHUSIASM';
    export type INCREMENT_ENTHUSIASM = typeof INCREMENT_ENTHUSIASM;
    
    
    export const DECREMENT_ENTHUSIASM = 'DECREMENT_ENTHUSIASM';
    export type DECREMENT_ENTHUSIASM = typeof DECREMENT_ENTHUSIASM;

    添加action,放入src/actions/index.tsx

    import * as constants from '../constants'
    
    export interface IncrementEnthusiasm {
      type: constants.INCREMENT_ENTHUSIASM;
    }
    
    export interface DecrementEnthusiasm {
      type: constants.DECREMENT_ENTHUSIASM;
    }
    
    export type EnthusiasmAction = IncrementEnthusiasm | DecrementEnthusiasm;
    
    export function incrementEnthusiasm(): IncrementEnthusiasm {
      return {
        type: constants.INCREMENT_ENTHUSIASM
      }
    }
    
    export function decrementEnthusiasm(): DecrementEnthusiasm {
      return {
        type: constants.DECREMENT_ENTHUSIASM
      }
    }

    添加reducer,放入src/reducers/index.tsx

    import { EnthusiasmAction } from '../actions';
    import { StoreState } from '../types/index';
    import { INCREMENT_ENTHUSIASM, DECREMENT_ENTHUSIASM } from '../constants/index';
    
    export function enthusiasm(state: StoreState, action: EnthusiasmAction): StoreState {
      switch (action.type) {
        case INCREMENT_ENTHUSIASM:
          return { ...state, enthusiasmLevel: state.enthusiasmLevel + 1 };
        case DECREMENT_ENTHUSIASM:
          return { ...state, enthusiasmLevel: Math.max(1, state.enthusiasmLevel - 1) };
      }
      return state;
    }

    修改一下Hello组件,以下是修改后的代码:

    import * as React from 'react';
    
    export interface Props {
      name: string;
      enthusiasmLevel?: number;
      onIncrement?: () => void;
      onDecrement?: () => void;
    }
    
    export default function Hello({ name, enthusiasmLevel = 1, onIncrement, onDecrement }: Props) {
      if (enthusiasmLevel <= 0) {
        throw new Error('You could be a little more enthusiastic. :D');
      }
    
      return (
        <div className="hello">
          <div className="greeting">
            Hello {name + getExclamationMarks(enthusiasmLevel)}
          </div>
          <div>
            <button onClick={onDecrement}>-</button>
            <button onClick={onIncrement}>+</button>
          </div>
        </div>
      );
    }
    
    function getExclamationMarks(numChars: number) {
      return Array(numChars + 1).join('!');
    }

    此时我们页面已经修改成功了,但点击没有反应,因为我们还没有连接到redux的store中。

    添加一个container来链接Hello组件,放入src/containers/Hello.tsx中

    import Hello from '../components/Hello';
    import * as actions from '../actions/';
    import { StoreState } from '../types/index';
    import { connect, Dispatch } from 'react-redux';
    
    export function mapStateToProps({ enthusiasmLevel, languageName }: StoreState) {
      return {
        enthusiasmLevel,
        name: languageName,
      }
    }
    
    export function mapDispatchToProps(dispatch: Dispatch<actions.EnthusiasmAction>) {
      return {
        onIncrement: () => dispatch(actions.incrementEnthusiasm()),
        onDecrement: () => dispatch(actions.decrementEnthusiasm()),
      }
    }
    
    export default connect(mapStateToProps, mapDispatchToProps)(Hello);

    创建一个initState,来定义store初始的值,放入/src/store/initState.tsx中

    export default {
      enthusiasmLevel: 1,
      languageName: 'TypeScript',
    }

    创建一个store,放入/src/store/configureStore.tsx中

    import { createStore } from 'redux';
    import { enthusiasm } from '../reducers/index';
    import { StoreState } from '../types/index';
    import initState from './initState';
    export default function () {
      const store = createStore<StoreState>(enthusiasm, initState);
      return store;
    }

    最后修改一下入口文件index.tsx

    import * as React from "react";
    import * as ReactDOM from "react-dom";
    import Hello from './containers/Hello';
    import { Provider } from 'react-redux';
    import configureStore from './store/configureStore';
    
    const store = configureStore();
    ReactDOM.render(
      <Provider store={store}>
        <Hello />
      </Provider>,
      document.getElementById('root') as HTMLElement
    );

    至此,一个简单的redux就弄好了。可以点击按钮增加/删除感叹号了。

    但是现在还有很多不完善的地方,比如Hello组件竟然是一个函数,再比如reducer竟然只有一个(解决这两个问题的过程中会有一些bug待我们解决)。

    放心,这些都将在下面的“添加一个够用的Redux”中解决。

    添加一个够用的Redux

    很明显,一个简单的redux在我们稍微大一点的开发中是明显不够用的。

    所以我们来改写一下我们的代码。

    首当其冲的就是我们的Hello组件。我们把Hello组件改成class的形式

    export default class Hello extends React.Component<Props, {}> {
      constructor(props: Props) {
        super(props);
      }
      render() {
        const { name, enthusiasmLevel = 1, onIncrement, onDecrement } = this.props;
    
        if (enthusiasmLevel <= 0) {
          throw new Error('You could be a little more enthusiastic. :D');
        }
    
        return (
          <div className="hello">
            <div className="greeting">
              Hello {name + getExclamationMarks(enthusiasmLevel)}
            </div>
            <div>
              <button onClick={onDecrement}>-</button>
              <button onClick={onIncrement}>+</button>
            </div>
          </div>
        );
      }
    }

    保存,编译中,然后就报错了!

    ------------------------------------------------------------------------------------------------

    ERROR in ./src/containers/Hello.tsx
    (20,61): error TS2345: Argument of type 'typeof Hello' is not assignable to parameter of type 'ComponentType<{ enthusiasmLevel: number; name: string; } & { onIncrement: () => IncrementEnthusia...'.
    Type 'typeof Hello' is not assignable to type 'StatelessComponent<{ enthusiasmLevel: number; name: string; } & { onIncrement: () => IncrementEnt...'.
    Type 'typeof Hello' provides no match for the signature '(props: { enthusiasmLevel: number; name: string; } & { onIncrement: () => IncrementEnthusiasm; onDecrement: () => DecrementEnthusiasm; } & { children?: ReactNode; }, context?: any): ReactElement<any>'.

    ------------------------------------------------------------------------------------------------

    赶紧复制,然后google一下,就能找到我们要的答案TypeScript-React-Starter | Issues#29,从别人的回答来看貌似是一个bug?那我们就按回答来更改一下我们的Hello容器

    export function mergeProps(stateProps: Object, dispatchProps: Object, ownProps: Object) {
      return Object.assign({}, ownProps, stateProps, dispatchProps);
    }
    
    export default connect(
      mapStateToProps,
      mapDispatchToProps,
      mergeProps)(Hello);

    刚改完,还没等保存,IDE就提醒我们有一个错误:

    Property 'assign' does not exist on type 'ObjectConstructor'.

    很明显,是因为Object没有assign这个方法,有三种解决方式:

    1、安装Object-assign这个npm包,用这个包去替代。

    2、在tsconfig.json中把target从"es5"修改为"es6"。

    3、在tsconfig.json的compilerOptions中添加属性"lib": [

    "dom",
    "es6",
    "dom.iterable",
    "scripthost"
    ]
    (推荐,感谢大佬同事提供的解决方法)
    第三个方法中的lib就是你写代码用的语言的标准,如果要用async的话,还可以加一个es7

    然后再次编译,发现还是依旧报错。只不过这次错误信息改了:

    ------------------------------------------------------------------------------------------------

    ERROR in ./src/index.tsx
    (10,5): error TS2322: Type '{}' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<Component<Pick<Props, "name" | "enthusiasmLevel" |...'.
    Type '{}' is not assignable to type 'Readonly<Pick<Props, "name" | "enthusiasmLevel" | "onIncrement" | "onDecrement"> & Object>'.
    Property 'name' is missing in type '{}'.

    ------------------------------------------------------------------------------------------------

    这次的报错是在index.tsx中,可以看到是因为在Hello组件我们定义的接口中name的属性是必须传的,但是在index.tsx中没有显示的传过去。

    但是如果你的浏览器是chrome并安装了react插件的话可以看到编译后的代码是有传的

    姑且就当做是一个bug吧,解决方案有两种,一种是的index.tsx中给Hello容器加上一个name

    ReactDOM.render(
      <Provider store={store}>
        <Hello name="123"/>
      </Provider>,
      document.getElementById('root') as HTMLElement
    );

    这里就算加上name也还是直接显示的store中的name。所以我们这里采用这种方式,并且后面加上React-router之后这段代码就会改掉,就不会有这个问题了。

    另一种是在Hello组件中把name属性改成非必要属性:

    export interface Props {
      name?: string;
      enthusiasmLevel?: number;
      onIncrement?: () => void;
      onDecrement?: () => void;
    }

    这种方式不推荐。

    好了,到现在为止组件更改完成了。

    接下来我们解决多个reducer的问题。

    首先我们把initState的默认值更改一下:

    export default {
      demo: {
        enthusiasmLevel: 1,
        languageName: 'TypeScript',
      }
    }

    当然还有/src/types/index.tsx也要更改:

    export interface demo {
      languageName: string;
      enthusiasmLevel?: number;
    }
    export interface StoreState {
      demo: demo;
    }

    然后讲/src/reducers/index.tsx命名为demo.tsx,并对内容进行修改:

    import { EnthusiasmAction } from '../actions';
    import { demo } from '../types/index';
    import { INCREMENT_ENTHUSIASM, DECREMENT_ENTHUSIASM } from '../constants/index';
    import initState from '../store/initState';
    export function enthusiasm(state: demo = initState.demo, action: EnthusiasmAction): demo {
      switch (action.type) {
        case INCREMENT_ENTHUSIASM:
          return { ...state, enthusiasmLevel: state.enthusiasmLevel + 1 };
        case DECREMENT_ENTHUSIASM:
          return { ...state, enthusiasmLevel: Math.max(1, state.enthusiasmLevel - 1) };
      }
      return state;
    }

    其实就是把对应的接口类型进行了更改,并给state添加了默认值。

    然后新建一个index.tsx文件,并添加以下内容:

    import { combineReducers } from 'redux';
    import { enthusiasm } from './demo';
    const rootReducer = combineReducers({
      demo: enthusiasm
    });
    
    export default rootReducer;

    相对应的,也需要修改Hello容器中的引用的值:

    export function mapStateToProps({ demo: { enthusiasmLevel, languageName } }: StoreState) {
      return {
        enthusiasmLevel,
        name: languageName,
      }
    }

     最后修改一下configureStore中的引用的reducer:

    import { createStore } from 'redux';
    import  reducers  from '../reducers/index';
    import { StoreState } from '../types/index';
    import initState from './initState';
    export default function () {
      const store = createStore<StoreState>(reducers, initState);
      return store;
    }

    更改完毕,保存。报错...

    ------------------------------------------------------------------------------------

    ERROR in ./src/store/configureStore.tsx
    (6,41): error TS2345: Argument of type 'Reducer<{}>' is not assignable to parameter of type 'Reducer<StoreState>'.
    Type '{}' is not assignable to type 'StoreState'.
    Property 'demo' is missing in type '{}'.

    ------------------------------------------------------------------------------------

    是不是感觉很熟悉,和之前index.tsx中关于Hello组件的报错几乎一样。

    这里也有两种解决方案,一种是找到configureStore.tsx中的 const store = createStore<StoreState>(reducers, initState); 把 <StoreState> 泛型删除。

    第二种是和之前一样的,找到/src/types/index.tsx,将 demo: demo; 加上一个?使之变为非必须的属性 demo?: demo; 我们这里就采用这种方法。

    这里究竟是bug还是其他什么原因,希望有大神能解答。

    至此,我们够用的redux就已经完成了。

    添加一个React-Router

    需要注意的是,现在react-router已经到了V4版本了,并且官方说这是一个完全重写的版本。所以在我不太熟悉的情况下,保险起见还是先选择V3版本,等以后再更新。

    安装依赖

    npm i -S react-router@3.0.5 @types/react-router@3.0.5

    在src目录下创建文件routers.tsx,并添加以下内容:

    import * as React from 'react';
    import { Route, IndexRoute } from 'react-router';
    import Hello from './containers/Hello';
    
    export default (
      <Route path="/">
        <IndexRoute component={Hello} />
        <Route path="/demo">
          <IndexRoute component={Hello} />
        </Route>
      </Route>
    );

    为了显示路由的作用,就加了一个demo路径。

    然后在index.tsx中加上我们的路由

    import * as React from "react";
    import * as ReactDOM from "react-dom";
    import { Provider } from 'react-redux';
    import configureStore from './store/configureStore';
    import { Router, browserHistory } from 'react-router';
    import routes from './routes';
    const store = configureStore();
    ReactDOM.render(
      <Provider store={store}>
        <Router history={browserHistory} routes={routes} />
      </Provider>,
      document.getElementById('root') as HTMLElement
    );

    由于我们添加的是browserHistory作为路由,不是hashHistory,所以我们需要对服务器做一些路由配置才行。至于为什么,请自行搜索,这里不做说明了。如果不想用过多设置,也可以直接把browserHistory替换为hashHistory即可。

    这里我们的开发服务器就是webpack-dev-server,所以我们对webpack.dev.congfig.js进行更改:

    const webpack = require('webpack');
    const config = require('./webpack.common.config');
    config.devServer = {
      hot: true,
      publicPath: '/dist/',
      historyApiFallback: {
        index: './index.html'
      },
    }
    config.plugins.push(new webpack.HotModuleReplacementPlugin());
    module.exports = config;

    其实就是当服务器找不到路由目录时将页面指向index.html即可。

    因为更改了配置,所以我们需要重启服务器 npm start 

    进入localhost:8080/demo

    页面有显示Hello组件,说明配置成功了。

    添加React-Router-Redux

    这里同样由于React-Router版本大更新的问题,所以也要严格控制版本。

    安装依赖

    npm i -S react-router-redux@4.0.8 @types/react-router-redux@4.0.48

    更改index.tsx代码如下:

    import * as React from "react";
    import * as ReactDOM from "react-dom";
    import { Provider } from 'react-redux';
    import configureStore from './store/configureStore';
    import { Router, browserHistory } from 'react-router';
    import routes from './routes';
    import { syncHistoryWithStore } from 'react-router-redux';
    
    const store = configureStore();
    const history = syncHistoryWithStore(browserHistory, store);
    
    ReactDOM.render(
      <Provider store={store}>
        <Router history={history} routes={routes} />
      </Provider>,
      document.getElementById('root') as HTMLElement
    );

    然后在src/reducers/index.tsx中添加上routerReducer

    import { combineReducers } from 'redux';
    import { routerReducer } from 'react-router-redux';
    import { enthusiasm } from './demo';
    const rootReducer = combineReducers({
      demo: enthusiasm,
      routing: routerReducer
    });
    
    export default rootReducer;

    OK,非常简单。

    添加开发必备配置

    本来是不打算写一这部分的,毕竟整个基础环境搭下来就只剩loader部分没有写了,而loader配置基本上github对应的库上都有写。

    但是我自己装载loader的时候遇到了一些问题,所以这里写出来,避免更多的人踩坑了。

    css-loader配置

    安装依赖

    npm i -D css-loader@0.28.7 style-loader@0.19.0

    css-loader用来加载css文件,style-loader用来把加载好的文件放入html中的style标签里,所以这两个loader必须配合使用。

    编写匹配规则,在webpack.common.config.js中的module.rules中添加如下规则:

    { test: /.css$/, loader: 'style-loader!css-loader' }

    然后我们创建/src/components/hello.css,并输入以下内容:

    .hello{
      background:#000;
    }
    .greeting{
      color:#fff;
    }

    然后在/src/components/Hello.tsx中引入:

    import './hello.css';

    ok,到目前为止我们的loader还没有出现问题。

    但是你以为这样就结束了么?那是不可能的,不然我写这部分的目的是什么,手动滑稽。

    现在我们想要用css modules,不知道什么是css modules的请点击CSS Modules 用法教程

    所以把webpack.common.config.js中刚刚添加的规则修改成以下内容:

    { test: /.css$/, loader: "style-loader!css-loader?modules" }

    然后更改下/src/components/Hello.tsx对css的引用

    import style from "./hello.css";

    然后重新编译,报错TS2307: Cannot find module './hello.css'.

    什么鬼?找不到css?惊喜不惊喜? 

    顺手把问题往谷歌一丢,就能找到别人的也碰到了这个问题,下面也有一些解决方案 在这

    看下来大概就是有两种解决方案:

    1、使用typed-css-modules解决。

    2、使用require的方式。

    我毫不犹豫的选择了第二种,因为第一种只能解决css的引入问题。那如果我要引入图片文件呢?所以最终还是要用require。

    这部分如果有其他解决方案的话,请大神告诉我一下,不甚感激。

    这个报错是typescript报错,我们只需要在src目录下增加index.d.ts文件即可,内容如下:

    declare module '*.scss' {
      const content: any
      export default content
    }

    接下来我们改一下/src/components/Hello.tsx中的引入方式:

    const style = require('./hello.css');

    如果你是一步步跟着来的话,应该会碰到和我一样的报错:error TS2304: Cannot find name 'require'.

    这是因为我们没有引入@types/node声明文件,所以我们需要安装一下。

    npm i -D @types/node@8.0.34

    重新编译一下,终于没有问题了。

    接下来继续修改/src/components/Hello.tsx,将class部分修改成css modules的模式

    <div className={style.hello}>
        <div className={style.greeting}>
            Hello {name + getExclamationMarks(enthusiasmLevel)}
        </div>
        <div>
            <button onClick={onDecrement}>-</button>
            <button onClick={onIncrement}>+</button>
        </div>
    </div>    

    保存,ok,样式生效。

    需要注意的是,如果我们引入了其他的组件库,比如antd的话,就不能这样直接使用css modules,如果想要使用必须配置如下两条规则:

    { test: /.css$/, loader: "style-loader!css-loader", include: /node_modules/ },
    { test: /.css$/, loader: "style-loader!css-loader?modules", exclude: /node_modules/ },

    意思应该也都懂了,使用modules的时候需要排除node_modules里引入的那些库。

    file-loader配置

    安装依赖

    npm i -D file-loader@1.1.5

    在webpack.common.config.js的module.rules中添加规则:

    { test: /.(png|jpe?g|gif)/, loader: "file-loader" }

    然后把在src下创建文件夹img,并放入一张图片,我这里是x.png

    在/src/components/Hello.tsx中引入图片,并在JSX中添加一个img标签,同样也需要用require引入

    //引入
    const imgX = require('../img/x.png');
    
    //JSX部分
    <div className={style.hello}>
        <div className={style.greeting}>
            Hello {name + getExclamationMarks(enthusiasmLevel)}
        </div>
        <div>
            <button onClick={onDecrement}>-</button>
            <button onClick={onIncrement}>+</button>
         </div>
         <img src={imgX} alt="imgX"/>
    </div>

    保存,进入localhost:8080,然而图片并没有出现。

    但是如果我们审查元素的话是能看到有img这个元素的,也就是说引入的位置出了问题。我们之前说过,webpack-dev-server编译出来的文件是在内存中的,并且目录是/dist/,但是我们可以很明显的看到我们img的src值是没有/dist/目录的

    所以,我们需要在webpack.common.config.js的output中再加一条属性 publicPath:'/dist/' ,这个属性的具体含义请看详解Webpack2的那些路径

    重新编译,ok,图片显示出来了。

     其他的常用loader,比如babel,postcss我添加的时候没有遇到什么问题,所以就不贴出来了,如果有人反馈,我再解答吧。

    按需加载

    typescript + react-router + webpack 实现按需打包/加载

     

    结束语

    如果在React-Router和React-Router-Redux的配置中有什么报错,基本上是npm包的版本问题,请删除node_modules并按照我指定的版本重新安装。

    总结:安装过程中确实碰到了各种各样的问题,尤其是Router的包附带的history版本问题,弄了很久。看似很简单的教程,背后有我踩过无数的坑,不过好在还是完成了。

    之后还要继续集成ant-design,以及生产环境的配置,这些都将会在本教程继续更新。

    完整代码已放入github

    参考资料:TypeScript-React-Starter

    React与webpack | TypeScript Handbook(中文版)

    webpack HMR

    详解Webpack2的那些路径

    TypeScript-React-Starter | Issues#29

    webpack-dev-server使用react-router browserHistory的配置

  • 相关阅读:
    ORA02292:integrity constraint(xx) violated child record found 外键关联,无法删除记录
    自定义设置Ext.grid.gridPanel样式
    修改了grid的前3行的颜色为红色
    PLSQL导入导出表的正确步骤
    1000条数据写入到txt文件中,并且做了换行
    webservice 存根方式
    java日期时间
    extjs 页面打开时表格自动加载后台传来的json数据
    处理Clob数据(转)关于oracle中大对象处理的一些方法和实例
    44个提高博客影响力的方法
  • 原文地址:https://www.cnblogs.com/baqiphp/p/7647912.html
Copyright © 2020-2023  润新知