react简单模版

This commit is contained in:
zhouxhere 2022-01-12 11:33:25 +08:00
commit a4d18ff768
29 changed files with 20956 additions and 0 deletions

17
.babelrc.js Normal file
View File

@ -0,0 +1,17 @@
module.exports = {
presets: [
[
'@babel/preset-env',
{
// Allow importing core-js in entrypoint and use browserlist to select polyfills
useBuiltIns: 'entry',
// Set the corejs version we are using to avoid warnings in console
corejs: 3,
// Exclude transforms that make all code slower
exclude: ['transform-typeof-symbol'],
},
],
['@babel/preset-react', { useBuiltIns: true }],
],
plugins: [],
}

0
.env Normal file
View File

20
.eslintrc.js Normal file
View File

@ -0,0 +1,20 @@
module.exports = {
env: {
browser: true,
node: true,
es6: true,
},
parser: '@babel/eslint-parser',
parserOptions: {
sourceType: 'module',
ecamVersion: 6,
ecmaFeatures: {
jsx: true,
},
},
extends: ['eslint:recommended', 'plugin:react/recommended', 'prettier'],
plugins: ['@babel', 'prettier'],
rules: {
'prettier/prettier': 'error',
},
}

23
.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

21
.prettierrc.js Normal file
View File

@ -0,0 +1,21 @@
module.exports = {
printWidth: 80, //单行长度
tabWidth: 2, //缩进长度
useTabs: false, //使用空格代替tab缩进
semi: false, //句末使用分号
singleQuote: true, //使用单引号
quoteProps: 'as-needed', //仅在必需时为对象的key添加引号
jsxSingleQuote: true, // jsx中使用单引号
trailingComma: 'es5', //多行时尽可能打印尾随逗号
bracketSpacing: true, //在对象前后添加空格-eg: { foo: bar }
jsxBracketSameLine: true, //多属性html标签的>’折行放置
arrowParens: 'always', //单参数箭头函数参数周围使用圆括号-eg: (x) => x
requirePragma: false, //无需顶部注释即可格式化
insertPragma: false, //在已被preitter格式化的文件顶部加上标注
proseWrap: 'preserve',
htmlWhitespaceSensitivity: 'ignore', //对HTML全局空白不敏感
vueIndentScriptAndStyle: false, //不对vue中的script及style标签缩进
endOfLine: 'lf', //结束行形式
embeddedLanguageFormatting: 'auto', //对引用代码进行格式化
}

9
.svgrrc.js Normal file
View File

@ -0,0 +1,9 @@
module.exports = {
prettier: false,
svgo: false,
svgoConfig: {
plugins: [{ removeViewBox: false }],
},
titleProp: true,
ref: true,
}

20214
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

76
package.json Normal file
View File

@ -0,0 +1,76 @@
{
"name": "react-template",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"serve": "webpack serve --open --config webpack.dev.js",
"build": "webpack --config webpack.prod.js",
"pre-commit": "lint-staged"
},
"keywords": [],
"author": "zhouxhere",
"license": "ISC",
"dependencies": {
"axios": "^0.24.0",
"core-js": "^3.20.2",
"moment": "^2.29.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-redux": "^7.2.6",
"react-router-dom": "^6.2.1",
"redux": "^4.1.2",
"redux-thunk": "^2.4.1",
"regenerator-runtime": "^0.13.9"
},
"devDependencies": {
"@babel/core": "^7.16.7",
"@babel/eslint-parser": "^7.16.5",
"@babel/eslint-plugin": "^7.16.5",
"@babel/preset-env": "^7.16.7",
"@babel/preset-react": "^7.16.7",
"@svgr/webpack": "^6.1.2",
"babel-loader": "^8.2.3",
"babel-preset-react-app": "^10.0.1",
"case-sensitive-paths-webpack-plugin": "^2.4.0",
"css-loader": "^6.5.1",
"css-minimizer-webpack-plugin": "^3.3.1",
"dotenv": "^10.0.0",
"dotenv-webpack": "^7.0.3",
"eslint": "^8.6.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.28.0",
"eslint-webpack-plugin": "^3.1.1",
"html-webpack-plugin": "^5.5.0",
"husky": "^7.0.4",
"less": "^4.1.2",
"less-loader": "^10.2.0",
"lint-staged": "^12.1.7",
"postcss": "^8.4.5",
"postcss-flexbugs-fixes": "^5.0.2",
"postcss-loader": "^6.2.1",
"postcss-normalize": "^10.0.1",
"postcss-preset-env": "^7.1.0",
"prettier": "^2.5.1",
"react-dev-utils": "^12.0.0",
"react-refresh": "^0.11.0",
"resolve-url-loader": "^4.0.0",
"source-map-loader": "^3.0.0",
"style-loader": "^3.3.1",
"terser-webpack-plugin": "^5.3.0",
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.7.1",
"webpack-manifest-plugin": "^4.0.2",
"webpack-merge": "^5.8.0"
},
"lint-staged": {
"src/**/*.{js,jsx,vue}": [
"prettier --write",
"eslint --fix",
"git add"
]
}
}

