Vuex从入门到精通(三)

时间:2022-04-27
本文章向大家介绍Vuex从入门到精通(三),主要内容包括开始、环境搭建、项目开发、最后、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

开始

前言

这一节,我们将通过一个实战案例 :

  • 动态展示从后台返回的新闻列表
  • 允许用户根据来源, 和 内容&标题 中的关键字对新闻列表进行筛选

来加深对 vuex 的理解

工具

element-ui 是由国内饿了么团队开发的一套vue组件, 相比于其他的一些组件库,如

iview

( GitHub: https://github.com/iview/iview )

( 官网文档: https://www.iviewui.com/docs/guide )

它提供了更丰富的组件内容, 更友好的UI风格和API。

你可以使用或者不使用它, 这里仅提供一种选择。

环境搭建

在任意目录下打开控制台, 依次完成以下操作

1. 使用vue-cli + webpack 初始化项目结构

vue init webpack news

2. 安装项目依赖

cnpm i

3. 安装vuex & element-ui

cnpm i vuex element-ui -S

4. 启动项目服务

npm start

项目开发

需求分析

根据需求

  • 动态展示从后台返回的新闻数据
  • 允许用户根据来源, 和 内容&标题 中的关键字对新闻进行筛选

我们先来分析一下前后台交互的数据有哪些 :

  • type: String    标识筛选类型,按来源(from) or 按内容(content)
  • keyword: String    用户输入的关键字
  • news: Array    后台返回的新闻列表

如果筛选类型是动态返回的, 还需要添加一个

  • modes: Array    由后台返回的可筛选类型列表

关于 筛选 的执行,这里有两种方案

  1. 当数据总量不是很大的时候,首屏加载时请求所有数据, 根据 用户输入 在浏览器端对所有数据进行筛选。 优点: 筛选响应速度快
  2. 当数据总量很大的时候,将用户输入作为参数向后台发送请求,返回符合筛选条件的数据。 优点: 首屏加载体验良好, 能够及时同步数据 (指后台数据库中被更改的数据)

在当前网络性能已不成障碍的前提下,一般采用第二种方案。

由此分析可得与后台交互的接口 :

axios.get('/searchMode').then(res => {}).catch(err => {})
axios.get('/news', { type, keyword }).then(res => {}).catch(err => {})

项目资源目录

开发流程

伪造数据接口

由于案例没有和后台连接,所以我们先来伪造一下数据接口

src/ajax/news.js

const _news = [
  {
    id: 0,
    title: '习近平论和平',
    content: 'n' +
    '      <p style="text-align:center;">n' +
    '      </p>n' +
    '      <p>&nbsp; &nbsp; 今年(2017年)12月13日,是第四个南京大屠杀死难者国家公祭日,也是南京大屠杀死难同胞80周年祭。80年来,曾经的苦难和血泪不敢忘,也不能忘。那沉重哭墙之上镌刻的每一个名字,都是我们刻骨铭心的历史。</p>n' +
    '      <p>  为了永不忘却的纪念,2017年国家公祭日来临之际,荔枝新闻和我苏客户端推出特别策划“为历史拂尘,祈和平永续”互动。愿擦亮的名字守护历史的真相,愿和平的祈愿温暖寒冬的殇城。</p>n' +
    '      <p style="text-align: center;"><img src="http://static.jstv.com/img/2017/12/8/20171281512721396004_214.png" border="0" alt=""></p>n' +
    '      <p>  “为历史拂尘,祈和平永续”互动页面以哭墙为背景。侵华日军南京大屠杀遇难同胞纪念馆原馆长朱成山曾说:“在当年惨绝人寰的南京大屠杀中,30万遇难同胞,没有坟墓,只有一个名字,哭墙就是他们共同的墓碑。这样的祭奠,其实是一种情感的表达,南京大屠杀的历史,我们应该永远铭记”。</p>n' +
    '      <p>  的确,一切苦难皆有名字,每个名字都是一部历史。哭墙是情感的表达、个体的史书。它无声,可那嵌进时间的笔画里是30万个体的愤怒、呼喊和哀叫;它无泪,可那饱蘸生命的笔墨里是30万个体的血泪与凋萎。</p>n' +
    '      <p>  <strong>进入“为历史拂尘,祈和平永续”互动页面后,刻有万余遇难者名字的哭墙会持续滚动。</strong></p>n' +
    '      <p style="text-align: center;"><img src="http://static.jstv.com/img/2017/12/6/20171261512526500996_214.gif" border="0" alt=""></p>n' +
    '      <p>  尽管灰暗,但却是最真最痛的历史;尽管沉重,但却是必须永远铭记的真相。</p>n' +
    '      <p>  对于这面沉重的史书,我们的每一次触碰即是一次告慰。我们的每一次擦拭即是一次纪念。</p>n' +
    '      <p>  从擦亮每一个遇难者的名字开始,为历史拂尘,守护真相。<strong>轻触哭墙,现在就可以践行你的那份力量——</strong></p>n' +
    '      <p style="text-align: center;"><img src="http://static.jstv.com/img/2017/12/6/20171261512549549335_214.png" border="0" alt=""></p>n' +
    '      <p>  昭昭前事,惕惕后人。这一擦亮历史的寓意寄望提醒:无穷的远方,无数的人们,都与你我有关。这样擦亮名字的行动希望告慰那些逝去的生命,铭记就是最好的纪念。</p>n' +
    '      <p>  每一个逝去的生命都是一颗星星。<strong>擦亮名字的行动完成后,页面上属于逝者的那颗星星会升入银河。</strong></p>n' +
    '      <p></p>n' +
    '      <p style="text-align: center;"><img src="http://static.jstv.com/img/2017/12/8/20171281512721645459_214.gif" border="0" alt=""></p>n' +
    '      <p>  <strong>页面上同时会生成独属于你的那张海报。长按即可保存海报</strong>,永久纪念这份刻骨铭心的历史,永久记录这份守护真相的行动。</p>n' +
    '      <p style="text-align: center;"><img src="http://static.jstv.com/img/2017/12/8/20171281512721406459_214.png" border="0" alt=""></p>n' +
    '      <p>  此刻,距离2017年12月13日不到24小时。呜咽的警报将一再提醒我们那一年冬天的殇城有多寒冷、多黑暗。</p>n' +
    '      <p>  这份铭记历史、守护真相、传递和平的使命,与你我有关。</p><p>  现在,就开始你的<strong>【擦亮历史】</strong>行动n' +
    '    </p>n',
    from: '环球军事',
    active: false,
    createAt: '2017-12-13 08:15:00'
  },
  {
    id: 1,
    title: '以史为鉴 才能面向未来',
    content: '<p style="font-size: 16px;">   原标题:以史为鉴,才能面向未来(国际论坛)</p>n' +
    '      <p style="font-size: 16px;">   村山富市</p>n' +
    '      <p style="font-size: 16px;">   在思考日中关系时,最令我难忘的是1995年——日本战败50周年之际,我作为日本首相发表了“村山谈话”。“村山谈话”指出,日本在不久的过去一段时期,国策有错误,走了战争的道路,殖民统治和侵略给许多国家,特别是亚洲各国人民带来了巨大的损害和痛苦,对此表示深刻的反省和由衷的歉意,并向在这段历史中受到灾难的所有国内外人士表示沉痛的哀悼。我认为,日本必须正确认识日中关系的历史事实,并在深刻反省的基础上,与中国开展交往。</p>n' +
    '      <p style="font-size: 16px;">   二战结束之后,日本一些政客不断在历史问题上作出错误言行,伤害了中国等亚洲国家人民的感情,使日本无法取信于亚洲各国。“村山谈话”得到包括亚洲国家在内的世界各国的高度评价,如果“村山谈话”的精神能够得到日本政界人士的继承,相信日本与亚洲各国将会更好地构建友好共处的关系。为此,在辞任日本首相两年后,我在1998年参观了侵华日军南京大屠杀遇难同胞纪念馆,并敬献了写有“前事不忘,后事之师”的花圈,发出正视历史、以史为鉴的信号,希望推动日中关系向前发展。</p>n' +
    '      <p style="font-size: 16px;">   2014年,中国全国人大常委会通过决定,将每年的12月13日设立为南京大屠杀死难者国家公祭日。近日,加拿大安大略省议会通过了有关“设立南京大屠杀纪念日”的动议。日本军国主义的侵略,给中国人民造成了巨大伤害。包括中国、加拿大在内的世界各地纪念包括南京大屠杀在内的历史惨案,具有重要的意义。</p>n' +
    '      <br/>n' +
    '      <p style="font-size: 16px;">   历史是一面镜子。只有以史为鉴,才能面向未来。日本军国主义发动的侵略战争,给中国等亚洲各国人民带来深重灾难,对于包括南京大屠杀在内的历史事实,我们应该永远铭记于心。日本政府和政界人士应该始终保持“以史为鉴,才能面向未来”的姿态,这非常重要。</p>n' +
    '      <p style="font-size: 16px;">   当前,日本执政党正在推进修改宪法的进程,试图修改宪法第九条。“放弃发动战争权利”的日本宪法第九条,是日本向全世界发出的和平宣言。修改宪法第九条,会给包括亚洲在内的世界各国发出危险信号,损害日本作为和平国家的信用,百害而无一利。日本应该放弃这种篡改宪法第九条的愚蠢行径。</p>n' +
    '      <p style="font-size: 16px;">   对日本政府来说,与中国约定好的事情,必须要切实遵守。如果不遵守相关约定,两国间就无法建立起信赖关系,外交工作也难以开展。日中之间的四个政治文件和四点原则共识并没有过时,对现在来说依然非常重要。应该在日中四个政治文件和四点原则共识的基础上,本着以史为鉴、面向未来的精神发展两国关系。</p>n' +
    '      <p style="font-size: 16px;">   2017年不仅是日中邦交正常化45周年,同时也是标志着日本发动全面侵华战争的“卢沟桥事变”80周年。我们不应该只纪念邦交正常化,对日中两国来说,更应该去了解从卢沟桥事变到邦交正常化这段时间里发生了什么,去了解中国人民经历了多少苦难。</p>n' +
    '      <p style="font-size: 16px;">   像去年访问美国珍珠港一样,如果安倍首相访问中国,理所当然应该去访问当年侵华日军给中国人民造成巨大灾难的场所。</p>n' +
    '      <p style="font-size: 16px;">   (作者为日本前首相)</p>',
    from: '人民日报',
    active: false,
    createAt: '2017-12-13 05:49:00'
  },
  {
    id: 2,
    title: '用在钓鱼岛?美媒称日本承认对远程导弹的兴趣',
    content:
    '        <p style="text-align: center; font-size: 16px;">n' +
    '          <img alt="" src="http://himg2.huanqiu.com/attachment2010/2017/1213/08/03/20171213080338993.jpg" style="width: 570px; height: 294px;">n' +
    '        </p>n' +
    '        <p style="font-size: 16px;">  【环球网军事12月13日报道】据美国《防务新闻》11日报道,日本方面“确认了对远程导弹的兴趣”,日本的42架F-35A隐形战斗机将获得最先进的“联合攻击导弹(JSM)”,此外日本还将认真研究为现役F-15J战斗机配备远程导弹的可能性。报道特别提到,日本官方否认采购该导弹是为对付朝鲜,而是用来防卫包括钓鱼岛在内的“偏远岛屿”。</p>n' +
    '        <p style="font-size: 16px;">  报道称,日本防卫大臣小野寺五典证实,防卫省将在明年4月提交的2018年预算中预留额外资金,以购买美国的先进导弹。他表示,防卫省还计划资助研究将洛克希德·马丁公司研制的AGM-158B及在此基础上发展的AGM-158C远程反舰导弹整合到日本F-15J战斗机上的可行性。据称,JSM导弹是目前唯一能在F-35内部武器舱内携带的远程隐身巡航导弹。</p>n' +
    '        <p style="font-size: 16px;">  报道称,小野寺五典的最新表态与他三天前在新闻发布会上的说法大相径庭,当时他宣称日本“尚未决定引进远程导弹,也没有在2018财政年度预算中预留出相关资金”。</p>n' +
    '        <p style="font-size: 16px;">  此前有日本媒体称,如果朝鲜向日本发射弹道导弹,日本可能会用这种巡航导弹对朝鲜目标进行打击。但《防务新闻》称,日本官方明确表示,“这些导弹‘将专门用于防卫日本’,特别是那些所谓的‘偏远岛屿’——后者经常被视为对中日纷争的钓鱼岛的委婉说法”。</p>n' +
    '        <p style="font-size: 16px;">  美国“外交学者”网站11日也强调,日本防卫大臣坚持认为,购买新型导弹不是为了对敌方基地进行攻击。这些新型导弹将专门用于保卫自卫队的宙斯盾驱逐舰,因为日本防御朝鲜弹道导弹的第一道防线就是6艘装备宙斯盾系统的导弹驱逐舰。</p>n' +
    '        <p style="font-size: 16px;">  不过接受采访的中国专家认为,上述巡航导弹是典型的进攻性武器,用来保护日本驱逐舰的说法根本经不住推敲。日本引进上述导弹将极大提高自卫队对地对海攻击能力。这些先进导弹将日本战机挂载导弹的攻击范围从300公里猛增到500甚至1000公里,可在对方防空区以外发射,而且它们均可采用低空隐身突防方式,较难防御和拦截。▲ (张亦驰)</p>n' +
    '    ',
    from: '环球时报',
    active: false,
    createAt: '2017-12-13 08:01:00'
  }
]
const _searchMode = [
  {
    id: 1,
    label: '内容',
    value: 'content'
  },
  {
    id: 0,
    label: '来源',
    value: 'from'
  }
]

export default {
  getSearchMode (cb) {
    setTimeout(() => {
      cb(_searchMode)
    }, 100)
  },
  getNews (type, keyword, cb) {
    setTimeout(() => {
      switch (_searchMode.indexOf(_searchMode.find(m => m.value === type))) {
        case 0:
          cb(_news.filter(n => n.title.indexOf(keyword) >= 0 || n.content.indexOf(keyword) >= 0))
          break
        case 1:
          cb(_news.filter(n => n.from === keyword))
          break
        default:
          cb(_news)
          break
      }
    }, 100)
  }
}

创建vuex store

 配置 mutations 名称常量

src/store/types/news.js

export const SET_NEWS = 'SET_NEWS'
export const SET_SEARCH_MODE = 'SET_SEARCH_MODE'

创建 store.news

 src/store/modules/news.js

import * as types from '../types/news'
import news from '../../ajax/news'

const state = {
  keyword: '',
  type: '',
  modes: [],
  now: []
}

const getters = {
  keyword: state => state.keyword,
  type: state => state.type,
  modes: state => state.modes,
  now: state => state.now
}

const actions = {
  getSearchMode ({commit}) {
    news.getSearchMode((modes) => commit(types.SET_SEARCH_MODE, {modes}))
  },
  getNews ({commit, state}) {
    news.getNews(state.type, state.keyword, (news) => commit(types.SET_NEWS, {news}))
  }
}

const mutations = {
  [types.SET_NEWS] (state, {news}) {
    state.now = news
  },
  [types.SET_SEARCH_MODE] (state, {modes}) {
    state.modes = modes
    state.type = modes[0].value
  },
  updateType (state, type) {
    state.type = type
  },
  updateKeyword (state, keyword) {
    state.keyword = keyword
  }
}

export default {
  namespaced: true,
  state,
  getters,
  actions,
  mutations
}

创建 store

src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import news from './modules/news'

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    news
  }
})

