Skip to content

React 项目搭建 ​

一、创建项目 ​

bash
yarn global add create-react-app
yarn create react-app demo
yarn create react-app demo --template typescript
yarn global add create-react-app
yarn create react-app demo
yarn create react-app demo --template typescript

二、目录结构 ​

src/
  assets/        — 静态资源
  common/        — 公共工具
  components/    — 公共组件
  router/        — 路由配置
  store/         — 状态管理
src/
  assets/        — 静态资源
  common/        — 公共工具
  components/    — 公共组件
  router/        — 路由配置
  store/         — 状态管理

三、配置 Webpack ​

安装 ​

bash
yarn add react-app-rewired customize-cra -D
yarn add react-app-rewired customize-cra -D

package.json ​

json
"scripts": {
  "start": "react-app-rewired start",
  "build": "react-app-rewired build",
  "test": "react-app-rewired test",
  "eject": "react-scripts eject"
}
"scripts": {
  "start": "react-app-rewired start",
  "build": "react-app-rewired build",
  "test": "react-app-rewired test",
  "eject": "react-scripts eject"
}

config-overrides.js ​

jsx
const { override, addLessLoader, fixBabelImports, addWebpackAlias } = require('customize-cra')
const path = require('path')

module.exports = override(
  fixBabelImports('import', { libraryName: 'antd-mobile', style: 'css' }),
  addLessLoader(),
  addWebpackAlias({
    '@': path.resolve(__dirname, 'src'),
    components: path.resolve(__dirname, 'src/components')
  })
)
const { override, addLessLoader, fixBabelImports, addWebpackAlias } = require('customize-cra')
const path = require('path')

module.exports = override(
  fixBabelImports('import', { libraryName: 'antd-mobile', style: 'css' }),
  addLessLoader(),
  addWebpackAlias({
    '@': path.resolve(__dirname, 'src'),
    components: path.resolve(__dirname, 'src/components')
  })
)

跨域代理 ​

jsx
const devServerConfig = () => config => ({
  ...config,
  compress: true,
  proxy: {
    '/api': {
      target: 'https://api.example.com',
      changeOrigin: true
    }
  }
})

module.exports = {
  webpack: override(/* ... */),
  devServer: overrideDevServer(devServerConfig())
}
const devServerConfig = () => config => ({
  ...config,
  compress: true,
  proxy: {
    '/api': {
      target: 'https://api.example.com',
      changeOrigin: true
    }
  }
})

module.exports = {
  webpack: override(/* ... */),
  devServer: overrideDevServer(devServerConfig())
}

打包配置 ​

jsx
const addCustomize = () => config => {
  if (process.env.NODE_ENV === 'production') {
    config.devtool = false
    config.output.path = __dirname + '../dist/demo'
    config.output.publicPath = './demo'
    config.plugins.push(
      new CompressionWebpackPlugin({
        test: /\.js$|\.css$/,
        threshold: 1024
      })
    )
  }
  return config
}
const addCustomize = () => config => {
  if (process.env.NODE_ENV === 'production') {
    config.devtool = false
    config.output.path = __dirname + '../dist/demo'
    config.output.publicPath = './demo'
    config.plugins.push(
      new CompressionWebpackPlugin({
        test: /\.js$|\.css$/,
        threshold: 1024
      })
    )
  }
  return config
}

生产环境去除 console ​

jsx
const dropConsole = () => config => {
  if (config.optimization.minimizer) {
    config.optimization.minimizer.forEach(minimizer => {
      if (minimizer.constructor.name === 'TerserPlugin') {
        minimizer.options.terserOptions.compress.drop_console = true
      }
    })
  }
  return config
}
const dropConsole = () => config => {
  if (config.optimization.minimizer) {
    config.optimization.minimizer.forEach(minimizer => {
      if (minimizer.constructor.name === 'TerserPlugin') {
        minimizer.options.terserOptions.compress.drop_console = true
      }
    })
  }
  return config
}

