成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Vue + TypeScript + Element 項(xiàng)目實(shí)踐(簡(jiǎn)潔時(shí)尚博客網(wǎng)站)及踩坑記

luckyyulin / 935人閱讀

摘要:前言本文講解如何在項(xiàng)目中使用來(lái)搭建并開發(fā)項(xiàng)目,并在此過(guò)程中踩過(guò)的坑。具有類型系統(tǒng),且是的超集,在年勢(shì)頭迅猛,可謂遍地開花。年將會(huì)更加普及,能夠熟練掌握,并使用開發(fā)過(guò)項(xiàng)目,將更加成為前端開發(fā)者的優(yōu)勢(shì)。

前言

本文講解如何在 Vue 項(xiàng)目中使用 TypeScript 來(lái)搭建并開發(fā)項(xiàng)目,并在此過(guò)程中踩過(guò)的坑 。

TypeScript 具有類型系統(tǒng),且是 JavaScript 的超集,TypeScript 在 2018年 勢(shì)頭迅猛,可謂遍地開花。

Vue3.0 將使用 TS 重寫,重寫后的 Vue3.0 將更好的支持 TS。2019 年 TypeScript 將會(huì)更加普及,能夠熟練掌握 TS,并使用 TS 開發(fā)過(guò)項(xiàng)目,將更加成為前端開發(fā)者的優(yōu)勢(shì)。

所以筆者就當(dāng)然也要學(xué)這個(gè)必備技能,就以 邊學(xué)邊實(shí)踐 的方式,做個(gè)博客項(xiàng)目來(lái)玩玩。

此項(xiàng)目是基于 Vue 全家桶 + TypeScript + Element-UI 的技術(shù)棧,且已經(jīng)開源,github 地址 blog-vue-typescript 。

因?yàn)橹皩懥似?Vue 項(xiàng)目搭建的相關(guān)文章 基于vue+mint-ui的mobile-h5的項(xiàng)目說(shuō)明 ,有不少人加我微信,要源碼來(lái)學(xué)習(xí),但是這個(gè)是我司的項(xiàng)目,不能提供原碼。

所以做一個(gè)不是我司的項(xiàng)目,且又是 vue 相關(guān)的項(xiàng)目來(lái)練手并開源吧。

1. 效果

效果圖:

pc 端

移動(dòng)端

完整效果請(qǐng)看:https://biaochenxuying.cn

2. 功能 已經(jīng)完成功能

[x] 登錄

[x] 注冊(cè)

[x] 文章列表

[x] 文章歸檔

[x] 標(biāo)簽

[x] 關(guān)于

[x] 點(diǎn)贊與評(píng)論

[x] 留言

[x] 歷程

[x] 文章詳情(支持代碼語(yǔ)法高亮)

[x] 文章詳情目錄

[x] 移動(dòng)端適配

[x] github 授權(quán)登錄

待優(yōu)化或者實(shí)現(xiàn)

[ ] 使用 vuex-class

[ ] 更多 TypeScript 的優(yōu)化技巧

[ ] 服務(wù)器渲染 SSR

3. 前端主要技術(shù)

所有技術(shù)都是當(dāng)前最新的。

vue: ^2.6.6

typescript : ^3.2.1

element-ui: 2.6.3

vue-router : ^3.0.1

webpack: 4.28.4

vuex: ^3.0.1

axios:0.18.0

redux: 4.0.0

highlight.js: 9.15.6

marked:0.6.1

4. 5 分鐘上手 TypeScript

如果沒有一點(diǎn)點(diǎn)基礎(chǔ),可能沒學(xué)過(guò) TypeScript 的讀者會(huì)看不懂往下的內(nèi)容,所以先學(xué)點(diǎn)基礎(chǔ)。

TypeScript 的靜態(tài)類型檢查是個(gè)好東西,可以避免很多不必要的錯(cuò)誤, 不用在調(diào)試或者項(xiàng)目上線的時(shí)候才發(fā)現(xiàn)問(wèn)題 。

類型注解

TypeScript 里的類型注解是一種輕量級(jí)的為函數(shù)或變量添加約束的方式。變量定義時(shí)也要定義他的類型,比如常見的 :

// 布爾值
let isDone: boolean = false; // 相當(dāng)于 js 的 let isDone = false;
// 變量定義之后不可以隨便變更它的類型
isDone = true // 不報(bào)錯(cuò)
isDone = "我要變?yōu)樽址? // 報(bào)錯(cuò)
// 數(shù)字
let decLiteral: number = 6; // 相當(dāng)于 js 的 let decLiteral = 6;
// 字符串
let name: string = "bob";  // 相當(dāng)于 js 的 let name = "bob";
// 數(shù)組
 // 第一種,可以在元素類型后面接上 [],表示由此類型元素組成的一個(gè)數(shù)組:
let list: number[] = [1, 2, 3]; // 相當(dāng)于 js 的let list = [1, 2, 3];
// 第二種方式是使用數(shù)組泛型,Array<元素類型>:
let list: Array = [1, 2, 3]; // 相當(dāng)于 js 的let list = [1, 2, 3];
// 在 TypeScript 中,我們使用接口(Interfaces)來(lái)定義 對(duì)象 的類型。
interface Person {
    name: string;
    age: number;
}
let tom: Person = {
    name: "Tom",
    age: 25
};
// 以上 對(duì)象 的代碼相當(dāng)于 
let tom = {
    name: "Tom",
    age: 25
};
// Any 可以隨便變更類型 (當(dāng)這個(gè)值可能來(lái)自于動(dòng)態(tài)的內(nèi)容,比如來(lái)自用戶輸入或第三方代碼庫(kù))
let notSure: any = 4;
notSure = "我可以隨便變更類型" // 不報(bào)錯(cuò)
notSure = false;  // 不報(bào)錯(cuò)
// Void 當(dāng)一個(gè)函數(shù)沒有返回值時(shí),你通常會(huì)見到其返回值類型是 void
function warnUser(): void {
    console.log("This is my warning message");
}
// 方法的參數(shù)也要定義類型,不知道就定義為 any
function fetch(url: string, id : number, params: any): void {
    console.log("fetch");
}

