本教程不用react-script
这种工具,用的webpack
+ eslint(可选)
+ prettier(可选)
+ babel
,使用编辑器用的vscode。
以下会涉及到ts语法和redux
、react-router-dom
的一些技巧,也是入门时学习到的。
# babel
yarn add @babel/core @babel/preset-reac @babel/preset-typescript @babel/preset-env -D
# webpack
yarn add webpack webpack-cli webpack-dev-server html-webpack-plugin babel-loader css-loader fork-ts-checker-webpack-plugin -D
# react-hot-reload
yarn add @pmmmwh/react-refresh-webpack-plugin react-refresh type-fest -D
# react 相关
yarn add react react-dom react-redux react-router-dom redux-thunk
# 以上的这些react相关全部装个type,例子
# yarn add @types/react @types/react-dom ... -D
# eslint 可选
yarn add babel-eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint eslint-config-prettier eslint-plugin-prettier eslint-plugin-react-hooks -D
这里我的工程文件都放在./src
里
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@root/*": ["src/*"]
},
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": true,
"lib": [
"es6",
"es7",
"dom"
],
"target": "es5",
"jsx": "react",
"allowJs": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true
},
"include": [
"./src/**/*"
]
}
{
"presets": [
"@babel/preset-env",
"@babel/preset-typescript",
"@babel/preset-react"
]
}
module.exports = {
root: true,
env: {
commonjs: true,
es6: true,
browser: true,
node: true,
},
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'prettier'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
'plugin:prettier/recommended',
'prettier',
],
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
ignorePatterns: ['dist', 'lib'],
rules: {
'@typescript-eslint/no-var-requires': 0,
'@typescript-eslint/no-unused-vars': 0,
'prefer-const': 0,
'@typescript-eslint/no-explicit-any': 0,
'react-hooks/exhaustive-deps': 0,
},
}
{
"semi": false,
"singleQuote": true
}
const path = require('path')
const HTMLWebpackPlugin = require('html-webpack-plugin')
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
module.exports = {
entry: './src/index.tsx',
output: {
filename: 'app.js',
path: path.join(__dirname, './dist'),
publicPath: '/',
},
context: path.resolve(__dirname, './src'),
devtool: 'cheap-module-source-map',
devServer: {
hot: true,
// enable HMR on the server
compress: true,
contentBase: path.resolve(__dirname, './src'),
// match the output path
publicPath: URL,
historyApiFallback: true,
},
mode: 'development',
module: {
rules: [
{
test: /\.[tj]sx?$/,
exclude: /node_modules/,
use: ['babel-loader'],
},
{
test: /\.css$/i,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: {
mode: 'global',
localIdentName: '[name]__[local]--[hash:base64:5]',
},
importLoaders: 2,
},
},
],
},
],
},
resolve: {
alias: {
'@root': path.resolve(__dirname, './src'),
},
extensions: ['.ts', '.tsx', '.js', '.json'],
},
plugins: [
// 用来检测开发中ts语法错误问题
new ForkTsCheckerWebpackPlugin({
typescript: {
configFile: path.resolve(__dirname, './tsconfig.json'),
},
}),
new ReactRefreshWebpackPlugin(),
new HTMLWebpackPlugin({
template: './src/app.html',
filename: 'index.html',
}),
],
}
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import App from '@root/App'
ReactDOM.render(<App />, document.getElementById('app'))
import * as React from 'react'
import { Provider } from 'react-redux'
import { BrowserRouter, Route, Switch } from 'react-router-dom'
// 这里redux下面会给写法
import store from './reducers'
const App: React.FC = function App(props) {
return (
<Provider store={store}>
<BrowserRouter>
<Switch>
<Route path="/a1">
<div>page a1</div>
</Route>
<Route path="/">
<div>home</div>
</Route>
</Switch>
</BrowserRouter>
</Provider>
)
}
export default App
这里以多个模块组合为例
// 定义state
export type FolderDstate = {
folders: {
name: string
}[]
isLoading: boolean
loaded: boolean
}
let dstate: FolderDstate = {
folder: [],
isLoading: false,
loaded: false,
}
// 集合所有action
export type FolderAction =
| FolderLoad
| FolderUpdate
type FolderLoad = {
type: 'folder/load'
folders: {
name: string
}[]
}
type FolderUpdate = {
type: 'folder/update'
isLoading?: boolean
loaded?: boolean
}
let folder = (state = dstate, action: FolderAction): FolderDstate => {
switch (action.type) {
case 'folder/update': {
return {
...state,
isLoading: action.isLoading ?? state.isLoading,
loaded: action.loaded ?? state.loaded,
}
}
case 'folder/load': {
return {
...state,
folders: [...action.folders],
}
}
}
export default folder
这里用的switch
是因为vscode会有case
提示
并且每块case
中的变量都是相对独立于上面定义的type的
import { combineReducers, createStore, applyMiddleware, compose } from 'redux'
import thunkMiddleware from 'redux-thunk'
import folder, { FolderAction, FolderDstate } from './folder'
let rootReducer = combineReducers({
folder,
})
let store = (function configureStore() {
// 这里是添加插件使chrome的redux插件能检测并使用
// 这里window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__会被ts爆出错误,以下会给解决方案
const middlewares = [thunkMiddleware]
const middlewaresEnhancer = applyMiddleware(...middlewares)
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(
rootReducer,
composeEnhancers(middlewaresEnhancer)
)
return store
})()
export default store
// 这里放进去所有的模块默认state type
export type StateBase = {
folder: FolderDstate
//other: otherDstate
}
// 这里放进去所有的action集合,用|连接
export type RootActions = FolderAction /*| otherAction */
import * as React from 'react'
import { connect, ConnectedProps } from 'react-redux'
// --- 这里的Dispatch最好用一个ts文件集中管理,这里方便就放一起了 ---
import { RootActions, StateBase } from '@root/reducers'
import { Dispatch } from 'redux'
export const loadFolder = (isForce = false) => (
dispatch: Dispatch<RootActions>,
getState: () => StateBase
): Promise<void> => {
const state = getState()
if (state.folder.isLoading || (!isForce && state.user.loaded)) return
dispatch({ type: 'folder/load', folders: [] })
dispatch({ type: 'folder/update', loaded:true })
}
// -------------
// 定义传入参数,ConnectedProps是把connect里的所有方法和变量都打包一起了
type Props = ConnectedProps<typeof connector> & {
//自定义参数
}
const Comp1: React.FC<Props> = (props) => {
React.useEffect(()=>{
props.loadFolder()
},[])
return <div>{`${props.loaded}`}</div>
}
const mapStateToProps = (state: StateBase) => {
return {
loaded: state.folder.loaded,
}
}
const mapDispatchToProps = {
loadFolder
}
let connector = connect(mapStateToProps, mapDispatchToProps)
export default connector(Comp1)
loadFolder
里的dispatch
方法提示,都会根据type提示需要的参数
import { RootActions } from '@root/reducers'
import { useDispatch } from 'react-redux'
export const useDp = (): React.Dispatch<RootActions> =>
useDispatch<React.Dispatch<RootActions>>()
在./src
下随便建个什么的global.d.ts
,放一些ts没法检测的全局变量
interface Window {
__REDUX_DEVTOOLS_EXTENSION_COMPOSE__: (...any: any) => any
}
export type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K]
}
// 这里FolderDstate的所有属性就变成全选了
type n = DeepPartial<FolderDstate>
如果用了eslint使用object类型他会报错,这样就是动态key了
type dykey = {
[key:string]:any
}
npx webpack serve --config webpack.dev.js
就ok了