记录一次React+TypeScript的开发历程

之前一直使用Vue框架,这次打算学习一下React顺便学习一下TypeScript,于是就有了这个项目。在开发过程中遇到了一些问题,感觉这些坑或者说刚开始学不知道的要注意的点,是有必要记录下来的。

建立一个React + TypeScript + Sass + Webpack的项目

初始化项目

首先我们用$ yarn init初始化一个项目,这样我们就在项目根目录得到一个package.json文件,这个文件是项目配置文件,我们需要对他进行一些改动, 向其json中加入如下代码:

"scripts": {
    "dev": "npx webpack --config webpack.config.js"
}

上面的配置作用是:指定项目运行命令

下载依赖

其次,我们把需要的包都下载下来:

$ yarn add react react-dom core-js regenerator-runtime
$ yarn add -D @babel/core @bable/preset-env @babel/preset-react @babel/preset-typescript
$ yarn add -D typescript
$ yarn add -D @types/react @types/react-dom
$ yarn add -D node-sass
$ yarn add -D webpack webpack-cli
$ yarn add -D babel-loader css-loader sass-loader source-map-loader style-loader ts-loader

这些包都是干什么的呢?

  • 第一行:下载React相关
  • 第二行:下载Babel相关
  • 第三行:下载TypeScript
  • 第四行:下载TypeScript相关。

为什么要下载@types/xxx包?
你可能会遇到Could not find a declaration file for module 'xxx'的问题,这个问题是因为TypeScript还不认识相关包,要想让typescript认识他们,就要下载相应的@types/xxx包。

  • 第五行:下载SaSS
  • 第六行:下载Webpack
  • 第七行:下载Webpack所需要的loader。

为什么要下载loader?
因为Webpack是由Node.js编写的项目打包工具,这就意味着它只能认识JavaScript文件,要想让他认识其他类型的文件,就要使用到这些loader包去加载、编译、解析。

项目的相关配置

配置Webpack

在项目根目录中新建一个webpack.config.js文件,内容如下:

module.exports = {
  mode: "development",
  watch: true,    // 让webpack监听项目,项目更新后立即进行重新编译,实现开发过程中的即时更新
  entry: "./src/index.tsx",    // 项目入口
  output: {        // 编译打包后的项目输出
    filename: "bundle.js",
    path: __dirname + "/dist"
  },
  resolve: {
    extensions: [".ts", ".tsx", ".js", ".json"]
  },
  devtool: "source-map",
  module: {
    rules: [    // 配置解析规则,为被正则匹配到的文件指定不同的loader
      { test: /\.scss$/, use: [ "style-loader", "css-loader", "sass-loader" ] },    // loader链,从右至左解析输出文件
      { test: /\.tsx?$/, loader: "babel-loader" },
      { test: /\.tsx?$/, loader: "ts-loader" },
      { enforce: "pre", test: /\.js$/, loader: "source-map-loader" }
    ]
  }
};

Webpack相关知识
Webpack从项目入口开始检索依赖,将所有依赖性经过loader、编译之后,打包输出至出口文件

配置Babel

在项目根目录中新建一个.babelrc文件,内容如下:

{
  "presets": [
    "@babel/react",
    "@babel/typescript",
    [
      "@babel/env",
      {
        "modules": false,
        "targets": {
          "chrome": "58",
          "ie": "11"
        }
      }
    ]
  ],
}

为什么要用Babel?
Babel可以帮我们把typescript文件编译成javascript文件,并且能够实现对某些浏览器的兼容编译,即编译成置顶环境支持的语法,可以理解为一个polyfill机。

配置TypeScript

在项目根目录中新建一个tsconfig.json文件,内容如下:

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

开始开发

在配置完项目之后,我们的项目结构应该是这样的

.
├── .babelrc
├── node_modules
|    └─ ...
├── package.json
├── tsconfig.json
├── webpack.config.js
└── yarn.lock

什么是yarn.lock文件?
yarn.lock文件中储存着你这个项目所需要用到的包的信息。这样别人想运行你的项目时,就不需要一个一个下载你项目中所用到的依赖,只需要运行一下yarn install就可以下载下来全部依赖,你就也不需要将巨大的node_modules文件上传至git仓库了。同理package-lock.json也是相同的作用,不过只是它是npm的依赖文件。

首先我们需要一个public/index.html的入口文件,其内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>React Project</title>
</head>
<body>
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root"></div>
  <!-- bundle.js即在webpack中配置的编译出口文件 -->
  <script src="../dist/bundle.js"></script>
</body>
</html>

其次是src/index.tsx文件,即webpack中配置的项目入口文件,内容如下:

import * as React from 'react';
import * as ReactDom from 'react-dom';

ReactDom.render(
    <div>Hello World!</div>,
    document.getElementById('root')
);

而开发过程中的scss代码均保存为.scss文件,webpack就可以自动将其编译输出为css文件。

项目运行

还记得之前在package.json中配置的运行命令,现在我们在项目根目录下运行:

$ yarn run dev

就可以让webpack监听项目目录,这样当文件发生变动时,webpack就可以自动将文件编译、打包、输出到./dist文件夹下。
这时我们打开public/index.html即可成功运行看到Hello World!字样,项目就运行成功了。

在项目中使用其他库

React-router

下载依赖:

yarn add react-router react-router-dom
yarn add -D @types/react-router @types/react-router-dom

index.tsx代码:

import * as React from 'react';
import * as ReactDom from 'react-dom';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
// import Components
import MenuBar from "./components/MenuBar";
// import Pages
import Article from "./pages/Article";
import About from "./pages/About";
import ArticleList from "./pages/ArticleList";

