適合新手學習vue+vuex+vue-router練習的項目

vue練手項目

目的

這個項目主要是用vue+vuex實現一個單頁面應用,純粹是熟悉vue全家桶相關開發模式,用於練手很是合適。javascript

着手開發完了以後能夠學的東西:css

  1. 熟悉vue單文件組件開發方式
  2. 熟悉如何寫一個vue插件
  3. 熟悉如何使用vue-router以及掛載路由鉤子函數
  4. 熟悉vuex是如何運做的,模塊化維護應用狀態數據
  5. 體驗typescript的開發方式

若是想學vue的不妨進來看看vue

技術棧

  • vue
  • vuex
  • vue-router
  • typescript

開始

開始以前,仍是有必要去vue官網學習一下vue,至少得有個大體的瞭解,後面在用到vue-router和vuex時,再去對應的倉庫看文檔就能夠了。java

建立項目能夠用vue-cli,具體看這裏git

結構

項目結構通常來講很是重要,定義好的目錄結構,很是利於後續的項目維護,以及別人閱讀理解。下面就是這個項目的結構,應該看一下就知道是幹什麼的,大體說一下。github

項目結構分爲靜態資源目錄,api接口請求目錄,組件目錄,插件目錄,路由配置目錄,公共樣式目錄,狀態維護目錄,工具類目錄,頁面視圖目錄。vue-router

圖片描述

單頁面組件

vue開發通常都是單頁面組件的方式,即一個以vue爲後綴的文件就是一個組件,組件裏包含了template模版,script腳本,style樣式,組件內的邏輯能夠徹底封裝在裏面,對外能夠提供接受的Props數據,能夠對外發射一個事件emit,或者將外部組件組合到本身內部的slot裏面。vuex

<template>
  <div class="topNav">
    <ul class="list">
      <li class="item left">
        <app-icon :link="left" @click.native.stop="clickLeft" />
      </li>
    </ul>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Emit , Vue } from 'vue-property-decorator';
import AppIcon from './AppIcon.vue';
import {PREFIX} from '@/store/modules/user/CONSTANTS';

@Component({
  components: {
    AppIcon,
  },
})
export default class TopNav extends Vue {
  @Prop({required: true})
  private left!: string;
  private get avatar() {
    return this.$store.state[PREFIX].avatar;
  }
  private clickLeft() {
    this.$emit('left');
  }
}
</script>

<style lang="scss" scoped>
@import '../scss/theme.scss';
.topNav {
  background: $topBarBgColor;
  position: fixed;
}
</style>

配置路由

因爲在客戶端渲染的單頁面應用,須要在客戶端配置路由,實現頁面間的切換。開發vue時官方推薦使用vue-router,在配置這個項目時,因爲考慮登陸態的維護,因此對路由配置加了meta數據,並增長了路由跳轉鉤子函數,進行鑑權控制受登陸態的頁面。vue-cli

import Vue from 'vue';
import Router from 'vue-router';
import Sign from '@/views/Sign.vue';
import Me from '@/views/Me.vue';
import { hasLogin } from '@/util/session';

Vue.use(Router);

const router = new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'sign',
      component: Sign,
    },
    {
      path: '/me',
      name: 'me',
      component: Me,
      meta: { requiredAuth: true },
    },
  ],
});

router.beforeEach((to, from, next) => {
  if (to.matched.some((record) => record.meta.requiredAuth)) {
    // this route requires auth, check if logged in
    // if not, redirect to login page.
    if (!hasLogin()) {
      next({
        path: '/',
        query: { redirect: to.fullPath },
      });
    } else {
      next();
    }
  } else {
    next(); // 確保必定要調用 next()
  }
});

export default router;

vue插件編寫

對於那種須要全組件共享,或者全局注入的方法等可使用vue插件。其實,vue-router和vuex實際就是vue的插件,在入口處,調Vue.use(Router); 就能夠了,好比 Vue.use(Router);typescript

一個插件,能夠是一個函數,或者一個包含install方法的對象,在調用Vue.use時,會調用install方法。

在插件裏,咱們能夠

  1. 添加全局方法或者屬性,
  2. 添加全局資源
  3. 經過全局 mixin 方法添加一些組件選項
  4. 添加 Vue 實例方法
import Vue, { VueConstructor, PluginObject } from 'vue';
import Loading from './Loading.vue';

type ShowFunc = () => () => void;

const plugin: PluginObject<{}> = {
  install(Vue: VueConstructor, options = {}) {
    const CONSTRUCTOR = Vue.extend(Loading);
    let cache: Vue & { show: ShowFunc } | null = null;

    function loading(): () => void {
      const loadingComponent = cache || (cache = new CONSTRUCTOR());
      if (!loadingComponent.$el) {
        const vm = loadingComponent.$mount();
        (document.querySelector('body') as HTMLElement).appendChild(vm.$el);
      }
      return loadingComponent.show();
    }
    Vue.prototype.$loading = loading;
  },
};

export default plugin;

狀態管理

單頁面應用的狀態管理使用vuex,上面提到了,它就是一個vue的插件,會在組件實例上注入$store對象,這個對象就是new Vuex.Store(),相比redux ,我以爲vuex簡單不少。使用須要注意一下幾點就能夠了,

  1. 改變state,始終是經過commit一個mutation方式進行,mutation函數裏必須是同步改變state,不能異步改變state。對應redux中,就是reducer函數的功能了。
  2. 對於異步改變state,能夠經過dispatch一個action,action裏面異步獲取數據以後在commit一個對應的mutation。這個在redux裏,是經過中間件處理異步action的。
  3. 對於state的過濾篩選,能夠定義getter,getter是緩存依賴的。
  4. 對於大型複雜的state,能夠採用模塊化的方式管理各個模塊的state,這個跟redux的思想是同樣的。

本次項目也是用模塊化的管理狀態的方式,把整個應用的狀態以業務劃分爲子狀態,最後在modules中合併

modules: {
    user,
    list,
    filter,
  },

對於單個模塊的state,按照上面的注意點便可以。

// user模塊的state
import { ActionTree, MutationTree, ActionContext } from 'vuex';
import { login, loginOut, LoginInfo } from '@/api/login';
import { getUserInfo, getUserActions } from '@/api/user';
import { User } from './user';
import { RootState } from '../../rootstate';

const namespaced = true;

/* initial state */
const state = () => ({
  id: null,
  username: null,
  email: null,
  avatar: null,
  likes_count: null,
  goings_count: null,
  past_count: null,
});

/* user actions */
const actions: ActionTree<User, RootState> = {
  login({ commit, state }: ActionContext<User, RootState>, payload: LoginInfo) {
    return login(payload).then(
      ({ token, user }: { token: string; user: User }) => {
        commit('saveToken', token, { root: true });
        commit('saveUser', user);
      },
    );
  },
  getUserInfo({ commit, state }: ActionContext<User, RootState>) {
    return getUserInfo().then((user: User) => {
      commit('saveUser', user);
    });
  },
};

/* user mutations */
const mutations: MutationTree<User> = {
  saveUser(state, user) {
    state.id = user.id;
    state.username = user.username;
    state.email = user.email;
    state.avatar = user.avatar;
    state.likes_count = user.likes_count;
    state.goings_count = user.goings_count;
    state.past_count = user.past_count;
  },
};

export default {
  state,
  actions,
  mutations,
  namespaced,
};
相關文章
相關標籤/搜索