<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>

          如何寫(xiě)好前端業(yè)務(wù)代碼?(一些個(gè)人小見(jiàn)解)

          共 10158字,需瀏覽 21分鐘

           ·

          2021-08-05 11:37

          點(diǎn)擊上方 程序員成長(zhǎng)指北,關(guān)注公眾號(hào)

          回復(fù)1,加入高級(jí)Node交流群

          前言

          原本只是想簡(jiǎn)單群發(fā)一下,但是預(yù)覽之后看到格式不友好,還是簡(jiǎn)單寫(xiě)一篇文章吧,這是我工作一年半來(lái)自己總結(jié)出來(lái)的一些經(jīng)驗(yàn)。

          分層

          對(duì)于業(yè)務(wù)代碼來(lái)說(shuō),大部分的前端應(yīng)用都還是以展示數(shù)據(jù)為主,無(wú)非是從接口拿到數(shù)據(jù),進(jìn)行一系列數(shù)據(jù)格式化后,顯示在頁(yè)面當(dāng)中。

          首先,應(yīng)當(dāng)盡可能的進(jìn)行分層,傳統(tǒng)的mvc分層很適用于前端開(kāi)發(fā),但對(duì)于復(fù)雜頁(yè)面來(lái)說(shuō),隨著業(yè)務(wù)邏輯增加,往往會(huì)造成controller臃腫的問(wèn)題。因此,在此之上,可以將controller其分成formatter、service等等。

          下面這是一些分層后簡(jiǎn)單的目錄結(jié)構(gòu)。

              + pages
                  + hotelList
                      + components
                          + Header.jsx
                      + formatter
                          + index.js
                      + share
                          + constants.js
                          + utils.js
                      + view.js
                      + controller.js
                      + model.js



          Service

          統(tǒng)一管理所有請(qǐng)求路徑,并且將頁(yè)面中涉及到的網(wǎng)絡(luò)請(qǐng)求封裝為class。

          // api.js
          export default {
              HOTELLIST'/hotelList',
              HOTELDETAIL'/hotelDetail'
          }

          // Service.js
          class Service {
              fetchHotelList = (params) => {
                  return fetch(HOTELLIST, params);
              }
              fetchHotelDetail = (params) => {
                  return fetch(HOTELLIST, params);
              }
          }
          export default new Service

          這樣帶來(lái)的好處就是,很清楚的知道頁(yè)面中涉及了哪些請(qǐng)求,如果使用了TypeScript,后續(xù)某個(gè)請(qǐng)求方法名修改了后,在所有調(diào)用的地方也會(huì)提示錯(cuò)誤,非常方便。

          formatter

          formatter層儲(chǔ)存一些格式化數(shù)據(jù)的方法,這些方法接收數(shù)據(jù),返回新的數(shù)據(jù),不應(yīng)該再涉及到其他的邏輯,這樣有利于單元測(cè)試。單個(gè)format函數(shù)也不應(yīng)該格式化過(guò)多數(shù)據(jù),函數(shù)應(yīng)該根據(jù)功能進(jìn)行適當(dāng)拆分,合理復(fù)用。

          mvc

          顧名思義,controller就是mvc中的c,controller應(yīng)該是處理各種副作用操作(網(wǎng)絡(luò)請(qǐng)求、緩存等等)的地方。

          當(dāng)處理一個(gè)請(qǐng)求的時(shí)候,controller會(huì)調(diào)用service里面對(duì)應(yīng)的方法,拿到數(shù)據(jù)后再調(diào)用formatter的方法,將格式化后的數(shù)據(jù)存入store中,展示到頁(yè)面上。

          class Controller {
              fetchAndSaveHotelList = () => async (dispatch) => {
                  const params = {}
                  this.showLoading();
                  try {
                      const res = await Service.fetchHotelList(params)
                      const hotelList = formatHotelList(res.Data && res.Data.HotelList)
                      dispatch({
                          type'UPDATE_HOTELLIST',
                          hotelList
                      })
                  } catch (err) {
                      this.showError(err);
                  } finally {
                      this.hideLoading();
                  }
              }
          }

          view則是指react組件,建議盡量用純函數(shù)組件,有了hooks之后,react也會(huì)變得更加純粹(實(shí)際上有狀態(tài)組件也可以看做一個(gè)mvc的結(jié)構(gòu),state是model,render是view,各種handler方法是controller)。

          對(duì)于react來(lái)說(shuō),最外層的一般稱(chēng)作容器組件,我們會(huì)在容器組件里面進(jìn)行網(wǎng)絡(luò)請(qǐng)求等副作用的操作。

          在這里,容器組件里面的一些邏輯也可以剝離出來(lái)放到controller中(react-imvc就是這種做法),這樣可以給controller賦予生命周期,容器組件只用于純展示。

          我們將容器組件的生命周期放到wrapper這個(gè)高階組件中,并在里面調(diào)用controller里面封裝的生命周期,這樣我們可以就編寫(xiě)更加純粹的view,例如:

          wrapper.js

          // wrapper.js(偽代碼)
          const Wrapper = (components) => {
              return class extends Component {
                  constructor(props) {
                      super(props)
                  }
                  componentWillMount() {
                      this.props.pageWillMount()
                  }
                  componentDidMount() {
                      this.props.pageDidMount()
                  }
                  componentWillUnmount() {
                       this.props.pageWillLeave()
                  }
                  render() {
                      const {
                          store: state,
                          actions
                      } = this.props
                      return view({state, actions})
                  }
              }
          }



          view.js


          // view.js
          function view({
              state,
              actions
          }
          {

              return (
                  <>
                      <Header 
                          title={state.title} 
                          handleBack={actions.goBackPage}
                      />
                      <Body />
                      <Footer />
                  </>
              )
          }
          export default Wrapper(view)


          controller.js
          // controller.js
          class Controller {
              pageDidMount() {
                  this.bindScrollEvent('on')
                  console.log('page did mount')
              }
              pageWillLeave() {
                  this.bindScrollEvent('off')
                  console.log('page will leave')
              }
              bindScrollEvent(status) {
                  if (status === 'on') {
                      this.bindScrollEvent('off');
                      window.addEventListener('scroll'this.handleScroll);
                  } else if (status === 'off') {
                      window.removeEventListener('scroll'this.handleScroll);
                  }
              }
              // 滾動(dòng)事件
              handleScroll() {
              }
          }



          其他

          對(duì)于埋點(diǎn)來(lái)說(shuō),原本也應(yīng)該放到controller中,但也是可以獨(dú)立出來(lái)一個(gè)tracelog層,至于tracelog層如何實(shí)現(xiàn)和調(diào)用,還是看個(gè)人愛(ài)好,我比較喜歡用發(fā)布訂閱的形式。

          如果還涉及到緩存,那我們也可以再分出來(lái)一個(gè)storage層,這里存放對(duì)緩存進(jìn)行增刪查改的各種操作。

          對(duì)于一些常用的固定不變的值,也可以放到constants.js,通過(guò)引入constants來(lái)獲取值,這樣便于后續(xù)維護(hù)。

          // constants.js
          export const cityMapping = {
              '1''北京',
              '2''上海'
          }
          export const traceKey = {
              'loading''PAGE_LOADING'
          }

          // tracelog.js
          class TraceLog {
              traceLoading = (params) => {
                  tracelog(traceKey.loading, params);
              }
          }
          export default new TraceLog

          // storage.js
          export default class Storage {
              static get instance() {
                  // 
              }
              setName(name) {
                  //
              }
              getName() {
                  //
              }
          }

          數(shù)據(jù)與交互

          不過(guò)也不代表著這樣寫(xiě)就夠了,分層只能夠保證代碼結(jié)構(gòu)上的清晰,真正想寫(xiě)出好的業(yè)務(wù)代碼,最重要的還是你對(duì)業(yè)務(wù)邏輯足夠清晰,頁(yè)面上的數(shù)據(jù)流動(dòng)是怎樣的?數(shù)據(jù)結(jié)構(gòu)怎么設(shè)計(jì)更加合理?頁(yè)面上有哪些交互?這些交互會(huì)帶來(lái)哪些影響?

          以如下酒店列表頁(yè)為例,這個(gè)頁(yè)面看似簡(jiǎn)單,實(shí)際上包含了很多復(fù)雜的交互。

          上方的是四個(gè)篩選項(xiàng)菜單,點(diǎn)開(kāi)后里面包含了很多子類(lèi)篩選項(xiàng),比如篩選里面包括了雙床、大床、三床,價(jià)格/星級(jí)里面包含了高檔/豪華、¥150-¥300等等。

          下方是快捷篩選項(xiàng),對(duì)應(yīng)了部分篩選項(xiàng)菜單里面的子類(lèi)篩選項(xiàng)。

          當(dāng)我們選中篩選里面的雙床后,下方的雙床也會(huì)被默認(rèn)選中,反之當(dāng)我們選中下方的雙床后,篩選類(lèi)別里面的雙床也會(huì)被選中,名稱(chēng)還會(huì)回顯到原來(lái)的篩選上。

          除此之外,我們點(diǎn)擊搜索框后,輸入'雙床',聯(lián)想詞會(huì)出現(xiàn)雙床,并表示這是個(gè)篩選項(xiàng),如果用戶(hù)選中了這個(gè)雙床,我們依然需要篩選項(xiàng)和快捷篩選項(xiàng)默認(rèn)選中。

          這三個(gè)地方都涉及到了篩選項(xiàng),并且修改一個(gè),其他兩個(gè)地方就要跟著改變,更何況三者的數(shù)據(jù)來(lái)自于三個(gè)不同的接口數(shù)據(jù),這是多么蛋疼的一件事情!





          我借助這個(gè)例子來(lái)說(shuō)明,在開(kāi)始寫(xiě)頁(yè)面之前,一定要對(duì)頁(yè)面中的隱藏交互和數(shù)據(jù)流動(dòng)很熟悉,也需要去設(shè)計(jì)更加合理的數(shù)據(jù)結(jié)構(gòu)。

          對(duì)于深層次的列表結(jié)構(gòu),鍵值對(duì)會(huì)比數(shù)組查詢(xún)速度更快,但是卻不能保證順序,這個(gè)時(shí)候就需要犧牲空間來(lái)?yè)Q時(shí)間。

          總結(jié)

          在開(kāi)始寫(xiě)業(yè)務(wù)之前,理應(yīng)先想清楚需求和業(yè)務(wù)邏輯,設(shè)計(jì)出合理的數(shù)據(jù)結(jié)構(gòu),對(duì)代碼進(jìn)行好的分層,這樣在一定程度上可以寫(xiě)出可維護(hù)性更高的代碼。

          代碼例子:目前對(duì)前半段寫(xiě)了一個(gè)簡(jiǎn)單的例子,有需要的可以學(xué)習(xí)下:https://github.com/yinguangyao/mvc-demo

          如果覺(jué)得這篇文章還不錯(cuò)
          點(diǎn)擊下面卡片關(guān)注我
          來(lái)個(gè)【分享、點(diǎn)贊、在看】三連支持一下吧

             “分享、點(diǎn)贊、在看” 支持一波 

          瀏覽 68
          點(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>
                  男女操逼在线免费观看 | SM在线观看免费版 | 欧美三级台湾三级少妇 | 成人三级片二区 | 国产人妻国产毛片 |