前端架构_前端架构师是做什么的

前端架构_前端架构师是做什么的前言:react、redux、react-router构建项目。 一、前端架构是什么 前端架构的特殊性 前端不是一个独立的子系统,又横跨整个系统 分散性:前端工程化 页面的抽象、解耦、组合 可控:脚手架、开发规范等 高效:框架、组件库、Mock平台,构建部署工具等 抽象 页面UI抽象:组件 通用逻辑

前言:react、redux、react-router构建项目。


一、前端架构是什么

前端架构_前端架构师是做什么的前端架构_前端架构师是做什么的

前端架构的特殊性

前端不是一个独立的子系统,又横跨整个系统

分散性:前端工程化

页面的抽象、解耦、组合

可控:脚手架、开发规范等

高效:框架、组件库、Mock平台,构建部署工具等

抽象

页面UI抽象:组件

通用逻辑抽象:领域实体、网络请求、异常处理等

 

二、案例分析

功能路径

展示:首页->详情页

搜索:搜索页->结果页

购买:登录->下单->我的订单->注销

 

三、前端架构之工程化准备:技术选型和项目脚手架

技术选型考虑的三要素

业务满足程度

技术栈的成熟度(使用人数、周边生态、仓库维护等)

团队的熟悉度

技术选型

UI层:React

路由:React Router

状态管理:Redux

脚手架

Create React App

1
npx create-react-app dianping-react

 

四、前端架构之工程化准备:基本规范

基本规范

目录结构

构建体系

Mock数据

前端架构_前端架构师是做什么的前端架构_前端架构师是做什么的

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
//likes.json
 
[
  
{
    
"id"

"p-1"
,
    
"shopIds"
: [
"s-1"
,
"s-1"
,
"s-1"
],
    
"shop"

"院落创意菜"
,
    
"tag"

"免预约"
,
    
"picture"

"https://p0.meituan.net/deal/e6864ed9ce87966af11d922d5ef7350532676.jpg.webp@180w_180h_1e_1c_1l_80q|watermark=0"
,
    
"product"

"「3店通用」百香果(冷饮)1扎"
,
    
"currentPrice"
: 19.9,
    
"oldPrice"
: 48,
    
"saleDesc"

"已售6034"
  
},
  
{
    
"id"

"p-2"
,
    
"shopIds"
: [
"s-2"
],
    
"shop"

"正一味"
,
    
"tag"

"免预约"
,
    
"picture"

"https://p0.meituan.net/deal/4d32b2d9704fda15aeb5b4dc1d4852e2328759.jpg%40180w_180h_1e_1c_1l_80q%7Cwatermark%3D0"
,
    
"product"

"[5店通用] 肥牛石锅拌饭+鸡蛋羹1份"
,
    
"currentPrice"
: 29,
    
"oldPrice"
: 41,
    
"saleDesc"

"已售15500"
  
},
  
{
    
"id"

"p-3"
,
    
"shopIds"
: [
"s-3"
,
"s-3"
],
    
"shop"

"Salud冻酸奶"
,
    
"tag"

"免预约"
,
    
"picture"

"https://p0.meituan.net/deal/b7935e03809c771e42dfa20784ca6e5228827.jpg.webp@180w_180h_1e_1c_1l_80q|watermark=0"
,
    
"product"

"[2店通用] 冻酸奶(小杯)1杯"
,
    
"currentPrice"
: 20,
    
"oldPrice"
: 25,
    
"saleDesc"

"已售88719"
  
},
  
{
    
"id"

"p-4"
,
    
"shopIds"
: [
"s-4"
],
    
"shop"

"吉野家"
,
    
"tag"

"免预约"
,
    
"picture"

"https://p0.meituan.net/deal/63a28065fa6f3a7e88271d474e1a721d32912.jpg%40180w_180h_1e_1c_1l_80q%7Cwatermark%3D0"
,
    
"product"

"吉汁烧鱼+中杯汽水/紫菜蛋花汤1份"
,
    
"currentPrice"
: 14,
    
"oldPrice"
: 23.5,
    
"saleDesc"

"已售53548"
  
},
  
{
    
"id"

"p-5"
,
    
"shopIds"
: [
"s-5"
],
    
"shop"

"醉面 一碗醉香的肉酱面"
,
    
"tag"

"免预约"
,
    
"picture"

"https://p1.meituan.net/deal/a5d9800b5879d596100bfa40ca631396114262.jpg.webp@180w_180h_1e_1c_1l_80q|watermark=0"
,
    
"product"

"单人套餐"
,
    
"currentPrice"
: 17.5,
    
"oldPrice"
: 20,
    
"saleDesc"

"已售23976"
  
}
]

 

