Vuex的狀態(tài)管理

本文整理來自深入Vue3+TypeScript技術(shù)棧-coderwhy大神新課,只作為個(gè)人筆記記錄使用,請(qǐng)大家多支持王紅元老師紧阔。

什么是狀態(tài)管理

在開發(fā)中辰狡,我們會(huì)的應(yīng)用程序需要處理各種各樣的數(shù)據(jù)涣澡,這些數(shù)據(jù)需要保存在我們應(yīng)用程序中的某一個(gè)位置纠脾,對(duì)于這些數(shù)據(jù)的管理我們就稱之為狀態(tài)管理锤窑。

在前面我們是如何管理自己的狀態(tài)呢?

  • 在Vue開發(fā)中大诸,我們使用組件化的開發(fā)方式,而在組件中我們定義data或者在setup中返回使用的數(shù)據(jù)贯卦,這些數(shù)據(jù)我們稱之為state资柔;
  • 在模塊template中我們可以使用這些數(shù)據(jù),模塊最終會(huì)被渲染成DOM撵割,我們稱之為View贿堰;
  • 在模塊中我們會(huì)產(chǎn)生一些行為事件,處理這些行為事件時(shí)啡彬,有可能會(huì)修改state羹与,這些行為事件我們稱之為actions;

復(fù)雜的狀態(tài)管理

JavaScript需要管理的狀態(tài)越來越多庶灿,越來越復(fù)雜纵搁,這些狀態(tài)包括服務(wù)器返回的數(shù)據(jù)、緩存數(shù)據(jù)往踢、用戶操作產(chǎn)生的數(shù)據(jù)等等腾誉,也包括一些UI的狀態(tài),比如某些元素是否被選中峻呕,是否顯示加載動(dòng)效利职,當(dāng)前分頁。

當(dāng)我們的應(yīng)用遇到多個(gè)組件共享狀態(tài)時(shí)瘦癌,單向數(shù)據(jù)流的簡潔性很容易被破壞猪贪,因?yàn)槎鄠€(gè)視圖依賴于同一狀態(tài)佩憾,來自不同視圖的行為需要變更同一狀態(tài)哮伟。

我們是否可以通過組件數(shù)據(jù)的傳遞來完成呢干花?
對(duì)于一些簡單的狀態(tài),確實(shí)可以通過props的傳遞或者Provide的方式來共享狀態(tài)楞黄,但是對(duì)于復(fù)雜的狀態(tài)管理來說池凄,顯然單純通過傳遞和共享的方式是不足以解決問題的,比如兄弟組件如何共享數(shù)據(jù)呢鬼廓?

Vuex的狀態(tài)管理

管理不斷變化的state本身是非常困難的肿仑,因?yàn)闋顟B(tài)之間相互會(huì)存在依賴,一個(gè)狀態(tài)的變化會(huì)引起另一個(gè)狀態(tài)的變化碎税,View頁面也有可能會(huì)引起狀態(tài)的變化尤慰。當(dāng)應(yīng)用程序復(fù)雜時(shí),state在什么時(shí)候雷蹂,因?yàn)槭裁丛蚨l(fā)生了變化伟端,發(fā)生了怎么樣的變化,會(huì)變得非常難以控制和追蹤匪煌。

因此责蝠,我們是否可以考慮將組件的內(nèi)部狀態(tài)抽離出來,以一個(gè)全局單例的方式來管理呢萎庭?
在這種模式下霜医,我們的組件樹構(gòu)成了一個(gè)巨大的 “視圖View”,不管在樹的哪個(gè)位置驳规,任何組件都能獲取狀態(tài)或者觸發(fā)行為肴敛,通過定義和隔離狀態(tài)管理中的各個(gè)概念,并通過強(qiáng)制性的規(guī)則來維護(hù)視圖和狀態(tài)間的獨(dú)立性吗购,我們的代碼邊會(huì)變得更加結(jié)構(gòu)化和易于維護(hù)医男、跟蹤。

這就是Vuex背后的基本思想巩搏,它借鑒了Flux昨登、Redux、Elm(純函數(shù)語言贯底,redux有借鑒它的思想)丰辣。

Vuex有五大核心:stategetters禽捆、mutations笙什、actionsmodules胚想,下面我們慢慢講琐凭。

Vuex的安裝

首先第一步需要安裝vuex,我們這里使用的是vuex4.x浊服,安裝的時(shí)候需要添加 next 指定版本统屈。

npm install vuex@next

創(chuàng)建Store

每一個(gè)Vuex應(yīng)用的核心就是store(倉庫)胚吁,store本質(zhì)上是一個(gè)容器,它包含著你的應(yīng)用中大部分的狀態(tài)(state)愁憔。

Vuex和單純的全局對(duì)象有什么區(qū)別呢腕扶?
第一:Vuex的狀態(tài)存儲(chǔ)是響應(yīng)式的。當(dāng)Vue組件從store中讀取狀態(tài)的時(shí)候吨掌,若store中的狀態(tài)發(fā)生變化半抱,那么相應(yīng)的組件也會(huì)被更新。
第二:你不能直接改變store中的狀態(tài)膜宋。改變store中的狀態(tài)的唯一途徑就是提交 (commit) mutation窿侈,這樣使得我們可以方便的跟蹤每一個(gè)狀態(tài)的變化,從而讓我們能夠通過一些工具幫助我們更好的管理應(yīng)用的狀態(tài)秋茫。

使用步驟:
① 創(chuàng)建Store對(duì)象史简;
② 在app中通過插件安裝;

組件中使用store

在組件中使用store学辱,我們按照如下的方式:

  • 在模板中使用乘瓤;
  • 在options api中使用环形,比如computed策泣;
  • 在setup中使用;

Vue devtool

vue其實(shí)提供了一個(gè)devtools抬吟,方便我們對(duì)組件或者vuex進(jìn)行調(diào)試萨咕。我們需要安裝beta版本支持vue3,目前是6.0.0 beta15火本。

有兩種常見的安裝方式:
方式一:通過chrome的商店危队;
方式二:手動(dòng)下載代碼,編譯钙畔、安裝茫陆;

由于某些原因我們可能不能正常登錄Chrome商店,所以可以選擇第二種擎析;

手動(dòng)安裝devtool

