如何管理 vue 項目中的數據?

vuex

如何管理 vue 項目的數據?這個問題彷佛早已經有答案了,無非就是使用 vuex ,全局 store,整個應用維護一個超大的 Object,界面的顯示狀況隨着超大 Object 的變化而變化。javascript

看起來很簡單,不就維護一個 Object 嘛,實際上,要想組織好數據這塊代碼,必須事先對項目的數據結構理解得很是透徹,而後像設計數據庫表同樣把各個 module 的樣子設計出來。實際上,我的以爲設計 vuex 的 module 比設計數據庫表複雜得多:html

  • 一、像數據庫同樣設計各個業務實體的外貌,這部分設計難度應該和數據庫表設計差很少;前端

  • 二、維護一堆 ajax 請求狀態;vue

  • 三、如何優雅地複用 module。好比有一個 PersonListModule,在一個頁面上有兩處要用到 PersonListModule 中的列表數據:一個是要在表格控件裏面展現,一個是要在下拉控件裏面展現,每一個控件中展現的列表數據篩選條件不同;java

  • 四、如何同步 vuex 中的數據和服務器端數據。vuex 的超大 Object 能夠看作服務器端數據在客戶端內存中的一個緩存,怎麼設計這個緩存的同步策略?git

對於三、4兩個問題,結合起來更恐怖:同步服務器端數據到 PersonListModule 的同時,還要考慮如何從 PersonListModule 中篩選出分頁數據到頁面展現,還要篩選出多個列表,還要考慮在什麼時機從新更新「緩存」,想一想就頭大。github

假設咱們能力很強大,設計出了能完美應對上述問題的 store 方案,還有一個大問題攔着咱們呢:如何保證這套設計的可擴展性?由於業務系統變化無窮,不知道何時產品經理又有新想法了,咱們得設計能很好地應對變化無窮的需求嗎?ajax

爲何這麼難?問題究竟出如今哪裏?

vuex 的思惟模式主要是從數據着手,由數據推導出界面的樣子,這就須要先設計好 store 結構了。要設計好 store 結構,目測必須具有以下特質的工程師才能作好:vuex

  • 一、對項目業務瞭解很是深刻;數據庫

  • 二、具有超強的抽象思惟能力;

  • 三、經驗豐富,能儘可能想到設計出的 store 結構能應付哪些狀況、不能應付哪些狀況。

第2條的門檻實在是過高了,能作到的前端工程師估計沒多少。

怎麼辦?

咱們不該該從數據推導出界面,而應該從界面推導出數據,逐層抽象。

好比如今要仿一個新浪微博首頁,頁面上主要包含的數據有:分組信息、微博列表、我的信息、一些推薦信息等,那麼就設計一個只針對該頁面的 module ,大體結構爲:

const homePageModule = {
  state: {
    groupList: [{
        id: 1,
        name: '名人明星',
        unread: 1
      },
      {
        id: 2,
        name: '同事',
        unread: 0
      }
    ],
    groupListExtraInfo: {
      // 初始顯示多少個小組
      initShowCount: 5,
      loading: true
    },
    weiboList: [{
      id: 1,
      content: '<p>震驚部</p>',
      author: 'yibuyisheng',
      createTime: '20170719234422'
    }],
    weiboListPageInfo: {
      loadingStatus: 'QUIET', // 三種取值:QUIET -> 沒有加載;UP -> 向上加載;DOWN -> 向下加載
      // weiboList 的開始時間,可用這個時間戳作下一次的向上加載
      startTime: '20170719234422',
      // weiboList 的結束時間,可用這個時間戳作下一次的向下加載
      endTime: '20170719234422'
    },
    self: {
      id: 1,
      nickname: 'yibuyisheng',
      email: 'yibuyisheng@163.com',
      avatar: 'http://weibo.com/2674779523/profile?rightmod=1&wvr=6&mod=personinfo',
      followedCount: 405,
      followerCount: 235,
      weiboCount: 1321
    },
    recommendMovies: [
      ...
    ],
    recommendTopics: [
      ...
    ]
    ...
  },
  mutations: {
    updateWeiboList(state, list) {
      ...
    }
  },
  actions: {
    appendWeiboList() {
      ...
    },
    prependWeiboList() {
      ...
    }
  }
};

針對這個頁面,這個結構,各個處理邏輯就具體化、特殊化了,代碼寫起來很是輕鬆。

代碼複用?

假設如今有個小組頁面,點進去後能夠看到該小組全部成員發的微博,由於是一個新的頁面,因此須要新起一個 module ,這也意味着要重複寫一遍 weiboList 相關的代碼,豈不蛋疼!

此時能夠考慮寫一個 createWeiboListModule() 函數,用於建立這種通用 module ,而後再寫一個 mergeModules() 函數,把 createWeiboListModule() 函數建立出來的 module 對象和各頁面特殊的 module 合併起來,樣子看起來大體是這樣:

mergeModules(createWeiboListModule(), {
  state: {
    ...
  },
  mutations: {
    ...
  },
  actions: {
    ...
  }
});