15
post.config.js Normal file
View File

@ -0,0 +1,15 @@
module.exports = {
plugins: [
'postcss-flexbugs-fixes',
[
'postcss-preset-env',
{
autoprefixer: {
flexbox: 'no-2009',
},
stage: 3,
},
],
'postcss-normalize',
],
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

25
public/index.html Normal file
View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link rel="manifest" href="manifest.json" />
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

BIN
public/logo192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
public/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

25
public/manifest.json Normal file
View File

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

3
public/robots.txt Normal file
View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

16
src/index.js Normal file
View File

@ -0,0 +1,16 @@
import 'core-js'
import 'regenerator-runtime'
import React from 'react'
import ReactDOM from 'react-dom'
import Router from './router'
import { Provider } from 'react-redux'
import store from './store'
const element = (
<Provider store={store}>
<Router />
</Provider>
)
ReactDOM.render(element, document.getElementById('root'))

17
src/layout/basic/index.js Normal file
View File

@ -0,0 +1,17 @@
import React from 'react'
import { useSelector } from 'react-redux'
import { Outlet } from 'react-router-dom'
const BasicLayout = () => {
const count = useSelector((state) => state.count)
return (
<div>
<h2>basic layout</h2>
<p>count:{count}</p>
<Outlet />
</div>
)
}
export default BasicLayout

24
src/page/home/index.js Normal file
View File

@ -0,0 +1,24 @@
import React, { useCallback } from 'react'
import { useDispatch } from 'react-redux'
const Home = () => {
const dispatch = useDispatch()
const addCount = useCallback(() => {
dispatch({ type: 'ADD_COUNT' })
}, [dispatch])
const delCount = useCallback(() => {
dispatch({ type: 'DEL_COUNT' })
}, [dispatch])
return (
<div>
<h3>home page</h3>
<button onClick={addCount}>add</button>
<button onClick={delCount}>del</button>
</div>
)
}
export default Home

18
src/router/index.js Normal file
View File

@ -0,0 +1,18 @@
import React from 'react'
import { BrowserRouter, Route, Routes } from 'react-router-dom'
import BasicLayout from '../layout/basic'
import Home from '../page/home'
const Router = () => {
return (
<BrowserRouter>
<Routes>
<Route path='/' element={<BasicLayout />}>
<Route element={<Home />} index />
</Route>
</Routes>
</BrowserRouter>
)
}
export default Router

View File

@ -0,0 +1,39 @@
// 将 redux 数据 持久化 至 localstorage
const storageEnhancer = (next) => (reducer) => {
let store = next(reducer)
let persistedState
let initialState = store.getState()
try {
persistedState = JSON.parse(localStorage.getItem('redux'))
if (persistedState && initialState) {
Object.keys(initialState).forEach((key) => {
if (persistedState[key]) {
initialState[key] = persistedState[key]
}
})
store = next(reducer, initialState)
}
if (initialState) {
localStorage.setItem('redux', JSON.stringify(initialState))
}
} catch (e) {
console.warn('Failed to retrieve initialize state from localStorage:', e)
}
// 主要代码
store.subscribe(async function () {
const state = await store.getState()
try {
localStorage.setItem('redux', JSON.stringify(state))
} catch (e) {
console.warn('Unable to persist state to localStorage:', e)
}
})
return store
}
export default storageEnhancer

17
src/store/index.js Normal file
View File

@ -0,0 +1,17 @@
import { createStore, combineReducers, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import storageEnhancer from './enhancer/storage'
import loggerMiddleware from './middleware/logger'
import countReducer from './reducer/count'
import todoReducer from './reducer/todo'
const reducer = combineReducers({
todo: todoReducer,
count: countReducer,
})
const middlewareEnhancer = applyMiddleware(loggerMiddleware, thunk)
const composedEnhancers = compose(middlewareEnhancer, storageEnhancer)
const store = createStore(reducer, composedEnhancers)
export default store

View File

@ -0,0 +1,10 @@
const loggerMiddleware = (store) => (next) => (action) => {
console.group(action.type)
console.info('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
console.groupEnd()
return result
}
export default loggerMiddleware

View File

@ -0,0 +1,22 @@
import { storeTool } from '../../util/store'
const addCount = (countState) => {
return countState + 1
}
const setCount = (countState, param) => {
return param
}
const delCount = (countState) => {
return countState <= 0 ? 0 : countState - 1
}
const countReducer = storeTool.createReducer(0, {
ADD_COUNT: addCount,
SET_COUNT: setCount,
DEL_COUNT: delCount,
})
export const countActions = { addCount, setCount, delCount }
export default countReducer

30
src/store/reducer/todo.js Normal file
View File

@ -0,0 +1,30 @@
import { storeTool } from '../../util/store'
const addTodo = (todoState, param) => {
const newTodo = todoState.concat(param)
return newTodo
}
const editTodo = (todoState, param) => {
const _index = todoState.findIndex((item) => item.id === param.id)
if (_index < 0) return newTodo
const newTodo = storeTool.updateItemInArray(todoState, _index, (item) => {
return storeTool.updateObject(item, param)
})
return newTodo
}
const delTodo = (todoState, param) => {
const _index = todoState.findIndex((item) => item.id === param.id)
if (_index < 0) return newTodo
const newTodo = todoState.splice(_index, 1)
return newTodo
}
const todoReducer = storeTool.createReducer([{ id: 1, name: 'hello' }], {
ADD_TODO: addTodo,
EDIT_TODO: editTodo,
DEL_TODO: delTodo,
})
export default todoReducer

25
src/util/request.js Normal file
View File

@ -0,0 +1,25 @@
import axios from 'axios'
const defaultConfig = {
baseURL: process.env.BASE_URL,
timeout: 5 * 1000,
}
const requestHandler = (config) => {
return config
}
const responseHandler = (response) => {
return response
}
const errorHandler = (error) => {
return Promise.reject(error)
}
const request = axios.create(defaultConfig)
request.interceptors.request.use(requestHandler, errorHandler)
request.interceptors.response.use(responseHandler, errorHandler)
export default request

28
src/util/store.js Normal file
View File

@ -0,0 +1,28 @@
const updateObject = (oldVal, newVal) => {
return Object.assign({}, oldVal, newVal)
}
const updateItemInArray = (array, index, updateCallback) => {
const updatedArray = array.map((_item, _index) => {
if (_index !== index) {
return _item
}
const updatedItem = updateCallback(_item)
return updatedItem
})
return updatedArray
}
const createReducer = (initialState, handlers) => {
return function reducer(state = initialState, action) {
if (Object.prototype.hasOwnProperty.call(handlers, action.type)) {
return handlers[action.type](state, action)
} else {
return state
}
}
}
export const storeTool = { createReducer, updateObject, updateItemInArray }

179
webpack.common.js Normal file
View File

@ -0,0 +1,179 @@
const path = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { WebpackManifestPlugin } = require('webpack-manifest-plugin')
const ESLintPlugin = require('eslint-webpack-plugin')
const Dotenv = require('dotenv-webpack')
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
assetModuleFilename: 'static/media/[name].[hash][ext]',
},
module: {
rules: [
{
test: [/\.avif$/],
type: 'asset',
mimetype: 'image/avif',
parser: {
dataUrlCondition: {
maxSize: 1024 * 10,
},
},
},
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 1024 * 10,
},
},
},
{
type: 'asset',
resourceQuery: /url/,
},
{
test: /\.svg$/,
issuer: {
and: [/\.(ts|tsx|js|jsx|md|mdx)$/],
},
use: [require.resolve('@svgr/webpack')],
},
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
exclude: /(node_modules|bower_components)/,
loader: require.resolve('babel-loader'),
options: {
cacheDirectory: true,
cacheCompression: false,
},
},
{
test: /\.css$/,
exclude: /\.module\.css$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
modules: {
mode: 'icss',
},
},
},
require.resolve('postcss-loader'),
],
sideEffects: true,
},
{
test: /\.module\.css$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
modules: true,
},
},
require.resolve('postcss-loader'),
],
},
{
test: /\.less$/,
exclude: /\.module\.less$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 3,
modules: {
mode: 'icss',
},
},
},
require.resolve('postcss-loader'),
require.resolve('less-loader'),
],
sideEffects: true,
},
{
test: /\.module\.less$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 3,
modules: true,
},
},
require.resolve('postcss-loader'),
require.resolve('less-loader'),
],
},
],
},
resolve: {
modules: ['node_modules'],
extensions: [
'web.mjs',
'mjs',
'web.js',
'js',
'web.ts',
'ts',
'web.tsx',
'tsx',
'json',
'web.jsx',
'jsx',
'...',
],
alias: {
'react-native': 'react-native-web',
src: path.resolve(__dirname, 'src'),
},
},
plugins: [
new HtmlWebpackPlugin({
inject: true,
template: path.resolve(__dirname, 'public/index.html'),
}),
new ESLintPlugin({
extensions: ['js', 'jsx', 'ts', 'tsx'],
}),
new WebpackManifestPlugin({
fileName: 'asset-manifest.json',
// publicPath:
generate: (seed, files, entrypoints) => {
const manifestFiles = files.reduce((manifest, file) => {
manifest[file.name] = file.path
return manifest
}, seed)
const entrypointFiles = entrypoints.main.filter(
(fileName) => !fileName.endsWith('.map')
)
return {
files: manifestFiles,
entrypoints: entrypointFiles,
}
},
}),
new webpack.IgnorePlugin({
resourceRegExp: /^\.\/locale$/,
contextRegExp: /moment$/,
}),
new Dotenv(),
],
cache: {
type: 'memory',
},
}

