从零搭建react项目总结

566 阅读3分钟

react hooks子组件使用

从参数中获取children, 直接使用{children}渲染

webpack开启css module

module:{
    rules:{
        {
        test: /\.(sa|sc)ss$/,
        exclude: /node_modules/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              reloadAll: true,
              esModule: true,
              modules: {
                namedExport: true,
              },
            }
          },
          {
            loader: "css-loader",
            options: {
              modules: {
                localIdentName: '[name]__[local]--[hash:base64:5]',
                exportGlobals: true,
                namedExport: true,
              },
            }
          },
          {
            loader: 'postcss-loader'
          },
          {
            loader: 'sass-loader',
          },
        ]
      },
    
    }
}

使用css module

import * as style from "./index.module.scss"

<div className={style.container}/>

commit规范化

Commitizen使用

Commitizen是一个撰写合格 Commit message 的工具

npm install -g commitizen

在项目目录中使用:

commitizen init cz-conventional-changelog --save --save-exact

凡是用到git commit命令,一律改为使用git cz

生成changelog:

$ npm install -g conventional-changelog-cli
$ cd my-project
$ conventional-changelog -p angular -i CHANGELOG.md -s

移动适配问题

flexible在华为手机会有各种样式问题, 配合react使用postcss-pxtorem转换rem

宽高、间距尽量用百分比

添加微信限制

export const addWeixinLimit = () => {
  if (process.env.NODE_ENV === 'production') {
    let ua = navigator.userAgent.toLowerCase();
    let isWeixin = ua.match(/micromessenger/i) == 'micromessenger' && (ua.match(/wxwork/i) != 'wxwork');
    if (!isWeixin) {
      document.head.innerHTML = '<title>抱歉,出错了</title><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0"><link rel="stylesheet" type="text/css" href="https://res.wx.qq.com/open/libs/weui/0.4.1/weui.css">';
      document.body.innerHTML = '<div class="weui_msg"><div class="weui_icon_area"><i class="weui_icon_info weui_icon_msg"></i></div><div class="weui_text_area"><h4 class="weui_msg_title">请在微信客户端打开链接</h4></div></div>';
    }
  }
}

配置react-hot-loader

安装后,在.babelrc文件添加

    "plugins": [
      ["react-hot-loader/babel"]
    ]

在index.js文件包装App根组件


import { hot } from 'react-hot-loader/root';

const HotApp = hot(
  () => <Provider store={store}>
    <App />
  </Provider>
)

ReactDOM.render(
  <HotApp />,
  document.getElementById('root')
);

react-router配置

配置异步加载路由组件,创建一个AsyncComponent

import React, { Component } from "react";

export default function asyncComponent(importComponent) {
  class AsyncComponent extends Component {
    constructor(props) {
      super(props);

      this.state = {
        component: null
      };
    }

    async componentDidMount() {
      const { default: component } = await importComponent();

      this.setState({
        component: component
      });
    }

    render() {
      const C = this.state.component;

      return C ? <C {...this.props} /> : null;
    }
  }

  return AsyncComponent;
}

在route.js中引入

import AsyncComponent from '@/components/common/AsyncComponent'
const Home = AsyncComponent(() => import('@/pages/Home'))

页面路由重置滚动条到顶部

创建ScrollToTop.js组件

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

class ScrollToTop extends Component {
  componentDidUpdate(prevProps) {
    if (this.props.location.pathname !== prevProps.location.pathname) {
      window.scrollTo(0, 0);
    }
  }

  render() {
    return this.props.children;
  }
}

export default withRouter(ScrollToTop);

在路由渲染的时候,包装一层route

import ScrollToTop from "@/utils/ScrollToTop.js"
function RenderRouters({ routes }) {
  return routes.map((item) =>
    <ScrollToTop
      key={item.name}
    ><Route
        path={item.path}
        render={() => (<item.component />)
        } />
    </ScrollToTop>
  )
}

嵌套路由

<Switch>
    <Redirect exact from='/score' to='/score/step' />
    <Route path="/score/step"/>
<Switch/>

切记嵌套路由不要用exact精准匹配,否则会找不到子路由

配置redux

创建types

export default {

  /*
  * 加载状态
  * COMMON
  * Loading开始
  * Loading结束
  * */
  LOADING_START: 'LOADING_START',
  LOADING_END: 'LOADING_END',

  // 获取论坛数据
  FETCH_FORUM: 'FETCH_FORUM'
}

创建actions

import commonTypes from "@/store/types/common"
import { getForumData } from "@/api/common"
export function startLoading() {
  return {
    type: commonTypes.LOADING_START,
  }
}

export function endLoading() {
  return {
    type: commonTypes.LOADING_END,
  }
}