以上是最簡(jiǎn)單的一些知識(shí)點(diǎn),更多知識(shí)請(qǐng)看 TypeScript 中文官網(wǎng)

5. 5 分鐘上手 Vue +TypeScript

vue-class-component?

vue-class-component 對(duì)?Vue?組件進(jìn)行了一層封裝,讓?Vue?組件語(yǔ)法在結(jié)合了?TypeScript?語(yǔ)法之后更加扁平化:



上面的代碼跟下面的代碼作用是一樣的:



vue-property-decorator?

vue-property-decorator 是在?vue-class-component?上增強(qiáng)了更多的結(jié)合?Vue?特性的裝飾器,新增了這 7 個(gè)裝飾器:

@Emit

@Inject

@Model

@Prop

@Provide

@Watch

@Component?(從?vue-class-component?繼承)

在這里列舉幾個(gè)常用的@Prop/@Watch/@Component, 更多信息,詳見官方文檔

import { Component, Emit, Inject, Model, Prop, Provide, Vue, Watch } from "vue-property-decorator"

@Component
export class MyComponent extends Vue {
  
  @Prop()
  propA: number = 1

  @Prop({ default: "default value" })
  propB: string

  @Prop([String, Boolean])
  propC: string | boolean

  @Prop({ type: null })
  propD: any

  @Watch("child")
  onChildChanged(val: string, oldVal: string) { }
}

上面的代碼相當(dāng)于:

export default {
  props: {
    checked: Boolean,
    propA: Number,
    propB: {
      type: String,
      default: "default value"
    },
    propC: [String, Boolean],
    propD: { type: null }
  }
  methods: {
    onChildChanged(val, oldVal) { }
  },
  watch: {
    "child": {
      handler: "onChildChanged",
      immediate: false,
      deep: false
    }
  }
}

vuex-class

vuex-class?:在?vue-class-component?寫法中 綁定?vuex 。

import Vue from "vue"
import Component from "vue-class-component"
import {
  State,
  Getter,
  Action,
  Mutation,
  namespace
} from "vuex-class"

const someModule = namespace("path/to/module")

@Component
export class MyComp extends Vue {
  @State("foo") stateFoo
  @State(state => state.bar) stateBar
  @Getter("foo") getterFoo
  @Action("foo") actionFoo
  @Mutation("foo") mutationFoo
  @someModule.Getter("foo") moduleGetterFoo

  // If the argument is omitted, use the property name
  // for each state/getter/action/mutation type
  @State foo
  @Getter bar
  @Action baz
  @Mutation qux

  created () {
    this.stateFoo // -> store.state.foo
    this.stateBar // -> store.state.bar
    this.getterFoo // -> store.getters.foo
    this.actionFoo({ value: true }) // -> store.dispatch("foo", { value: true })
    this.mutationFoo({ value: true }) // -> store.commit("foo", { value: true })
    this.moduleGetterFoo // -> store.getters["path/to/module/foo"]
  }
}
6. 用 vue-cli 搭建 項(xiàng)目

筆者使用最新的 vue-cli 3 搭建項(xiàng)目,詳細(xì)的教程,請(qǐng)看我之前寫的 vue-cli3.x 新特性及踩坑記,里面已經(jīng)有詳細(xì)講解 ,但文章里面的配置和此項(xiàng)目不同的是,我加入了 TypeScript ,其他的配置都是 vue-cli 本來(lái)配好的了。詳情請(qǐng)看 vue-cli 官網(wǎng) 。

6.1 安裝及構(gòu)建項(xiàng)目目錄

安裝的依賴:

安裝過(guò)程選擇的一些配置:

搭建好之后,初始項(xiàng)目結(jié)構(gòu)長(zhǎng)這樣:

├── public                          // 靜態(tài)頁(yè)面

├── src                             // 主目錄

    ├── assets                      // 靜態(tài)資源

    ├── components                  // 組件

    ├── views                       // 頁(yè)面

    ├── App.vue                     // 頁(yè)面主入口

    ├── main.ts                     // 腳本主入口

    ├── router.ts                   // 路由

    ├── shims-tsx.d.ts              // 相關(guān) tsx 模塊注入

    ├── shims-vue.d.ts              // Vue 模塊注入

    └── store.ts                    // vuex 配置

├── tests                           // 測(cè)試用例

├── .eslintrc.js                    // eslint 相關(guān)配置

├── .gitignore                      // git 忽略文件配置

├── babel.config.js                 // babel 配置

├── postcss.config.js               // postcss 配置

├── package.json                    // 依賴

└── tsconfig.json                   // ts 配置

奔著 大型項(xiàng)目的結(jié)構(gòu) 來(lái)改造項(xiàng)目結(jié)構(gòu),改造后 :

├── public                          // 靜態(tài)頁(yè)面

├── src                             // 主目錄

    ├── assets                      // 靜態(tài)資源

    ├── filters                     // 過(guò)濾

    ├── store                       // vuex 配置

    ├── less                        // 樣式

    ├── utils                       // 工具方法(axios封裝,全局方法等)

    ├── views                       // 頁(yè)面

    ├── App.vue                     // 頁(yè)面主入口

    ├── main.ts                     // 腳本主入口

    ├── router.ts                   // 路由

    ├── shime-global.d.ts           // 相關(guān) 全局或者插件 模塊注入

    ├── shims-tsx.d.ts              // 相關(guān) tsx 模塊注入

    ├── shims-vue.d.ts              // Vue 模塊注入, 使 TypeScript 支持 *.vue 后綴的文件

├── tests                           // 測(cè)試用例

├── .eslintrc.js                    // eslint 相關(guān)配置

├── postcss.config.js               // postcss 配置

├── .gitignore                      // git 忽略文件配置

├── babel.config.js                 // preset 記錄

├── package.json                    // 依賴

├── README.md                       // 項(xiàng)目 readme

├── tsconfig.json                   // ts 配置

└── vue.config.js                   // webpack 配置

