Vue3 + TS + Egg重构自己的小项目[总结]

9,269 阅读3分钟

vue-next(3.0)版本开源已经很长时间了,虽然还没有发布正式版,但不影响我们在自己的项目中尝试使用。本文是对自己的「电影预告片」项目刚刚用vue3重写后的总结。

项目演示地址

项目介绍

我在掘金上发布的第一篇文章就是关于此项目的介绍,没想到已经过去俩年了,回头看看这俩年的成长...... 打死自己的心都有了!!!🙃

项目整体的视觉/交互效果没有做改变,如下:

1

前端项目

项目创建

  1. 使用 vue-cli 工具可以方便的创建一个vue项目,其中选择技术栈时,勾选上TypeScript就好。

  2. 项目初始化好,通过 vue add vue-next 安装官方插件,使其vue版本升级到vue3.0

  3. 修改 shims-vue.d.ts 文件,引入 .vue 组件,类型是 defineComponent函数返回的类型

    declare module "*.vue" {
       import { defineComponent } from "vue";
       const Component: ReturnType<typeof defineComponent>;
       export default Component;
    }
    

涉及到需要改变的点

  1. 移除 filters 过滤器特性,官方原因是说 filters 功能完全可以用函数、计算属性来完成,没必要增加学习成本,并且 | 与位运算符是冲突的,同时还增加了模板解析复杂度。具体可以看 rfc

  2. 修改了v-model 的API,同时移除了 model 选项。在之前我们开发组件实现双向绑定,是通过 props.value ,调用 this.$emit('input') 来实现双向绑定。vue3.0将使用 modelValue 代替 valueemit('update:modelValue') 来代替 input 事件。具体可以看 rfc

<!-- 使用组件 -->
<Comp v-mode="text" />
// modelValue 通过 attrs 获取,emit用于触发事件
export default defineComponent({
  setup(props, { attrs, emit }) {
    const onChange = (e) => {
      emit('update:modelValue', e.target.value)
    }
    return { onChange } 	
  }
})
  1. Transition 组件的 className 改变,之前过渡效果是写在 xxx-enterxxx-leave-to css类名中,现在 xxx-enter 改为 xxx-enter-from。具体可以看 rfc

  2. 插件install的参数改变。之前写一个插件,可以直接使用install方法的入参 Vue ,将方法或属性挂载到 Vue.prototype 上。 现在install方法的入参是app。

// 之前插件挂载属性的方式
export default {
  install: (Vue, option) => {
    Vue.prototype.$axios = xxx;
  }
}
// vue3.0的方式
export default {
  install: (app, option) => {
    app.config.globalProperties.$axios = xxxx
  }
}
// 另一种方式 也可以使用 provide 、inject , 在composition API中使用
export const symbolKey = Symbol('_axios_');
// setup 函数里使用composition API
export function useAxios() {
  return inject(symbolKey)
}
// 执行插件insall方法注入
export default {
  install: (app, option) => {
    app.provide(symbolKey, xxx)
  }
}

其实vue3.0版本改动很多,但因为项目不是很复杂,所以涉及的不是很全面,大家可以去阅读rfcs查看改变的一些讨论。

Composition API的应用

该项目所有组件都使用了 Composition API 开发,基于 Composition API 我们可以更好的封装公用逻辑。

请求函数的封装

在此之前,项目的异步请求的loading要额外定义和维护,有了 Composition API 我们可以封装一个类似于React swr取数库的hook函数。

// 类似于这样
export function useRequest(url, params, config) {
  // 统一维护的变量,最后return出去
  const state = reactive({
    loading: false,
    error: false,
    data: config.initialData
  })
  
  const fetchFunc = () => {
    state.loading = true;
  	// 做请求的公用逻辑
    axios().then(response => {
       const result = response.data;
       state.data = result.data;
       state.loading = false;
    }).catch(err => {
       state.error = true;
    })
  }
  
  onMounted(() => {
    if (config.immediate) {
      fetchFunc();
    }
  });
  
  // toRefs 可以将state,拆解成多个ref,这样调用者就可以使用解构来拿到变量
  return { ...toRefs(state), fetch: fetchFunc };
}