全局样式表

src/assets/stylesheets/base.css

body {
  margin: 0;
}

/* 设置webkit内核滚动条样式 */
::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

::-webkit-scrollbar-track {
  border-radius: 5px;
  background-color: #F5F5F5;
}

::-webkit-scrollbar-thumb {
  border-radius: 5px;
  background-color: rgba(119, 119, 119, 0.7);
}

初始化App

src/main.js

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
// 引入element-ui 组件和样式文件
import element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import './assets/stylesheets/base.css'

Vue.config.productionTip = false

Vue.use(element)

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,
  template: '<App/>',
  components: { App }
})

开发组件

src/components/News.vue

这里用到了 element-ui (by 饿了么团队) 中的 :

<template>
  <div class="container" :style="{height: innerHeight}">
    <div class="container-header">
      <el-input placeholder="输入关键字.." v-model="keyword" @keyup.native="getNews">
        <el-select class="el-select" v-model="type" slot="prepend" placeholder="请选择.." @change="getNews">
          <el-option v-for="mode in modes" :key="mode.id" :label="mode.label" :value="mode.value"></el-option>
        </el-select>
      </el-input>
    </div>
    <div class="container-body">
      <el-collapse  accordion>
        <el-collapse-item v-for="item in now" :key="item.id">
          <template slot="title">
            <div class="collapse-item-profile">
              <label>创建时间:&nbsp;&nbsp;<span>{{item.createAt}}</span></label>&nbsp;&nbsp;&nbsp;&nbsp;
              <label>来源:&nbsp;&nbsp;<span>{{item.from}}</span></label>
            </div>
            <span class="collapse-item-title">{{item.title}}</span>
          </template>
          <div v-html="item.content"></div>
        </el-collapse-item>
      </el-collapse>
    </div>
  </div>