手動(dòng)下載代碼簿盅,編譯、安裝:

  • https://github.com/vuejs/devtools/tree/v6.0.0-beta.15下載代碼揍魂;
  • 打開項(xiàng)目桨醋,終端執(zhí)行 yarn install 安裝相關(guān)的依賴;
  • 終端執(zhí)行 yarn run build 打包现斋;
  • 打開瀏覽器喜最,點(diǎn)擊加載已解壓的擴(kuò)展程序;
  • 選擇剛才項(xiàng)目中的shell-chrome文件夾庄蹋,點(diǎn)擊選擇即可安裝成功瞬内;

單一狀態(tài)樹

Vuex 使用單一狀態(tài)樹迷雪,用一個(gè)對(duì)象就包含了全部的應(yīng)用層級(jí)的狀態(tài),采用的是SSOT虫蝶,Single Source of Truth振乏,也可以翻譯成單一數(shù)據(jù)源,這也意味著秉扑,每個(gè)應(yīng)用將僅僅包含一個(gè) store 實(shí)例慧邮,單狀態(tài)樹和模塊化并不沖突,后面我們會(huì)講到module的概念舟陆。

單一狀態(tài)樹的優(yōu)勢(shì):如果你的狀態(tài)信息是保存到多個(gè)Store對(duì)象中的误澳,那么之后的管理和維護(hù)等等都會(huì)變得特別困難,所以Vuex也使用了單一狀態(tài)樹來管理應(yīng)用層級(jí)的全部狀態(tài)秦躯。單一狀態(tài)樹能夠讓我們最直接的方式找到某個(gè)狀態(tài)的片段忆谓,而且在之后的維護(hù)和調(diào)試過程中,也可以非常方便的管理和維護(hù)踱承。

Vuex的簡單使用

和使用vue-router一樣倡缠,我們需要?jiǎng)?chuàng)建一個(gè)store文件夾,然后在store文件夾里創(chuàng)建一個(gè)index.js文件茎活,在index.js文件里面將store對(duì)象導(dǎo)出昙沦。然后在main.js文件里面使用store。

main.js文件代碼如下:

import { createApp } from 'vue'
import App from './App.vue'
//導(dǎo)入創(chuàng)建的store
import store from './store'

//使用store
createApp(App).use(store).mount('#app')

index.js代碼如下:

import { createStore } from "vuex"

const store = createStore({
  // 保存的數(shù)據(jù)
  // state是個(gè)函數(shù)载荔,返回一個(gè)對(duì)象盾饮,和data類似
  // 以前是個(gè)對(duì)象,現(xiàn)在我們推薦寫成一個(gè)函數(shù)
  state() {
    return {
      counter: 100,
      name: "why", 
      age: 18,
      height: 1.88
    }
  },
  // 對(duì)數(shù)據(jù)進(jìn)行一些加工
  getters: {
    doubleCounter(state) {
      return state.counter * 2
    }
  },
  //修改數(shù)據(jù)
  mutations: {
    increment(state) {
      state.counter++
    }
  }
});

export default store;

使用數(shù)據(jù)和修改數(shù)據(jù):

//使用vuex中的數(shù)據(jù)
<h2>App:{{ $store.state.counter }}</h2>

//通過commit調(diào)用mutations中的increment方法懒熙,從而修改數(shù)據(jù)
this.$store.commit("increment")

獲取組件狀態(tài):state

在前面我們已經(jīng)學(xué)習(xí)過如何在組件中獲取狀態(tài)了丘损,當(dāng)然,如果覺得那種方式有點(diǎn)繁瑣(表達(dá)式過長)工扎,我們可以使用計(jì)算屬性徘钥。

但是,如果我們有很多個(gè)狀態(tài)都需要獲取話肢娘,一個(gè)一個(gè)寫計(jì)算屬性會(huì)很麻煩呈础,我們可以使用mapState輔助函數(shù)。mapState接收的參數(shù)可以是數(shù)組類型或者對(duì)象類型蔬浙。我們也可以使用展開運(yùn)算符和來原有的computed混合在一起猪落。

在computed中使用mapState

<template>
  <div>
    <h2>Home:{{ $store.state.counter }}</h2>
    <h2>Home:{{ sCounter }}</h2>
    <h2>Home:{{ sName }}</h2>
    <!-- <h2>Home:{{ age }}</h2>
    <h2>Home:{{ height }}</h2> -->
  </div>
</template>

<script>
  import { mapState } from 'vuex'

  export default {
    computed: {
      fullName() {
        return "Kobe Bryant"
      },
      // 其他的計(jì)算屬性, 從state獲取,會(huì)自動(dòng)映射成計(jì)算屬性
      // ...mapState(["counter", "name", "age", "height"])
      // 傳入對(duì)象可以重命名
      ...mapState({
        sCounter: state => state.counter,
        sName: state => state.name
      })
    }
  }
</script>

<style scoped>
</style>

在setup中使用mapState

在setup中如果我們單個(gè)獲取狀態(tài)是非常簡單的,通過useStore拿到store后去獲取某個(gè)狀態(tài)即可畴博,但是如果我們需要使用 mapState 的功能呢笨忌?

<template>
  <div>
    <h2>Home:{{ $store.state.counter }}</h2>
    <hr>
    <h2>{{sCounter}}</h2>
    <h2>{{counter}}</h2>
    <h2>{{name}}</h2>
    <h2>{{age}}</h2>
    <h2>{{height}}</h2>
    <hr>
  </div>
</template>

<script>
  import { mapState, useStore } from 'vuex'
  import { computed } from 'vue'

  export default {
    computed: {
      fullName: function() {
        return "1fdasfdasfad"
      },
      ...mapState(["name", "age"])
    },

    setup() {
      const store = useStore()
      // setup中寫成計(jì)算屬性,這也是常用的方法
      const sCounter = computed(() => store.state.counter)
      // const sName = computed(() => store.state.name)
      // const sAge = computed(() => store.state.age)

      //返回的是函數(shù)數(shù)組
      const storeStateFns = mapState(["counter", "name", "age", "height"])

      // 我們需要做如下操作,將 {name: function, age: function, height: function} 轉(zhuǎn)成 {name: ref, age: ref, height: ref}
      const storeState = {}
      Object.keys(storeStateFns).forEach(fnKey => {
        // 雖然我們使用的是mapState的方式,vue內(nèi)部還是會(huì)通過this.$store.state獲取數(shù)據(jù),所以我們給它綁定this
        const fn = storeStateFns[fnKey].bind({$store: store})
        // 將fn用computed包裹一下
        storeState[fnKey] = computed(fn)
      })

      return {
        sCounter,
        ...storeState
      }
    }
  }