四、修改端口号 ​

bash
# 方法一:启动时设置
"start": "set PORT=9000 && react-scripts start"
# 方法一:启动时设置
"start": "set PORT=9000 && react-scripts start"

五、引入图片 ​

jsx
import IMG from '../assets/logo.png'

// 或通过 require
state = { img: require('../assets/logo.png') }

<img src={require('@/assets/logo.png')} />
import IMG from '../assets/logo.png'

// 或通过 require
state = { img: require('../assets/logo.png') }

<img src={require('@/assets/logo.png')} />

六、Stylus 支持 ​

bash
npm run eject
yarn add stylus stylus-loader
npm run eject
yarn add stylus stylus-loader

修改 config/webpack.config.js:

jsx
oneOf: [
  {
    test: /\.styl$/,
    loaders: ['style-loader', 'css-loader', 'stylus-loader']
  }
]
oneOf: [
  {
    test: /\.styl$/,
    loaders: ['style-loader', 'css-loader', 'stylus-loader']
  }
]

七、装饰器支持 ​

bash
npm i @babel/plugin-proposal-decorators
npm i @babel/plugin-proposal-decorators
jsx
const { override, addDecoratorsLegacy } = require('customize-cra')
module.exports = override(addDecoratorsLegacy())
const { override, addDecoratorsLegacy } = require('customize-cra')
module.exports = override(addDecoratorsLegacy())

八、px 转 rem ​

bash
npm i postcss-pxtorem -D
npm i postcss-pxtorem -D
jsx
addPostcssPlugins([require('postcss-pxtorem')({
  rootValue: 75,
  propList: ['*'],
  minPixelValue: 2,
  selectorBlackList: ['am-']
})])
addPostcssPlugins([require('postcss-pxtorem')({
  rootValue: 75,
  propList: ['*'],
  minPixelValue: 2,
  selectorBlackList: ['am-']
})])

九、代码规范 ​

Prettier ​

bash
npm install --save-dev --save-exact prettier
echo {}> .prettierrc.json
npm install --save-dev --save-exact prettier
echo {}> .prettierrc.json

ESLint ​

bash
npx eslint --init
npx eslint --init

Stylelint ​

bash
npm install --save-dev stylelint stylelint-config-standard stylelint-config-prettier
npm install --save-dev stylelint stylelint-config-standard stylelint-config-prettier

Commitlint ​

bash
npm install --save-dev @commitlint/config-conventional @commitlint/cli
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js
npm install --save-dev @commitlint/config-conventional @commitlint/cli
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js

lint-staged ​

json
"lint-staged": {
  "**/*.{ts,tsx}": "eslint . --cache --fix",
  "**/*.{css,less}": "stylelint --fix",
  "**/*": "prettier --write --ignore-unknown"
}
"lint-staged": {
  "**/*.{ts,tsx}": "eslint . --cache --fix",
  "**/*.{css,less}": "stylelint --fix",
  "**/*": "prettier --write --ignore-unknown"
}

Husky ​

bash
pnpm add husky -D
pnpm husky
npx husky add .husky/commit-msg
npx husky add .husky/pre-commit
pnpm add husky -D
pnpm husky
npx husky add .husky/commit-msg
npx husky add .husky/pre-commit

十、搭建 Redux + 持久化 ​

bash
npm i redux react-redux redux-persist redux-devtools-extension redux-thunk
npm i redux react-redux redux-persist redux-devtools-extension redux-thunk
jsx
import { applyMiddleware, createStore } from 'redux'
import thunk from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
import { persistReducer, persistStore } from 'redux-persist'
import storage from 'redux-persist/lib/storage'

const persistConfig = { key: 'root', storage }
const persistedReducer = persistReducer(persistConfig, rootReducer)