事件的封装

比如 touch 事件,我们不光要绑定事件和解绑时间,还需要在不同回调中共用一个副作用变量,我们就可以把它拆成 composable函数

// 类似于这样
export function useTouch(domRef, ref) {
  // 是否touch进行中标志位
  let initiated = false;
  // 监听domRef改变
  watch(domRef, (el, prev, onCleanup) => {
    const touchStart = (e: TouchEvent) => {
      e.preventDefault();
      initiated = true;
      callbacks.touchStart(e);
    };
   	const touchMove = (e: TouchEvent) => {
      e.preventDefault();
      if (!initiated) return;
      callbacks.touchMove(e);
    };
    const touchEnd = (e: TouchEvent) => {
      initiated = false;
      callbacks.touchEnd(e);
    };
    
    el.addEventListener("touchstart", touchStart);
    el.addEventListener("touchmove", touchMove);
    el.addEventListener("touchend", touchEnd);
	// 取消绑定
    onCleanup(() => {
      el.removeEventListener("touchstart", touchStart);
      el.removeEventListener("touchmove", touchMove);
      el.removeEventListener("touchend", touchEnd);
    });
  })
}

优化

增加了travis实现了提交自动构建发布,通过sshpass实现将静态资源发送到远程服务器。

language: node_js
node_js:
  - 12
branchs:
  - master
addons:
  apt:
    packages:
    - sshpass
install:
  "npm install"
script:
  - "npm run build"
after_success:
  - ./script/deploy.sh

其中 serverPassserverIP 都是在travis上配置的环境变量。

#!/usr/bin/env sh

# 确保脚本抛出遇到的错误
set -e

# 打包静态资源
npm run build

# 将dist文件发送到远程
sshpass -p ${serverPass} scp -o stricthostkeychecking=no -r dist/ root@${serverIP}:/home/web/movie-trailer

后端项目

后端从使用 koa 框架升级成 Egg 框架,使用 Koa 开发应用时,需要开发者下载或开发各种中间件来实现功能的增强,而 Egg 选择了 Koa 作为其基础框架,帮助开发者对其进行了一些增强。

Egg 除了提供了 ControllerService 层做业务处理之外,还提供了 extend 用于扩展自身的功能(多种扩展点)。比如我们可以扩展 Context,在其上下文上挂载返回结果的通用函数。

// app/extends/context.js
module.exports = {
  sendSuccess: function(result) {
    this.body = {
      code: 200,
      errMsg: '',
      data: result
    }
  },
  sendError: function(errorMsg) {
    this.body = {
      code: 300,
      errMsg: errorMsg,
    }
  }
};
// app/controller/keyword.js
class KeyWordController extends Controller {
  async index() {
    const { ctx, service } = this;

    const keywordList = await service.keyword.index();
	// 返回200成功
    ctx.sendSuccess(keywordList)
  }
}

在此之前,我是通过Python进行爬取豆瓣电影,然后通过crontab手动写入脚本, Egg 框架方便了开发者设置定时任务,使用了 Egg , 我们只需要在 app/schedule 目录下创建文件导出基于Subscription的类。

class CrawlingDouban extends Subscription {
  // 静态方法,定义执行时机
  static get schedule() {
    return {
      cron: '0 8 * * *', // 每天8点
      type: 'worker',
    };
  }
  async subscribe() {
  	// 爬取逻辑
  }
}

总结

重构过程中,还是会遇到一些问题,大部分是文档没读导致的,所以文档过一遍还是很重要的。希望此文章对大家有所帮助,感觉项目还算可以的,不要吝啬start哦!。

项目地址

参考