</script>

<style scoped>
</style>

默認(rèn)情況下,在setup中俱病,Vuex并沒有提供非常方便的使用mapState的方式官疲,這里我們進(jìn)行了一個(gè)函數(shù)的封裝袱结,新建useState.js文件,代碼如下:

import { computed } from 'vue'
import { mapState, useStore } from 'vuex'

export function useState(mapper) {
  // 拿到store對(duì)象
  const store = useStore()

  // 獲取到對(duì)應(yīng)的對(duì)象的functions: {name: function, age: function}
  const storeStateFns = mapState(mapper)

  // 對(duì)數(shù)據(jù)進(jìn)行轉(zhuǎn)換
  const storeState = {}
  Object.keys(storeStateFns).forEach(fnKey => {
    const fn = storeStateFns[fnKey].bind({$store: store})
    storeState[fnKey] = computed(fn)
  })

  return storeState
}

使用useState函數(shù)如下:

<template>
  <div>
    <h2>Home:{{ $store.state.counter }}</h2>
    <hr>
    <h2>{{counter}}</h2>
    <h2>{{name}}</h2>
    <h2>{{age}}</h2>
    <h2>{{height}}</h2>
    <h2>{{sCounter}}</h2>
    <h2>{{sName}}</h2>
    <hr>
  </div>
</template>

<script>
  //導(dǎo)入函數(shù)
  import { useState } from '../hooks/useState'

  export default {
    setup() {
      // useState可以傳數(shù)組或者對(duì)象,這是因?yàn)閮?nèi)部的mapState可以傳數(shù)組或者對(duì)象
      const storeState = useState(["counter", "name", "age", "height"])
      const storeState2 = useState({
        sCounter: state => state.counter,
        sName: state => state.name
      })

      return {
        ...storeState,
        ...storeState2
      }
    }
  }
</script>

<style scoped>
</style>

這樣我們就使用useState函數(shù)實(shí)現(xiàn)了原來computed中的mapState函數(shù)的功能途凫。

getters的基本使用

某些屬性我們可能需要經(jīng)過變化后來使用垢夹,這個(gè)時(shí)候可以使用getters。

getters: {
  // 第一個(gè)參數(shù)是state,第二個(gè)參數(shù)是getters
  // 返回一個(gè)值,使用:<h2>總價(jià)值: {{ $store.getters.totalPrice }}</h2>
  totalPrice(state, getters) {
    let totalPrice = 0
    for (const book of state.books) {
      totalPrice += book.count * book.price
    }
    //訪問另外一個(gè)getters
    return totalPrice * getters.currentDiscount
  },
  currentDiscount(state) {
    return state.discount * 0.9
  }
}

使用如下:

<h2>總價(jià)值: {{ $store.getters.totalPrice }}</h2>

getters第二個(gè)參數(shù)

getters可以接收第二個(gè)參數(shù)维费,從而實(shí)現(xiàn)在getters里面訪問另外一個(gè)getters果元,代碼如上。

getters的返回函數(shù)

getters中的函數(shù)本身犀盟,可以返回一個(gè)函數(shù)而晒,那么在使用的地方相當(dāng)于可以調(diào)用這個(gè)函數(shù):

getters: {
  //除了返回一個(gè)值,還可以返回一個(gè)函數(shù)
  //使用的時(shí)候調(diào)用函數(shù),傳入?yún)?shù)即可:<h2>總價(jià)值: {{ $store.getters.totalPriceCountGreaterN(1) }}</h2>
  totalPriceCountGreaterN(state, getters) {
    return function(n) {
      let totalPrice = 0
      for (const book of state.books) {
        if (book.count > n) {
          totalPrice += book.count * book.price
        }
      }
      return totalPrice * getters.currentDiscount
    }
  }
}

使用如下:

<h2>總價(jià)值: {{ $store.getters.totalPriceCountGreaterN(3) }}</h2>

getters返回模板字符串

getters: {
  // 返回模板字符串
  nameInfo(state) {
    return `name: ${state.name}`
  },
  ageInfo(state) {
    return `age: ${state.age}`
  },
  heightInfo(state) {
    return `height: ${state.height}`
  }
}

mapGetters輔助函數(shù)

和state一樣,如果我們感覺通過$store.getters.totalPrice獲取getters有點(diǎn)麻煩阅畴,可以將其寫成計(jì)算屬性(代碼如下)倡怎,但是如果getters特別多,我們一個(gè)一個(gè)寫計(jì)算屬性也會(huì)很麻煩贱枣,所以我們需要mapGetters輔助函數(shù)监署。

computed: {
  totalPrice() {
    return this.$store.getters.totalPrice
  }
}

在computed中使用mapGetters

computed: {
  //傳入數(shù)組
  ...mapGetters(["nameInfo", "ageInfo", "heightInfo"]),
  //傳入對(duì)象,重命名
  ...mapGetters({
    //這里和mapState的對(duì)象寫法不一樣,mapState的對(duì)象寫法是傳入一個(gè)函數(shù)纽哥,這里直接傳入一個(gè)字符串就可以
    sNameInfo: "nameInfo",
    sAgeInfo: "ageInfo"
  })
}

使用如下:

<h2>{{ ageInfo }}</h2>
<h2>{{ heightInfo }}</h2>
<h2>{{ sNameInfo }}</h2>
<h2>{{ sAgeInfo }}</h2>

在setup中使用mapGetters

和mapState一樣钠乏,在setup中我們可以直接將其寫成計(jì)算屬性,這也是常用的寫法:

setup() {
  const store = useStore()
  // setup中寫成計(jì)算屬性,這也是常用的方法
  const sCounter = computed(() => store.getters.counter)
  // const sName = computed(() => store.getters.name)
  // const sAge = computed(() => store.getters.age)

  return {
    sCounter,
  }
}