// 获取论坛信息
export function getForum() {
  return (dispatch) => {
    return getForumData().then((res) => {
      dispatch({
        type: commonTypes.FETCH_FORUM,
        payload: res.data
      })
      return res.data
    })
  }
}

创建reducer

import commonTypes from "@/store/types/common"
const initialState = {
  isLoading: false,
  forumData: {}
};
function commonReducer(state = initialState, action) {
  switch (action.type) {
  case commonTypes.LOADING_START:
    return {
      ...state,
      isLoading: true,
    };
  case commonTypes.LOADING_END:
    return {
      ...state,
      isLoading: false,
    };
  case commonTypes.FETCH_FORUM:
    return {
      ...state,
      forumData: action.payload
    };

  default:
    return state
  }
}

export default commonReducer;

整合reducer

import { combineReducers } from 'redux'

import common from './common'
import user from './user'
export default combineReducers({
  common,
  user
})

创建store

import { createStore, compose, applyMiddleware } from 'redux'
import reducer from './reducers'
import thunk from 'redux-thunk'
const configureStore = (preloadedState) => createStore(
  reducer,
  preloadedState,
  compose(
    applyMiddleware(thunk),
  )
)
export default configureStore()

根节点传入store

import { Provider } from 'react-redux'
import store from '@/store'

<Provider store={store}>
    <App />
</Provider>

通过connect引入使用

import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as CommonAction from '@/store/actions/common';

class App extends Component {
  componentDidMount() {
    console.log(this.props)
    let token = LocalStorageUtil.get('token')
  }

  render() {

    const { isLoading } = this.props;
    const renderContent = (<BrowserRouter>
      <Fragment>
        <Route exact path="/" render={() => (
          <Redirect to="/home" />
        )} />
        <RenderRouters routes={routes}></RenderRouters>
      </Fragment>
    </BrowserRouter>)
    return (
      <div className="App">
        {
          isLoading
            ? (<ActivityIndicator
              toast
              text="加载中..."
              animating={isLoading}
            />)
            : renderContent
        }
      </div>
    )
  }

}


const mapStateToProps = (state, props) => ({
  isLoading: state.common.isLoading,
})

const mapDispatchToProps = (dispatch) => ({
  ...bindActionCreators(CommonAction, dispatch)
})

export default (connect(mapStateToProps, mapDispatchToProps)(App));

通过react hooks引入

import { useDispatch, useSelector } from "react-redux";
import { getForum } from "@/store/actions/common"

const { formData } = useSelector(state => state.user)
const dispatch = useDispatch()

dispatch(getForum())

配置webpack打包优化

配置HtmlWebpackPlugin

new HtmlWebpackPlugin({
    filename: utils.resolve('../dist/index.html'),
    template: 'public/index.html',
    inject: true,
    hash: true,
    minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
    }
}),

配置splitChunks 、js压缩、css压缩

    optimization: {
        minimize: true,
        minimizer: [
            new TerserPlugin({ // 压缩js
                parallel: true
            }),
            // 压缩css
            new OptimizeCSSAssetsPlugin({
                cssProcessorOptions: {
                    discardComments: { removeAll: true } // 移除注释
                }
            }),
        ],
        splitChunks: {
            chunks: "async",
            minSize: 30000,
            minChunks: 1,
            maxAsyncRequests: 5,
            maxInitialRequests: 3,
            automaticNameDelimiter: '~',
            name: true,
            cacheGroups: {
                vendors: {
                    test: /[\\/]node_modules[\\/]/,
                    priority: -10
                },
                default: {
                    minChunks: 2,
                    priority: -20,
                    reuseExistingChunk: true
                }
            }
        }
    }

antd-mobile + rc-form使用

使用InputItem

import { InputItem, Toast, Modal } from 'antd-mobile';
import { createForm } from 'rc-form';
const { getFieldProps, getFieldValue, validateFields, getFieldError } = props.form;


<InputItem
        {...getFieldProps('phone', {
          initialValue: formData.phone,
          rules: [{ required: true, message: '请输入手机号' }, { validator: (rule, value) => isPhoneNumber(value.replace(/\s+/g, "")), message: '请输入正确手机号' }]
        })}
        type="phone"
        placeholder="请输入手机号"
      ><span className={style.formText}>手机号码<span className={style.required}>*</span></span></InputItem>

使用validateFields进行验证

validateFields(async (error, value) => {
      for (const key of Object.keys(value)) {
        if (error && error[key] && error[key].errors.length > 0) {
          for (const err of error[key].errors) {
            Toast.fail(err['message'])
            return;
          }
        }
      }
  })

如果使用css module,修改默认样式要用:global 全局注入

:global {
  .am-list-item{
    background-color:transparent!important;
  }
}

总结

这个项目是从零开始搭建react的项目,搭建过程重新复习了webpack,也了解到react配置的一些技巧,更好地熟悉react