脚手架设置
/* npm5.2版本之前,执行npm init会在当前目录创建一个package.json文件
npm5.2以后,本质上调用npx,自动将react-app补全为create-react-app,继而下载并创建应用 */
npm init react-app my-admin --typescript
/* 这个命令只能执行一次,而且不可逆转,它会把react-scripts封装的一些webpack配置等全部解压到项目目录,项目结构中会多出config和script两个目录*/
npm eject
多环境配置
create-react-app 的官方文档可以发现,create-react-app 默认是支持多个环境配置文件的:
.env:默认
.env.local:本地替代。该文件适用于除测试以外的所有环境
.env.development,.env.test,.env.production:环境特定的设置
// .env.development .env.production
REACT_APP_NODE_ENV='development'
REACT_APP_URL = '/api/'
REACT_APP_TARGET = 'http://192.168.7.221:8080'
REACT_APP_OUTPUT_DIR = 'tyjr-admin'
REACT_APP_PUBLICPATH = './'
REACT_APP_IMG_URL = 'http://192.168.7.221:8080'
// package.json
"scripts": {
"start": "react-app-rewired start",
"build:dev": "dotenv -e .env.development react-app-rewired build",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
}
如果是ts,还要npm i @types/node -D
设置别名
// config/webpack.config.js
alias: {
// 此时的appSrc 就是项目的src目录
"@":path.resolve("src"),
},
设置代理
// create-react-app的版本在低于2.0,在package.json增加proxy配置
"proxy":{
"/api/**":{
"target":"https://easy-mock.com/mock/5c0f31837214cf627b8d43f0/", //需要代理的地址
"changeOrigin": true
}
}
/* create-react-app的版本高于2.0,在package.json只能配置string类型, 配置成如下:*/
"proxy": "https://easy-mock.com/mock/5c0f31837214cf627b8d43f0/",
// 更好的配置,建立 src/setupProxy.js 文件,安装http-proxy-middleware
const proxy = require("http-proxy-middleware");
module.exports = function(app) {
app.use(
proxy("/api", {
target: "http://localhost:3001/",
changeOrigin: true,
pathRewrite: {
"^/api": ""
}
})
)
// 配置多个代理
app.use(
proxy("/test", {
target: "https://easy-mock.com/mock/5c0f31837214cf627b8d43f0/",
changeOrigin: true
})
);
};
动态生成路由和路由权限判断
先了解react-router-dom:包含了路由组件、路由匹配组件、导航组件
- 路由组件
提供了两种路由组件: BrowserRouter, HashRouter其中BrowserRouter 是基于HTML5 history API (pushState, replaceState, popstate)事件
当然与之对应的还有HashRouter是基于window.location.hash - 路由匹配组件
由匹配组件有两种:Route和Switch,Switch把多个路由组合在一起
对于一个组件,可以设置三种属性:component、render、children来渲染出对应的内容 - 导航组件
有三种常用的导航组件,分别是:<Link>、<NavLink>、<Redirect>
。<NavLink>
是一种特殊的Link组件,匹配路径时,渲染的a标签带有active
react-router和react-router-dom不同之处就是后者比前者多出了 这样的 DOM 类组件
生成路由组件
为什么要生成路由组件?
当hash或者history跳转时,只是路径发生变化,内容不会变,所以还需要生成路径对应的组件(又称路由组件,即通知react去构建这个组件内容)
生成动态路由的方法:这三种方法可以一起使用
// 1、通过Route组件的component属性生成
import loadable from '@loadable/component'//动态加载组件,解决import()时变量失效问题
<Route exact path={pathname} component={loadable(() => import(`./${item.componentPath}`))} />
// 2、通过Route组件的render属性生成,比如嵌套路由中使用
genRouter(list){
let routerList= list.map((item)=>{
// 判断是否有子判断
if(item.menuChilds.length===0){//无子菜单
return <Route exact key={item.menuId} path={item.menuUrl} component={ loadable(() => import(`./${item.componentPath}`))}/>
}else{
// 有子菜单
//判断是否包含子组件
if(item.isContainChildren){ //嵌套在父级中,记得在在父组件中,添加 {this.props.children}在要嵌套的位置
return <Route key={item.menuId} path={item.menuUrl} render={() =>{
let ComponentName =loadable(() => import(`./${item.componentPath}`));
return <ComponentName {...this.props}>
{this.genRouter(item.menuChilds)}
</ComponentName>
}}>
</Route>
}else{ //不嵌套显示
return [...this.bindRouter(item.menuChilds),<Route key={item.menuId} path={item.menuUrl} component={ loadable(() => import(`./${item.componentPath}`))}/>]
}
}
})
return routerList;
}
/* 3、通过react-router-config配置静态路由
其源码就是一个高阶函数 利用一个map函数生成静态路由,底层还是用render属性
*/
import { renderRoutes } from 'react-router-config'
const routes = [
{
path: '/login',
component: Login,
},
]
<Switch>
{renderRoutes(routes)}
</Switch>
component和render的区别:
this 指向问题,component={组件}
组件直接挂载到router下面,render={()={组件}}
本身就是个组件,组件内部在引用你定义的组件,相当于又加了一层,this已经不一样了
路由权限判断
vue是通过路由中的全局守卫,进入路由时判断是否登录或注册,是就执行next展示当前页面,否就跳到登录页
react没有路由守卫,需要自己写,react如何判断进入路由呢?使用react-router,在react render的时候通过switch进行路由匹配
思路:判断是否有token,没有就跳到登录页,有就动态生成路由表,跳到目标路由
const RouterAuth: FC<RouterAuthProps> = (props) => {
// ...省略
// 在redux中请求接口,所以可以通过store拿到数据生成菜单列表
useEffect(() => {
dispatch(SetMenu())
setTimeout(()=>{
const arr = store.getState().userReducer.menu
setRouterList([...menuList, ...arr])
}, 200)
},[])
// 动态生成路由
const generateRoute = (list:any) =>{
return list.map((item:any, index:number)=>{
return <Route exact key={`${index}_${item.menuId}`} path={item.menuPath} component={ loadable(() => import(`@/pages${item.menuPath}/index`))}/>
})
}
const renderRouter=()=>{
// 如果是登录状态
if(!!token){
// 如果进入登录页面,则直接重定向至首页
if(pathname === '/login' || pathname === '/'){
return <Redirect to='/dashboard' />
}else{
// 动态生成路由表,跳到目标路由
return <Layout>{generateRoute(routerList)}</Layout>
}
}else{ //非登录状态,如果是白名单直接进入,否则跳转到登录页
return <Route path="/login" component={LoginPage}/>
}
}
return (
<Router>
<Switch>
<Route path="/" exact render={()=><Redirect to="/login"/>} />
<Route path="/404" component={NotFoundPage}/>
<Route path="/" render={()=> renderRouter()}/>
</Switch>
</Router>
)
}
也可以给每一个用户权限设定一个可以访问的路径数组,通过比较跳转的地址是否存在这个数组当中来进行相应的展示
<Switch>
{routes.map(item => {
return (
<Route
key={item.path}
path={item.path}
exact={item.exact}
render={props =>
!auth ? (
<item.component {...props} />
) : item.auth && item.auth.indexOf(auth) !== -1 ? (
<item.component {...props} />
) : (
// 这里也可以跳转到 403 页面
<Redirect to='/404' {...props} />
)
}></Route>
)
})}
<Redirect to='/404' />
</Switch>
登录时路由重定向
登录时判断location有没有search,有的话获取search,跳转到重定向地址,否则跳到系统首页
动态生成菜单
思路:动态生成路由组件时已经请求到菜单列表,存放在store里,所以只需要根据返回的菜单数据把菜单转成ant框架的菜单
兼容ie问题
1、使用阿里妈妈字体图标库,ie10报错,无法获取未定义或 null 引用的属性“firstChild”
解决:把引用的js移到body标签代码结束之后。因为console.log(target)为null,找到document.body是null