tsconfig.json 文件中指定了用來(lái)編譯這個(gè)項(xiàng)目的根文件和編譯選項(xiàng)。
本項(xiàng)目的 tsconfig.json 配置如下 :

{
    // 編譯選項(xiàng)
  "compilerOptions": {
    // 編譯輸出目標(biāo) ES 版本
    "target": "esnext",
    // 采用的模塊系統(tǒng)
    "module": "esnext",
    // 以嚴(yán)格模式解析
    "strict": true,
    "jsx": "preserve",
    // 從 tslib 導(dǎo)入外部幫助庫(kù): 比如__extends,__rest等
    "importHelpers": true,
    // 如何處理模塊
    "moduleResolution": "node",
    // 啟用裝飾器
    "experimentalDecorators": true,
    "esModuleInterop": true,
    // 允許從沒有設(shè)置默認(rèn)導(dǎo)出的模塊中默認(rèn)導(dǎo)入
    "allowSyntheticDefaultImports": true,
    // 定義一個(gè)變量就必須給它一個(gè)初始值
    "strictPropertyInitialization" : false,
    // 允許編譯javascript文件
    "allowJs": true,
    // 是否包含可以用于 debug 的 sourceMap
    "sourceMap": true,
    // 忽略 this 的類型檢查, Raise error on this expressions with an implied any type.
    "noImplicitThis": false,
    // 解析非相對(duì)模塊名的基準(zhǔn)目錄 
    "baseUrl": ".",
    // 給錯(cuò)誤和消息設(shè)置樣式,使用顏色和上下文。
    "pretty": true,
    // 設(shè)置引入的定義文件
    "types": ["webpack-env", "mocha", "chai"],
    // 指定特殊模塊的路徑
    "paths": {
      "@/*": ["src/*"]
    },
    // 編譯過(guò)程中需要引入的庫(kù)文件的列表
    "lib": ["esnext", "dom", "dom.iterable", "scripthost"]
  },
  // ts 管理的文件
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx"
  ],
  // ts 排除的文件
  "exclude": ["node_modules"]
}

更多配置請(qǐng)看官網(wǎng)的 tsconfig.json 的 編譯選項(xiàng)

本項(xiàng)目的 vue.config.js:

const path = require("path");
const sourceMap = process.env.NODE_ENV === "development";

module.exports = {
  // 基本路徑
  publicPath: "./",
  // 輸出文件目錄
  outputDir: "dist",
  // eslint-loader 是否在保存的時(shí)候檢查
  lintOnSave: false,
  // webpack配置
  // see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md
  chainWebpack: () => {},
  configureWebpack: config => {
    if (process.env.NODE_ENV === "production") {
      // 為生產(chǎn)環(huán)境修改配置...
      config.mode = "production";
    } else {
      // 為開發(fā)環(huán)境修改配置...
      config.mode = "development";
    }

    Object.assign(config, {
      // 開發(fā)生產(chǎn)共同配置
      resolve: {
        extensions: [".js", ".vue", ".json", ".ts", ".tsx"],
        alias: {
          vue$: "vue/dist/vue.js",
          "@": path.resolve(__dirname, "./src")
        }
      }
    });
  },
  // 生產(chǎn)環(huán)境是否生成 sourceMap 文件
  productionSourceMap: sourceMap,
  // css相關(guān)配置
  css: {
    // 是否使用css分離插件 ExtractTextPlugin
    extract: true,
    // 開啟 CSS source maps?
    sourceMap: false,
    // css預(yù)設(shè)器配置項(xiàng)
    loaderOptions: {},
    // 啟用 CSS modules for all css / pre-processor files.
    modules: false
  },
  // use thread-loader for babel & TS in production build
  // enabled by default if the machine has more than 1 cores
  parallel: require("os").cpus().length > 1,
  // PWA 插件相關(guān)配置
  // see https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa
  pwa: {},
  // webpack-dev-server 相關(guān)配置
  devServer: {
    open: process.platform === "darwin",
    host: "localhost",
    port: 3001, //8080,
    https: false,
    hotOnly: false,
    proxy: {
      // 設(shè)置代理
      // proxy all requests starting with /api to jsonplaceholder
      "/api": {
        // target: "https://emm.cmccbigdata.com:8443/",
        target: "http://localhost:3000/",
        // target: "http://47.106.136.114/",
        changeOrigin: true,
        ws: true,
        pathRewrite: {
          "^/api": ""
        }
      }
    },
    before: app => {}
  },
  // 第三方插件配置
  pluginOptions: {
    // ...
  }
};
6.2 安裝 element-ui

本來(lái)想搭配 iview-ui 來(lái)用的,但后續(xù)還想把這個(gè)項(xiàng)目搞成 ssr 的,而 vue + typescript + iview + Nuxt.js 的服務(wù)端渲染還有不少坑, 而 vue + typescript + element + Nuxt.js 對(duì) ssr 的支持已經(jīng)不錯(cuò)了,所以選擇了 element-ui 。

安裝:

npm i element-ui -S

按需引入, 借助?babel-plugin-component,我們可以只引入需要的組件,以達(dá)到減小項(xiàng)目體積的目的。

npm install babel-plugin-component -D

然后,將 babel.config.js 修改為:

module.exports = {
  presets: ["@vue/app"],
  plugins: [
    [
      "component",
      {
        libraryName: "element-ui",
        styleLibraryName: "theme-chalk"
      }
    ]
  ]
};

接下來(lái),如果你只希望引入部分組件,比如 Button 和 Select,那么需要在 main.js 中寫入以下內(nèi)容:

import Vue from "vue";
import { Button, Select } from "element-ui";
import App from "./App.vue";

Vue.component(Button.name, Button);
Vue.component(Select.name, Select);
/* 或?qū)憺? * Vue.use(Button)
 * Vue.use(Select)
 */

new Vue({
  el: "#app",
  render: h => h(App)
});
6.3 完善項(xiàng)目目錄與文件 route

使用路由懶加載功能。