但是如果屬性多了昵仅,我們就需要寫很多的計(jì)算屬性缓熟,所以在setup中使用mapGetters,我們可以封裝一個(gè)useGetters函數(shù)摔笤,新建useGetters.js文件,代碼如下:

import { computed } from 'vue'
import { mapGetters, useStore } from 'vuex'

export function useGetters(mapper) {
  // 拿到store對(duì)象
  const store = useStore()

  // 獲取到對(duì)應(yīng)的對(duì)象的functions: {name: function, age: function}
  const storeStateFns = mapGetters(mapper)

  // 對(duì)數(shù)據(jù)進(jìn)行轉(zhuǎn)換
  const storeState = {}
  Object.keys(storeStateFns).forEach(fnKey => {
    const fn = storeStateFns[fnKey].bind({$store: store})
    storeState[fnKey] = computed(fn)
  })

  return storeState
}

使用如下:

<template>
  <div>
    <h2>{{ nameInfo }}</h2>
    <h2>{{ ageInfo }}</h2>
    <h2>{{ heightInfo }}</h2>
  </div>
</template>

<script>
  // 導(dǎo)入函數(shù)
  import { useGetters } from '../hooks/useGetters'

  export default {
    computed: {

    },
    setup() {
      //使用useGetters函數(shù)
      const storeGetters = useGetters(["nameInfo", "ageInfo", "heightInfo"])
      return {
        ...storeGetters
      }
    }
  }
</script>

<style scoped>
</style>

封裝useState.js和useGetters.js

我們發(fā)現(xiàn)useState.js和useGetters.js代碼幾乎是一樣的垦写,所以我們新建useMapper.js文件吕世,重新封裝一下,代碼如下:

import { computed } from 'vue'
import { useStore } from 'vuex'

//具體是使用mapState還是使用mapGetters我們不清楚,所以讓外界傳進(jìn)來mapFn
export function useMapper(mapper, mapFn) {
  // 拿到store對(duì)象
  const store = useStore()

  // 獲取到對(duì)應(yīng)的對(duì)象的functions: {name: function, age: function}
  const storeStateFns = mapFn(mapper)

  // 對(duì)數(shù)據(jù)進(jìn)行轉(zhuǎn)換
  const storeState = {}
  Object.keys(storeStateFns).forEach(fnKey => {
    const fn = storeStateFns[fnKey].bind({$store: store})
    storeState[fnKey] = computed(fn)
  })

  return storeState
}

這時(shí)候useState.js代碼就是:

import { mapState } from 'vuex'
import { useMapper } from './useMapper'

export function useState(mapper) {
  return useMapper(mapper, mapState)
}

useGetters.js代碼就是:

import { mapGetters } from 'vuex'
import { useMapper } from './useMapper'

export function useGetters (mapper) {
  return useMapper(mapper, mapGetters)
}

這時(shí)候我們只需要新建一個(gè)index.js文件作為統(tǒng)一的出口梯投,下次我們使用的時(shí)候直接導(dǎo)入這個(gè)文件就行了命辖,代碼如下:

import { useGetters } from './useGetters';
import { useState } from './useState';

export {
  useGetters,
  useState
}

Mutations基本使用

Mutations定義如下:

mutations: {
  increment(state) {
    state.counter++;
  },
  decrement(state) {
    state.counter--;
  },
  [INCREMENT_N](state, payload) {
    state.counter += payload.n
  },
  addBannerData(state, payload) {
    state.banners = payload
  }
}

更改 Vuex 的 store 中的狀態(tài)的唯一方法是提交 mutation。

<template>
  <div>
    <h2>當(dāng)前計(jì)數(shù): {{ $store.state.counter }}</h2>
    <hr>
      <!-- 不傳參數(shù) -->
      <button @click="$store.commit('increment')">+1</button>
      <button @click="$store.commit('decrement')">-1</button>
     <!-- 傳參數(shù) -->
      <button @click="addTen">+10</button>
    <hr>
  </div>
</template>

<script>
  export default {
    methods: {
      addTen() {
        // 傳入一個(gè)參數(shù)
        // this.$store.commit('incrementN', 10)
        // 傳入多個(gè)參數(shù)就寫成一個(gè)對(duì)象
        // this.$store.commit('incrementN', {n: 10, name: "why", age: 18})
        // 這時(shí)候后面的參數(shù)就會(huì)被傳到incrementN函數(shù)的第二個(gè)參數(shù)

        // 另外一種提交風(fēng)格
        this.$store.commit({
          type: 'incrementN',
          n: 10, 
          name: "why", 
          age: 18
        })
      }
    }
  }
</script>

<style scoped>
</style>

Mutation常量類型

上面代碼還有個(gè)小問題分蓖,就是函數(shù)名incrementN容易寫錯(cuò)尔艇,而且寫錯(cuò)之后很難發(fā)現(xiàn),這時(shí)候我們可以將函數(shù)名incrementN定義成常量么鹤。

在store文件夾中新建mutation-types.js文件终娃,代碼如下:

export const INCREMENT_N = "increment_n"

定義mutation:

import { INCREMENT_N } from './mutation-types'

[INCREMENT_N](state, payload) {
  state.counter += payload.n
}

提交mutation:

import { INCREMENT_N } from '../store/mutation-types'

this.$store.commit(INCREMENT_N, 10)

mapMutations輔助函數(shù)

上面代碼,直接通過this.$store.commit(INCREMENT_N, 10)調(diào)用會(huì)很長蒸甜,封裝成一個(gè)addTen函數(shù)又會(huì)多了一個(gè)函數(shù)棠耕,這時(shí)候我們可以使用mapMutations輔助函數(shù)將我們需要使用的函數(shù)映射到methods里面余佛。當(dāng)我們使用mapMutations映射之后,調(diào)用方法窍荧,方法內(nèi)部會(huì)調(diào)用$store.commit辉巡,這樣我們就不用寫this.$store.commit(INCREMENT_N, 10)這樣很長的代碼了。

<template>
  <div>
    <h2>當(dāng)前計(jì)數(shù): {{ $store.state.counter }}</h2>
    <hr>
      <!-- 當(dāng)我們使用mapMutations映射之后,調(diào)用方法,方法內(nèi)部會(huì)調(diào)用$store.commit -->
      <button @click="increment">+1</button>
      <button @click="add">+1</button>
      <button @click="decrement">-1</button>
      <button @click="increment_n({n: 10})">+10</button>
    <hr>
  </div>