遇到須要複用的才抽取通用邏輯,很天然,很簡單。

怎麼結合 vue 組件?

上面的結構有一個很大的問題,就是不能很好地和 vue 組件結合。好比,要讓微博首頁和分組頁面中的微博列表能複用 weiboList 相關代碼,那麼 weiboList 涉及到的 state、action、mutation、getter 的命名都要儘可能保持一致,否則就要傳一個 nameMap(命名映射)給兩個頁面通用的 WeiboListComponent 組件,看起來就像這樣:

<weibo-list-component :name-map="{weiboList: 'homePageWeiboList'}"></weibo-list-component>

簡直蛋疼!

好吧,那就嚴格約束這兩個頁面的 state、action、mutation、getter 命名都保持一致吧!

簡直超級蛋疼!

此時能夠考慮用 namespace 來解決這個問題,好比上面的 homePageModule 能夠把 weiboList 拆分出來:

const store = new vuex.Store({
  ...,

  modules: {
    'page:home': {
      state: {
        groupList: [{
            id: 1,
            name: '名人明星',
            unread: 1
          },
          {
            id: 2,
            name: '同事',
            unread: 0
          }
        ],
        groupListExtraInfo: {
          // 初始顯示多少個小組
          initShowCount: 5,
          loading: true
        },
        self: {
          id: 1,
          nickname: 'yibuyisheng',
          email: 'yibuyisheng@163.com',
          avatar: 'http://weibo.com/2674779523/profile?rightmod=1&wvr=6&mod=personinfo',
          followedCount: 405,
          followerCount: 235,
          weiboCount: 1321
        },
        recommendMovies: [
          ...
        ],
        recommendTopics: [
            ...
          ]
          ...
      },
    },
    'page:home:weiboList': createWeiboListModule(...)
  }

  ...
});

這樣一來,只要給 vue 組件傳一個 namespace 參數就好了:

<weibo-list-component namespace="page:home:weiboList"></weibo-list-component>

嗯,看起來挺好的!

如何處理「store 緩存」?

能夠在上一個問題解決的基礎上,加上緩存功能,目測有大把現成的緩存策略能夠參考(服務器端都玩兒爛了),因爲絕大部分系統並不須要這層緩存功能,因此此處不贅述。

就這樣了嗎?

上述方案,思惟方向的確是致使最後執行起來輕鬆了不少,從具體到抽象的過程,很天然,符合思考習慣。可是最終的代碼仍是會很容易搞得很亂的:

  • 一、mergeModules() 要照顧各類合併策略;

  • 二、createXXXModule() 方法會抽出不少層。好比能夠從 createWeiboListModule() 抽出來 createContinuousListModule() ,用於構造通用的具有「向前向後」加載能力的列表 Module,最終可能會造成一條經常的「繼承鏈」,須要本身去定義維護這套繼承邏輯,心累。

其實上面兩條一看,就知道有現成的解決方案了: class。

參考此處實現:https://github.com/yibuyishen...(代碼還在完善中)。

具體業務代碼寫起來就像是這樣了:

class ContinuousList extends BaseModule {
    state = {
        list: [],
        pageInfo: {
            loadingStatus: 'QUIET',
            startTime: '20170720003939',
            endTime: '20170720003939'
        }
    }
    
    @action
    async appendList(...) { 
        ...
        const result = await request('some url', params);
        this.updateList(result.list);
        ...
    }
    
    @action
    prependList(...) { ... }
}

class WeiboList extends ContinuousList {
    
    @action
    async voteUp(...) {
        ...
        await request('some url', params);
        const weiboDetail = await updateWeibo('some url', params.weiboId);
        const newList = this.state.list.map((wb) => {
            return wb.id === weiboDetail.id ? weiboDetail : wb;
        });
        this.updateList(newList);
        ...
    }
}

@composition(WeiboList)
class HomePage extends BaseModule {

    $namespace = 'page:home:';
    
    ...
    @action
    requestRecommendInfo(...) {
        ...
    }
    ...
}

HomePage.register();

在對應的 HomePage.vue 裏面,大體是這樣:

<template>
    <div class="home-page-view">
        ...
        <weibo-list-component namespace="page:home:weiboList"></weibo-list-component>
        ...
    </div>
</template>
<script>
export default {
    created() {
        ...
        const constants = this.getConstants('page:home');
        this.$store.dispatch(constants.REQUEST_RECOMMEND_INFO, params);
        ...
    }
};
</script>

WeiboListComponent 組件大體是這樣:

<template>
    <div class="weibo-list-component">
        ...
    </div>
</template>
<script>
export default {
    props: {
        namespace: {
            type: String,
            required: true
        }
    },
    computed: {
        weiboList() {
            const constants = this.getConstants(this.namespace);
            return this.$store.getters[constants.LIST];
        }
    },
    created() {
        ...
        const constants = this.getConstants(this.namespace);
        this.$store.dispatch(constants.APPEND_LIST, params);
        ...
    }
};
</script>

總結

其實就是換一種思路:從界面推導數據,從具體到抽象。

相關文章
相關標籤/搜索