ReactDom.render(
  <BrowserRouter>
    <Switch>
      <Route exact path="/" component={MenuBar}/>
      <Route path="article" component={ArticleList}>
        <Route path=":id" component={Article} />
      </Route>
      <Route path="about" component={About} />
    </Switch>
  </BrowserRouter>,
  document.getElementById('root')
);

项目开发中遇到的坑

ReferenceError: regeneratorRuntime is not defined

这是当你使用了async/await之后Babel产生的错误,解决这个错误只需要安装一个包,并且增加一些.babelrc配置就可以了。

$ yarn add -D @babel/plugin-transform-runtime

.babelrc文件中添加如下配置:

"plugins": [
    "@babel/plugin-transform-runtime"
]

Module parse failed: Unexpected character '@'

没有添加css-loader/url-loader。
下载loader:

$ yarn add -D url-loader file-loader

webpack.config.js中添加配置:

{ test: /\.css$/, use: [ "style-loader", "css-loader"] },
{ test: /\.(png|woff|woff2|eot|ttf|svg)$/, loader: 'url-loader?limit=100000' }

另外,如果你在使用Next.js,注意要下载@zeit/next-css

$ yarn add @zeit/next-css

并在next.config.js中添加配置:

const withCss = require('@zeit/next-css');

module.exports = withCss({
  webpack: function (config) {
    config.module.rules.push({
      test: /\.(eot|woff|woff2|ttf|svg|png|jpg|gif)$/,
      use: {
        loader: 'url-loader',
        options: {
          limit: 100000,
          name: '[name].[ext]'
        }
      }
    });
    return config;
  }
});

如果有多个withXxx记得嵌套而不是并列:

const withCss = require('@zeit/next-css');
const withSass = require('@zeit/next-sass');

module.exports = withSass(withCss({
  webpack: function (config) {
    config.module.rules.push({
      test: /\.(eot|woff|woff2|ttf|svg|png|jpg|gif)$/,
      use: {
        loader: 'url-loader',
        options: {
          limit: 100000,
          name: '[name].[ext]'
        }
      }
    });
    return config;
  }
}));

React Input 输入中文的问题

问题描述: 在Uncontrolled Component下的onInput/onChange事件无法键入中文
参考文章: Controlled and uncontrolled component design pattern in React
解决方案:

  1. 使用CompositionEvent。在onCompositionStart/onCompositionUpdate的时候开启锁,onCompositionEnd的时候关闭锁,并在onChange的handler中判断:只有在锁关闭的时候更改Value。参考文章: 中文输入法与React文本输入框的问题与解决方案

    Chrome 53以后的版本中,onChange事件被调整到了onCompositionEnd之后执行,那么就需要在onCompositionEnd中对Chrome浏览器进行特判,并执行onChangeHandler中的逻辑.

  2. 将Uncontrolled Component替换为Controlled Component。这个方法最简单粗暴,也是最有效的。

原文链接:juejin.im

上一篇:vue-cli3环境上图片加载不出来经验小结
下一篇:react里面this.setState到底是干嘛的

相关推荐

  • 🌈 React 函数式组件优化

    1. React 性能优化思路 减少重新 render 的次数。 减少计算的量。主要是减少重复计算,对于函数式组件来说,每次 render 都会重新从头开始执行函数调用。

    1 个月前
  • 🆘 一次理解清楚,为什么使用 React useEffect 中使用 setInterval 获取的值不是最新的

    Intro 这篇文章将通过一个使用 React Hook 常遇到的问题(stale state)入手,尝试理解 Hook 的内部运行逻辑。 废话不多说,直接看示例Sandbox。

    3 个月前
  • 高频数据交换下Flutter与ReactNative的对比

    (标题图片来自网络,侵删) 后端使用go写的socketio服务模拟期货行情数据,每10ms推送10条行情数据 ReactNative已经尽力优化了。 Flutter由于没flutter-socket...

    2 年前
  • 高性能迷你React框架 anu1.3.0 发布

    anujs1.3.0是一款高性能React-like框架,是目前世界上对React16兼容最好的迷你库。 自React16起,相继推出createContext,createPortal, creat...

    3 年前
  • 高德地图 react-amap 实战

    react-amap 是基于 React 的高德地图组件。 1. 获取地图示例 react-amap 作为高德地图在 React 中的实现,实际使用中不可避免的需要通过地图对象调用各种方法,reac...

    1 年前
  • 高品质 React UI 组件

    A high quality UI Toolkit, A Component Library for React 16+. 💘 Installation npm install isui --s...

    3 年前
  • 骚操作!在react中使用vuex

    原文地址 前言 笔者最近在学习使用react,提到react就绕不过去redux。redux是一个状态管理架构,被广泛用于react项目中,但是redux并不是专为react而生,两者还需要react...

    2 年前
  • 项目文档说明:react + Ant Design 的 blog-react-admin

    前言 此 blog-react-admin 项目是基于 蚂蚁金服开源的 ant design pro 之上,用 react 全家桶 + Ant Design 的进行再次开发的,项目已经开源,项目地址...

    2 年前
  • 面试题:Hooks 与 React 生命周期的关系

    React 生命周期很多人都了解,但通常我们所了解的都是 单个组件 的生命周期,但针对 Hooks 组件、多个关联组件(父子组件和兄弟组件) 的生命周期又是怎么样的喃?你有思考和了解过吗,接下来我们将...

    1 年前
  • 面试官:请你在React中引入Vue3的@vue/reactivity,实现响应式。

    前言 React的状态管理是一个缤纷繁杂的大世界,光我知道的就不下数十种,其中有最出名immutable阵营的redux,有mutable阵营的mobx,react-easy-state,在hooks...

    9 个月前

官方社区

扫码加入 JavaScript 社区