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