React踩坑 - junruchen/junruchen.github.io GitHub Wiki

一、项目生成

  • create-react-app monkey-web 生成项目
  • cd monkey-web 进入项目
  • yarn start 启动项目

二、项目部署

  • 本地开发 yarn start
  • 线上部署 yarn build

三、参考文档

四、配置项

1、Ant Design UI库引入

  • yarn add antd 安装UI库
  • yarn add babel-plugin-import 实现按需引入

package.json/babel 中增加如下内容:

"plugins": [
    [
        "import",
        {
            "libraryName": "antd",
            "libraryDirectory": "es",
            "style": "css"
        }
    ]
]

组件使用 如下:

import React, { Component } from 'react';
import { Button } from 'antd'
import 'antd/dist/antd.css'

class App extends Component {
  render() {
    return (
        <Button type="primary">测试</Button>
    );
  }
}

export default App;

此时可以使用UI库的默认样式

2、自定义Ant Design UI库样式

  • 安装 lessless-loader
  • 使用命令 yarn run eject 暴露配置文件
  • webpack.config.dev.jswebpack.config.prod.js 中做如下修改:

a) 创建 antModifyVars.js 文件

'use strict';

const modifyVars = {
  'primary-color': '#E26A6A',
}

module.exports = modifyVars;

b) 创建 less 相关变量,参考默认sass的配置:

// style files regexes
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;

// 增加less部分
const lessRegex = /\.less/;
const lessModuleRegex = /\.module\.less$/;

c) 在 module.rulesoneOf, 仿照sass追加一下代码:

// Opt-in support for LESS (using .less extensions).
// Chains the less-loader with the css-loader and the style-loader
// to immediately apply all styles to the DOM.
// By default we support LESS Modules with the
// extensions .module.scss or .module.sass
{
    test: lessRegex,
    exclude: lessModuleRegex,
    use: getStyleLoaders({ importLoaders: 2 }, 'less-loader'),
},
// Adds support for CSS Modules, but using SASS
// using the extension .module.scss or .module.sass
{
    test: lessModuleRegex,
    use: getStyleLoaders(
    {
        importLoaders: 2,
        modules: true,
        getLocalIdent: getCSSModuleLocalIdent,
    },
    'less-loader'),
},

d) 在 getStyleLoaders,处理 less-loader

// dev下的配置
if (preProcessor) {
    let loader = {loader: require.resolve(preProcessor)}
    if (preProcessor === "less-loader") {
        loader.options.modifyVars = modifyVars
        loader.options.javascriptEnabled = true
    }
    loaders.push(loader);
}

// prod下的配置
if (preProcessor) {
    let loader = {
         loader: require.resolve(preProcessor),
         options: {
            sourceMap: shouldUseSourceMap,
        },
    }
    if (preProcessor === "less-loader") {
        loader.options.modifyVars = modifyVars
        loader.options.javascriptEnabled = true
    }
    loaders.push(loader);
  }

3、ES6 API支持,引入polyfills

增加低版本浏览器、IE浏览器对ES6API的支持,IE9,IE9+

方法一,安装 yarn add react-app-polyfill

// src/index.js中的【第一行】引入对IE9及更高版本的支持
import 'react-app-polyfill/ie9';

// 其他支持,如:对IE11及更高版本的支持
import 'react-app-polyfill/ie11';

方法二,安装 yarn add babel-polyfill

// webpack.base.conf.js中引入:
require("babel-polyfill")

// webpack.base.conf.js中配置:
entry: { app: ['babel-polyfill', './src/main.js'] }

4、引入 react-router-dom 路由

  • react-router-dom 依赖 react-router,所以使用npm安装依赖的时候,只需要安装相应环境下的库即可,不用再显式安装react-router。
  • 由于需要权限配置,所以增加AuthorizedRoute.js控制权限

5、配置别名,@ 指向 src 目录

webpack.base.conf.js 与 webpack.base.conf.js的配置一致,如下:

定义resolvePath方法,新版中resolve名称被占用
function resolvePath (dir) {
  return path.join(__dirname, '..', dir)
}

// resolve.alias中增加别名配置:
'@': resolvePath('src')

6、引入 axios

package.json底部增加以下代码解决跨域问题

// 新版本需要借助http-proxy-middleware,在src下创建setupProxy.js,内容:
// 会自动引用,不需要额外的配置
const proxy = require('http-proxy-middleware')

module.exports = function (app) {
  app.use(
    proxy(
      '/api', {
        target: 'http://**********',
        changeOrigin: true
      }
    )
  )
}

// 定义多个入口:
module.exports = function (app) {
  app.use(proxy('/api', { target: 'http://localhost:7001' }));
  app.use(proxy('/api2', { target: 'http://localhost:7001' }));
}

7、样式处理

使用react-css-modules实现组件内部样式与外部分离,使用方式:

import React from 'react'
import CSSModules from 'react-css-modules';
import { Button } from 'antd'
import styles from './Header.module.scss'