</template>

<script>
  import { mapMutations, mapState } from 'vuex'

  import { INCREMENT_N } from '../store/mutation-types'

  export default {
    //在methods中使用
    methods: {
      // 傳入數(shù)組
      ...mapMutations(["increment", "decrement", INCREMENT_N]),
      // 傳入對(duì)象,改名字
      ...mapMutations({
        add: "increment"
      })
    },
    setup() {
      // 在setup中使用就比mapState和mapGetters簡單多了
      const storeMutations = mapMutations(["increment", "decrement", INCREMENT_N])

      return {
        ...storeMutations
      }
    }
  }
</script>

<style scoped>
</style>

Mutation重要原則

一條重要的原則:mutation 必須是同步函數(shù)蕊退,這是因?yàn)閐evtool工具會(huì)記錄mutation的日記郊楣,每一條mutation被記錄,devtools都需要捕捉到前一狀態(tài)和后一狀態(tài)的快照瓤荔,但是在mutation中執(zhí)行異步操作痢甘,就無法追蹤到數(shù)據(jù)的變化,所以Vuex的重要原則中要求 mutation 必須是同步函數(shù)茉贡。

Actions的基本使用

Actions類似于Mutations塞栅,不同在于:

  • Action提交的是mutation,而不是直接變更狀態(tài)腔丧;
  • Action可以包含任意異步操作放椰;

如果一些網(wǎng)絡(luò)請(qǐng)求的數(shù)據(jù)是直接存到Vuex里面,那么網(wǎng)絡(luò)請(qǐng)求就沒必要寫到組件中了愉粤,直接寫到actions中砾医,這樣在組件中我們只需要做一次事件派發(fā)就行了。

actions: {
  // 第一個(gè)參數(shù)是context衣厘,第二個(gè)參數(shù)是我們調(diào)用dispatch傳遞過來的參數(shù){count: 100} 
  incrementAction(context, payload) {
    console.log(payload)
    // 延遲1s
    setTimeout(() => {
      context.commit('increment')
    }, 1000);
  },
  // context的其他屬性
  decrementAction({ commit, dispatch, state, rootState, getters, rootGetters }) {
    commit("decrement")
  },
  //如果一些網(wǎng)絡(luò)請(qǐng)求的數(shù)據(jù)是直接存到Vuex里面如蚜,那么網(wǎng)絡(luò)請(qǐng)求就沒必要寫到組件中了,直接寫到actions中即可
  getHomeMultidata(context) {
     //返回Promise對(duì)象影暴,好在then或者catch里面處理其他事情
     return new Promise((resolve, reject) => {
      axios.get("http://123.207.32.32:8000/home/multidata").then(res => {
        //拿到數(shù)據(jù)后提交commit
        context.commit("addBannerData", res.data.data.banner.list)
        resolve({name: "coderwhy", age: 18})
      }).catch(err => {
        reject(err)
      })
    })
  }
}

這里有一個(gè)非常重要的參數(shù)context错邦,context是一個(gè)和store實(shí)例均有相同方法和屬性的context對(duì)象。所以我們可以從其中獲取到commit方法來提交一個(gè)mutation型宙,或者通過 context.state 和 context.getters 來獲取 state 和 getters撬呢。但是為什么它不是store對(duì)象呢?這個(gè)等到我們講Modules時(shí)再具體來說妆兑。

Actions的分發(fā)操作

進(jìn)行action的分發(fā)使用的是 store 上的dispatch函數(shù):

<template>
  <div>
    <h2>當(dāng)前計(jì)數(shù): {{ $store.state.counter }}</h2>
    <hr>
      <button @click="increment">+1</button>
      <button @click="decrement">-1</button>
    <hr>
  </div>
</template>

<script>
  import axios from 'axios'

  export default {
    methods: {
      increment() {
        //分發(fā),攜帶參數(shù)
        this.$store.dispatch("incrementAction", {count: 100})
      },
      decrement() {
        //派發(fā)風(fēng)格(對(duì)象類型),攜帶參數(shù)
        this.$store.dispatch({
          type: "decrementAction",
          count: 100
        })
      }
    },
    mounted() {
      //分發(fā)
      this.$store.dispatch("getHomeMultidata")
    },
    setup() {
    }
  }
</script>

<style scoped>
</style>

mapActions輔助函數(shù)

和Mutations一樣魂拦,如果我們不想寫this.$store.dispatch("incrementAction", {count: 100})這些代碼,可以使用mapActions輔助函數(shù)搁嗓,它也有兩種寫法:數(shù)組類型和對(duì)象類型芯勘。

<template>
  <div>
    <h2>當(dāng)前計(jì)數(shù): {{ $store.state.counter }}</h2>
    <hr>
      <button @click="incrementAction">+1</button>
      <button @click="decrementAction">-1</button>
      <button @click="add">+1</button>
      <button @click="sub">-1</button>
    <hr>
  </div>
</template>

<script>
  import { mapActions } from 'vuex'

  export default {
    // 在methods中使用
    methods: {
      ...mapActions(["incrementAction", "decrementAction"]),
      ...mapActions({
        add: "incrementAction",
        sub: "decrementAction"
      })
    },
    // 在setup中使用
    setup() {
      const actions = mapActions(["incrementAction", "decrementAction"])
      const actions2 = mapActions({
        add: "incrementAction",
        sub: "decrementAction"
      })

      return {
        ...actions,
        ...actions2
      }
    }
  }
</script>

<style scoped>
</style>

Actions的異步操作

Action 通常是異步的,那么如何知道 Action 什么時(shí)候結(jié)束呢腺逛?
我們可以通過讓Action返回Promise荷愕,在Promise的then中來處理完成后的操作。

actions: {
  getHomeMultidata(context) {
    //返回Promise對(duì)象,好在then或者catch里面處理其他事情
    return new Promise((resolve, reject) => {
      axios.get("http://123.207.32.32:8000/home/multidata").then(res => {
        //拿到數(shù)據(jù)后提交commit
        context.commit("addBannerData", res.data.data.banner.list)
        //回調(diào)then
        resolve({name: "coderwhy", age: 18})
      }).catch(err => {
        //回調(diào)catch
        reject(err)
      })
    })
  }
}
<template>
  <div>
    <h2>當(dāng)前計(jì)數(shù): {{ $store.state.counter }}</h2>
    <hr>
      <button @click="incrementAction">+1</button>
      <button @click="decrementAction">-1</button>
      <button @click="add">+1</button>
      <button @click="sub">-1</button>
    <hr>
  </div>
