做项目,学习技术,学习框架都少不了项目框架的搭建;这里记录下自己搭建框架的过程。框架技术点react+TypeScript+redux+ant
yarn create react-app mini-turn-pc --template typescript
#or
npx create-react-app mini-turn-pc --template typescript
这里使用的是yarn,如果你使用的是 npm也没问题。
然后我们进入项目并启动。
cd mini-turn-pc
yarn start
此时浏览器会访问 http://localhost:3000 ,看到 Welcome to React 的界面第一步就算成功了。
yarn add antd
如果在App.tsx引入全部的 antd 组件的样式(对前端性能是个隐患)。一般采用按需加载,按组件来加载所需要的样式。
yarn add react-app-rewired customize-cra babel-plugin-import
babel-plugin-import 是一个用于按需加载组件代码和样式的 babel 插件
修改package.json配置
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
},
然后在项目根目录创建一个 config-overrides.js 用于修改默认配置。
// const rewireLess = require('react-app-rewire-less');
const {
override, fixBabelImports, addLessLoader,addWebpackAlias,addDecoratorsLegacy } = require('customize-cra');
const path = require('path')
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: true,//为true时支持less解析,自定义主题需要用到 less 变量覆盖功能
}),
addLessLoader({
javascriptEnabled: true,
modifyVars: {
"@primary-color": "#1DA57A" ,// 全局主色
"@link-color " : "#1DA57A", // 链接色
"@success-color " : "#52c41a", // 成功色
"@warning-color " : "#faad14", // 警告色
"@error-color " : "#f5222d", // 错误色
"@font-size-base " : "14px", // 主字号
"@heading-color " : "rgba(0, 0, 0, 0.85)", // 标题色
"@text-color " : "rgba(0, 0, 0, 0.65)", // 主文本色
"@text-color-secondary " : "rgba(0, 0, 0, 0.45)", // 次文本色
"@disabled-color " : "rgba(0, 0, 0, 0.25)", // 失效色
"@border-radius-base " : "4px", // 组件/浮层圆角
"@border-color-base " : "#d9d9d9", // 边框色
"@box-shadow-base " : "0 2px 8px rgba(0, 0, 0, 0.15)", // 浮层阴影
},
}),
addDecoratorsLegacy()
);
修改后重启 yarn start,如果看到一个绿色的按钮就说明配置成功了。
到这里react+ant配置就算完成了。
在前端3大框架中,不管是哪个都少不了状态管理。在这里使用的是redux,redux不只能在react中使用,也能在angular中配合服务使用。
首先加入包:
yarn add redux redux-thunk react-redux
若是出现找不到module的情况则需要加入 yarn add @types/react-redux
React-Redux是Redux的官方React绑定库。它能够使你的React组件从Redux store中读取数据,并且向store分发actions以更新数据。redux的原理这里不做介绍有兴趣的可自行查阅资料学习。
快捷链接-_-: React-Redux 中文文档.
这里就写上 redux 的demo:
(1) 创建常量文件: redux/constant/count.ts
export const ADD = 'ADD'
export const MINUS = 'MINUS'
(2) 创建actions文件: redux/actions/count.ts
import {
ADD, MINUS } from "../constant/count"
export const add = () => {
return {
type: ADD
}
}
export const minus = () => {
return {
type: MINUS
}
}
// 异步的action
export function asyncAdd () {
return (dispatch:any) => {
setTimeout(() => {
dispatch(add())
}, 2000)
}
}
(3) 创建reducer文件: redux/reducers/count.ts
import {
ADD, MINUS } from "../constant/count"
const INITIAL_STATE = {
num: 0
}
export default function counter (state = INITIAL_STATE, action:any) {
switch (action.type) {
case ADD:
return {
...state,
num: state.num + 1
}
case MINUS:
return {
...state,
num: state.num - 1
}
default:
return state
}
}
(4) 创建总文件index用来统一管理reducer文件 : redux/reducers/index.ts
(在actions和constant目录下都可以创建一个index统一来管理)
import {
combineReducers } from 'redux';
import counter from './count';
export default combineReducers({
counter
})
```
(5) 创建store文件 : redux/store.ts
```javascript
import {
createStore, combineReducers,applyMiddleware } from "redux";
import thunk from 'redux-thunk';
import reducers from "./reducers/index";
const middlewares = [thunk]
// 全局就管理一个store
export const store = createStore(
reducers,
applyMiddleware(...middlewares)
);
const unsbscribe = store.subscribe(() => {
console.log(store.getState());
});
(6) 在components下创建Count组件 : components/Count.tsx
import React, {
Component } from 'react'
import {
connect } from 'react-redux'
interface IProps{
onAdd : any,
onCut : any,
counter : {
num : number
}
}
class Counter extends Component<IProps,{
}> {
constructor(props:any){
super(props)
}
componentDidMount(){
// console.log(this.props);
console.log(this.props);
}
increment(){
console.log();
this.props.onAdd();
}
decrement(){
this.props.onCut();
}
render() {
return (
<p>
Clicked: {
this.props.counter.num} times
{
' '}
<button onClick={
this.increment.bind(this)}>+</button>
{
' '}
<button onClick={
this.decrement.bind(this)}>-</button>
</p>
)
}
}
//将state绑定到props的counter
const mapStateToProps = (state:any)=> {
console.log(state)
return {
counter: state.counter
}
};
//将action的所有方法绑定到props上
const mapDispatchToProps = (dispatch:any) => {
return {
onAdd: (counter:any)=> {
dispatch({
type: "ADD",counts:counter});
},
onCut: (counter:any)=> {
dispatch({
type: "MINUS",counts:counter});
}
};
};
//通过react-redux提供的connect方法将我们需要的state中的数据和actions中的方法绑定到props上
export default connect(
mapStateToProps,
mapDispatchToProps)(Counter);
(7) 在App.tsx组件中引入store.ts
import React, {
Component } from 'react';
import {
Provider} from 'react-redux';
import zhCN from 'antd/lib/locale-provider/zh_CN';
import {
ConfigProvider } from 'antd';
import * as serviceWorker from './serviceWorker';
import {
store } from "./redux/store"
/* 主路由 */
import IndexRouter from "./routes/IndexRouter";
// import { index } from "@actions/index";
// import { request } from "@services/request";
import './App.less';
class App extends Component<{
}> {
constructor(props:any){
super(props);
}
componentDidMount(){
// console.log(index)
// request({url:index}).then(res => {
// console.log(res);
// }).catch(error => {
// console.log(error);
// })
}
render() {
return (
<ConfigProvider locale={
zhCN}>
<Provider store={
store}>
<IndexRouter />
</Provider>
</ConfigProvider>
);
}
}
serviceWorker.unregister();
export default App;
(8) 在index组件引入Count组件
import React, {
Component } from 'react';
import {
Button,Layout } from 'antd';
import Counter from "@components/Count";
const {
Header, Footer, Sider, Content } = Layout;
interface iniData{
isLogin : boolean
}
class Index extends Component<iniData,iniData> {
constructor(props:any){
super(props);
this.state = {
isLogin : false
}
}
componentDidMount(){
document.title = 'index'
}
render() {
return (
<div>
<Layout>
<Header>Header</Header>
<Content>Content</Content>
<Counter />
<Button>{
this.state.isLogin?"login":"not_login"}</Button>
<Footer>Footer</Footer>
</Layout>
</div>
);
}
}
export default Index;
使用React构建的单页面应用,要想实现页面间的跳转,首先想到的就是使用路由。在React中,常用的有两个包可以实现这个需求,那就是react-router和react-router-dom。这里采用的是react-router-dom,如何使用自行查阅资料,这里只展示配置:
yarn add @types/react-router-dom
为了方便管理这里分了routs目录:
为了后面的layout布局,这里对路由按模块进行拆分
routes/IndexRouter.tsx 总路由文件
import React,{
Component} from "react";
import {
Route, BrowserRouter,Switch } from 'react-router-dom';
import Index from "@pages/index";
import pages4 from "@pages/pages4";
import Count from "@components/Count";
import Login from "@pages/sign/login/login";
/* router */
import AdminRouter from "./AdminRouter";
class IndexRouter extends Component {
render() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/" component={
Login} />
<Route path="/login" component={
Login} />
<Route path="/index" component={
Index} />
<Route path="/count" component={
Count} />
<Route path="/pages4/:id" component={
pages4} />
</Switch>
<Route path="/admin">
<AdminRouter />
</Route>
</BrowserRouter>
);
}
}
export default IndexRouter;
routes/AdminRouter.tsx 子路由文件
import React,{
Component} from "react";
import {
Route,Switch } from 'react-router-dom';
import AdminLayout from "@pages/layouts/admin-layout";
import Admin from "@pages/Admin";
import Home from "@pages/admin/home";
class AdminRouter extends Component {
render() {
return (
<AdminLayout>
<Switch>
<Route path="/admin" exact component={
Admin} />
<Route path="/admin/home" exact component={
Home} />
</Switch>
</AdminLayout>
);
}
}
export default AdminRouter;
react-router-dom 4.*版本后不在支持嵌套路由,必需写全路由。
一般的项目中,分为3种layout布局,首页、登录注册、个人中心等。其他的看就项目需要了。
一般每个页面中需要去引用header,footer,nav,menu,以及工具组件等共用性的组件。
采用layouts和路由模块拆分后,大大的减少了这些共用性组件的频繁引入。
无论是哪个项目都少不了,ajax请求。在react中有fetch来做ajax请求。当然你习惯了axios也是没问题的。
为了方便对状态的管理这里对fetch进行了简单的封装.
services/requestFetch.ts
import {
message } from 'antd';
import {
MethodType } from "./status";
import {
config } from "../config/config";
const checkStatus = (res:any) => {
if (res.status == 200) {
return res;
} else {
message.error(`网络请求失败,${
res.status}`);
}
};
/**
*用来捕获登录过期状态码等
* @param res
* @returns {*}
*/
const judgeOkState = async (res:any) => {
const cloneRes = await res.clone().json();
//TODO:可以在这里管控全局请求
// console.log('cloneRes', cloneRes);
return res;
};
/**
* 捕获失败
* @param e
*/
const handleError = (e:any) => {
if (e instanceof TypeError) {
message.error(`网络请求失败`);
}
};
class http {
/**
* 静态的fetch请求通用方法
* @param url
* @param options
* @returns {Promise}
*/
static async staticFetch(url:string, options:any): Promise<any> {
const defaultOptions = {
/*允许携带cookies*/
credentials: 'include',
/*允许跨域**/
mode: 'cors',
headers: {
token: '',
// 当请求方法是POST,如果不指定content-type是其他类型的话,默认为如下↓,要求参数传递样式为 key1=value1&key2=value2,但实际场景以json为多
// 'content-type': 'application/x-www-form-urlencoded',
},
};
const newOptions = {
...defaultOptions, ...options };
return fetch(url, newOptions)
.then(checkStatus)
.then(judgeOkState)
.then((res:any) => res.json())
.catch(handleError);
}
/**
*post请求方式
* @param url
* @returns {Promise}
*/
post(url:string, params = {
}) {
//一般我们常用场景用的是json,所以需要在headers加Content-Type类型
let options = {
method :MethodType.POST,
headers : config.header,
body : JSON.stringify(params)
};
return http.staticFetch(url, options);
}
/**
* put方法
* @param url
* @returns {Promise}
*/
put(url:any, params:any) {
let options = {
method :MethodType.PUT,
headers : config.header,
body : JSON.stringify(params)
};
return http.staticFetch(url, options);
}
/**
* get请求方式
* @param url
* @param options
*/
get(url:any, params:any) {
let str = "";
Object.keys(params).forEach(function(val){
str += val + '=' + encodeURIComponent(params[val]) + '&';
})
let options = {
method :MethodType.GET,
};
return http.staticFetch(url+str, options);
}
}
const requestFetch = new http(); //new生成实例
export default requestFetch;
services/status.ts
export const MethodType = {
GET: 'GET',
POST: 'POST',
PUT: 'PUT',
DELETE: 'DELETE',
PATCH:'PATCH'
};
config/config.ts
const config = {
basePath: "https://******.com",
header: {
'X-Requested-With': 'XMLHttpRequest',
// "content-type": "application/json;charset=UTF-8"
"content-type": "application/x-www-form-urlencoded"
},
contentType : "application/x-www-form-urlencoded"
// 更多的配置项
}
export {
config }
为了方便管理,api接口url将抽离成独立的目录,在redux中进行引入。
状态什么的,根据自己的项目接口状态进行调整。
在前后端分离的项目中,总会遇到跨域问题.在前端3大框架中都有着proxy代理功能.
react有2种方式设置代理,
一种是http-proxy-middleware ;
引入http-proxy-middleware包:
yarn add http-proxy-middleware
在src目录下创建setupProxy.js文件
const {
createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
// pathRewrite: {
// "^/api/": ""
// },
app.use(
'/api',
createProxyMiddleware({
target: 'https://zmyp.e-stronger.com',
changeOrigin: true,
secure: false,
})
);
};
另一种是直接在package.json加入 “proxy”: "http://m.kugo.com"即可.
路经问题都会带来很多的困扰,3大框架中都有着一个解决办法,别名的配置;
(1) 在config-overrides.js加入以下配置
const {
override, fixBabelImports, addLessLoader,addWebpackAlias,addDecoratorsLegacy } = require('customize-cra');
const path = require('path')
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: true,
}),
addLessLoader({
javascriptEnabled: true,
modifyVars: {
"@primary-color": "#1DA57A" ,// 全局主色
"@link-color " : "#1DA57A", // 链接色
"@success-color " : "#52c41a", // 成功色
"@warning-color " : "#faad14", // 警告色
"@error-color " : "#f5222d", // 错误色
"@font-size-base " : "14px", // 主字号
"@heading-color " : "rgba(0, 0, 0, 0.85)", // 标题色
"@text-color " : "rgba(0, 0, 0, 0.65)", // 主文本色
"@text-color-secondary " : "rgba(0, 0, 0, 0.45)", // 次文本色
"@disabled-color " : "rgba(0, 0, 0, 0.25)", // 失效色
"@border-radius-base " : "4px", // 组件/浮层圆角
"@border-color-base " : "#d9d9d9", // 边框色
"@box-shadow-base " : "0 2px 8px rgba(0, 0, 0, 0.15)", // 浮层阴影
},
}),
addWebpackAlias({
"@": path.resolve(__dirname, './src'),
"@assets": path.resolve(__dirname, './src/assets'),
"@static": path.resolve(__dirname, './src/static'),
"@pages": path.resolve(__dirname, './src/pages'),
"@redux": path.resolve(__dirname, './src/redux'),
"@routes": path.resolve(__dirname, './src/routes'),
"@config": path.resolve(__dirname, './src/config'),
"@layouts": path.resolve(__dirname, './src/layouts'),
"@components": path.resolve(__dirname, './src/components'),
"@services": path.resolve(__dirname, './src/services')
}),
addDecoratorsLegacy()
);
如果是用的VSCODE会有警告,需加入下面的配置:
项目根目录下创建paths.json文件
{
"compilerOptions": {
"baseUrl": "./",
"paths" : {
"@src/*" : ["src/"],
"@assets/*": ["src/assets/*"],
"@public/*": ["src/public/*"],
"@redux/*": ["src/redux/*"],
"@pages/*": ["src/pages/*"],
"@components/*": ["src/components/*"],
"@layouts/*": ["src/layouts/*"],
"@config/*": ["src/config/*"],
"@routes/*": ["src/routes/*"],
"@services/*": ["src/services/*"]
}
}
}
(2) 在tsconfig.json中引入paths.json
"extends": "./paths.json"
然后在组件中就可以直接使用@…了,不需要因为路经在…/…/了.
{
"name": "mini-turn-pc",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/js-cookie": "^2.2.5",
"@types/node": "^12.0.0",
"@types/react": "^16.9.0",
"@types/react-dom": "^16.9.0",
"@types/react-redux": "^7.1.7",
"@types/react-router-dom": "^5.1.4",
"antd": "^4.1.4",
"axios": "^0.19.2",
"babel-plugin-import": "^1.13.0",
"customize-cra": "^0.9.1",
"http-proxy-middleware": "^1.0.3",
"react": "^16.13.1",
"react-app-rewire-less": "^2.1.3",
"react-app-rewired": "^2.1.5",
"react-dom": "^16.13.1",
"react-redux": "^7.2.0",
"react-router-dom": "^5.1.2",
"react-scripts": "3.4.1",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0",
"typescript": "~3.7.2"
},
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
最后,此篇文章记录着react的学习成果,也是react项目应用级框架搭建思路的整理,也是方便以后开发项目的时候不用每次都去搭建,拿来扩展就好了.