前沿

由于接触vue较多,工作中也是用vue做的后台管理,最近闲下来了,就想着学习下React,在这里记录下在学习过程中的遇到的问题,以及笔记。

1. 目录结构

由于也是刚接触react就直接使用create-react-app官方的脚手架了,由于需要做一些自定义配置直接yarn eject把webpack相关配置暴露出来。整理后的目录结构如下图:

image

在这里就简单介绍下目录结构

  • 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 模块化 类似与Vuescope

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 (

CssModules

)
}

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