export const store = createStore(persistedReducer, composeWithDevTools(applyMiddleware(thunk)))
export const persistor = persistStore(store)
import { applyMiddleware, createStore } from 'redux'
import thunk from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
import { persistReducer, persistStore } from 'redux-persist'
import storage from 'redux-persist/lib/storage'

const persistConfig = { key: 'root', storage }
const persistedReducer = persistReducer(persistConfig, rootReducer)

export const store = createStore(persistedReducer, composeWithDevTools(applyMiddleware(thunk)))
export const persistor = persistStore(store)

十一、Hooks 版小型 Redux ​

context/reducer.js ​

js
export function reducer(state, { type, ...payloads }) {
  switch (type) {
    case 'SET_VALUE':
      return { ...state, [payloads.key]: payloads.value }
    case 'DELETE_VALUE': {
      const curState = { ...state }
      delete curState[payloads.key]
      return curState
    }
    default:
      return state
  }
}
export function reducer(state, { type, ...payloads }) {
  switch (type) {
    case 'SET_VALUE':
      return { ...state, [payloads.key]: payloads.value }
    case 'DELETE_VALUE': {
      const curState = { ...state }
      delete curState[payloads.key]
      return curState
    }
    default:
      return state
  }
}

context/index.js ​

jsx
import React, { useReducer } from 'react'
import reducer from './reducer'

const initState = {}
const Context = React.createContext(undefined)

export function usePageContext() {
  const ctx = React.useContext(Context)
  if (!ctx) throw new Error('必须在 Provider 中使用')
  return ctx
}

export function PageContext({ children }) {
  const [state, dispatch] = useReducer(reducer, initState)
  const setValue = (key, value) => dispatch({ type: 'SET_VALUE', key, value })
  const delValue = (key) => dispatch({ type: 'DELETE_VALUE', key })

  return (
    <Context.Provider value={{ state, dispatch, setValue, delValue }}>
      {children}
    </Context.Provider>
  )
}
import React, { useReducer } from 'react'
import reducer from './reducer'

const initState = {}
const Context = React.createContext(undefined)

export function usePageContext() {
  const ctx = React.useContext(Context)
  if (!ctx) throw new Error('必须在 Provider 中使用')
  return ctx
}

export function PageContext({ children }) {
  const [state, dispatch] = useReducer(reducer, initState)
  const setValue = (key, value) => dispatch({ type: 'SET_VALUE', key, value })
  const delValue = (key) => dispatch({ type: 'DELETE_VALUE', key })

  return (
    <Context.Provider value={{ state, dispatch, setValue, delValue }}>
      {children}
    </Context.Provider>
  )
}

十二、antd 使用技巧 ​

防止 message 重复弹出:

jsx
state = { timer: null }

message2 = (msg, delay) => {
  if (this.state.timer) return
  message.error(msg)
  this.state.timer = setTimeout(() => {
    this.state.timer = null
  }, delay)
}
state = { timer: null }

message2 = (msg, delay) => {
  if (this.state.timer) return
  message.error(msg)
  this.state.timer = setTimeout(() => {
    this.state.timer = null
  }, delay)
}

十三、百度埋点 ​

jsx
// utils/tracking.js
if (process.env.NODE_ENV === 'production') {
  var _hmt = _hmt || []
  ;(function() {
    var hm = document.createElement('script')
    hm.src = 'https://hm.baidu.com/hm.js?xxx'
    document.head.appendChild(hm)
  })()
  _hmt.push(['_setAutoPageview', false])
}
// utils/tracking.js
if (process.env.NODE_ENV === 'production') {
  var _hmt = _hmt || []
  ;(function() {
    var hm = document.createElement('script')
    hm.src = 'https://hm.baidu.com/hm.js?xxx'
    document.head.appendChild(hm)
  })()
  _hmt.push(['_setAutoPageview', false])
}
jsx
// 使用
import { trackData } from 'utils'
trackData(['_trackPageview', pathname])
// 使用
import { trackData } from 'utils'
trackData(['_trackPageview', pathname])