五、前端架构之抽象1:状态模块定义  

抽象1:状态模块定义

商品、店铺、订单、评论 —— 领域实体模块(entities)

各页面UI状态 —— UI模块

前端基础状态:登录态、全局异常信息

 前端架构_前端架构师是做什么的前端架构_前端架构师是做什么的前端架构_前端架构师是做什么的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//redux->modules->index.js
import 
{ combineReducer } from 
"redux"
;
import 
entities from 
"./entities"
;  
import 
home from 
"./home"
;
import 
detail from 
"./detail"
;
import 
app from 
"./app"
;
 
//合并成根reducer
const rootReducer = combineReducer({
    
entities,
    
home,
    
detail,
    
app
})

export default rootReducer

1
2
3
4
5
6
//各子reducer.js
const reducer = (state = {}, action) => {
    
return 
state;
}
 
export 
default 
reducer;  

  

六、前端架构之抽象2:网络请求层封装(redux-thunk) (redux中间件)

抽象2:网络请求层

原生的fetch API封装get、post方法

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
//utils->request.js
//设置响应的header,抽象成一个常量
const headers = 
new 
Headers({
   
"Accept"

"application/json"
,
   
"Content-Type"

"application/json"
})
 
//get方法处理get请求
function 
get(url) {
    
return 
fetch(url, {
        
method: 
"GET"
,
        
headers: headers
    
}).then(response => {      
//fetch返回的是一个promise对象,.then方法中可以解析出fetch API返回的数据
        
handleResponse(url, response);     
//response的通用处理:区分符合业务正常预期的response和异常的response
    
}).
catch
(err => {          
//catch中捕获异常,对异常的处理和handleResponse基本保持一致
        
console.log(`Request failed. url = ${url}. Message = ${err}`)
        
return 
Promise.reject({error: {
            
message: 
"Request failed."  
//不能说“服务端信息异常”了,因为还没到服务端
        
}})
    
})
}
 
//post方法处理post请求, 多一个data参数
function 
post(url, data) {
    
return 
fetch(url, {
        
method: 
"POST"
,
        
headers: headers,
        
body: data
    
}).then(response => {
        
handleResponse(url, response);
    
}).
catch
(err => {
        
console.log(`Request failed. url = ${url}. Message = ${err}`)
        
return 
Promise.reject({error: {
            
message: 
"Request failed."
        
}})
    
})
}
 
//基本的对response处理的函数(重在思路,项目都大致相同)
function 
handleResponse(url, response){
    
if
(response.status === 200){  
//符合业务预期的正常的response
        
return 
response.json()
    
}
else
{
        
console.log(`Request failed. url =  ${url}`)   
//输入错误信息
        
return 
Promise.reject({error: {  
//为了response可以继续被调用下去,即使在异常的情况下也要返回一个promise结构,生成一个reject状态的promise
            
message: 
"Request failed due to server error" 
        
}})
    
}
}
 
export 
{get, post} 

项目中使用到的url基础封装

1
2
3
4
5
6
7
8
9
10
//utils->url.js
//创建一个对象,对象中每一个属性是一个方法
export 
default 
{
  
//获取产品列表
  
getProductList: (path, rowIndex, pageSize) => `/mock/products/${path}.json?rowIndex=${rowIndex}&pageSize=${pageSize}`,
  
//获取产品详情
  
getProductDetail: (id) => `/mock/product_detail/${id}.json`,
  
//获取商品信息
  
getShopById: (id) => `/mock/shops/${id}.json`
} 

常规使用方式 (redux层比较臃肿、繁琐)

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
//redux->modules->home.js(首页)
import 
{get} from 
"../../utils/request"
import 
url from 
"../../utils/url"
 
//action types
export 
const types = {
    
//获取猜你喜欢请求: 值的第一部分以模块名(HOME)作为命名空间,防止action type在不同的模块中发生冲突, 第二部分为type名
    
FETCH_LIKES_REQUEST: 
"HOME/FETCH_LIKES_REQUEST"
,
    
//获取猜你喜欢请求成功
    
FETCH_LIKES_SUCCESS: 
"HOME/FETCH_LIKES_SUCCESS"
,
    
//获取猜你喜欢请求失败
    
FETCH_LIKES_FAILURE: 
"HOME/FETCH_LIKES_FAILURE"
}
 
