TypeScript__实战二

113 阅读2分钟

一、项目需要使用typeScript,如何把js切到ts

1. 引入和使用

webpack配置

创建新项目的命令,旧版本vue init,新版本vue create ${myProject} ${template}
具体webpack配置,看前面知识更详细,下面只是简单列举的三个注意点:
    a. entry - 入口
    b. extentions 加上ts文件area - 用于处理尝试的数据尾缀列表
    c. loaders - ts-loader,增加对于ts的处理  => 工程化

TS配置

// tsconfig.json文件
{
  "include": [
    "src/*",
    "src/**/*"
  ],
  "exclude": [
    "node_modules"
  ],
  "compilerOptions": {
    "strictPropertyInitialization": false,
    // types option has been previously configured
    "types": [
      // add node as an option
      "node"
    ],
    // typeRoots option has been previously configured
    "typeRoots": [
      // add path to @types
      "node_modules/@types"
    ],
    "strict": true,
    // 支持jsx语法,tsx
    "jsx": "preserve",
    "jsxFactory": "h",
    // 允许没有默认export的模块中,默认import
    "allowSyntheticDefaultImports": true,
    // 启用装饰器
    "experimentalDecorators": true,
    "strictFunctionTypes": false,
    "allowJs": true,
    "module": "esnext",
    "target": "es5",
    "moduleResolution": "node",
    "noImplicitAny": true,
    "lib": [
      "dom",
      "es5",
      "es6",
      "es7",
      "es2015.promise"
    ],
    "sourceMap": true,
    "pretty": true
  }
}

2. vue / vuex + typescript 铺开的梳理

<template>
<div>
  <vueComponent />
</div>
</template>
<script lang="ts">
//在vue中,与js版本不同,要写成ts需要转换的几点,
//以下1,2,3主要围绕挂载声明和类型声明方面:
// 1. 定义组件的方式上: 形式上 - extends
// const Component = {
  // TS无法断定内部为vue组件,需要额外做声明处理 - Vue.prototype.xxxx
// }

// 声明当前组件模块 Vue.component or Vue.extend
import Vue from 'vue'
const Component = Vue.extend({
  // 类型推断
})

// 2. 全面拥抱面向对象 - 官方vue-class-component把模块组件当成一个类
import Component from 'vue-class-component'

// @Component 本质 —— 类装饰器 => 利用装饰器,统一描述vue模板等概念
// @Componen 类装饰器使你的类成为一个Vue组件,用ts把你的类再封装一层
@Component({
  template: '<vueComponent />'
})
export default class myComponent extends Vue {
  message: string = 'Hello'
  onClick(): void {
    console.log(this.message);
  }
}

//3. 声明 - 利用ts的额外补充模块declare => 实现独立模块的声明,使之可以被独立使用
//   一个细节,不需要引入的使用它,因为这是一个相当于全局声明的东西
declare module '*.vue' {
  import Vue from 'vue'
  export default Vue
}
// 补充模块 - 通常使用.d.ts来做声明描述
declare module '/typings/vuePlugin.d.ts' {
  interface Vue  {
    myProps: string
  }
}
// 实例中使用
let vm = new Vue()
console.log(vm.myProps)

除了上面的声明vue模块方面,还有下面的业务模块数据方面:


// 4. 对于props里的属性 - vue提供propType原地声明props里的复合变量
import { propType } from 'vue'

interface customPayload {
  str: string,
  number: number,
  name: string
}

const Component = Vue.extend({
  props: {
    name: String,  // 字符串类型
    success: { type: String }, // 普通对象类型
    payload: {
      type: Object as propType<customPayload>
    },
    callback: {
      type: Function as propType<() => void>
    }
  }
})

// 5. 对于computed和method,computed 以及 method中包含
//     this且有return的方法需要声明返回类型,
//     说白了,就是在computed和method方法里,有this的地方就要指定类型
computed: {
  getMsg(): string {
    return this.click() + "!"
  }
}

methods: {
  click(): string {
    return this.message + 'zhaowa'
  }
}

// 6. 针对vuex的接入ts的情况 - 声明使用
// vuex.d.ts 声明模块 - ComponentCustomProperties
import { ComponentCustomProperties } from 'vue'

declare module '@vue/runtime-core' {
  interface State {
    count: number
  }