class Header extends React.Component {
  render () {
    return (
      <div>
        <Button type="primary" className="nowrap" styleName="test">测试</Button>
      </div>
    )
  }
}
export default CSSModules(Header, styles)

注意

  • 由于最新版create-react-app已经实现配置,无需再修改webpack配置文件
  • 上述方法可实现,同时使用antd样式、全局样式、组件私有样式
  • 特别注意组件私有样式文件的命名[name].module.scss

8、针对create-react-app 2.1.1之前的版本,引入 stylus

  • 安装 stylusstylus-loader
  • 使用命令 yarn run eject 暴露配置文件
  • webpack.config.dev.jswebpack.config.prod.jsmodule/rules/oneOf 中修改一下代码:
{
    test: /\.(css|styl)$/,
        use: [
            require.resolve('style-loader'),
            {
                loader: require.resolve('css-loader'),
                options: {
                  importLoaders: 1,
                },
            },
            {
                loader: require.resolve('postcss-loader'),
                options: {
                    // Necessary for external CSS imports to work
                    // https://github.com/facebookincubator/create-react-app/issues/2677
                    ident: 'postcss',
                    sourceMap: true,
                    plugins: () => [
                        require('postcss-flexbugs-fixes'),
                        autoprefixer({
                            browsers: [
                            '>1%',
                            'last 4 versions',
                            'Firefox ESR',
                            'not ie < 9', // React doesn't support IE8 anyway
                        ],
                        flexbox: 'no-2009',
                    }),
                ],
            },
        },
        {
            loader: require.resolve('stylus-loader'),
            options: {
                sourceMap: true,
            }
        },
    ],
},

五、开发过程中遇到的问题

1、warning提示:no-op

参考内容

问题描述

warning:Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method

名次解释 no-op

Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component.

也就是不能在卸载的组件中进行状态更新操作,如异步请求、事件、定时器等,在componentWillUnmount生命周期中都应该进行相应的取消处理

对于事件、定时器,只需要在componentWillUnmount方法中,进行取消事件监听或者清除定时器的操作即可

以下方案均针对异步操作带来的问题进行处理。

方案一:使用一个变量[_isMounted]来控制是否更新状态

import React from 'react'

class newComponent extends React.Component {
  _isMounted = false

  componentDidMount() {
    this._isMounted = true
    axios
      .get('https://hn.algolia.com/api/v1/search?query=react')
      .then(result =>
      if (!this._isMounted) return
        this.setState({
          news: result.data.hits,
        }),
      )
  }

  componentWillUnmount() {
    // 取消事件监听、清除定时器、控制异步请求
    this._isMounted = false
  }

  render() {
    return (
      ...
    )
  }
}

export default newComponent

方案二:比较粗暴的方式,直接更改setState函数

import React from 'react'

class newComponent extends React.Component {

  componentWillUnmount() {
    // 取消事件监听、清除定时器、控制异步请求
    this.setState = () => {
      return
    }
  }

  render() {
    return (
      ...
    )
  }
}

export default newComponent

方案三,可直接取消接口请求,如axios的CancelToken

2、react-router4.0版本中,不同场景下的路由跳转

参考

方案一,官方推荐,使用withRouter

import React from 'react'
import { withRouter } from 'react-router-dom'

class NewComponent extends React.Component {
  AFunction() {
    this.props.history.push('/path')
  }
  render() {
    return (
      ...
    )
  }
}
export default withRouter(NewComponent)

方案二,不推荐,使用context

import React from 'react'
import PropTypes from 'prop-types'
import { withRouter } from 'react-router-dom'

class NewComponent extends React.Component {
  static contextTypes = {
    router: PropTypes.object
  }
  constructor(props, context) {
     super(props, context);
  }
  AFunction() {
    this.context.router.history.push('/path')
  }
  render() {
    return (
      ...
    )
  }
}
export default NewComponent

方案三,使用history

在真实的业务场景中,经常需要在非react组件中使用路由跳转,如:接口统一处理场景下,用户登录失效时,跳转至登录页面。

由于react-router4.0中提供的BrowserRouter组件默认创建了history,并且未暴露出来,在非react中使用history会出现不起作用的情况

因此:可不使用BrowserRouter组件,自行创建一个history,如下:

a. 创建history

// src/history.js 一定要放在src的根目录下
import createHistory from 'history/createBrowserHistory'
const history = createHistory()
export default history

b. 配置router

// src/index.js  注意router上的history
import { Router, Link, Route } from 'react-router-dom'
import history from './history'

ReactDOM.render(
  <Provider store={store}>
    <Router history={history}>
      ...
    </Router>
  </Provider>,
  document.getElementById('root'),
)

c. 其他地方使用,如接口统一处理层

import history from '@/history';

export function addProduct(props) {
  return dispatch =>
    axios.post('xxx', props, config)
      .then(response => {
        history.push('/cart')
      })
}
⚠️ **GitHub.com Fallback** ⚠️