</template>

<script>
  import { onMounted } from "vue";
  import { useStore } from 'vuex'

  export default {
    setup() {
      const store = useStore()

      onMounted(() => {
        // 派發(fā)操作
        const promise = store.dispatch("getHomeMultidata")
        promise.then(res => {
          //請(qǐng)求成功的操作
          console.log(res)
        }).catch(err => {
          //請(qǐng)求失敗的操作
          console.log(err)
        })
      })
    }
  }
</script>

<style scoped>
</style>

Module的基本使用

由于使用單一狀態(tài)樹路翻,應(yīng)用的所有狀態(tài)會(huì)集中到一個(gè)比較大的對(duì)象狈癞,當(dāng)應(yīng)用變得非常復(fù)雜時(shí),store 對(duì)象就有可能變得相當(dāng)臃腫茂契。為了解決以上問題厌小,Vuex 允許我們將 store 分割成模塊(module)透硝,每個(gè)模塊擁有自己的 state罪治、getter、mutation屠缭、action、甚至是嵌套子模塊赘被。

在store文件夾中新建modules文件夾和index.js文件,modules文件夾用于存放模塊js文件瓦糟。

home.js文件如下:

const homeModule = {
  state() {
    return {
      homeCounter: 100
    }
  },
  getters: {
  },
  mutations: {
  },
  actions: {
  }
}

export default homeModule

user.js文件如下:

const userModule = {
  state() {
    return {
      userCounter: 10
    }
  },
  getters: {
  },
  mutations: {
  },
  actions: {
  }
}

export default userModule

index.js文件代碼如下:

import { createStore } from "vuex"
// 導(dǎo)入某個(gè)模塊相關(guān)的狀態(tài)管理
import home from './modules/home'
import user from './modules/user'

const store = createStore({
  // 保存的數(shù)據(jù)
  state() {
    return {
      rootCounter: 100
    }
  },
  // 對(duì)數(shù)據(jù)進(jìn)行一些加工
  getters: {
    doubleRootCounter(state) {
      return state.rootCounter * 2
    }
  },
  //修改數(shù)據(jù)
  mutations: {
    increment(state) {
      state.rootCounter++
    }
  },
  // 狀態(tài)管理,模塊劃分
  modules: {
    home,
    user
  }
});

export default store;

組件中使用如下:

<template>
  <div>
    <h2>{{ $store.state.rootCounter }}</h2>
    <!-- 先獲取home模塊,再通過home模塊拿數(shù)據(jù) -->
    <h2>{{ $store.state.home.homeCounter }}</h2>
    <h2>{{ $store.state.user.userCounter }}</h2>
  </div>
</template>

<script>
  export default {
    setup() {
    }
  }
</script>

<style scoped>
</style>

module的命名空間

先說兩個(gè)問題:

問題一:比如index.js里面的mutations內(nèi)有一個(gè)increment方法先嬉,home.js里面的mutations內(nèi)也有一個(gè)increment方法轧苫,當(dāng)我們提交commit觸發(fā)increment方法的時(shí)候,這兩個(gè)increment方法都會(huì)被調(diào)用疫蔓。這里有個(gè)問題含懊,因?yàn)橛袝r(shí)候我們不想兩個(gè)increment方法都被調(diào)用。

問題二:當(dāng)我們?cè)趆ome.js里面的getters里面定義一個(gè)doubleHomeCounter衅胀,無論你是通過$store.state.home.doubleHomeCounter還是$store.state.home.getters.doubleHomeCounter還是$store.getters.home.doubleHomeCounter其實(shí)都是拿不到doubleHomeCounter岔乔,這是因?yàn)槟J(rèn)做了個(gè)合并,我們需要通過$store.getters.doubleHomeCounter才能拿到doubleHomeCounter滚躯。這里有個(gè)問題雏门,因?yàn)槲覀儾恢纃oubleHomeCounter到底是哪個(gè)模塊里面的嘿歌。

這是因?yàn)椋J(rèn)情況下茁影,模塊內(nèi)部的mutation和action仍然是注冊(cè)在全局的命名空間中的宙帝,這樣使得多個(gè)模塊能夠?qū)ν粋€(gè)mutation或action作出響應(yīng),Getters 同樣也默認(rèn)注冊(cè)在全局命名空間募闲。

如果我們希望模塊具有更高的封裝度和復(fù)用性步脓,可以添加 namespaced: true 的方式使其成為帶命名空間的模塊,這樣當(dāng)模塊被注冊(cè)后蝇更,它的所有 getter沪编、mutation 及 action 都會(huì)自動(dòng)根據(jù)模塊注冊(cè)的路徑調(diào)整命名。

當(dāng)我們加上namespaced: true后年扩,每個(gè)模塊就是獨(dú)立的了蚁廓,我們就要指定是哪個(gè)模塊,如下:

<template>
  <div>
    <!-- 獲取根的rootCounter -->
    <h2>root:{{ $store.state.rootCounter }}</h2>
    <!-- 獲取home的homeCounter -->
    <h2>home:{{ $store.state.home.homeCounter }}</h2>
    <!-- 獲取user的userCounter -->
    <h2>user:{{ $store.state.user.userCounter }}</h2>

    <hr>
    <!-- 獲取home的getters里面的doubleHomeCounter -->
    <h2>{{ $store.getters["home/doubleHomeCounter"] }}</h2>

    <button @click="homeIncrement">home+1</button>
    <button @click="homeIncrementAction">home+1</button>
  </div>
</template>

<script>
  export default {
    methods: {
      homeIncrement() {
        // 指定home模塊的commit
        this.$store.commit("home/increment")
      },
      homeIncrementAction() {
        // 指定home模塊的dispatch
        this.$store.dispatch("home/incrementAction")
      }
    }
  }
</script>

<style scoped>
</style>

子module的一些參數(shù)