</template>

<script>
  import {mapGetters, mapActions, mapMutations} from 'vuex'

  export default {
    name: 'News',
    mounted () {
      this.$nextTick(function () {
        this.getSearchMode()
        this.getNews()
      })
    },
    methods: {
      ...mapActions('news', [
        'getSearchMode',
        'getNews'
      ]),
      ...mapMutations('news', [
        'updateType',
        'updateKeyword'
      ])
    },
    computed: {
      ...mapGetters('news', [ 'now', 'modes' ]),
      type: {
        // 修改计算属性的 getter & setter 使之可以双向数据绑定
        get () {
          return this.$store.state.news.type
        },
        set (value) {
    	  // 提交 mutation 改变 store.state
          this.updateType(value)
        }
      },
      keyword: {
        get () {
          return this.$store.state.news.keyword
        },
        set (value) {
          this.updateKeyword(value)
        }
      },
      innerHeight () {
        return window.innerHeight - 16 + 'px'
      }
    }
  }
</script>

<style>
  .el-collapse-item__wrap {
    background-color: #fed;
  }
</style>

<style scoped>
  .el-select {
    width:130px;
  }
  .container {
    width: 1068px;
    margin: 8px auto;
    overflow: auto;
  }
  .container-header,.container-body {
    width: 1048px;
  }
  .collapse-item-title {
    font-size: 1.25rem;
    font-weight: 600;
    padding-left: 15px;
  }
  .collapse-item-profile {
    display: inline-block;
    float: right;
    margin-right: 100px;
  }
  .collapse-item-profile>label {
    font-size: .75rem;
    font-weight: 400;
    color: #555;
  }
  .collapse-item-profile>label>span {
    color: #777;
  }
</style>

绑定组件

src/App.vue

<template>
  <div id="app">
    <news></news>
  </div>
</template>

<script>
  import News from './components/News.vue'

  export default {
    components: { News }
  }
</script>

最后

运行结果

结言

本节源码 :

https://github.com/lonelydawn/news