//action: 所有的action放在一个actions对象下
export 
const actions = {
    
//获取猜你喜欢数据的action
    
loadLikes: () => {
        
return 
(dispatch, getState) => {  
//返回一个函数,接收dispatch 和 getState两个参数
          
dispatch(fetchLikesRequest());  
//第一步:dispatch一个请求开始的action type
            
return 
get(url.getProductList(0, 10)).then(    
//通过get方法进行网络请求
                
data => {                 
//请求成功时,dispatch出去data
                    
dispatch(fetchLikesSuccess(data))
                    
//其实在开发中还需要dispatcn一个module->product中提供的action,由product的reducer中处理,才能将数据保存如product中
                    
//dispatch(action)
                
},
                
error => {                
//请求失败时,dispatch出去error
                    
dispatch(fetchLikesFailure(error))
                
}
           
)
        
}
    
}
}
 
 
//action creator
//不被外部组件调用的,为action type所创建的action creator(所以不把它定义在actions内部,而定义在外部,且不把它导出export)
const fetchLikesRequest = () => ({
    
type: types.FETCH_LIKES_REQUEST
})
 
const fetchLikesSuccess = (data) => ({
    
type: types.FETCH_LIKES_SUCCESS,
    
data
})
 
const fetchLikesFailure = (error) => ({
    
type: types.FETCH_LIKES_FAILURE,
    
error
})
 
//reducer:根据action type处理不同逻辑
const reducer = (state = {}, action) => {
    
switch
(action.type) {
        
case 
types.FETCH_LIKES_REQUEST:   
//获取请求
        
//todo
        
case 
types.FETCH_LIKES_SUCCESS:   
//请求成功
        
//todo
        
case 
types.FETCH_LIKES_FAILURE:   
//请求失败
        
//todo
        
default
:
            
return 
state;
    
}
    
return 
state;
}
 
export 
default 
reducer;

1
2
3
4
5
6
//redux->modules->entities->products.js
const reducer = (state = {}, action) => {
    
return 
state;
}
 
export 
default 
reducer;  

使用redux中间件封装(简化模板式内容的编写)

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
//redux->modules->home.js(首页)
import 
{get} from 
"../../utils/request"
import 
url from 
"../../utils/url"
import 
{ FETCH_DATA } from 
"../middleware/api"
import 
{ schema } from 
"./entities/products"
 
export 
const types = {
    
//获取猜你喜欢请求
    
FETCH_LIKES_REQUEST: 
"HOME/FETCH_LIKES_REQUEST"
,
    
//获取猜你喜欢请求成功
    
FETCH_LIKES_SUCCESS: 
"HOME/FETCH_LIKES_SUCCESS"
,
    
//获取猜你喜欢请求失败
    
FETCH_LIKES_FAILURE: 
"HOME/FETCH_LIKES_FAILURE"
}
 
//简化模板式内容需要的特殊结构 —— 代表使用redux-thunk进行网络请求的过程
//(
//   FETCH_DATA:{          //表明action是用来获取数据的    
//        types:['request', 'success", 'fail'],
//        endpoint: url,   //描述请求对应的url
//        //schema在数据库中代表表的结构,这里代表领域实体的结构
//        schema: {        //需要的原因:假设获取的是商品数据,当中间件获取到商品数据后还需要对数组格式的数据作进一步“扁平化”处理,转译成Key:Value形式
//             id: "product_id",  //领域数据中的哪一个属性可以代表这个领域实体的id值
//             name: 'products'   //正在处理的是哪一个领域实体(相当于中间件在处理数据库表时哪一张表的名字)
//        }
//    }
//}
 
export 
const actions = {
    
//简化版的action
    
loadLikes: () => {
        
return 
(dispatch, getState) => {
            
const endpoint = url.getProductList(0, 10)
            
return 
dispatch(fetchLikes(endpoint))   
//dispatch特殊的action,发送获取请求的(中间件)处理
        
}
    
}
}
 
//特殊的action, 用中间件可以处理的结构
const fetchLikes = (endpoint) => ({
   
[FETCH_DATA]: { 
       
types: [
           
types.FETCH_LIKES_REQUEST,
           
types.FETCH_LIKES_SUCCESS,
           
types.FETCH_LIKES_FAILURE
       
],
       
endpoint,
       
schema
   
},
   
//params 如果有额外的参数params, 当获取请求成功(已经发送FETCH_LIKES_SUCCESS)后,
   
//       希望params可以被后面的action接收到, action需要做【增强处理】
})
 
