<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          前端mock完美解決方案實(shí)戰(zhàn)

          共 22027字,需瀏覽 45分鐘

           ·

          2021-02-04 21:30



          作者:rebareba
          來源:https://segmentfault.com/a/1190000038320901



          寫在前面,本文閱讀需要一定Nodejs的相關(guān)知識(shí),因?yàn)闀?huì)擴(kuò)展webpack的相關(guān)功能,并且實(shí)現(xiàn)需要遵守一定約定和Ajax封裝。沉淀的腳手架也放到Github上供給同學(xué)參考React-Starter, 使用手冊(cè)還沒寫完善, 整體思路和React還是Vue無關(guān),如果對(duì)大家有收獲記得Star下。


          它有這些功能:


          • 開發(fā)打包有不同配置
          • eslint 驗(yàn)證
          • 代碼風(fēng)格統(tǒng)一
          • commit 規(guī)范驗(yàn)證
          • 接口mock
          • 熱更新
          • 異步組件



          Mock功能介紹


          市面上講前端mock怎么做的文章很多,整體上閱讀下來的沒有一個(gè)真正站在前端角度上讓我覺得強(qiáng)大和易用的。下面就說下我期望的前端mock要有哪些功能:

          1. mock功能和前端代碼解耦
          2. 一個(gè)接口支持多種mock情況
          3. 無需依賴另外的后端服務(wù)和第三方庫
          4. 能在network看到mock接口的請(qǐng)求且能區(qū)分
          5. mock數(shù)據(jù)、接口配置和頁面在同一個(gè)目錄下
          6. mock配置改變無需重啟前端dev
          7. 生產(chǎn)打包可以把mock數(shù)據(jù)注入到打包的js中走前端mock
          8. 對(duì)于后端已有的接口也能快速把Response數(shù)據(jù)轉(zhuǎn)化為mock數(shù)據(jù)

          上面的這些功能我講其中幾點(diǎn)的作用:

          對(duì)于第7點(diǎn)的作用是后續(xù)項(xiàng)目開發(fā)完成,在完全沒有開發(fā)后端服務(wù)的情況下,也可以進(jìn)行演示。這對(duì)于一些ToB定制的項(xiàng)目來沉淀項(xiàng)目地圖(案例)很有作用。
          對(duì)于第8點(diǎn)在開發(fā)環(huán)境后端服務(wù)經(jīng)常不穩(wěn)定下,不依賴后端也能做頁面開發(fā),核心是能實(shí)現(xiàn)一鍵生成mock數(shù)據(jù)。




          配置解耦


          耦合情況


          什么是前端配置解耦,首先讓我們看下平時(shí)配置耦合情況有哪些:

          • webpack-dev后端測(cè)試環(huán)境變了需要改git跟蹤的代碼
          • dev和build的時(shí)候 需要改git跟蹤的代碼
          • 開發(fā)的時(shí)候想這個(gè)接口mock 需要改git跟蹤的代碼 mockUrl ,mock?

          如何解決


          前端依賴的配置解耦的思路是配置文件conf.json是在dev或build的時(shí)候動(dòng)態(tài)生成的,然后該文件在前端項(xiàng)目引用:

          ├──?config
          │???├──?conf.json????????????????????????????????????#?git?不跟蹤
          │???├──?config.js????????????????????????????????????#?git?不跟蹤
          │???├──?config_default.js
          │???├──?index.js
          │???└──?webpack.config.js
          ├──?jsconfig.json
          ├──?mock.json????????????????????????????????????????????#?git?不跟蹤

          webpack配置文件引入js的配置,生成conf.json

          //?config/index.js
          const?_?=?require("lodash");
          let?config?=?_.cloneDeep(require("./config_default"))
          try?{
          ??const?envConfig?=?require( ./config )?//?eslint-disable-line
          ??config?=?_.merge(config,?envConfig);
          }?catch?(e)?{
          ????//?
          }
          module.exports?=?config;

          默認(rèn)使用config_default.js 的內(nèi)容,如果有config.js 則覆蓋,開發(fā)的時(shí)候復(fù)制config_default.js 為config.js 后續(xù)相關(guān)配置可以修改config.js即可。

          //?config/config_default.js
          const?pkg?=?require("../package.json");
          module.exports?=?{
          ??projectName:?pkg.name,
          ??version:?pkg.version,
          ??port:?8888,
          ??proxy:?{
          ????"/render-server/api/*":?{
          ??????target:?`http://192.168.1.8:8888`,
          ??????changeOrigin:?true,?//?支持跨域請(qǐng)求
          ??????secure:?true,?//?支持?https
          ????},
          ??},
          ??...
          ??conf:?{
          ????dev:?{
          ??????title:?"前端模板",
          ??????pathPrefix:?"/react-starter",?//?統(tǒng)一前端路徑前綴
          ??????apiPrefix:?"/api/react-starter",?//
          ??????debug:?true,
          ??????delay:?500,????//?mock數(shù)據(jù)模擬延遲
          ??????mock:?{
          ????????//?"global.login":?"success",
          ????????//?"global.loginInfo":?"success",
          ??????}
          ????},
          ????build:?{
          ??????title:?"前端模板",
          ??????pathPrefix:?"/react-starter",
          ??????apiPrefix:?"/api/react-starter",
          ??????debug:?false,
          ??????mock:?{}
          ????}
          ??}
          };

          在開發(fā)或打包的時(shí)候根據(jù)環(huán)境變量使用conf.dev或conf.build 生成conf.json文件內(nèi)容

          //?package.json
          {
          ??"name":?"react-starter",
          ??"version":?"1.0.0",
          ??"description":?"react前端開發(fā)腳手架",
          ??"main":?"index.js",
          ??"scripts":?{
          ????"start":?"webpack-dev-server?--config? ./config/webpack.config.js ?--open?--mode?development",
          ????"build":?"cross-env?BUILD_ENV=VERSION?webpack?--config? ./config/webpack.config.js ?--mode?production?--progress?--display-modules?&&?npm?run?tar",
          ????"build-mock":?"node?./scripts/build-mock.js?"
          ??},
          ??...
          }

          指定webpack路徑是./config/webpack.config.js

          然后在webpack.config.js中引入配置并生成conf.json文件

          //?config/webpack.config.js
          const?config?=?require( . )
          const?env?=?process.env.BUILD_ENV??? build ?:? dev
          const?confJson?=?env?===? build ???config.conf.build?:?config.conf.dev
          fs.writeFileSync(path.join(__dirname,? ./conf.json ),??JSON.stringify(confGlobal,?null,? ))

          引用配置


          在src/common/utils.jsx文件中暴露出配置項(xiàng),配置也可以通過window.conf來覆蓋

          //?src/common/utils.jsx
          import?conf?from? @/config/conf.json
          export?const?config?=?Object.assign(conf,?window.conf)

          然后就可以在各個(gè)頁面中使用

          import?{config}?from? @src/common/utils 
          class?App?extends?Component?{
          ??render()?{
          ????return?(
          ??????history={history}>
          ????????
          ??????????${config.pathPrefix}`}?component={Home}?/>
          ??????????"/"?to={`${config.pathPrefix}`}?/>
          ????????

          ??????
          ????)
          ??}
          }
          ReactDOM.render(
          ?????,
          ??document.getElementById( root ),
          )



          Mock實(shí)現(xiàn)


          效果


          為了實(shí)現(xiàn)我們想要的mock的相關(guān)功能,首先是否開啟mock的配置解耦可以通過上面說的方式來實(shí)現(xiàn),我們一般在頁面異步請(qǐng)求的時(shí)候都會(huì)目錄定義一個(gè)io.js的文件, 里面定義了當(dāng)前頁面需要調(diào)用的相關(guān)后端接口:

          //?src/pages/login/login-io.js
          import?{createIo}?from? @src/io

          const?apis?=?{
          ??//?登錄
          ??login:?{
          ????method:? POST ,
          ????url:? /auth/login ,
          ??},
          ??//?登出
          ??logout:?{
          ????method:? POST ,
          ????url:? /auth/logout ,
          ??},
          }
          export?default?createIo(apis,? login )?//?對(duì)應(yīng)login-mock.json

          上面定義了登錄和登出接口,我們希望對(duì)應(yīng)開啟的mock請(qǐng)求能使用當(dāng)前目錄下的login-mock.json文件的內(nèi)容

          //?src/pages/login/login-mock.json
          {
          ????"login":?{
          ????????"failed":?{
          ????????????"success":?false,
          ????????????"code":?"ERROR_PASS_ERROR",
          ????????????"content":?null,
          ????????????"message":?"賬號(hào)或密碼錯(cuò)誤!"
          ????????},
          ????????"success":?{
          ????????????"success":?true,
          ????????????"code":?0,
          ????????????"content":?{
          ????????????????"name":?"admin",
          ????????????????"nickname":?"超級(jí)管理員",
          ????????????????"permission":?15
          ????????????},
          ????????????"message":?""
          ????????}
          ????},
          ????"logout":?{
          ????????"success":?{
          ????????????"success":?true,
          ????????????"code":?0,
          ????????????"content":?null,
          ????????????"message":?""
          ????????}
          ????}
          }

          在調(diào)用logout登出這個(gè)Ajax請(qǐng)求的時(shí)候且我們的conf.json中配置的是"login.logout": "success"?就返回login-mock.json中的login.success 的內(nèi)容,配置沒有匹配到就請(qǐng)求轉(zhuǎn)發(fā)到后端服務(wù)。

          //?config/conf.json
          {
          ????"title":?"前端后臺(tái)模板",
          ????"pathPrefix":?"/react-starter",
          ????"apiPrefix":?"/api/react-starter",
          ????"debug":?true,
          ????"delay":?500,
          ????"mock":?{
          ????????"login.logout":?"success"
          ????}
          }

          這是我們最終要實(shí)現(xiàn)的效果,這里有一個(gè)約定:項(xiàng)目目錄下所有以-mock.jsom文件結(jié)尾的文件為mock文件,且文件名不能重復(fù)

          思路


          在webpack配置項(xiàng)中devServer的proxy配置接口的轉(zhuǎn)發(fā)設(shè)置,接口轉(zhuǎn)發(fā)使用了功能強(qiáng)大的?
          http-proxy-middleware?(https://github.com/chimurai/http-proxy-middleware)
          軟件包, 我們約定proxy的配置格式是:

          ??proxy:?{
          ????"/api/react-starter/*":?{
          ??????target:?`http://192.168.90.68:8888`,
          ??????changeOrigin:?true,
          ??????secure:?true,
          ??????//?onError:?(),
          ??????//?onProxyRes,
          ??????//?onProxyReq??
          ????},
          ??},

          它有幾個(gè)事件觸發(fā)的配置:

          • option.onError?出現(xiàn)錯(cuò)誤
          • option.onProxyRes?后端響應(yīng)后
          • option.onProxyReq?請(qǐng)求轉(zhuǎn)發(fā)前
          • option.onProxyReqWs
          • option.onOpen
          • option.onClose

          所以我們需要定制這幾個(gè)事情的處理,主要是請(qǐng)求轉(zhuǎn)發(fā)前和請(qǐng)求處理后

          onProxyReq


          想在這里來實(shí)現(xiàn)mock的處理, 如果匹配到了mock數(shù)據(jù)我們就直接響應(yīng),就不轉(zhuǎn)發(fā)請(qǐng)求到后端。怎么做呢:思路是依賴請(qǐng)求頭,dev情況下前端在調(diào)用的時(shí)候能否注入約定好的請(qǐng)求頭 告訴我需要尋找哪個(gè)mock數(shù)據(jù)項(xiàng), 我們約定Header:

          • mock-key?來匹配mock文件如login-mock.json的內(nèi)容, 如login
          • mock-method?來匹配對(duì)應(yīng)文件內(nèi)容的方法項(xiàng) 如logout

          然后conf.json中mock配置尋找到具體的響應(yīng)項(xiàng)目如:"login.logout": "success/failed"的內(nèi)容

          onProxyRes


          如果調(diào)用了真實(shí)的后端請(qǐng)求,就把請(qǐng)求的響應(yīng)數(shù)據(jù)緩存下來,緩存到api-cache目錄下文件格式mock-key.mock-method.json

          ├──?api-cache????????????????????????????????????#?git?不跟蹤
          │???├──?login.login.json
          │???└──?login.logout.json

          //?api-cache/global.logout.json
          {
          ????"success":?{
          ????????"date":?"2020-11-17?05:32:17",
          ????????"method":?"POST",
          ????????"path":?"/render-server/api/logout",
          ????????"url":?"/render-server/api/logout",
          ????????"resHeader":?{
          ????????????"content-type":?"application/json;?charset=utf-8",
          ??????...
          ????????},
          ????????"reqHeader":?{
          ????????????"host":?"127.0.0.1:8888",
          ????????????"mock-key":?"login",
          ????????????"mock-method":?"logout"
          ??????...
          ????????},
          ????????"query":?{},
          ????????"reqBody":?{},
          ????????"resBody":?{
          ????????????"success":?true,
          ????????????"code":?0,
          ????????????"content":?null,
          ????????????"message":?""
          ????????}
          ????}
          }

          這樣做的目的是為了后續(xù)實(shí)現(xiàn)一鍵生成mock文件。



          前端接口封裝


          使用


          上面我們看到定義了接口的io配置:

          //?src/pages/login/login-io.js
          import?{createIo}?from? @src/io

          const?apis?=?{
          ??//?登錄
          ??login:?{
          ????method:? POST ,
          ????url:? /auth/login ,
          ??},
          ??//?登出
          ??logout:?{
          ????method:? POST ,
          ????url:? /auth/logout ,
          ??},
          }
          export?default?createIo(apis,? login )?//?login注冊(cè)到header的mock-key

          我們?cè)趕tore中使用

          //?src/pages/login/login-store.js

          import?{observable,?action,?runInAction}?from? mobx
          import?io?from? ./login-io
          //?import?{config,?log}?from? ./utils

          export?class?LoginStore?{
          ??//?用戶信息
          ??@observable?userInfo
          ??//?登陸操作
          [email protected]
          ??async?login(params)?{
          ????const?{success,?content}?=?await?io.login(params)
          ????if?(!success)?return
          ????runInAction(()?=>?{
          ??????this.userInfo?=?content
          ????})
          ??}
          }
          export?default?LoginStore

          通過 createIo(apis, ?login )?的封裝在調(diào)用的時(shí)候就可以非常簡(jiǎn)單的來傳遞請(qǐng)求參數(shù),簡(jiǎn)單模式下會(huì)判斷參數(shù)是到body還是到query中。復(fù)雜的也可以支持比如可以header,query, body等這里不演示了。

          createIo 請(qǐng)求封裝


          這個(gè)是前端接口封裝的關(guān)鍵地方,也是mock請(qǐng)求頭注入的地方

          //?src/io/index.jsx
          import?{message,?Modal}?from? antd
          import?{config,?log,?history}?from? @src/common/utils
          import?{ERROR_CODE}?from? @src/common/constant
          import?creatRequest?from? @src/common/request
          let?mockData?=?{}
          try?{
          ??//?eslint-disable-next-line?global-require,?import/no-unresolved
          ??mockData?=?require( @/mock.json )
          }?catch?(e)?{
          ??log(e)
          }

          let?reloginFlag?=?false
          //?創(chuàng)建一個(gè)request
          export?const?request?=?creatRequest({
          ??//?自定義的請(qǐng)求頭
          ??headers:?{ Content-Type :? application/json },
          ??//?配置默認(rèn)返回?cái)?shù)據(jù)處理
          ??action:?(data)?=>?{
          ????//?統(tǒng)一處理未登錄的彈框
          ????if?(data.success?===?false?&&?data.code?===?ERROR_CODE.UN_LOGIN?&&?!reloginFlag)?{
          ??????reloginFlag?=?true
          ??????//?TODO?這里可能統(tǒng)一跳轉(zhuǎn)到?也可以是彈窗點(diǎn)擊跳轉(zhuǎn)
          ??????Modal.confirm({
          ????????title:? 重新登錄 ,
          ????????content:? ,
          ????????onOk:?()?=>?{
          ??????????//?location.reload()
          ??????????history.push(`${config.pathPrefix}/login?redirect=${window.location.pathname}${window.location.search}`)
          ??????????reloginFlag?=?false
          ????????},
          ??????})
          ????}
          ??},
          ??//?是否錯(cuò)誤顯示message
          ??showError:?true,
          ??message,
          ??//?是否以拋出異常的方式?默認(rèn)false?{success:?boolean判斷}
          ??throwError:?false,
          ??//?mock?數(shù)據(jù)請(qǐng)求的等待時(shí)間
          ??delay:?config.delay,
          ??//?日志打印
          ??log,
          })

          //?標(biāo)識(shí)是否是簡(jiǎn)單傳參數(shù),?值為true標(biāo)識(shí)復(fù)雜封裝
          export?const?rejectToData?=?Symbol( flag )

          /**
          ?*?創(chuàng)建請(qǐng)求IO的封裝
          ?*?@param?ioContent?{any?{?url:?string?method?:?string?mock?:?any?apiPrefix?:?string}}
          ??}
          ?*?@param?name?mock數(shù)據(jù)的對(duì)應(yīng)文件去除-mock.json后的
          ?*/
          export?const?createIo?=?(ioContent,?name?=? )?=>?{
          ??const?content?=?{}
          ??Object.keys(ioContent).forEach((key)?=>?{
          ????/**
          ?????*?@param?{baseURL?:?string,?rejectToData?:?boolean,??params?:?{},?query?:?{},?timeout?:?number,?action?(data:?any):?any,?headers?:?{},??body?:?any,?data?:?any,???mock?:?any}
          ?????*?@returns?{message,?content,?code,success:?boolean}
          ?????*/
          ????content[key]?=?async?(options?=?{})?=>?{
          ??????//?這里判斷簡(jiǎn)單請(qǐng)求封裝?rejectToData=true?表示復(fù)雜封裝
          ??????if?(!options[rejectToData])?{
          ????????options?=?{
          ??????????data:?options,
          ????????}
          ??????}
          ??????delete?options[rejectToData]
          ??????if?(
          ????????config.debug?===?false?&&
          ????????name?&&
          ????????config.mock?&&
          ????????config.mock[`${name}.${key}`]?&&
          ????????mockData[name]?&&
          ????????mockData[name][key]
          ??????)?{?//?判斷是否是生產(chǎn)打包?mock注入到代碼中
          ????????ioContent[key].mock?=?JSON.parse(JSON.stringify(mockData[name][key][config.mock[`${name}.${key}`]]))
          ??????}?else?if?(name?&&?config.debug?===?true)?{?//注入?mock請(qǐng)求頭
          ????????if?(options.headers)?{
          ??????????options.headers[ mock-key ]?=?name
          ??????????options.headers[ mock-method ]?=?key
          ????????}?else?{
          ??????????options.headers?=?{ mock-key :?name,? mock-method :?key}
          ????????}
          ??????}
          ??????const?option?=?{...ioContent[key],?...options}

          ??????option.url?=?((option.apiPrefix???option.apiPrefix?:?config.apiPrefix)?||? )?+?option.url

          ??????return?request(option)
          ????}
          ??})
          ??return?content
          }

          這里對(duì)request也做進(jìn)一步的封裝,配置項(xiàng)設(shè)置了一些默認(rèn)的處理設(shè)置。比如通用的請(qǐng)求響應(yīng)失敗的是否有一個(gè)message, 未登錄的情況是否有一個(gè)彈窗提示點(diǎn)擊跳轉(zhuǎn)登陸頁。如果你想定義多個(gè)通用處理可以再創(chuàng)建一個(gè)request2和createIo2。

          request封裝axios


          是基于axios的二次封裝, 并不是非常通用,主要是在約定的請(qǐng)求失敗和成功的處理有定制,如果需要可以自己修改使用。

          import?axios?from? axios 

          //?配置接口參數(shù)
          //?declare?interface?Options?{
          //???url:?string
          //???baseURL?:?string
          //???//?默認(rèn)GET
          //???method?:?Method
          //???//?標(biāo)識(shí)是否注入到data參數(shù)
          //???rejectToData?:?boolean
          //???//?是否直接彈出message?默認(rèn)是
          //???showError?:?boolean
          //???//?指定?回調(diào)操作?默認(rèn)登錄處理
          //???action?(data:?any):?any
          //???headers?:?{
          //?????[index:?string]:?string
          //???}
          //???timeout?:?number
          //???//?指定路由參數(shù)
          //???params?:?{
          //?????[index:?string]:?string
          //???}
          //???//?指定url參數(shù)
          //???query?:?any
          //???//?指定body?參數(shù)
          //???body?:?any
          //???//?混合處理?Get到url,?delete?post?到body,?也替換路由參數(shù)?在createIo封裝
          //???data?:?any
          //???mock?:?any
          //?}
          //?ajax?請(qǐng)求的統(tǒng)一封裝
          //?TODO?1.?對(duì)jsonp請(qǐng)求的封裝?2.?重復(fù)請(qǐng)求

          /**
          ?*?返回ajax?請(qǐng)求的統(tǒng)一封裝
          ?*?@param?Object?option?請(qǐng)求配置
          ?*?@param?{boolean}?opts.showError?是否錯(cuò)誤調(diào)用message的error方法
          ?*?@param?{object}?opts.message??包含?.error方法?showError?true的時(shí)候調(diào)用
          ?*?@param?{boolean}?opts.throwError?是否出錯(cuò)拋出異常
          ?*?@param?{function}?opts.action??包含?自定義默認(rèn)處理?比如未登錄的處理
          ?*?@param?{object}?opts.headers??請(qǐng)求頭默認(rèn)content-type:?application/json
          ?*?@param?{number}?opts.timeout??超時(shí)?默認(rèn)60秒
          ?*?@param?{number}?opts.delay???mock請(qǐng)求延遲
          ?*?@returns?{function}?{params,?url,?headers,?query,?data,?mock}?data混合處理?Get到url,?delete?post?到body,?也替換路由參數(shù)?在createIo封裝
          ?*/
          export?default?function?request(option?=?{})?{
          ??return?async?(optionData)?=>?{
          ????const?options?=?{
          ??????url:? ,
          ??????method:? GET ,
          ??????showError:?option.showError?!==?false,
          ??????timeout:?option.timeout?||?60?*?1000,
          ??????action:?option.action,
          ??????...optionData,
          ??????headers:?{ X-Requested-With :? XMLHttpRequest ,?...option.headers,?...optionData.headers},
          ????}
          ????//?簡(jiǎn)單請(qǐng)求處理
          ????if?(options.data)?{
          ??????if?(typeof?options.data?===? object )?{
          ????????Object.keys(options.data).forEach((key)?=>?{
          ??????????if?(key[0]?===? : ?&&?options.data)?{
          ????????????options.url?=?options.url.replace(key,?encodeURIComponent(options.data[key]))
          ????????????delete?options.data[key]
          ??????????}
          ????????})
          ??????}
          ??????if?((options.method?||? ).toLowerCase()?===? get ?||?(options.method?||? ).toLowerCase()?===? head )?{
          ????????options.query?=?Object.assign(options.data,?options.query)
          ??????}?else?{
          ????????options.body?=?Object.assign(options.data,?options.body)
          ??????}
          ????}
          ????//?路由參數(shù)處理
          ????if?(typeof?options.params?===? object )?{
          ??????Object.keys(options.params).forEach((key)?=>?{
          ????????if?(key[0]?===? : ?&&?options.params)?{
          ??????????options.url?=?options.url.replace(key,?encodeURIComponent(options.params[key]))
          ????????}
          ??????})
          ????}
          ????//?query?參數(shù)處理
          ????if?(options.query)?{
          ??????const?paramsArray?=?[]
          ??????Object.keys(options.query).forEach((key)?=>?{
          ????????if?(options.query[key]?!==?undefined)?{
          ??????????paramsArray.push(`${key}=${encodeURIComponent(options.query[key])}`)
          ????????}
          ??????})
          ??????if?(paramsArray.length?>?0?&&?options.url.search(/?/)?===?-1)?{
          ????????options.url?+=?`?${paramsArray.join( & )}`
          ??????}?else?if?(paramsArray.length?>?0)?{
          ????????options.url?+=?`&${paramsArray.join( & )}`
          ??????}
          ????}
          ????if?(option.log)?{
          ??????option.log( request??options ,?options.method,?options.url)
          ??????option.log(options)
          ????}
          ????if?(options.headers[ Content-Type ]?===? application/json ?&&?options.body?&&?typeof?options.body?!==? string )?{
          ??????options.body?=?JSON.stringify(options.body)
          ????}
          ????let?retData?=?{success:?false}
          ????//?mock?處理
          ????if?(options.mock)?{
          ??????retData?=?await?new?Promise((resolve)?=>
          ????????setTimeout(()?=>?{
          ??????????resolve(options.mock)
          ????????},?option.delay?||?500),
          ??????)
          ????}?else?{
          ??????try?{
          ????????const?opts?=?{
          ??????????url:?options.url,
          ??????????baseURL:?options.baseURL,
          ??????????params:?options.params,
          ??????????method:?options.method,
          ??????????headers:?options.headers,
          ??????????data:?options.body,
          ??????????timeout:?options.timeout,
          ????????}
          ????????const?{data}?=?await?axios(opts)
          ????????retData?=?data
          ??????}?catch?(err)?{
          ????????retData.success?=?false
          ????????retData.message?=?err.message
          ????????if?(err.response)?{
          ??????????retData.status?=?err.response.status
          ??????????retData.content?=?err.response.data
          ??????????retData.message?=?`瀏覽器請(qǐng)求非正常返回:?狀態(tài)碼?${retData.status}`
          ????????}
          ??????}
          ????}

          ????//?自動(dòng)處理錯(cuò)誤消息
          ????if?(options.showError?&&?retData.success?===?false?&&?retData.message?&&?option.message)?{
          ??????option.message.error(retData.message)
          ????}
          ????//?處理Action
          ????if?(options.action)?{
          ??????options.action(retData)
          ????}
          ????if?(option.log?&&?options.mock)?{
          ??????option.log( request?response: ,?JSON.stringify(retData))
          ????}
          ????if?(option.throwError?&&?!retData.success)?{
          ??????const?err?=?new?Error(retData.message)
          ??????err.code?=?retData.code
          ??????err.content?=?retData.content
          ??????err.status?=?retData.status
          ??????throw?err
          ????}
          ????return?retData
          ??}
          }

          一鍵生成mock


          根據(jù)api-cache下的接口緩存和定義的xxx-mock.json文件來生成。

          #?"build-mock":?"node?./scripts/build-mock.js"
          #?所有:
          npm?run?build-mock?mockAll?
          #?單個(gè)mock文件:
          npm?run?build-mock?login
          #?單個(gè)mock接口:
          npm?run?build-mock?login.logout
          #?復(fù)雜?
          npm?run?build-mock?login.logout?user

          具體代碼參考build-mock.js
          https://github.com/rebareba/react-starter/blob/main/scripts/build-mock.js

          mock.json文件生成


          為了在build打包的時(shí)候把mock數(shù)據(jù)注入到前端代碼中去,使得mock.json文件內(nèi)容盡可能的小,會(huì)根據(jù)conf.json的配置項(xiàng)來動(dòng)態(tài)生成mock.json的內(nèi)容,如果build里面沒有開啟mock項(xiàng),內(nèi)容就會(huì)是一個(gè)空json數(shù)據(jù)。當(dāng)然后端接口代理處理內(nèi)存中也映射了一份該mock.json的內(nèi)容。這里需要做幾個(gè)事情:

          • 根據(jù)配置動(dòng)態(tài)生成mock.json的內(nèi)容
          • 監(jiān)聽config文件夾 判斷關(guān)于mock的配置項(xiàng)是否有改變重新生成mock.json

          //?scripts/webpack-init.js?在wenpack配置文件中初始化
          const?path?=?require( path )
          const?fs?=?require( fs )
          const?{syncWalkDir}?=?require( ./util )
          let?confGlobal?=?{}
          let?mockJsonData?=?{}
          exports.getConf?=?()?=>?confGlobal
          exports.getMockJson?=()?=>?mockJsonData

          /**
          ?*?初始化項(xiàng)目的配置?動(dòng)態(tài)生成mock.json和config/conf.json
          ?*?@param?{string}?env??dev|build
          ?*/
          ?exports.init?=?(env?=?process.env.BUILD_ENV??? build ?:? dev )?=>?{
          ???
          ??delete?require.cache[require.resolve( ../config )]
          ??const?config??=?require( ../config )
          ??const?confJson?=?env?===? build ???config.conf.build?:?config.conf.dev
          ??confGlobal?=?confJson
          ??//?1.根據(jù)環(huán)境變量來生成
          ??fs.writeFileSync(path.join(__dirname,? ../config/conf.json ),??JSON.stringify(confGlobal,?null,? ))
          ??buildMock(confJson)
          ?}
          ?
          ?//?生成mock文件數(shù)據(jù)
          ?const?buildMock?=?(conf)?=>?{
          ??//?2.動(dòng)態(tài)生成mock數(shù)據(jù)?讀取src文件夾下面所有以?-mock.json結(jié)尾的文件?存儲(chǔ)到io/index.json文件當(dāng)中
          ??let?mockJson?=?{}
          ??const?mockFiles?=?syncWalkDir(path.join(__dirname,? ../src ),?(file)?=>?/-mock.json$/.test(file))
          ??console.log( build?mocks:?->>>>>>>>>>>>>>>>>>>>>>> )
          ??mockFiles.forEach((filePath)?=>?{
          ????const?p?=?path.parse(filePath)
          ????const?mockKey?=?p.name.substr(0,?p.name.length?-?5)
          ????console.log(mockKey,?filePath)
          ????if?(mockJson[mockKey])?{
          ??????console.error(`有相同的mock文件名稱${p.name}?存在`,?filePath)
          ????}
          ????delete?require.cache[require.resolve(filePath)]
          ????mockJson[mockKey]?=?require(filePath)
          ??})
          ??//?如果是打包環(huán)境,?最小化mock資源數(shù)據(jù)
          ??const?mockMap?=?conf.mock?||?{}
          ??const?buildMockJson?=?{}
          ??Object.keys(mockMap).forEach((key)?=>?{
          ????const?[name,?method]?=?key.split( . )
          ????if?(mockJson[name][method]?&&?mockJson[name][method][mockMap[key]])?{
          ??????if?(!buildMockJson[name])?buildMockJson[name]?=?{}
          ??????if?(!buildMockJson[name][method])?buildMockJson[name][method]?=?{}
          ??????buildMockJson[name][method][mockMap[key]]?=?mockJson[name][method][mockMap[key]]
          ????}
          ??})
          ??mockJsonData?=?buildMockJson
          ??fs.writeFileSync(path.join(__dirname,? ../mock.json ),?JSON.stringify(buildMockJson,?null,? ))
          ?}
          ?
          ?//?監(jiān)聽配置文件目錄下的config.js和config_default.js
          const?confPath?=?path.join(__dirname,? ../config )

          if?((env?=?process.env.BUILD_ENV??? build ?:? dev )?===? dev )?{
          ??fs.watch(confPath,?async?(event,?filename)?=>?{
          ????if?(filename?===? config.js ?||?filename?===? config_default.js )?{
          ??????delete?require.cache[path.join(confPath,?filename)]
          ??????delete?require.cache[require.resolve( ../config )]
          ??????const?config??=?require( ../config )
          ??????//?console.log( config ,?JSON.stringify(config))
          ??????const?env?=?process.env.BUILD_ENV??? build ?:? dev
          ??????const?confJson?=?env?===? build ???config.conf.build?:?config.conf.dev
          ??????if?(JSON.stringify(confJson)?!==?JSON.stringify(confGlobal))?{
          ????????this.init()
          ??????}
          ????}
          ??});
          }

          接口代理處理


          onProxyReqonProxyRes
          實(shí)現(xiàn)上面思路里面說的onProxyReq和onProxyRes 響應(yīng)處理

          util.js
          https://github.com/rebareba/react-starter/blob/main/scripts/util.js

          //?scripts/api-proxy-cache?
          const?fs?=?require( fs )
          const?path?=?require( path )
          const?moment?=?require( moment )
          const?{getConf,?getMockJson}?=?require( ./webpack-init )
          const?API_CACHE_DIR?=?path.join(__dirname,? ../api-cache )
          const?{jsonParse,?getBody}?=?require( ./util )

          fs.mkdirSync(API_CACHE_DIR,{recursive:?true})

          module.exports?=?{
          ??//?代理前處理
          ??onProxyReq:?async?(_,?req,?res)?=>?{
          ????req.reqBody?=?await?getBody(req)
          ????const?{ mock-method :?mockMethod,? mock-key :?mockKey}?=?req.headers
          ????//?eslint-disable-next-line?no-console
          ????console.log(`Ajax?請(qǐng)求:?${mockKey}.${mockMethod}`,req.method,?req.url)
          ????//?eslint-disable-next-line?no-console
          ????req.reqBody?&&?console.log(JSON.stringify(req.reqBody,?null,? ))
          ????if?(mockKey?&&?mockMethod)?{
          ??????req.mockKey?=?mockKey
          ??????req.mockMethod?=?mockMethod
          ??????const?conf?=?getConf()
          ??????const?mockJson?=?getMockJson()
          ??????if?(conf.mock?&&?conf.mock[`${mockKey}.${mockMethod}`]?&&?mockJson[mockKey]?&&?mockJson[mockKey][mockMethod])?{
          ????????//?eslint-disable-next-line?no-console
          ????????console.log(`use?mock?data?${mockKey}.${mockMethod}:`,?conf.mock[`${mockKey}.${mockMethod}`],? color:?green )
          ????????res.mock?=?true
          ????????res.append( isMock , yes )
          ????????res.send(mockJson[mockKey][mockMethod][conf.mock[`${mockKey}.${mockMethod}`]])
          ??????}
          ?????
          ????}
          ??},
          ??//?響應(yīng)緩存接口
          ??onProxyRes:?async?(res,?req)?=>?{
          ????const?{method,?url,?query,?path:?reqPath,?mockKey,?mockMethod}?=?req
          ????
          ????if?(mockKey?&&?mockMethod?&&?res.statusCode?===?200)?{
          ??????
          ??????let?resBody?=?await?getBody(res)
          ??????resBody?=?jsonParse(resBody)
          ??????const?filePath?=?path.join(API_CACHE_DIR,?`${mockKey}.${mockMethod}.json`)
          ??????let??data?=?{}
          ??????if?(fs.existsSync(filePath))?{
          ????????data?=?jsonParse(fs.readFileSync(filePath).toString())
          ??????}
          ??????const?cacheObj?=?{
          ????????date:?moment().format( YYYY-MM-DD?hh:mm:ss ),
          ????????method,
          ????????path:?reqPath,
          ????????url,
          ????????resHeader:?res.headers,
          ????????reqHeader:?req.headers,
          ????????query,
          ????????reqBody:?await?jsonParse(req.reqBody),
          ????????resBody:?resBody
          ??????}
          ??????if?(resBody.success?===?false)?{
          ????????data.failed?=?cacheObj
          ??????}?else?{
          ????????data.success?=?cacheObj
          ??????}
          ??????//?eslint-disable-next-line?no-console
          ??????fs.writeFile(filePath,?JSON.stringify(data, ,? ),?(err)?=>?{?err?&&?console.log( writeFile ,?err)})
          ????}
          ??},
          ??//?后端服務(wù)沒啟的異常處理
          ??onError(err,?req,?res)?{
          ????setTimeout(()?=>?{
          ?????if?(!res.mock)?{
          ???????res.writeHead(500,?{
          ????????? Content-Type :? text/plain ,
          ???????});
          ???????res.end( Something?went?wrong.?And?we?are?reporting?a?custom?error?message. );
          ?????}
          ???},?10)
          ??}
          }

          webpack配置
          在webpack配置中引入使用

          const?config?=?require( . )
          //?config/webpack.config.js
          const?{init}?=?require( ../scripts/webpack-init );
          init();
          //?接口請(qǐng)求本地緩存
          const?apiProxyCache?=?require( ../scripts/api-proxy-cache )
          for(let?key?in?config.proxy)?{
          ??config.proxy[key]?=?Object.assign(config.proxy[key],?apiProxyCache);
          }

          const?webpackConf?=?{
          ??devServer:?{
          ????contentBase:?path.join(__dirname,? .. ),?//?本地服務(wù)器所加載的頁面所在的目錄
          ????inline:?true,
          ????port:?config.port,
          ????publicPath:? / ,
          ????historyApiFallback:?{
          ??????disableDotRule:?true,
          ??????//?指明哪些路徑映射到哪個(gè)html
          ??????rewrites:?config.rewrites,
          ????},
          ????host:? 127.0.0.1 ,
          ????hot:?true,
          ????proxy:?config.proxy,
          ??},
          }



          總結(jié)


          mock做好其實(shí)在我們前端實(shí)際中還是很有必要的,做過的項(xiàng)目如果后端被鏟除了想要回憶就可以使用mock讓項(xiàng)目跑起來,可以尋找一些實(shí)現(xiàn)的效果來進(jìn)行代碼復(fù)用。當(dāng)前介紹的mock流程實(shí)現(xiàn)有很多定制的開發(fā),但是真正完成后,團(tuán)隊(duì)中的成員只是使用還是比較簡(jiǎn)單配置即可。

          關(guān)于前端項(xiàng)目部署我也分享了一個(gè)BFF 層,當(dāng)前做的還不是很完善,也分享給大家參考

          Render-Server
          https://github.com/rebareba/render-server
          主要功能包含:

          • 一鍵部署 npm run deploy
          • 支持集群部署配置
          • 是一個(gè)文件服務(wù)
          • 是一個(gè)靜態(tài)資源服務(wù)
          • 在線可視化部署前端項(xiàng)目
          • 配置熱更新
          • 在線Postman及接口文檔
          • 支持前端路由渲染, 支持模板
          • 接口代理及路徑替換
          • Web安全支持 Ajax請(qǐng)求驗(yàn)證,Referer 校驗(yàn)
          • 支持插件開發(fā)和在線配置 可實(shí)現(xiàn):前端模板參數(shù)注入、請(qǐng)求頭注入、IP白名單、接口mock、會(huì)話、第三方登陸等等

          -?END -
          瀏覽 98
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  www操逼| 亚洲黄色一级电影 | 亲子乱婬-一级A片 | 国产欧美自拍 | 日本破处视频 |