export default new Router({
  mode: "history",
  routes: [
    {
      path: "/",
      name: "home",
      component: () => import(/* webpackChunkName: "home" */ "./views/home.vue")
    },
    {
      path: "/articles",
      name: "articles",
      // route level code-splitting
      // this generates a separate chunk (articles.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () =>
        import(/* webpackChunkName: "articles" */ "./views/articles.vue")
    },
  ]
});
utils

utils/utils.ts 常用函數(shù)的封裝, 比如 事件的節(jié)流(throttle)與防抖(debounce)方法:

// fn是我們需要包裝的事件回調(diào), delay是時(shí)間間隔的閾值
export function throttle(fn: Function, delay: number) {
  // last為上一次觸發(fā)回調(diào)的時(shí)間, timer是定時(shí)器
  let last = 0,
    timer: any = null;
  // 將throttle處理結(jié)果當(dāng)作函數(shù)返回
  return function() {
    // 保留調(diào)用時(shí)的this上下文
    let context = this;
    // 保留調(diào)用時(shí)傳入的參數(shù)
    let args = arguments;
    // 記錄本次觸發(fā)回調(diào)的時(shí)間
    let now = +new Date();
    // 判斷上次觸發(fā)的時(shí)間和本次觸發(fā)的時(shí)間差是否小于時(shí)間間隔的閾值
    if (now - last < delay) {
      // 如果時(shí)間間隔小于我們?cè)O(shè)定的時(shí)間間隔閾值,則為本次觸發(fā)操作設(shè)立一個(gè)新的定時(shí)器
      clearTimeout(timer);
      timer = setTimeout(function() {
        last = now;
        fn.apply(context, args);
      }, delay);
    } else {
      // 如果時(shí)間間隔超出了我們?cè)O(shè)定的時(shí)間間隔閾值,那就不等了,無(wú)論如何要反饋給用戶一次響應(yīng)
      last = now;
      fn.apply(context, args);
    }
  };
}

utils/config.ts 配置文件,比如 github 授權(quán)登錄的回調(diào)地址、client_id、client_secret 等。

const config = {
  "oauth_uri": "https://github.com/login/oauth/authorize",
  "redirect_uri": "https://biaochenxuying.cn/login",
  "client_id": "XXXXXXXXXX",
  "client_secret": "XXXXXXXXXX",
};

// 本地開發(fā)環(huán)境下
if (process.env.NODE_ENV === "development") {
  config.redirect_uri = "http://localhost:3001/login"
  config.client_id = "502176cec65773057a9e"
  config.client_secret = "65d444de381a026301a2c7cffb6952b9a86ac235"
}
export default config;

如果你的生產(chǎn)環(huán)境也要 github 登錄授權(quán)的話,請(qǐng)?jiān)?github 上申請(qǐng)一個(gè) Oauth App ,把你的 redirect_uri,client_id,client_secret 的信息填在 config 里面即可。具體詳情請(qǐng)看我寫的這篇文章 github 授權(quán)登錄教程與如何設(shè)計(jì)第三方授權(quán)登錄的用戶表

utils/urls.ts 請(qǐng)求接口地址,統(tǒng)一管理。

// url的鏈接
export const urls: object = {
  login: "login",
  register: "register",
  getArticleList: "getArticleList",
};
export default urls;

utils/https.ts axios 請(qǐng)求的封裝。

import axios from "axios";

// 創(chuàng)建axios實(shí)例
let service: any = {};
service = axios.create({
    baseURL: "/api", // api的base_url
    timeout: 50000 // 請(qǐng)求超時(shí)時(shí)間
  });

// request攔截器 axios的一些配置
service.interceptors.request.use(
  (config: any) => {
    return config;
  },
  (error: any) => {
    // Do something with request error
    console.error("error:", error); // for debug
    Promise.reject(error);
  }
);

// respone攔截器 axios的一些配置
service.interceptors.response.use(
  (response: any) => {
    return response;
  },
  (error: any) => {
    console.error("error:" + error); // for debug
    return Promise.reject(error);
  }
);

export default service;

把 urls 和 https 掛載到 main.ts 里面的 Vue 的 prototype 上面。

import service from "./utils/https";
import urls from "./utils/urls";

Vue.prototype.$https = service; // 其他頁(yè)面在使用 axios 的時(shí)候直接  this.$http 就可以了
Vue.prototype.$urls = urls; // 其他頁(yè)面在使用 urls 的時(shí)候直接  this.$urls 就可以了

然后就可以統(tǒng)一管理接口,而且調(diào)用起來(lái)也很方便啦。比如下面 文章列表的請(qǐng)求。

async handleSearch() {
    this.isLoading = true;
    const res: any = await this.$https.get(this.$urls.getArticleList, {
      params: this.params
    });
    this.isLoading = false;
    if (res.status === 200) {
      if (res.data.code === 0) {
        const data: any = res.data.data;
        this.articlesList = [...this.articlesList, ...data.list];
        this.total = data.count;
        this.params.pageNum++;
        if (this.total === this.articlesList.length) {
          this.isLoadEnd = true;
        }
      } else {
        this.$message({
          message: res.data.message,
          type: "error"
        });
      }
    } else {
      this.$message({
        message: "網(wǎng)絡(luò)錯(cuò)誤!",
        type: "error"
      });
    }
  }
store ( Vuex )

一般大型的項(xiàng)目都有很多模塊的,比如本項(xiàng)目中有公共信息(比如 token )、 用戶模塊、文章模塊。

├── modules                         // 模塊

    ├── user.ts                     // 用戶模塊 
    
    ├── article.ts                 // 文章模塊 

├── types.ts                        // 類型

└── index.ts                        // vuex 主入口

store/index.ts 存放公共的信息,并導(dǎo)入其他模塊

import Vue from "vue";
import Vuex from "vuex";
import * as types from "./types";
import user from "./modules/user";
import article from "./modules/article";

Vue.use(Vuex);
const initPageState = () => {
  return {
    token: ""
  };
};
const store = new Vuex.Store({
  strict: process.env.NODE_ENV !== "production",
  // 具體模塊
  modules: {
    user,
    article
  },
  state: initPageState(),
  mutations: {
    [types.SAVE_TOKEN](state: any, pageState: any) {
      for (const prop in pageState) {
        state[prop] = pageState[prop];
      }
    }
  },
  actions: {}
});