const reducer = (state = {}, action) => {
    
switch
(action.type) {
        
case 
types.FETCH_LIKES_REQUEST:
        
//todo
        
case 
types.FETCH_LIKES_SUCCESS:
        
//todo
        
case 
types.FETCH_LIKES_FAILURE:
        
//todo
        
default
:
            
return 
state;
    
}
    
return 
state;
}
 
export 
default 
reducer;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//redux->modules->entities->products.js
//schema在数据库中代表的是表的结构,这里代表领域实体的结构
export 
const schema = {
    
name: 
'products'
,  
//领域实体的名字,products挂载到redux的store的属性的名称,保持和文件名相同
    
id: 
'id'           
//标识了领域实体的哪一个字段是用来作为id解锁数据的
}
 
const reducer = (state = {}, action) => {
    
if
(action.response && action.response.products){ 
//(如果)获取到的数据是一个对象{[name]: KvObj, ids},name是领域实体名字,这里是products
        
//将获取到的数据保存【合并】到当前的products【领域数据状态】中,并且数据是通过中间件扁平化的key value形式的数据
        
return 
{...state, ...action.response.products}
    
}
    
return 
state;
}
 
export 
default 
reducer;

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
//redux->middleware->api.js
import 
{ get } from 
"../../utils/request"    
//对get请求进行中间件的封装
                                             
// update(修改)、delete(删除)同理,只是在调用api请求成功的数据处理中有一些区别
                                             
// 大众点评项目只是纯前端项目,不能直接进行修改和删除的api处理,这里不作展示
 
//经过中间件处理的action所具有的标识
export 
const FETCH_DATA = 
'FETCH_DATA'
 
//中间件的函数式声明
export 
default 
store => next => action => {
     
    
const callAPI = action[FETCH_DATA]   
//解析有FETCH_DATA字段的action就是是需要中间件处理的action
     
    
//类型判断:如果是undefined,表明action不是一个用来获取数据的action,而是一个其它类型的action, 中间件放过对这个action的处理
    
if
(
typeof 
callAPI === 
'undefined'
){ 
        
return 
next(action)              
//直接交由后面的中间件进行处理
    
}
 
    
const { endpoint, schema, types } = callAPI  
//交由这个中间件进行处理的action的三个属性,必须符合一定的规范
 
    
if
(
typeof 
endpoint !== 
'string'
){
        
throw 
new 
Error(
'endpoint必须为字符串类型的URL'
)
    
}
    
if
(!schema){
        
throw 
new 
Error(
'必须指定领域实体的schema'
)
    
}
    
if
(!Array.isArray(types) && types.length !== 3){
        
throw 
new 
Error(
'需要指定一个包含了3个action type的数组'
)
    
}
    
if
(!types.every(type => 
typeof 
type === 
'string'
)){
        
throw 
new 
Error(
'action type必须为字符串类型'
)
    
}
 
    
//【增强版的action】——保证额外的参数data会被继续传递下去
    
const actionWith = data => {
        
const finalAction = {...action, ...data}  
//在原有的action基础上,扩展了data
        
delete 
finalAction[FETCH_DATA]            
//将原action的FETCH_DATA层级的属性删除掉
                                                  
//因为经过中间件处理后,再往后面的action传递的时候就已经不需要FETCH_DATA这一层级的属性了
        
return 
finalAction
    
}
 
    
const [requestType, successType, failureType] = types  
 
    
next(actionWith({type: requestType}))       
//调用next,代表有一个请求要发送
    
return 
fetchData(endpoint, schema).then(    
//【真正的数据请求】—— 调用定义好的fetchData方法,返回的是promise结构
        
response => next(actionWith({           
//拿到经过处理的response, 调用next发送响应成功的action
            
type: successType,                 
            
response                            
//获取到的数据response —— 是一个对象 {[name]: KvObj, ids},name是领域实体名字如products
        
}))
        
error => next(actionWith({
            
type: failureType,
            
error: error.message || 
'获取数据失败'
        
}))
    
)
}
 
//【执行网络请求】
const fetchData = (endpoint, schema) => {
    
return 
get(endpoint).then(data => {         
//对get请求进行中间件的封装, endpoint对应请求的url, 解析获取到的数据data
        
return 
normalizeData(data, schema)      
//调用normalizeData方法,对获取到的data数据,根据schema进行扁平化处理
    
})
}
 