18
webpack.dev.js Normal file
View File

@ -0,0 +1,18 @@
const CaseSensitivePathsWebpackPlugin = require('case-sensitive-paths-webpack-plugin')
const { merge } = require('webpack-merge')
const common = require('./webpack.common')
module.exports = merge(common, {
mode: 'development',
devtool: 'inline-source-map',
output: {
filename: 'static/js/bundle.js',
chunkFilename: 'static/js/[name].chunk.js',
},
plugins: [new CaseSensitivePathsWebpackPlugin()],
devServer: {
static: './public',
hot: true,
proxy: {},
},
})

65
webpack.prod.js Normal file
View File

@ -0,0 +1,65 @@
const { merge } = require('webpack-merge')
const common = require('./webpack.common')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const TerserPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
module.exports = merge(common, {
mode: 'production',
output: {
fileName: 'static/js/[name].[contenthash:8].js',
chunkFilename: 'static/js/[name].[contenthash:8].chunk.js',
},
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
parse: {
// We want terser to parse ecma 8 code. However, we don't want it
// to apply any minification steps that turns valid ecma 5 code
// into invalid ecma 5 code. This is why the 'compress' and 'output'
// sections only apply transformations that are ecma 5 safe
// https://github.com/facebook/create-react-app/pull/4234
ecma: 8,
},
compress: {
ecma: 5,
warnings: false,
// Disabled because of an issue with Uglify breaking seemingly valid code:
// https://github.com/facebook/create-react-app/issues/2376
// Pending further investigation:
// https://github.com/mishoo/UglifyJS2/issues/2011
comparisons: false,
// Disabled because of an issue with Terser breaking valid code:
// https://github.com/facebook/create-react-app/issues/5250
// Pending further investigation:
// https://github.com/terser-js/terser/issues/120
inline: 2,
},
mangle: {
safari10: true,
},
// Added for profiling in devtools
keep_classnames: false,
keep_fnames: false,
output: {
ecma: 5,
comments: false,
// Turned on because emoji and regex is not minified properly using default
// https://github.com/facebook/create-react-app/issues/2488
ascii_only: true,
},
},
}),
new CssMinimizerPlugin(),
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
}),
],
})