對(duì)于模塊內(nèi)部的 mutation 和 getter厨幻,接收的第一個(gè)參數(shù)是模塊的局部狀態(tài)對(duì)象相嵌,其他參數(shù)如下:

home.js文件代碼如下:

const homeModule = {
  namespaced: true,
  state() {
    return {
      homeCounter: 100
    }
  },
  getters: {
    // 前面我們講了在根里面的getters里面有state和getters兩個(gè)參數(shù), 其實(shí)在子模塊的getters里面還有更多的參數(shù)
    // state就是上面的state
    // getters用戶獲取其他的getters
    // rootState拿到根的state
    // 拿到根getters
    doubleHomeCounter(state, getters, rootState, rootGetters) {
      return state.homeCounter * 2
    },
    otherGetter(state) {
      return 100
    }
  },
  mutations: {
    increment(state) {
      state.homeCounter++
    }
  },
  actions: {
    // 一個(gè)參數(shù)對(duì)齊進(jìn)行解構(gòu)
    // commit: 提交
    // dispatch: 分發(fā)
    // state: 當(dāng)前state
    // rootState: 根state
    // getters: 當(dāng)前getters
    // rootGetters: 根getters
    incrementAction({commit, dispatch, state, rootState, getters, rootGetters}) {
      // 這里提交commit,默認(rèn)提交的是當(dāng)前模塊的commit
      commit("increment")
      // 如果我們想提交根模塊的commit,需要第三個(gè)參數(shù)
      // 參數(shù)一: 提交的方法名字
      // 參數(shù)二: payload,也就是傳遞給increment方法的參數(shù)
      // 參數(shù)三: {root: true}代表是根的commit
      commit("increment", null, {root: true})

      // 同理dispatch也是一樣
      // dispatch("incrementAction", null, {root: true})

      // 上面incrementAction的參數(shù)其實(shí)是一個(gè)context,它和store不一樣,它會(huì)引用root的一些東西,比如rootState和rootGetters
      // 但是store就沒這些參數(shù),這也是他們的區(qū)別
    }
  }
}

export default homeModule

上面incrementAction的參數(shù)其實(shí)是一個(gè)context,它和store不一樣况脆,它會(huì)引用root的一些東西饭宾,比如rootState和rootGetters,但是store就沒這些參數(shù)格了,這也是它們的區(qū)別看铆。

module的輔助函數(shù)

前面我們使用輔助函數(shù)是這樣使用的:

computed: {
  ...mapState(["homeCounter"]),
  ...mapGetters(["doubleHomeCounter"])
},
methods: {
  ...mapMutations(["increment"]),
  ...mapActions(["incrementAction"]),
},

這樣寫只是獲取根里面的,如果不是根里面盛末,就需要指定模塊名了弹惦。

方式一:通過完整的模塊空間名稱來查找(用的少);
方式二:第一個(gè)參數(shù)傳入模塊空間名稱悄但,后面寫上要使用的屬性(用的多)棠隐;
方式三:通過 createNamespacedHelpers 生成一個(gè)模塊的輔助函數(shù)(用的多);

關(guān)于方式一檐嚣、方式二的示例如下助泽,一般方式二用的比較多。

<script>
  import { mapState, mapGetters, mapMutations, mapActions } from "vuex";

  export default {
    computed: {
      // 1.寫法一:
      // ...mapState({
        homeCounter: state => state.home.homeCounter
      }),
      ...mapGetters({
        doubleHomeCounter: "home/doubleHomeCounter"
      })

      // 2.寫法二:
      ...mapState("home", ["homeCounter"]),
      ...mapGetters("home", ["doubleHomeCounter"])
    },
    methods: {
      // 1.寫法一:
      ...mapMutations({
        increment: "home/increment"
      }),
      ...mapActions({
        incrementAction: "home/incrementAction"
      }),

      // 2.寫法二
      ...mapMutations("home", ["increment"]),
      ...mapActions("home", ["incrementAction"]),
    },
  }
</script>

第三種方式:我們可以使用vuex里的createNamespacedHelpers函數(shù)嚎京,然后解構(gòu)函數(shù)的返回值嗡贺。

<script>
  // 導(dǎo)入函數(shù)
  import { createNamespacedHelpers } from "vuex";

  // 解構(gòu)返回值
  const { mapState, mapGetters, mapMutations, mapActions } = createNamespacedHelpers("home")

  export default {
    computed: {
      // 3.寫法三:
      ...mapState(["homeCounter"]),
      ...mapGetters(["doubleHomeCounter"])
    },
    methods: {
      // 3.寫法三:
      ...mapMutations(["increment"]),
      ...mapActions(["incrementAction"]),
    },
  }
</script>

通過createNamespacedHelpers函數(shù),我們的寫法就和以前在根里面寫的一樣了鞍帝,更推薦這種方式暑刃。

在setup中使用

對(duì)于state和getters,如果在setup中使用我們可以用以前封裝的useState.js和useGetters.js膜眠,但是以前我們封裝useState.js和useGetters.js的時(shí)候沒有考慮模塊,所以現(xiàn)在我們要重新封裝。

useState.js文件代碼:

import { mapState, createNamespacedHelpers } from 'vuex'
import { useMapper } from './useMapper'

export function useState(moduleName, mapper) {
  let mapperFn = mapState
  // 傳模塊名和數(shù)組
  if (typeof moduleName === 'string' && moduleName.length > 0) {
    mapperFn = createNamespacedHelpers(moduleName).mapState
  } else {
    // 只傳數(shù)組就是從根里面獲取
    mapper = moduleName
  }

  return useMapper(mapper, mapperFn)
}

useGetters.js文件代碼:

import { mapGetters, createNamespacedHelpers } from 'vuex'
import { useMapper } from './useMapper'

export function useGetters(moduleName, mapper) {
  let mapperFn = mapGetters
  if (typeof moduleName === 'string' && moduleName.length > 0) {
    mapperFn = createNamespacedHelpers(moduleName).mapGetters
  } else {
    mapper = moduleName
  }

  return useMapper(mapper, mapperFn)
}

組件中使用如下:

setup() {
  // {homeCounter: function}
  const state = useState(["rootCounter"])
  const rootGetters = useGetters(["doubleRootCounter"])
  const getters = useGetters("home", ["doubleHomeCounter"])

  const mutations = mapMutations(["increment"])
  const actions = mapActions(["incrementAction"])

  return {
    ...state,
    ...getters,
    ...rootGetters
    ...mutations,
    ...actions
  }
}

