由于接触vue
较多,工作中也是用vue
做的后台管理,最近闲下来了,就想着学习下React
,在这里记录下在学习过程中的遇到的问题,以及笔记。
github
中的项目react-admin再次表示大大的感谢?由于也是刚接触react
就直接使用create-react-app
官方的脚手架了,由于需要做一些自定义配置直接yarn eject
把webpack相关配置暴露出来。整理后的目录结构如下图:
在这里就简单介绍下目录结构
build
: 打包之后的代码存在的目录config
: webpack 相关配置scripts
: webpack 打包以及开发启动目录CNAME
: 由于打包后的文件是 GitHub Pages
绑定来自阿里云的域名,在这里配置下deploy.sh
: 打包后的文件发布到 GitHub Pages
相关操作
src
目录是我们的源代码所处的位置
containers
: 页面所处的位置,也就是后台管理的内容区域。services
: 与ajax
相关配置我们做后台管理有时候想要在进入页面之前做点什么,这个时候就会用到路由拦截,react-router-dom
没有像Vue-router
那样的beforeRouter
生命周期的钩子函数,那就需要我们自己来做这件事情了,尝试了很多办法,总算是把这个事情解决了,先看代码:
// react-loadable 是一个动态导入加载组件的高阶组件.
// 一个组件是import的组件,另一个是渲染组件 当用户进入页面是 加载异步组件,页面中显示的只是 loading....
import React from 'react'
import loadable from 'react-loadable'
function asyncImport(loader) {
function Loading(props) {
if (props.error)
return <div> Error! </div>
else if (props.pastDelay)
return (
<div>
正在加载组件数据...
</div>
)
else
return null
}
return loadable({
loader,
loading: Loading,
})
}
export {
asyncImport
}
import { asyncImport } from '../utils/routerLoadable'
// 这里习惯性的使用 定义vue-router 的规则
const routes = [
{
name: 'Home',
path: '/app',
component: asyncImport(() => import('../containers/Home')),
meta: {
title: '首页',
rules: ['loginRequired']
}
},
{
name: 'Home1',
path: '/app1',
component: asyncImport(() => import('../containers/Home1')),
meta: {
title: '首页1',
rules: []
}
},
// .......
]
export default routes
import React from 'react'
import { Route, Switch, Redirect } from 'react-router-dom'
import routes from './api'
import queryString from 'query-string'
// 权限限制规则
const requiredRules = {
/**
* 判断是否登录
* @return true 条件满足 通过权限验证
*/
loginRequired (path) {
const localStorage = window.localStorage
const strReactAdminUserInfo = localStorage.getItem('react-admin_user') || '{}'
const reactAdminUserInfo = JSON.parse(strReactAdminUserInfo)
return !!reactAdminUserInfo.userName || '/login'
}
}
/**
* @param {Protected:登陆拦截(函数组建)}
* @return {还是一个Route组件,这个Route组建使用的是Route三大渲染方式(component、render、children)的render方式}
*/
const Protected = ({component: Comp, ...rest}) => {
const { exact, path, meta, setTagPage, ...otherRest } = rest
return (
<Route {...rest} render={ () => {
const { title } = meta
document.title = title || 'react-admin'
// 路由拦截 进入页面前 检查 逐个调用 meta里面的 rules 的规则.
// eg: /app 路由 rules是 ['loginRequired'], 存在登录验证规则, 进入页面之前会验证是否登录
if (meta.rules && meta.rules instanceof Array) {
const middlewares = meta.rules.map(item => requiredRules[item])
for (let i = 0; i < middlewares.length; i++) {
const result = middlewares[i](path)
if (result) {
return <Redirect to={result}/>
}
}
}
return <Comp {...otherRest}/>
}}/>
)
}
const routerApp = (props) => {
const query = queryString.parse(props.location.search)
props.match.query = query
return (
<Switch>
{
routes.map((item) => (
<Protected
{...props}
path={ item.path }
component={ item.component }
key={ item.path }
exact
meta={item.meta}>
</Protected>
))
}
<Route render={() => <Redirect to='/404'/>} />
</Switch>
)
}
export default routerApp
// LoadingBar 是自己写的一个React组件
import axios from 'axios'
import LoadingBar from '@/components/LoadingBar'
let apiBaseURL = 'https://www.easy-mock.com/mock/5d088415bdc26d23199ba01a'
// const apiBaseURL = 'https://www.fastmock.site/mock/4dcea17ec42f04835302140b4dadeacc'
const instance = axios.create({
baseURL: apiBaseURL,
timeout: 5000
})
// request
instance.interceptors.request.use((config) => {
LoadingBar.start()
return config
}, (err) => {
LoadingBar.error()
return Promise.reject(err)
})
// response
instance.interceptors.response.use((response) => {
LoadingBar.finish()
return response.data
}, (err) => {
LoadingBar.error()
return Promise.reject(err)
})
export default instance
详细请跳转 传送门
css
模块化 类似与Vue
的 scope
reactjs
没有像vue style scoped
那样简单的配置就能实现css 模块化了,在这里是借助了第三方的插件 babel-plugin-react-css-modules
实现css 模块化。
配置过程中也遇到了一些坑~
create-react-app
在创建项目的时候已经做了模块化处理,默认匹配*.module.(less|sass|css)的文件做模块化处理
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
// 这里是自己添加的less配置
const lessRegex = /\.(less)$/;
const lessModuleRegex = /\.module\.(less)$/;
如果不做一些处理的话使用方法, 这样写className
只能写驼峰,书写麻烦
import React from 'react'
import cssModule from './index.module.less'
const CssModules = (props) => {
return (
<div className={cssModule.className}>CssModules</div>
)
}
export default CssModules
babel-plugin-react-css-modules
yarn add babel-plugin-react-css-modules postcss-less -D
{
test: lessModuleRegex,
use: getStyleLoaders({
importLoaders: 2,
modules: true,
// getLocalIdent: getCSSModuleLocalIdent,
localIdentName: '[path][name]__[local]',
sourceMap: isEnvProduction && shouldUseSourceMap,
},
'less-loader'
),
},
.babelrc.js
module.exports = {
"presets": [
"react-app"
],
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
[
"react-css-modules",
{
"exclude": 'node_modules',
"generateScopedName": '[path][name]__[local]',
"filetypes": {
".less": {
"syntax": "postcss-less"
}
}
}
]
]
}
[path][name]__[local]
babel generateScopedName
与 webpack css-loader 配置的localIdentName
要保持一致, 本来要配置不同的hash值确保真正的名字不重复[path][name]__[local]__[hash:8]
结果打包出来的代码 css样式 class 与 html属性的class 名字hash不一致, 不知道怎么解决,未完待续~~ TODO。
配置完成的代码如下
import React from 'react'
import './index.module.less'
// 使用了 styleName 的类名就是模块化的class 通时也可以与className混合使用
const CssModules = (props) => {
return (
<div styleName="css-module">CssModules</div>
)
}
export default CssModules