前沿
由于接触vue
较多,工作中也是用vue
做的后台管理,最近闲下来了,就想着学习下React
,在这里记录下在学习过程中的遇到的问题,以及笔记。
- 项目中也参考的
github
中的项目react-admin再次表示大大的感谢😄 - 项目中的mock数据是借用了 easy-mock (由于有时候请求不稳定也会换)fastmock。
- 在线体验地址 https://react.closeeyes.cn
- github完整代码 🐛
1. 目录结构
由于也是刚接触react
就直接使用create-react-app
官方的脚手架了,由于需要做一些自定义配置直接yarn eject
把webpack相关配置暴露出来。整理后的目录结构如下图:
在这里就简单介绍下目录结构
build
: 打包之后的代码存在的目录config
: webpack 相关配置scripts
: webpack 打包以及开发启动目录CNAME
: 由于打包后的文件是GitHub Pages
绑定来自阿里云的域名,在这里配置下deploy.sh
: 打包后的文件发布到GitHub Pages
相关操作
src
目录是我们的源代码所处的位置
containers
: 页面所处的位置,也就是后台管理的内容区域。services
: 与ajax
相关配置
2. 路由拦截与路由的懒加载
我们做后台管理有时候想要在进入页面之前做点什么,这个时候就会用到路由拦截,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
3. axios 请求拦截
// 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
4. redux
详细请跳转 传送门
5. 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)$/; const lessRegex = /\.(less)$/; const lessModuleRegex = /\.module\.(less)$/;
如果不做一些处理的话使用方法, 这样写className
只能写驼峰,书写麻烦
import React from 'react'
import cssModule from './index.module.less'
const CssModules = (props) => {
return (
)
}
export default CssModules
- 配置 `babel-plugin-react-css-modules` ```bash yarn add babel-plugin-react-css-modules postcss-less -D
- webpack配置
{ 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" } } } ] ] }
- css名字的规则
[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