nexttick

官方解釋:將回調(diào)推遲到下一個(gè) DOM 更新周期之后執(zhí)行宵膨。在更改了一些數(shù)據(jù)以等待 DOM 更新后立即使用它架谎。

比如我們有下面的需求:點(diǎn)擊一個(gè)按鈕,我們會(huì)修改在h2中顯示的message辟躏,message被修改后谷扣,獲取h2的高度。

實(shí)現(xiàn)上面的案例我們有三種方式:
方式一:在點(diǎn)擊按鈕后立即獲取到h2的高度(錯(cuò)誤的做法)捎琐;
方式二:在updated生命周期函數(shù)中獲取h2的高度(但是其他數(shù)據(jù)更新会涎,也會(huì)執(zhí)行該操作);
方式三:使用nexttick函數(shù)瑞凑;

<template>
  <div>
    <h2>{{counter}}</h2>
    <button @click="increment">+1</button>
    <h2 class="title" ref="titleRef">{{message}}</h2>
    <button @click="addMessageContent">添加內(nèi)容</button>
  </div>
</template>

<script>
  import { ref, onUpdated, nextTick } from "vue";

  export default {
    setup() {
      const message = ref("")
      const titleRef = ref(null)
      const counter = ref(0)

      const addMessageContent = () => {
        message.value += "哈哈哈哈哈哈哈哈哈哈"
        // 先更新DOM
        nextTick(() => {
          // 再打印高度
          console.log(titleRef.value.offsetHeight)
        })
      }
      // nextTick就相當(dāng)于將我們要執(zhí)行的操作延遲到DOM更新完之后再執(zhí)行

      const increment = () => {
        for (let i = 0; i < 100; i++) {
          counter.value++
        }
      }

      // 方式二:在updated生命周期函數(shù)中獲取h2的高度(但是其他數(shù)據(jù)更新末秃,也會(huì)執(zhí)行該操作)
      // 比如點(diǎn)擊+1的操作,onUpdated也會(huì)回調(diào)
      onUpdated(() => {
        console.log(titleRef.value.offsetHeight)
      })

      return {
        message,
        counter,
        increment,
        titleRef,
        addMessageContent
      }
    }
  }
</script>

<style scoped>
  .title {
    width: 120px;
  }
</style>

nextTick就相當(dāng)于將我們要執(zhí)行的操作延遲到DOM更新完之后再執(zhí)行。

那么nexttick是如何做到的呢籽御?事件循環(huán)练慕,也就是event loop。

historyApiFallback

historyApiFallback是開發(fā)中一個(gè)非常常見的屬性技掏,它主要的作用是解決SPA頁面在路由跳轉(zhuǎn)之后铃将,進(jìn)行頁面刷新時(shí),返回404的錯(cuò)誤哑梳。

  • boolean值劲阎,默認(rèn)是false,如果設(shè)置為true鸠真,那么在刷新時(shí)悯仙,返回404錯(cuò)誤時(shí),會(huì)自動(dòng)返回 index.html 的內(nèi)容弧哎。
  • object類型的值雁比,可以配置rewrites屬性,可以配置from來匹配路徑撤嫩,決定要跳轉(zhuǎn)到哪個(gè)頁面偎捎。

事實(shí)上devServer中實(shí)現(xiàn)historyApiFallback功能是通過 connect-history-api-fallback 庫實(shí)現(xiàn)的,可以自己查看文檔序攘。Nginx配置的截圖如下:

實(shí)際我們開發(fā)中也沒有進(jìn)行配置茴她,但是刷新的時(shí)候也不會(huì)有404錯(cuò)誤,這是因?yàn)閣ebpack默認(rèn)幫我們配置了historyApiFallback: true程奠,如下:

如果把true改成false丈牢,重新運(yùn)行項(xiàng)目,刷新瞄沙,就會(huì)發(fā)現(xiàn)報(bào)錯(cuò)了:

那么如果我們真想把historyApiFallback改成false己沛,還要去修改源碼嗎慌核?修改源碼固然可以,但是不推薦申尼,我們可以新建vue.config.js文件垮卓,這個(gè)文件的內(nèi)容會(huì)被讀取最后合并到webpack內(nèi)部,代碼如下:

module.exports = {
  configureWebpack: {
    devServer: {
      historyApiFallback: true
    }
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末师幕,一起剝皮案震驚了整個(gè)濱河市粟按,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌霹粥,老刑警劉巖灭将,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異后控,居然都是意外死亡庙曙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門忆蚀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來矾利,“玉大人,你說我怎么就攤上這事馋袜∧衅欤” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵欣鳖,是天一觀的道長察皇。 經(jīng)常有香客問我,道長泽台,這世上最難降的妖魔是什么什荣? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮怀酷,結(jié)果婚禮上稻爬,老公的妹妹穿的比我還像新娘。我一直安慰自己蜕依,他們只是感情好桅锄,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布啊片。 她就那樣靜靜地躺著戒傻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪眯亦。 梳的紋絲不亂的頭發(fā)上檐束,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天辫秧,我揣著相機(jī)與錄音,去河邊找鬼被丧。 笑死盟戏,一個(gè)胖子當(dāng)著我的面吹牛绪妹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播抓半,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼喂急,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了笛求?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤糕簿,失蹤者是張志新(化名)和其女友劉穎探入,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體懂诗,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蜂嗽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了殃恒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片植旧。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖离唐,靈堂內(nèi)的尸體忽然破棺而出病附,到底是詐尸還是另有隱情,我是刑警寧澤亥鬓,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布完沪,位于F島的核電站,受9級(jí)特大地震影響嵌戈,放射性物質(zhì)發(fā)生泄漏覆积。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一熟呛、第九天 我趴在偏房一處隱蔽的房頂上張望宽档。 院中可真熱鬧,春花似錦庵朝、人聲如沸吗冤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽欣孤。三九已至,卻和暖如春昔逗,著一層夾襖步出監(jiān)牢的瞬間降传,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國打工勾怒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留婆排,地道東北人声旺。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像段只,于是被迫代替她去往敵國和親腮猖。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容