基于umi的React项目结构介绍
本项目是基于umi搭建的,具体可以参考 Ant Design 实战教程(beta 版)
项目搭建
以下命令顺序执行,用来完成umi的初始化
mkdir demo2
cd demo2
npm init -y
npm i antd --save
npm i umi --save-dev
然后,我们开始建项目的文件目录
- demo2
- dist // 存放编译后的文件
- config
- config.js // umi的配置文件
- mock
- index.js // 前端模拟请求数据
- src // 应用的所有代码
- actions // 处理异步请求
- assets // 静态资源
- components // 公用组件
- pages // 业务逻辑页面
- reducers // reducer 状态处理
- util // 公用方法
- index.html // 项目模板
- index.js // 项目入口
- package.json // npm init 自动生成
// src/pages/index.js
import React, { Component } from 'react'
class App extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div className="app-container">
<p>hello world</p>
</div>
);
}
}
export default App;
修改package.json
...
"scripts": {
+ "dev": "umi dev",
+ "build": "umi build"
}
...
我们运行 npm run dev 就能看到hello world.
现在我们使用的都是umi的默认配置,我们还需要自己的配置
修改配置文件
添加 umi-plugin-react 插件
npm i umi-plugin-react --save-dev
配置 config/config.js 文件
export default {
plugins: [
['umi-plugin-react', {
antd:true, //使用antd
dva:false //不使用dva,我们直接用redux
}],
],
// 使用自定义的router,如果不使用则是用umi的约定式路由(按文件夹路径来)
// 这里的path都是相对于pages文件,例如:
// routes:[{
// path:'/box', // 指定路由地址(url)
// component:'Box/index' //引用的组件路径
// }]
// 引用的就是pages文件夹下 Box/index.js文件
routes:[{
path:'/',
component:'index'
}]
}
使用Routes配置
首先我们使用antd的Layout布局,修改src/pages/index.js
import React, { Component } from 'react';
import { Layout } from 'antd';
const { Header, Content, Footer } = Layout;
class App extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div className="app-container">
<Header>header</Header>
<Content>content</Content>
<Footer>footer</Footer>
</div>
);
}
}
export default App;
重新运行 npm run dev,页面就会显示出当前的界面
在pages下新建home.js文件
import React, { Component } from 'react';
class Home extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div>
Home 页面
</div>
);
}
}
export default Home;
修改文件
// src/pages/index.js
...
<Content>content</Content>
==> <Content>{this.props.children}</Content>
...
// config/config.js
...
routes: [{
path: '/',
component: 'index',
+ routes: [{
+ path: '/',
+ component: './home'
+ }]
}]
...
保存后页面就会发生变化,Content中就会显示 Home的内容
在umi中使用scss
在umi中可以直接使用css,但是并不支持scss,我们需要加两个loader,
直接npm安装 node-sass和sass-loader 即可,剩余的事情umi已经帮我们做好了。
npm i --save-dev node-sass sass-loader
在src/assets下新建文件夹
- assets
+ - img
+ - css
+ -style.scss // 这个样式文件一般来说存放全局的样式
在src/pages/index.js 引用style.scss
import '../assets/css/style.scss';
在home.js同级新建home.scss 文件
.home-container{
.red{
color:red;
}
}
在home.js引用,并修改render
// 第一种使用scss方法
// 使用这种方法的时候样式名称不能用 "-" ,不然在使用的时候会报错
...
import homeStyle from './home.scss';
...
...
render() {
return (
<div className={homeStyle.home_container}>
<p className={homeStyle.red}>Home 页面</p>
</div>
);
}
...
// 第二种使用scss方法
...
import './home.scss';
...
...
render() {
return (
<div className="home_container">
<p className="red">Home 页面</p>
</div>
);
}
...
用第二种方法的情况:
刷新页面发现并没有变化,打开浏览器调试窗口,查看sources
找到引用的css文件,搜索可以看到好像我们的样式确实是存在的,只不过被加上了其他的后缀(为了保证不会出现全局污染)
ps:这个问题当时我找了好久
1.png这个是umi自己默认加上,我们并不想要这个东西,在config/config.js文件中添加配置
...
cssLoaderOptions:{
localIdentName:'[local]'
}
...
完善routes
现在我们的项目只有一个home页面,我们多加几个,来实现跳转的功能
简单页面跳转
在home.js同级添加文件 center.js
import React, { Component } from 'react';
class Center extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div className="center-container">
<p>这里是个人中心页面</p>
</div>
);
}
}
export default Center;
修改home.js
...
import Link from 'umi/link';
...
...
render() {
return (
<div className="home-container">
<p className="red">Home 页面</p>
<Link to="/center">个人中心</Link>
</div>
);
}
...
修改config.js
routes: [{
path: '/',
component: 'index',
routes: [{
path: '/',
component: './home'
}, {
+ path: '/center',
+ component: './center'
}]
}]
点击页面的"个人中心",即可跳转到个人中心页面
事件跳转
除了点击Link跳转页面外,我们还有其他的跳转需求,比如:返回上一个页面,或者登录后跳转。这些都可以算是事件跳转,
修改center.js
...
import { Button } from 'antd';
import router from 'umi/router';
...
...
onGoBackHome = () => {
router.goBack()
// router.push('/')
}
render() {
return (
<div className="center-container">
<p>这里是个人中心页面</p>
<Button onClick={this.onGoBackHome}>返回主页</Button>
</div>
);
}
...
在项目中使用redux
在umi中,redux是封装在dva中的,但是我们想用原始的那种redux (仅仅是个人原因),我们就不去使用dva的模式。
在项目中actions文件主要用于处理请求、异步等,reducers文件则是处理数据以及其他的改变
创建store
在reducers目录下新建文件
因为在umi中会自动导入redux和react-redux包,所以我们不需要在安装,可以直接使用
// src/reducers/home.js
//初始化一个homeStore
const initHomeStore = {
isShowDesc: true
}
const homeStore = (state = initHomeStore, action) => {
switch (action.type) {
case 'home-show-desc':
return {
...state,
isShowDesc: action.value
}
default:
return state;
}
}
export default homeStore;
// src/reducers/index.js
// 可以把每一个页面/模块都写一个store文件,然后在index中合并成一个store
import { combineReducers } from 'redux';
import homeStore from './home';
// 合并store
const appStore = combineReducers({
homeStore,
});
export default appStore;
使用store
store文件创建好了之后,修改pages/index.js
//导入文件
...
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import appStore from '../reducers';
const store = createStore(appStore);
...
//修改render内容
render() {
return (
+ <Provider store={store}>
<div className="app-container">
<Header>header</Header>
<Content>{this.props.children}</Content>
<Footer>footer</Footer>
</div>
+ </Provider>
);
}
这样我们可以在所有的页面都使用store内容
修改 pages/home.js
//导入
...
import { connect } from 'react-redux';
...
class Home extends Component {
...
render() {
const { isShowDesc } = this.props;
return (
<div className="home-container">
<p className="red">Home 页面</p>
<Link to="/center">个人中心</Link>
{
isShowDesc ?
<p>这里是详情信息</p> : ''
}
</div>
);
}
}
// mapStateToProps 方法传的state是全局的store,我们只需要homeStore,返回homeStore
const mapStateToProps = state => {
console.log('state:', state);
return state.homeStore;
}
export default connect(mapStateToProps)(Home);
重新启动 npm run dev
此时页面上看不到详情内容,
手动修改一下reducers/home.js 的isShowDesc值为true,保存之后在页面上就能看到详情内容
手动修改只是测试一下
下面我们来利用dispatch修改isShowDesc的值(这里不使用更简单的组件state属性来处理)
给home页面添加一个button,点击button来显示/隐藏详情
...
import { Button } from 'antd';
...
class Home extends Component {
...
onButtonClick = () => {
const { onToggerDesc, isShowDesc } = this.props;
onToggerDesc(!isShowDesc);
}
render() {
const { isShowDesc } = this.props;
return (
<div className="home-container">
...
<Button onClick={this.onButtonClick}>显示/隐藏</Button>
{
isShowDesc ?
<p>这里是详细信息</p> : ''
}
</div>
);
}
}
const mapStateToProps ...
const mapDispatchToProps = dispatch => ({
onToggerDesc: (value) => {
dispatch({ type: 'home-show-desc', value })
}
});
export default connect(mapStateToProps, mapDispatchToProps)(Home);
这样我们就能够点击按钮来改变详情的状态
网络请求
网络请求使用fetch,使用mock模拟数据
fetch的使用
安装 fetch、mockjs
npm i --save whatwg-fetch mockjs
封装一下请求方法,并使用mockjs模拟请求
// src/util/index.js
import 'whatwg-fetch';
export const get = (url, params) => {
let newUlr = url;
let count = 0;
if (params) {
newUlr += '?';
for (key in params) {
let value = params[key];
if (typeof value === 'object') {
value = JSON.stringify(value);
}
count++;
newUlr += ((count > 1 ? '&' : '') + key + '=' + value);
}
}
return fetch(newUlr);
}
// src/actions/index.js
import { get } from '../util';
export const getList = ({ dispatch, params }) => {
get('/getlist')
.then((response) => {
return response.json()
}).then((value) => {
dispatch({ type: 'home-get-list', value: value.list })
}).catch((ex) => {
///失败
});
}
// mock/index.js
import mockjs from 'mockjs';
export default {
"/getlist": mockjs.mock({
"list|10": [{
city: "@city",
name: "@cname",
"sex|0-1": 1,
email: "@email",
"id|1-10": 5
}]
})
}
在home.js中使用
...
import { Button, Table } from 'antd';
import { getList } from '../actions';
...
class Home extends Component {
...
componentDidMount() {
this.props.onGetList();
}
...
render() {
const { isShowDesc, listData } = this.props;
const columns = [{
title: '姓名',
dataIndex: 'name'
}, {
title: '城市',
dataIndex: 'city'
}, {
title: '性别',
dataIndex: 'sex',
render: (text, record) => (text === 0 ? '女' : '男')
}, {
title: '邮箱',
dataIndex: 'email'
}];
return (
<div className="home-container">
...
<Table dataSource={listData} columns={columns} rowKey="name" />
</div>
);
}
}
const mapStateToProps = ...
const mapDispatchToProps = dispatch => ({
...
onGetList: () => {
getList({ dispatch });
}
});
export default connect(mapStateToProps, mapDispatchToProps)(Home);
保存,刷新页面后就能看到数据
Antd 主题定制
Antd的主题定制有好几种方法,该项目是基于umi的,所以就是用umi配置的方法来定制。
在config/config.js 文件中添加配置
theme: 'src/assets/css/theme.js'
在src/assets/css 下新建theme.js文件
这里只修改一个属性值 (其他属性可以参考文档)
module.exports = {
'primary-color': '#f39700',
};
编译后,home页面的按钮主题就被修改了
至此一个基于umi的react项目结构就讲完了。