写此文的目的:
- 整理汇总react的实战经验
- 方便技术栈以vue为主,需要从0搭建react项目的人
- 把vue2.x的开发习惯合理地搬到react项目
涉及技术栈:
- react:react官方文档,先全部过一遍对react的写法有点印象。
- create-react-app:官方推荐的react单页应用脚手架。和vue-cli不同,包括vue-router、vuex、webpack配置等内容,在这个脚手架里都不包含,需要自己单独配置。
- react-app-rewired: 用于在不需要eject的情况下,给react进行webpack配置。(eject指的是create-react-app的其中一个命令,运行之后会把所有详细的webpack配置全放开出来,有点像vue-cli的2.x版本的webpack配置的文件目录)
- react-router-dom:用来设置页面路由。
- mobx:状态管理的库。类似于vuex。
项目搭建:
之前我们在写vue项目的时候,vue-cli这个脚手架已经很贴心地帮我们把所有可配置的东西都搭建好了。比如router、状态管理的库、webpack配置的入口和格式。下文搭建的项目已包括对移动端的适配。
但是react官方的create-react-app装好之后啥都没有。
所以这时候需要我们装好项目之后,再装一堆其他用于补充功能的库。首先先用create-react-app创建基本的目录结构。
1. 创建项目
npm install -g create-react-app
create-react-app myProject
yarn start
2. 安装项目需要的库
npm install react-app-rewired customize-cra /1 用来配置webpack配置的,两个库需要搭配使用
postcss-pxtorem /2 px转rem库,用于移动端适配
less less-loader /4 less的配置
react-router-dom /5 路由库
loadable-components /6 搭配路由,使页面能够按需加载的库
--save
3.进行webpack配置
- 创建好目录并安装好上述的库之后就可以进行webpack的配置了。配置主要是用到了上述的react-app-rewired、customize-cra来开放webpack的配置。
- 首先在根目录下,创建一个文件
config-overrides.js,然后按照下面的代码配置。下面的代码已经包括了解决跨域的proxy配置、文件夹引入的别名alias配置、配置lessloader、用于移动端的将px转换为rem的样式适配。
/* config-overrides.js */
// 这里是我要用一些配置信息
const {
override,
addPostcssPlugins,
addWebpackAlias,
addLessLoader,
overrideDevServer,
} = require('customize-cra');
const path = require('path');
//解决跨域的peoxy配置
const devServerConfig = () => config => {
return {
...config,
port: 3000,
proxy: {
'/test': {
target: 'https://www.baidu.com',
changeOrigin: true,
pathRewrite: {
'^/test': '/test',
}
},
}
};
};
module.exports = {
webpack: override(
// 文件夹引入的别名alias配置
addWebpackAlias({
['@']: path.resolve(__dirname, 'src'),
['components']: path.resolve(__dirname, 'src/components'),
['@pic']: path.resolve(__dirname, 'src/assets/pic'),
['@common']: path.resolve(__dirname, 'src/utils/common'),
}),
// 配置lessloader
addLessLoader(),
//配置pxtorem,对移动端的项目的px转换成rem
addPostcssPlugins([require('postcss-pxtorem')({
rootValue: 37.5,
propWhiteList: [],
minPixelValue: 2
})])
),
devServer: overrideDevServer(devServerConfig())
};
- 设置好之后然后修改package.json文件中的
scripts,把之前用react-scripts跑起来的命令改为用react-app-rewired。如下面的代码所示。之后再正常地运行npm run start即可。
"scripts": {
- "start": "react-scripts start",
+ "start": "react-app-rewired start",
- "build": "react-scripts build",
+ "build": "react-app-rewired build",
- "test": "react-scripts test --env=jsdom",
+ "test": "react-app-rewired test --env=jsdom",
- "eject": "react-scripts eject"
}
需要注意的点有:
1.上面的addLessLoader必须要写在addPostcssPlugins之前,否则less中的css样式将不会将px值转换为rem
2.如果需要其他更多的webpack配置可以在customize-cra的github官方仓库找找,或者google、百度搜索现成的配置参照一下
3.上面配置好lessloader之后,之后可以直接在jsx文件中引入less文件,就可以添加组件的样式了
4.postcss-pxtorem的库引入之后,你还设置一段配置当前页面的html的fontsize的代码,直接在全局引入即可
4.router实现
- 路由及嵌套路由配置
路由这边就比较麻烦了,首先得去看看react-router官网看看,大致了解一遍路由的格式和写法。
然后我们这边仿照vue-router的配置文件,先把路由地址的配置大致先写好,方便我们之后的代码理解。需要注意的点有:
loadable-components是用于实现页面按需加载的库。- 下面代码中的二级路由childrens的path需要把之前页面的path也加上,和vue-router的配置文件有点不一样。
下面是参照vue-router把react路由单独提取到一个文件index.js的写法。
import loadable from "loadable-components"; //按需加载的库
export const routes = [{
path: '/home',
component: loadable(() => import("../views/home/index.jsx")),
childrens:[
{
path: '/home/mypage', //注意这里的写法要把父路径也带上
component: loadable(() => import("../views/myPage/index.jsx")),
},
{
path: '/home/search',
component: loadable(() => import("../views/search/index.jsx")),
}
]
},
{
path: '/mypage',
component: loadable(() => import("../views/myPage/index.jsx")),
},
{
path: '/search',
component: loadable(() => import("../views/search/index.jsx")),
},
{
path: '/createsearch',
component: loadable(() => import("../views/createSearch/index.jsx")),
},
{
path: '/stockresult',
component: loadable(() => import("../views/stockResult/index.jsx")),
}
]
上面的路由文件,如果是在vue的话,只要在main.js里将引入上面文件导出的router,然后再使用<router-view />来展示内容。但是在react这边的代码就有点不一样了。整个逻辑都得用react的方式实现一遍。
读取路由配置的main.js代码如下:
//以下代码已实现基础路由以及嵌套的子路由
import React from "react";
import { HashRouter as Router, Route, Switch } from "react-router-dom";
//react的路由库,这里引入HashRouter就是使用hash模式的路由,history模式的话是BrowserRouter
import { routes } from './index.js'
const renderRoutes = (routeData = [], extraProps = {}) =>
routeData ? (
<Switch>
{routeData.map((route, i) => (
<Route
key={route.key || i}
path={route.path}
exact={route.exact}
strict={route.strict}
render={props => {
//判断是不是还有子路由
if (route.childrens) {
return <route.component {...props} {...extraProps} route={route}>
{renderRoutes(route.childrens)}
</route.component>
}
else {
return <route.component {...props} {...extraProps} route={route} />
}
}}
/>
))}
</Switch>
) : null;
function AppRouter() {
return (
<Router>
{
renderRoutes(routes)
}
</Router>
)
}
export default AppRouter;
最后再把写好的router/main.jsx文件,在全局的入口文件引入即可。
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './router/main.jsx';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
接下来如果是在父页面中需要嵌套展示children页面的话,比如当前的url为http://localhost:3000/#/home/search。在vue中的写法是直接在home组件中写入<router-view/>,子路由的页面内容就会显示在对应的空间。
但是在react中,如果根据根据上面的index.js文件的写法一样的话。需要在home这个组件的render中写入下面的代码,然后router的path匹配的话,下面的代码就能渲染出../views/search/index.jsx组件的内容。
{
this.props.children.props.children
}
- 路由跳转
接下来还有很重要的替代vue中常用的this.$router.push(),路由跳转的方法。在react中d有两种方法:
- 第一种就是使用react-router提供的hook,能够获取url参数和进行路由跳转
//useHistory
//useHistory可以让组件内部直接访问history,无须通过props获取。路由跳转可用history.push()实现。
import { useHistory } from "react-router-dom";
const Contact = () => {
const history = useHistory();
return (
<Fragment>
<h1>Contact</h1>
<button onClick={() => history.push("/")}>Go to home</button>
</Fragment>
);
};
//useParams
//获取url的参数
const About = () => {
const { name } = useParams();
return (
// props.match.params.name
<Fragment>
{name !== "John Doe" ? <Redirect to="/" /> : null}
<h1>About {name}</h1>
<Route component={Contact} />
</Fragment>
);
};
//useLocation
//useLocation 会返回当前 URL的 location对象
import { useLocation } from "react-router-dom";
const Contact = () => {
const { pathname } = useLocation();
return (
<Fragment>
<h1>Contact</h1>
<p>Current URL: {pathname}</p >
</Fragment>
);
};
- 第二种的大致思路就是引入
withRouter高阶函数,然后使用props中的hitroy.push来进行跳转。 示例代码如下:
import React from 'react'
import { withRouter } from "react-router-dom";
function Main(props) {
let jumpResultPage = (query) => {
props.history.push({
pathname: '/stockresult', //pathname是跳转的路径
state: { from: props.location.pathname }
//state是需要传递的数据,在跳转过去的页面中也可以用props.location.state进行获取。
})
}
return (
<div className="hotMain" onClick={jumpResultPage}></div>
);
}
export default withRouter(Main)
history具体其他的跳转参数可以参照官方文档
5.配置mobx
mobx是和vuex概念相似的状态管理库。基本使用方法如下:
import { makeAutoObservable } from 'mobx'
// 构造响应对象
export const store = makeAutoObservable({
resultData: '', //类似于vuex中的state
changeResultData(data) {
this.resultData = data //类似于vuex中的mutation
}
})
你可以把上述的代码当做是vuecli项目中vuex创建的store/index.js文件,vue中的state和mutation直接定义为makeAutoObservable参数。
然后在需要使用的地方引入刚刚封装好的store
import { store } from './mobx/index.js' //这个就是上面写的代码的文件
//改变值的方法
store.changeResultData('你需要改变的值')
//获取值的方法
let value = store.resultData
其他笔记
- react的class名字不能用保留字,如icon
- 有时候页面报错,然后改过来之后,react进行热更新了,但页面还是会停留在上一次的报错页面,需要手动刷新页面
- BrowserRouter是history模式的路由。HashRouter是hash模式的路由。直接import使用就可以了。这点和vue区别挺大的。
import {
BrowserRouter as Router,
Route,
Link
} from 'react-router-dom';
#改为
import {
HashRouter as Router,
Route,
Link
} from 'react-router-dom';
- 在上面提到webpack配置中的
config-overrides.js,需要注意先引入lessloader然后再引入addPostcssPlugins对当前px值的样式转换为rem。这两个的先后顺序很重要。如果调换过来的话会出现less文件的样式都没有被转换成rem的情况。
addLessLoader(),
addPostcssPlugins([require('postcss-pxtorem')({
rootValue: 37.5,
propWhiteList: [],
minPixelValue: 2
// propWhiteList: [],
// selectorBlackList: ['weui-']
})])
- 注意用withRouter包装过之后入参变为了一个对象,所以需要确保withRouter包装的组件的最外层的元素只有一个。如果出现多个的话会报错。
- react的17版本不能用react-loadable,会报错。在npm官方仓库中有新版本的库。
感悟
- 个人感觉react其实学习难度很大一部分在于官方文档没有vue清晰、其他工具库种类过多,每个库的文档质量参差不齐。但其实都定好一套流程之后,就清晰很多了。用习惯之后其实用vue和react效率都很高。
- 如果有需要的我再弄一个github仓库把上面的代码弄个demo