//根据schema, 将获取的数据【扁平化处理】
const normalizeData = (data, schema) => {
    
const {id, name} = schema
    
let 
kvObj = {}  
//领域数据的扁平化结构 —— 定义kvObj作为最后存储扁平化数据【对象】的变量
    
let 
ids = []    
//领域数据的有序性 —— 定义ids【数组结构】存储数组当中获取的每一项的id
    
if
(Array.isArray(data)){     
//如果返回到的data是一个数组
        
data.forEach(item => {
            
kvObj[item[id]] = item
            
ids.push(item[id])
        
})
    

else 
{                     
//如果返回到的data是一个对象
        
kvObj[data[id]] = data
        
ids.push(data[id])
    
}
    
return 
{
        
[name]: kvObj,  
//不同领域实体的名字,如products
        
ids
    
}
} 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//redux->store.js
import 
{ createStore, applyMiddleware } from 
"redux"
<br>
//处理异步请求(action)的中间件
import 
thunk from 
"redux-thunk"
import 
api from 
"./middleware/api" 
import 
rootReducer from 
"./modules"
 
 
let 
store;
 
if 
(
    
process.env.NODE_ENV !== 
"production" 
&&
    
window.__REDUX_DEVTOOLS_EXTENSION__
) {
    
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__;
    
store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk, api)));

else 
{
    
store = createStore(rootReducer, applyMiddleware(thunk, api));  
//将中间件api添加到redux的store中
}
 
export 
default 
store

 

七、前端架构之抽象3:通用错误处理

抽象3:通用错误处理  

错误信息组件 —— ErrorToast会在一定时间内消失

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
//component->ErrorToast->index.js
import 
React, { Component } from 
'react'
import 
"./style.css"
;
 
class 
ErrorToast 
extends 
Component {
    
render() {
        
const { msg } = 
this
.props
 
        
return 
(
            
<div className=
"errorToast"
>
                
<div className=
"errorToast_text"
>
                   
{msg}
                
</div>
            
</div>
        
);
    
}
 
    
componentDidMount() {
        
this
.timer = setTimeout(() => {
            
this
.props.clearError();
        
}, 3000)
    
}
 
    
componentWillUnmount() {
        
if
(
this
.timer) {
            
clearTimeout(
this
.timer)
        
}
    
}
}
 
export 
default 
ErrorToast;

错误状态  

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
//redux->modlues->app.js
/**
 
* 前端的通用基础状态
 
*/
const initialState = {
    
error: 
null
}
 
export 
const types = {
    
CLEAR_ERROR: 
"APP/CLEAR_ERROR"
}
 
//action creators
export 
const actions = {
    
clearError: () => ({
        
type: types.CLEAR_ERROR
    
})
}
 
const reducer = (state = initialState, action) => {
    
const { type, error } = action
    
if
(type === types.CLEAR_ERROR) {
        
return 
{...state, error: 
null
}
    
}
else 
if
(error){
        
return 
{...state, error: error}
    
}
    
return 
state;
}
 
export 
default 
reducer;
 
//selectors
export 
const getError = (state) => {
    
return 
state.app.error
}

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
//containers->App->index.js
import 
React, { Component } from 
'react'
;
import 
{ bindActionCreators } from 
'redux'
;
import 
{ connect } from 
'react-redux'
;
import 
ErrorToast from 
"../../components/ErrorToast"
;
import 
{ actions as appActions, getError } from 
'../../redux/modules/app'
import 
'./style.css'
;
 
 
class 
App 
extends 
Component {
  
render() {
    
const {error, appActions: {clearError}} = 
this
.props;
    
return 
(
        
<div className=
"App"
>
          
{error ? <ErrorToast msg={error} clearError={clearError}/> : 
null
}
        
</div>
    
)
  
}
}
 
const mapStateToProps = (state, props) => {
  
return 
{
    
error: getError(state)
  
}
}
 
const mapDispatchToProps = (dispatch) => {
  
return 
{
    
appActions: bindActionCreators(appActions, dispatch)
  
}
}
 
export 
default 
connect(mapStateToProps, mapDispatchToProps)(App);


注:项目来自慕课网  

人与人的差距是:每天24小时除了工作和睡觉的8小时,剩下的8小时,你以为别人都和你一样发呆或者刷视频

今天的文章前端架构_前端架构师是做什么的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/50774.html

(0)
编程小号编程小号
上一篇 2023-09-01
下一篇 2023-09-01

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注