export default store;

types.ts

// 公共 token
export const SAVE_TOKEN = "SAVE_TOKEN";

// 用戶
export const SAVE_USER = "SAVE_USER";

user.ts

import * as types from "../types";

const initPageState = () => {
  return {
    userInfo: {
      _id: "",
      name: "",
      avator: ""
    }
  };
};
const user = {
  state: initPageState(),
  mutations: {
    [types.SAVE_USER](state: any, pageState: any) {
      for (const prop in pageState) {
        state[prop] = pageState[prop];
      }
    }
  },
  actions: {}
};

export default user;
7. markdown 渲染

markdown 渲染效果圖:

markdown 渲染 采用了開源的 marked, 代碼高亮用了 highlight.js 。

用法:

第一步:npm i marked highlight.js --save

npm i marked highlight.js --save

第二步: 導(dǎo)入封裝成 markdown.js,將文章詳情由字符串轉(zhuǎn)成 html, 并抽離出文章目錄。

marked 的封裝 得感謝這位老哥。

const highlight = require("highlight.js");
const marked = require("marked");
const tocObj = {
  add: function(text, level) {
    var anchor = `#toc${level}${++this.index}`;
    this.toc.push({ anchor: anchor, level: level, text: text });
    return anchor;
  },
  // 使用堆棧的方式處理嵌套的ul,li,level即ul的嵌套層次,1是最外層
  // 
    //
  • //
      //
    • //
    //
  • //
toHTML: function() { let levelStack = []; let result = ""; const addStartUL = () => { result += "
    "; }; const addEndUL = () => { result += "
"; }; const addLI = (anchor, text) => { result += "
  • " + text + "
  • "; }; this.toc.forEach(function(item) { let levelIndex = levelStack.indexOf(item.level); // 沒有找到相應(yīng)level的ul標(biāo)簽,則將li放入新增的ul中 if (levelIndex === -1) { levelStack.unshift(item.level); addStartUL(); addLI(item.anchor, item.text); } // 找到了相應(yīng)level的ul標(biāo)簽,并且在棧頂?shù)奈恢脛t直接將li放在此ul下 else if (levelIndex === 0) { addLI(item.anchor, item.text); } // 找到了相應(yīng)level的ul標(biāo)簽,但是不在棧頂位置,需要將之前的所有l(wèi)evel出棧并且打上閉合標(biāo)簽,最后新增li else { while (levelIndex--) { levelStack.shift(); addEndUL(); } addLI(item.anchor, item.text); } }); // 如果棧中還有l(wèi)evel,全部出棧打上閉合標(biāo)簽 while (levelStack.length) { levelStack.shift(); addEndUL(); } // 清理先前數(shù)據(jù)供下次使用 this.toc = []; this.index = 0; return result; }, toc: [], index: 0 }; class MarkUtils { constructor() { this.rendererMD = new marked.Renderer(); this.rendererMD.heading = function(text, level, raw) { var anchor = tocObj.add(text, level); return `${text} `; }; highlight.configure({ useBR: true }); marked.setOptions({ renderer: this.rendererMD, headerIds: false, gfm: true, tables: true, breaks: false, pedantic: false, sanitize: false, smartLists: true, smartypants: false, highlight: function(code) { return highlight.highlightAuto(code).value; } }); } async marked(data) { if (data) { let content = await marked(data); // 文章內(nèi)容 let toc = tocObj.toHTML(); // 文章目錄 return { content: content, toc: toc }; } else { return null; } } } const markdown = new MarkUtils(); export default markdown;

    第三步: 使用

    import markdown from "@/utils/markdown";
    
    // 獲取文章詳情
    async handleSearch() {
        const res: any = await this.$https.post(
          this.$urls.getArticleDetail,
          this.params
        );
        if (res.status === 200) {
          if (res.data.code === 0) {
            this.articleDetail = res.data.data;
           // 使用 marked 轉(zhuǎn)換
            const article = markdown.marked(res.data.data.content);
            article.then((response: any) => {
              this.articleDetail.content = response.content;
              this.articleDetail.toc = response.toc;
            });
          } else {
            // ...
        } else {
         // ... 
        }
      }
    
    // 渲染
    

    第四步:引入 monokai_sublime 的 css 樣式

    第五步:對(duì) markdown 樣式的補(bǔ)充

    如果不補(bǔ)充樣式,是沒有黑色背景的,字體大小等也會(huì)比較小,圖片也不會(huì)居中顯示

    /*對(duì) markdown 樣式的補(bǔ)充*/
    pre {
        display: block;
        padding: 10px;
        margin: 0 0 10px;
        font-size: 14px;
        line-height: 1.42857143;
        color: #abb2bf;
        background: #282c34;
        word-break: break-all;
        word-wrap: break-word;
        overflow: auto;
    }
    h1,h2,h3,h4,h5,h6{
        margin-top: 1em;
        /* margin-bottom: 1em; */
    }
    strong {
        font-weight: bold;
    }
    
    p > code:not([class]) {
        padding: 2px 4px;
        font-size: 90%;
        color: #c7254e;
        background-color: #f9f2f4;
        border-radius: 4px;
    }
    p img{
        /* 圖片居中 */
        margin: 0 auto;
        display: flex;
    }
    
    #content {
        font-family: "Microsoft YaHei",  "sans-serif";
        font-size: 16px;
        line-height: 30px;
    }
    
    #content .desc ul,#content .desc ol {
        color: #333333;
        margin: 1.5em 0 0 25px;
    }
    
    #content .desc h1, #content .desc h2 {
        border-bottom: 1px solid #eee;
        padding-bottom: 10px;
    }
    
    #content .desc a {
        color: #009a61;
    }
    8. 注意點(diǎn)

    關(guān)于 頁(yè)面

    對(duì)于 關(guān)于 的頁(yè)面,其實(shí)是一篇文章來(lái)的,根據(jù)文章類型 type 來(lái)決定的,數(shù)據(jù)庫(kù)里面 type 為 3
    的文章,只能有一篇就是 博主介紹 ;達(dá)到了想什么時(shí)候修改內(nèi)容都可以。

    所以當(dāng) 當(dāng)前路由 === "/about" 時(shí)就是請(qǐng)求類型為 博主介紹 的文章。

    type: 3,  // 文章類型: 1:普通文章;2:是博主簡(jiǎn)歷;3 :是博主簡(jiǎn)介;

    移動(dòng)端適配

    移動(dòng)端使用 rem 單位適配。

    // 屏幕適配( window.screen.width / 移動(dòng)端設(shè)計(jì)稿寬 * 100)也即是 (window.screen.width / 750 * 100)  ——*100 為了方便計(jì)算。即 font-size 值是手機(jī) deviceWidth 與設(shè)計(jì)稿比值的 100 倍
    document.getElementsByTagName("html")[0].style.fontSize = window.screen.width / 7.5 + "px";

    如上:通過(guò)查詢屏幕寬度,動(dòng)態(tài)的設(shè)置 html 的 font-size 值,移動(dòng)端的設(shè)計(jì)稿大多以寬為 750 px 來(lái)設(shè)置的。

    比如在設(shè)計(jì)圖上一個(gè) 150 * 250 的盒子(單位 px):

    原本在 css 中的寫法:

    width: 150px;
    heigth: 250px;

    通過(guò)上述換算后,在 css 中對(duì)應(yīng)的 rem 值只需要寫:

    width: 1.5rem; // 150 / 100 rem
    heigth: 2.5rem; // 250 / 100 rem

    如果你的移動(dòng)端的設(shè)計(jì)稿是以寬為 1080 px 來(lái)設(shè)置的話,就用 window.screen.width / 10.8 吧。

    9. 踩坑記

    1. 讓 vue 識(shí)別全局方法/變量

    我們經(jīng)常在 main.ts 中給 vue.prototype 掛載實(shí)例或者內(nèi)容,以方便在組件里面使用。

    import service from "./utils/https";
    import urls from "./utils/urls";
    
    Vue.prototype.$https = service; // 其他頁(yè)面在使用 axios 的時(shí)候直接  this.$http 就可以了
    Vue.prototype.$urls = urls; // 其他頁(yè)面在使用 urls 的時(shí)候直接  this.$urls 就可以了

    然而當(dāng)你在組件中直接 this.$http 或者 this.$urls 時(shí)會(huì)報(bào)錯(cuò)的,那是因?yàn)?$http 和 $urls 屬性,并沒有在 vue 實(shí)例中聲明。

    再比如使用 Element-uI 的 meesage。

    import { Message } from "element-ui";
    
    Vue.prototype.$message = Message;

    之前用法如下圖:

      this.$message({
        message: "恭喜你,這是一條成功消息",
        type: "success"
      })

    然而還是會(huì)報(bào)錯(cuò)的。

    再比如 監(jiān)聽路由的變化:

    import { Vue, Watch } from "vue-property-decorator";
    import Component from "vue-class-component";
    import { Route } from "vue-router";
    
    @Component
    export default class App extends Vue {
    
      @Watch("$route")
      routeChange(val: Route, oldVal: Route) {
          //  do something
      }
    }

    只是這樣寫的話,監(jiān)聽 $route 還是會(huì)報(bào)錯(cuò)的。

    想要以上三種做法都正常執(zhí)行,就還要補(bǔ)充如下內(nèi)容:

    在 src 下的 shims-vue.d.ts 中加入要掛載的內(nèi)容。 表示 vue 里面的 this 下有這些東西。

    import VueRouter, { Route } from "vue-router";
    
    declare module "vue/types/vue" {
      interface Vue {
        $router: VueRouter; // 這表示this下有這個(gè)東西
        $route: Route;
        $https: any; // 不知道類型就定為 any 吧(偷懶)
        $urls: any;
        $Message: any;
      }
    }

    2. 引入的模塊要聲明

    比如 在組件里面使用 window.document 或者 document.querySelector 的時(shí)候會(huì)報(bào)錯(cuò)的,npm run build 不給通過(guò)。

    再比如:按需引用 element 的組件與動(dòng)畫組件:

    import { Button } from "element-ui";
    import CollapseTransition from "element-ui/lib/transitions/collapse-transition";

    npm run serve 時(shí)可以執(zhí)行,但是在 npm run build 的時(shí)候,會(huì)直接報(bào)錯(cuò)的,因?yàn)闆]有聲明。

    正確做法:

    我在 src 下新建一個(gè)文件 shime-global.d.ts ,加入內(nèi)容如下:

    // 聲明全局的 window ,不然使用 window.XX 時(shí)會(huì)報(bào)錯(cuò)
    declare var window: Window;
    declare var document: Document;
    
    declare module "element-ui/lib/transitions/collapse-transition";
    declare module "element-ui";

    當(dāng)然,這個(gè)文件你加在其他地方也可以,起其他名字都 OK。

    但是即使配置了以上方法之后,有些地方使用 document.XXX ,比如 document.title 的時(shí)候,npm run build 還是通過(guò)不了,所以只能這樣了:

    3. this 的類型檢查

    比如之前的 事件的節(jié)流(throttle)與防抖(debounce)方法:

    export function throttle(fn: Function, delay: number) {
      return function() {
        // 保留調(diào)用時(shí)的 this 上下文
        let context = this;
    }

    function 里面的 this 在 npm run serve 時(shí)會(huì)報(bào)錯(cuò)的,因?yàn)?tyescript 檢測(cè)到它不是在類(class)里面。

    正確做法:

    在根目錄的 tsconfig.json 里面加上 "noImplicitThis": false ,忽略 this 的類型檢查。

    // 忽略 this 的類型檢查, Raise error on this expressions with an implied any type.
    "noImplicitThis": false,

    4. import 的 .vue 文件

    import .vue 的文件的時(shí)候,要補(bǔ)全 .vue 的后綴,不然 npm run build 會(huì)報(bào)錯(cuò)的。

    比如:

    import Nav from "@/components/nav"; // @ is an alias to /src
    import Footer from "@/components/footer"; // @ is an alias to /src

    要修改為:

    import Nav from "@/components/nav.vue"; // @ is an alias to /src
    import Footer from "@/components/footer.vue"; // @ is an alias to /src

    5. 裝飾器 @Component

    報(bào)錯(cuò)。

    以下才是正確,因?yàn)檫@里的 Vue 是從 vue-property-decorator import 來(lái)的。

    6. 路由的組件導(dǎo)航守衛(wèi)失效

    vue-class-component 官網(wǎng)里面的路由的導(dǎo)航鉤子的用法是沒有效果的 Adding Custom Hooks

    路由的導(dǎo)航鉤子不屬于 Vue 本身,這會(huì)導(dǎo)致 class 組件轉(zhuǎn)義到配置對(duì)象時(shí)導(dǎo)航鉤子無(wú)效,因此如果要使用導(dǎo)航鉤子需要在 router 的配置里聲明(網(wǎng)上別人說(shuō)的,還沒實(shí)踐,不確定是否可行)。

    7. tsconfig.json 的 strictPropertyInitialization 設(shè)為 false,不然你定義一個(gè)變量就必須給它一個(gè)初始值。

    position: sticky;

    本項(xiàng)目中的文章詳情的目錄就是用了 sticky。

    .anchor {
      position: sticky;
      top: 213px;
      margin-top: 213px;
    }

    position:sticky 是 css 定位新增屬性;可以說(shuō)是相對(duì)定位 relative 和固定定位 fixed 的結(jié)合;它主要用在對(duì) scroll 事件的監(jiān)聽上;簡(jiǎn)單來(lái)說(shuō),在滑動(dòng)過(guò)程中,某個(gè)元素距離其父元素的距離達(dá)到 sticky 粘性定位的要求時(shí)(比如 top:100px );position:sticky 這時(shí)的效果相當(dāng)于 fixed 定位,固定到適當(dāng)位置。

    用法像上面那樣用即可,但是有使用條件:

    1、父元素不能 overflow:hidden 或者 overflow:auto 屬性。
    2、必須指定 top、bottom、left、right 4 個(gè)值之一,否則只會(huì)處于相對(duì)定位
    3、父元素的高度不能低于 sticky 元素的高度
    4、sticky 元素僅在其父元素內(nèi)生效

    8. eslint 報(bào)找不到文件和裝飾器的錯(cuò)

    App.vue 中只是寫了引用文件而已,而且 webpack 和 tsconfig.josn 里面已經(jīng)配置了別名了的。

    import Nav from "@/components/nav.vue"; // @ is an alias to /src
    import Slider from "@/components/slider.vue"; // @ is an alias to /src
    import Footer from "@/components/footer.vue"; // @ is an alias to /src
    import ArrowUp from "@/components/arrowUp.vue"; // @ is an alias to /src
    import { isMobileOrPc } from "@/utils/utils";

    但是,還是會(huì)報(bào)如下的錯(cuò):

    只是代碼不影響文件的打包,而且本地與生產(chǎn)環(huán)境的代碼也正常,沒報(bào)錯(cuò)而已。

    這個(gè) eslint 的檢測(cè)目前還沒找到相關(guān)的配置可以把這些錯(cuò)誤去掉。

    9. 路由模式修改為 history

    因?yàn)槲恼略斍轫?yè)面有目錄,點(diǎn)擊目錄時(shí)定位定相應(yīng)的內(nèi)容,但是這個(gè)目錄定位內(nèi)容是根據(jù)錨點(diǎn)來(lái)做的,如果路由模式為 hash 模式的話,本來(lái)文章詳情頁(yè)面的路由就是 #articleDetail 了,再點(diǎn)擊目錄的話(比如 #title2 ),會(huì)在 #articleDetail 后面再加上 #title2,一刷新會(huì)找不到這個(gè)頁(yè)面的。

    10. Build Setup
     # clone
    git clone https://github.com/biaochenxuying/blog-vue-typescript.git
    # cd
    cd  blog-vue-typescript
    # install dependencies
    npm install
    # Compiles and hot-reloads for development
    npm run serve
    # Compiles and minifies for production
    npm run build
    ### Run your tests
    npm run test
    ### Lints and fixes files
    npm run lint
    ### Run your unit tests
    npm run test:unit

    Customize configuration

    See Configuration Reference.

    如果要看有后臺(tái)數(shù)據(jù)完整的效果,是要和后臺(tái)項(xiàng)目 blog-node 一起運(yùn)行才行的,不然接口請(qǐng)求會(huì)失敗。

    雖然引入了 mock 了,但是還沒有時(shí)間做模擬數(shù)據(jù),想看具體效果,請(qǐng)穩(wěn)步到我的網(wǎng)站上查看 https://biaochenxuying.cn

    11. 項(xiàng)目地址與系列相關(guān)文章

    基于 Vue + TypeScript + Element 的 blog-vue-typescript 前臺(tái)展示: https://github.com/biaochenxuying/blog-vue-typescript

    基于 react + node + express + ant + mongodb 的博客前臺(tái),這個(gè)是筆者之前做的,效果和這個(gè)類似,地址如下:
    blog-react 前臺(tái)展示: https://github.com/biaochenxuying/blog-react

    推薦閱讀 :

    本博客系統(tǒng)的系列文章:

    react + node + express + ant + mongodb 的簡(jiǎn)潔兼時(shí)尚的博客網(wǎng)站

    react + Ant Design + 支持 markdown 的 blog-react 項(xiàng)目文檔說(shuō)明

    基于 node + express + mongodb 的 blog-node 項(xiàng)目文檔說(shuō)明

    服務(wù)器小白的我,是如何將node+mongodb項(xiàng)目部署在服務(wù)器上并進(jìn)行性能優(yōu)化的

    github 授權(quán)登錄教程與如何設(shè)計(jì)第三方授權(quán)登錄的用戶表

    一次網(wǎng)站的性能優(yōu)化之路 -- 天下武功,唯快不破

    Vue + TypeScript + Element 搭建簡(jiǎn)潔時(shí)尚的博客網(wǎng)站及踩坑記

    12. 最后

    筆者也是初學(xué) TS ,如果文章有錯(cuò)的地方,請(qǐng)指出,感謝。

    一開始用 Vue + TS 來(lái)搭建時(shí),我也是挺抵觸的,因?yàn)椴攘撕枚嗫?,而且很多類型檢查方面也挺煩人。后面解決了,明白原理之后,是越用越爽,哈哈。

    權(quán)衡

    如何更好的利用 JS 的動(dòng)態(tài)性和 TS 的靜態(tài)特質(zhì),我們需要結(jié)合項(xiàng)目的實(shí)際情況來(lái)進(jìn)行綜合判斷。一些建議:

    如果是中小型項(xiàng)目,且生命周期不是很長(zhǎng),那就直接用 JS 吧,不要被 TS 束縛住了手腳。

    如果是大型應(yīng)用,且生命周期比較長(zhǎng),那建議試試 TS。

    如果是框架、庫(kù)之類的公共模塊,那更建議用 TS 了。

    至于到底用不用TS,還是要看實(shí)際項(xiàng)目規(guī)模、項(xiàng)目生命周期、團(tuán)隊(duì)規(guī)模、團(tuán)隊(duì)成員情況等實(shí)際情況綜合考慮。

    其實(shí)本項(xiàng)目也是小項(xiàng)目來(lái)的,其實(shí)并不太適合加入 TypeScript ,不過(guò)這個(gè)項(xiàng)目是個(gè)人的項(xiàng)目,是為了練手用的,所以就無(wú)傷大大雅。

    未來(lái),class-compoent 也將成為主流,現(xiàn)在寫 TypeScript 以后進(jìn)行 3.0 的遷移會(huì)更加方便。

    每天下班后,用幾個(gè)晚上的時(shí)間來(lái)寫這篇文章,碼字不易,如果您覺得這篇文章不錯(cuò)或者對(duì)你有所幫助,請(qǐng)給個(gè)贊或者星吧,你的點(diǎn)贊就是我繼續(xù)創(chuàng)作的最大動(dòng)力。

    參考文章:

    vue + typescript 項(xiàng)目起手式

    TypeScript + 大型項(xiàng)目實(shí)戰(zhàn)

    對(duì) 全棧修煉 有興趣的朋友可以掃下方二維碼關(guān)注我的公眾號(hào)

    我會(huì)不定期更新有價(jià)值的內(nèi)容,長(zhǎng)期運(yùn)營(yíng)。

    關(guān)注公眾號(hào)并回復(fù) 福利 可領(lǐng)取免費(fèi)學(xué)習(xí)資料,福利詳情請(qǐng)猛戳: Python、Java、Linux、Go、node、vue、react、javaScript

    文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

    轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/109257.html

    相關(guān)文章

    • 前端解決第三方圖片防盜鏈的辦法 - html referrer 訪問(wèn)圖片資源403問(wèn)題

      摘要:具體問(wèn)題,就是中通過(guò)標(biāo)簽引入一個(gè)第三方的圖片地址,報(bào)。解決方案如原網(wǎng)址顯示此圖片來(lái)自微信公眾平臺(tái),未經(jīng)允許不得應(yīng)用方法在標(biāo)簽里加這樣存在第三方網(wǎng)站上的圖片,在你的網(wǎng)站上就可以訪問(wèn)了。 showImg(https://segmentfault.com/img/bVbtK8u?w=436&h=284); 問(wèn)題 筆者網(wǎng)站的圖片都是上傳到第三方網(wǎng)站上的,比如 簡(jiǎn)書、掘金、七牛云上的,但是最近簡(jiǎn)...

      xuxueli 評(píng)論0 收藏0
    • vue-cli3.x 新特性及踩坑記

      摘要:前言都到了,所以是時(shí)候玩轉(zhuǎn)一下的新特性了。安裝的包名稱由改成了。方法一原因的配置改變了,導(dǎo)致正確的不能用。打開終端,切換到根路徑文件里面修改為方法二是默認(rèn)路徑修改了路徑會(huì)出現(xiàn)錯(cuò)誤。按上面的方法修改完,再全局卸載果然就成功了。 showImg(https://segmentfault.com/img/remote/1460000016423946); 前言 vue-cli 都到 3.0....

      xiaoqibTn 評(píng)論0 收藏0
    • 實(shí)現(xiàn)一個(gè)前端路由,如何實(shí)現(xiàn)瀏覽器的前進(jìn)與后退 ?

      摘要:執(zhí)行過(guò)程如下實(shí)現(xiàn)瀏覽器的前進(jìn)后退第二個(gè)方法就是用兩個(gè)棧實(shí)現(xiàn)瀏覽器的前進(jìn)后退功能。我們使用兩個(gè)棧,和,我們把首次瀏覽的頁(yè)面依次壓入棧,當(dāng)點(diǎn)擊后退按鈕時(shí),再依次從棧中出棧,并將出棧的數(shù)據(jù)依次放入棧。 showImg(https://segmentfault.com/img/bVbtK6U?w=1280&h=910); 如果要你實(shí)現(xiàn)一個(gè)前端路由,應(yīng)該如何實(shí)現(xiàn)瀏覽器的前進(jìn)與后退 ? 2. 問(wèn)題...

      劉東 評(píng)論0 收藏0
    • 關(guān)于Vue2一些值得推薦的文章 -- 五、六月份

      摘要:五六月份推薦集合查看最新的請(qǐng)點(diǎn)擊集前端最近很火的框架資源定時(shí)更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥雀呼晴,侵曉窺檐語(yǔ)。葉上初陽(yáng)乾宿雨,水面清圓,一一風(fēng)荷舉。家住吳門,久作長(zhǎng)安旅。五月漁郎相憶否。小楫輕舟,夢(mèng)入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請(qǐng)::點(diǎn)擊::集web前端最近很火的vue2框架資源;定時(shí)更新,歡迎 Star 一下。 蘇...

      sutaking 評(píng)論0 收藏0

    發(fā)表評(píng)論

    0條評(píng)論

    最新活動(dòng)
    閱讀需要支付1元查看
    <