虽然不是第一次接触react,但之前都没有写博客记录,好记性不如烂笔头,希望以后养成多做笔记的好习惯吧。
关于react
react 作为当下最流行的前端框架,之所以能被如此多前端开发者所喜爱自然是有原因的。react有一个很重要的特性——限制(Constraint),这里的“限制”并不是贬义,因为对于大部分开发者来说,代码的可维护性和扩展性并不是很好,所以只有施加“限制”,代码的管理才会更加简单。必须按照react的规范来写代码,不然程序就跑不了,就是这么霸道。
react 还有很多优势,个人认为最重要的就是虚拟DOM和组件化。
虚拟DOM:现在的框架很少有直接操作DOM的,虽然jQuery曾经称霸一方。当页面上的DOM树改变的时候,虚拟DOM机制会将前后DOM树进行对比(通过一些高效的算法),如果两个DOM树有不一样的地方,react只会针对不一样的地方进行修改,自然就提高了性能。举个栗子:现在的html结构是这样
1
2
3
4
5
<ul >
<li > 1</li >
<li > 2</li >
<li > 3</li >
</ul >
你想让它变成这样1
2
3
4
5
6
<ul >
<li > 4</li >
<li > 5</li >
<li > 6</li >
<li > 7</li >
</ul >
如果是原始的DOM操作,你需要先将开始的三个li
删除,然后再将后面的四个li
添加进去;可能你也会这样操作:将三个li
的innerHTML
修改,只需要添加一个7
就行了。那么恭喜你,做的很好。react有个很重要的思想,状态管理。只要DOM节点的对应的state发生了改变,react就会将其标注为“脏状态”,在一个Event loop 结束后,对这些有标注的地方进行修改,并且只对修改了的地方重新渲染,减少了很多冗余的DOM操作。具体实现可以参考 深度剖析:如何实现一个 Virtual DOM 算法
组件化:简单的说下好处,可以模板化,可复用,可嵌套的UI模块,当然组件之间会存在依赖关系。可以将UI组件当作独立的js模块使用,推荐使用ES6的import来引用组件模块,当然也可以配合CommonJs、AMD、CMD等规范来require我们需要的组件模块,最重要的是处理好依赖关系。
项目 前面说了这么多,终于到重点了。这个项目是参照慕课网实战视频做的,视频地址:React高级实战 打造大众点评 WebApp
启动:
1
2
3
npm i
npm run mock
npm start
项目预览
项目源码
项目目录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
│ package.json
│ README.md
├─build
│
├─config
│
├─mock
│ │ server.js
│ ├─detail
│ ├─home
│ ├─public
│ ├─search
│ └─user
│
├─public
│
├─scripts
│ build.js
│ start.js
│ test.js
│
└─src
│ config.js
│ index.js
│ index.less
│
├─actions
│
├─components
│
├─containers
│
├─fetch
│
├─reducers
│
├─router
│ index.jsx
│
├─static
│
└─util
webpack配置 首先,项目采用 create-react-app 脚手架搭建,很多配置都已经帮我们写好了,但是仍需要自定义一些配置,所以我 npm run eject
。对于这个命令,官方是这样解释的:
Note: this is a one-way operation. Once you eject, you can’t go back!
If you aren’t satisfied with the build tool and configuration choices, you can eject at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except eject will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
You don’t have to ever use eject. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
这个项目我使用less编译css样式文件,所以需要添加配置:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{
test: /\.(css|less)$/ ,
use: [
require .resolve('style-loader' ),
{
loader: require .resolve('css-loader' ),
options: {
importLoaders: 1 ,
},
},
{
loader: require .resolve('postcss-loader' ),
options: {
ident: 'postcss' ,
plugins: () => [
require ('postcss-flexbugs-fixes' ),
autoprefixer({
browsers: [
'>1%' ,
'last 4 versions' ,
'Firefox ESR' ,
'not ie < 9' ,
],
flexbox: 'no-2009' ,
}),
],
},
},
{
loader: require .resolve('less-loader' )
},
],
},
在开发环境和产品环境都需要配置,即在webpack.config.dev.js
和webpack.config.prod.js
都需要配置
前端路由配置 然后就是前端页面路由配置,视频使用的是react-router2.0的版本,我用的是4.0版本,接触过的都知道,这两者差异很大。
history 不用props传递,使用HashRouter
Route 不能嵌套使用,都在同一层级
想要匹配所有不存在的路由,就不传path
动态参数 :param 可选参数 :param? 精确匹配 exact
代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
import { HashRouter, Route, Switch } from 'react-router-dom'
<HashRouter>
<Switch>
<Route path='/' exact component={Home} />
<Route path='/city' component={City}/>
<Route path='/search/:category/:keyword' component={Search}/>
<Route path='/detail/:id' component={Detail}/>
<Route path='/login' component={Login}/>
<Route path='/user' component={User}/>
<Route component={NotFound}/>
</Switch>
</ HashRouter>
服务器配置 我这里使用的koa2.0来做后端接口的模拟
由于我们的数据是采用静态数据模拟的,所以请求数据其实就是请求js文件,并将数据返回
代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
const Koa = require ('koa' )
const Router = require ('koa-router' )
const path = require ('path' )
const static = require ('koa-static' )
const cors = require ('koa2-cors' );
const bodyParser = require ('koa-body' )();
const app = new Koa()
const router = new Router()
app.use(cors({
credentials: true ,
allowMethods: ['GET' , 'POST' , 'DELETE' ],
allowHeaders: ['Content-Type' , 'Authorization' , 'Accept' ],
}))
app.use(static (
path.join(__dirname, '/public' )
))
router.get('/data/homead' , async (ctx) => {
ctx.body = require ('./home/ad' )
})
const homeListData = require ('./home/list' )
router.get('/data/homelist/:cityname/:page' , async (ctx) => {
const pageSize = 1
const { cityname, page } = ctx.params
let filterData = homeListData.filter(item =>
item.cityname === cityname
)
let hasMore = pageSize * page >= filterData.length ? false : true
ctx.body = {
result: filterData.slice(0 , page * pageSize),
hasMore: hasMore
}
})
const searchListData = require ('./search/list' )
router.get('/data/search/:cityname/:category/:keyword/:page' , async (ctx) => {
const pageSize = 1
const { cityname, category, keyword, page } = ctx.params
let filterData
let filterData1 = searchListData.filter(item =>
item.cityname === cityname
)
if (keyword === 'all' ) {
filterData = filterData1.filter(item =>
item.category.includes(category)
)
} else {
filterData = filterData1.filter(item =>
item.title.includes(keyword) || item.subTitle.includes(keyword)
)
}
let hasMore = pageSize * page >= filterData.length ? false : true
ctx.body = {
result: filterData.slice(0 , page * pageSize),
hasMore: hasMore
}
})
const detailData = require ('./detail/info' )
router.get('/data/detail/info/:id' , async (ctx) => {
let { id } = ctx.params
id = parseInt (id)
const filterData = detailData.filter(item =>
item.id === id
)
ctx.body = filterData
})
const CommentData = require ('./detail/comment' )
router.get('/data/detail/comment/:id/:page' , async (ctx) => {
const pageSize = 2
const { id, page } = ctx.params
let filterData = CommentData
let hasMore = pageSize * page >= filterData.length ? false : true
ctx.body = {
result: filterData.slice(0 , page * pageSize),
hasMore: hasMore
}
})
router.post('/data/login' , bodyParser, async (ctx) => {
const { username, password } = ctx.request.body
if (username === 'linxun' && password === 'linxun' ) {
ctx.body = {
status: 1 ,
msg: '登录成功'
}
} else {
ctx.body = {
status: 0 ,
msg: '用户名或密码错误'
}
}
})
router.get('/data/collect/:id' , async (ctx) => {
let { id } = ctx.params
id = parseInt (id, 10 )
const filterData = detailData.filter(item =>
item.id === id
)
ctx.body = filterData
})
const orderListData = require ('./user/orderList' )
router.get('/data/orderList' , async (ctx) => {
ctx.body = orderListData
})
app.use(router.routes()).use(router.allowedMethods())
app.listen(8000 , () => {
console .log('route-use-middleware is starting at port 8000' )
})
前端数据请求 由于我们的数据是采用静态数据模拟的,所以请求数据其实就是请求js文件,并将数据返回
这里使用fetch和promise,在组件中只需要发送请求,然后在.then()
中即可拿到需要的数据
代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import 'whatwg-fetch'
import 'es6-promise'
export function get (url ) {
return fetch(url, {
headers: {
'Accept' : 'application/json, text/plain, */*' ,
}
})
}
export function post (url, paramsObj ) {
return fetch(url, {
method: 'POST' ,
headers: {
'Accept' : 'application/json, text/plain, */*' ,
'Content-Type' : 'application/x-www-form-urlencoded'
},
body: obj2params(paramsObj)
});
}
function obj2params (obj ) {
let result = '' , item;
for (item in obj) {
result += '&' + item + '=' + encodeURIComponent (obj[item]);
}
result = result.length > 0 ? result.slice(1 ) : result;
return result;
}
react与redux连接 在智能组件中
代码如下:1
2
3
4
5
6
7
8
9
10
function mapStateToProps (state ) {
return { ... }
}
import { bindActionCreators } from 'redux'
function mapDispatchToProps (dispatch ) {
return { actionCreater : bindActionCreators(actionCreater, dispatch) }
}
主要的代码就是这些,样式和素材都是视频中的。