  interface ComponentCustomProperties {
    $store: Store<State>
  }
}

// 7. api形式编码实现 - 官方推荐
// store.ts
import { InjectionKey } from 'vue'
import {
  createStore,
  Store
} from 'vuex'

export interface State {
  count: number
}

export const key: InjectionKey<Store<State>> = Symbol()

export const store = createStore<State>({
  state: {
    count: 0
  }
})
// ##################################
// main.ts
import { createApp } from 'vue'
import { store, key } from './store'

const app = createApp({
  // 传入参数
})

// 利用了provider & inject
app.use(store, key) // => 传入injection Key => vue高级使用里会提到vue.use

app.mount('#app')

// ####################################
// 消费方
import { useStore } from 'vuex'
import { key } from './store'

export default {
  const store = useStore(key)

  store.state.count
}
// 标准接口形式的功能引入,核心利用了vue的provide&inject

// 8. vuex面向对象 - 使用vuex-class
import { State, Action, Getter } from "vuex-class"

export default class App extends Vue {
  // 属性装饰器,整合了store状态
  @State login: boolean;

  // 事件装饰器,整合了store方法
  @Action setInit: () => void;

  get isLogin: boolean;

  mounted() {
    this.setInit();
  }
}
</script>

3. 正式代码

以下列出主要的文件,不包括配置文件,此例子功能不重要,重要的是为了构建整体脉络,让自己体会引入ts之后,各个文件有哪些改变,以及这些改变如何彼此打配合,方便以后真实开发的流畅性。

    // typings/vue-shims.d.ts
    declare module '*.vue' {
      import Vue from 'vue'
      export default Vue
    }

    // typings/store.d.ts
    declare namespace StoreState {
        export interface plate {
            // 板块
            url: string,
            imgUrl: string,
            name: string
        }

        export interface prod {
            // 商品
            url: string,
            imgUrl: string,
            prodName: string,
            desc: string,
            price: string
        }
    }
    // typings/ajax.d.ts
    declare namespace Ajax {
        // 返回
        export interface AjaxResponse {
            data: AjaxResponse;
        }

        // 返回接口数据
        export interface AjaxResponse {
            // 状态码
            code: number;
            result: any;
            message?: string;
        }
    }

    // main.ts
    import Vue from 'vue'
    import App from './App.vue'
    import router from './router'
    import store from './store'
    import './assets/mock'

    Vue.config.productionTip = false

    const app: Vue = new Vue({
      el: '#app',
      router,
      store,
      render: h => h(App)
    })

    export default app

    // App.vue
    <template>
    <div id="app">
        <router-view></router-view>
    </div>
    </template>

    <script lang="ts">
    import Vue from "vue";
    import Component from "vue-class-component";

    @Component
    export default class App extends Vue {}
    </script>

    <style lang="stylus">
    body {
      font-size: 14px;
      margin: 0;
    }

    .divider {
      height: 10px;
      background-color: #f0f0f0;
    }
    </style>

    // router/index.ts
    import Vue, { AsyncComponent } from 'vue'
    import Router, { RouteConfig } from 'vue-router'

    const main: AsyncComponent = (): any => import('@/pages/main.vue')

    Vue.use(Router)

    const routes: RouteConfig[] = [
      {
        path: '/',
        redirect: '/index'
      },
      {
        path: '/index',
        name: 'main',
        component: main
      }
    ]

    const router: Router = new Router({
      mode: 'history',
      base: '/',
      routes
    })

    export default router

    //pages/main.vue
    <template>
      <div class="main">
        <div class="header">上海</div>
        <div class="main-content">
          <plates></plates>
          <prodList></prodList>
        </div>
      </div>
    </template>

    <script lang="ts">
    import Vue from "vue";
    import Component from "vue-class-component";
    import { State, Action, Getter } from "vuex-class";

    import prodList from '../components/prodList.vue';
    import plates from '../components/plates.vue';

    @Component({
      components: {
        prodList,
        plates
      }
    })
    export default class App extends Vue {
      @Action initAjax: () => void;

      mounted() {
        this.initAjax();
      }
    }
    </script>

    <style lang="stylus">
    .main {
      .header {
        background-color: #ff1395;
        padding: 20px;
        width: 100%;
      }
      .main-content {
        padding: 10px;
      }
    }
    </style>

    // components/plates.vue
    <template>
        <div class="plates">
            <div class="plate-flex" v-for="(page, index) in plates" :key="index">
                <div class="plate" v-for="(module, index) in page" :key="index">
                    <img :src="module.imgUrl" />
                    <div class="name"> {{ module.name }} </div>
                </div>
            </div>
        </div>
    </template>

    <script lang="ts">
    import Vue from "vue";
    import Component from "vue-class-component";
    import { State, Action } from "vuex-class";

    @Component({
        name: 'plates'
    })
    export default class plates extends Vue {
        @State plates: StoreState.plate[]
    }
    </script>

    <style lang="stylus">
    .plates {

        .plate-flex {
            display: flex;

            .plate {
                flex: 1;
                text-align: center;

                img {
                    height: 30px;
                    width: 30px;
                }
                .name {
                    // font-size: 
                }
            }
        }
    }
    </style>
    // components/prodList.vue
    <template>
        <div class="prod-list">
            <div class="prod" v-for="(prod, index) in products" :key="index">
                <div class="prod-logo">
                    <img :src="prod.imgUrl" alt="img">
                </div>
                <div class="content">
                    {{prod.prodName}} - {{prod.desc}} - {{prod.price}}
                </div>
            </div>
        </div>
    </template>

    <script lang="ts">
    import Vue from "vue";
    import Component from "vue-class-component";
    import { State } from "vuex-class";

    @Component
    export default class prodList extends Vue {
        @State prodList: StoreState.prod[]

        get products(): StoreState.prod[] {
            const prodList = this.prodList;
            return prodList;
        }
    }
    </script>

    <style lang="stylus">
    .prod-list {
        padding-top: 20px;
        .prod {
            padding: 0 10px 10px 10px;
            display: flex;
        }
        .prod-logo {
            position: relative;

            img {
                width: 100px;
                height: 120px;
            }

            .content {

            }
        }
    }
    </style>
    
    // store/actions.ts
    import { ActionTree } from 'vuex'
    import axios from 'axios'
    import TYPES from './types'

    const actions: ActionTree<any, any> = {
        // 全局初始化
        async initAjax({ dispatch }) {
            // 获取板块
            dispatch('getPlates')
            // 获取列表
            dispatch('getProdList')
        },

        // 获取板块
        async getPlates({ state, commit }) {
            const res: Ajax.AjaxResponse = await axios.get('/plates').then(
                (res) => res.data
            ).catch(
                (e: string) => console.log(e)
            )

            if (res && res.code == 200) {
                commit(TYPES.SET_PLATES, res.result.list)
            }
        },

        // 获取板块
        async getProdList({ state, commit }) {
            const res: Ajax.AjaxResponse = await axios.get('/prodList').then(
                (res) => res.data
            ).catch(
                (e: string) => console.log(e)
            )

            if (res && res.code == 200) {
                commit(TYPES.SET_PROD_LIST, res.result.list)
            }
        }
    }

    // declare namespace Ajax {
    //     // 返回
    //     export interface AjaxResponse {
    //         data: AjaxResponse;
    //     }

    //     // 返回接口数据
    //     export interface AjaxResponse {
    //         // 状态码
    //         code: number;
    //         result: any;
    //         message?: string;
    //     }
    // }

    export default actions
    // store/index.ts
    import Vue from 'vue'
    import Vuex, { ActionTree, MutationTree } from 'vuex'
    import actions from './actions'
    import mutations from './mutations'

    Vue.use(Vuex)

    interface State {
      plates: StoreState.plate[],
      prodList: StoreState.prod[]
    }

    let state: State = {
      plates: [],
      prodList: []
    }

    export default new Vuex.Store({
      state,
      actions,
      mutations
    })

    // store/mutations.ts
    import TYPES from './types'
    import { MutationTree } from 'vuex'

    const mutations: MutationTree<any> = {
        [TYPES.SET_PLATES](state, plates): void {
            state.plates = plates
        },

        [TYPES.SET_PROD_LIST](state, prodList): void {
            state.prodList = prodList
        }
    }
    export default mutations
    // store/type.ts
    export default {
        SET_PLATES: 'SET_PLATES',
        SET_PROD_LIST: 'SET_PROD_LIST'
    }
    // store/getter.ts
    // 暂无内容