Skip to content

👆 vue3新特性对普通开发者、二次开发者(跨段)、源码维护者带来的好处

js
// 在vue实例上挂载全局组件
Vue.component('el-counter', {
  data(){
    return {count: 1}
  },
  template: '<button @click="count++">Clicked {{ count }} times.</button>'
})

// 引入vue-router
let VueRouter = require('vue-router')
// 在vue实例上通过use全局挂载router插件
Vue.use(VueRouter)

但是由于全局的 Vue 只有一个,所以当我们在一个页面的多个应用中独立使用 Vue 就会非常困难。 (基本不会有。。。

js
Vue.component('el-counter',...)

new Vue({el:'#app1'})
new Vue({el:'#app2'})

为了解决这个问题,Vue 3 引入一个新的 API ,createApp,来解决这个问题,也就是新增了 App 的概念。全局的组件、插件都独立地注册在这个 App 内部,很好的解决了上面提到的两个实例容易造成混淆的问题。

js
const { createApp } = Vue
const app = createApp({})
app.component(...)
app.use(...)
app.mount('#app1')

const app2 = createApp({})
app2.mount('#app2')

createApp 还移除了很多我们常见的写法,比如在 createApp 中,就不再支持 filter、$on、$off、$set、$delete 等 API slotslot-scope 两者实现了合并,而 directive 注册指令的 API 等也有变化

@vue/compat 还可以很好地帮助你学习版本之间的差异 “阿里妈妈”出品的 gogocode,通过编译原理,由vue2代码转AST,替换AST成vue3,再生成vue3代码

vite官方收集的一些和vite相关的项目,包括插件,模板,都分类整理好的,地址如下 https://github.com/vitejs/awesome-vite

  1. vuex 的数据本地持久化插件
  2. 接口数据的mock, json-server
  3. 埋点的sdk
  4. @vueuse 库,封装常用的hooks

集成 EditorConfig 配置,集成 husky 和 lint-staged,代码提交时候检查 ESLint 规则,通过再提交

Composition API 和 <script setup> 而让你感到好奇的 <script setup> 本质上是以一种更精简的方式来书写 Composition API

对于 ref 返回的响应式数据,我们需要修改 .value 才能生效,而在 <script setup> 标签内定义的变量和函数,都可以在模板中直接使用

直接 import TodoList.vue 组件,然后 <script setup> 会自动把组件注册到当前组件

没有setup的compositiob写法

js
import { ref } from "vue";
export default {
  setup() {
    let count = ref(1)
    function add() {
      count.value++
    }
    return {
      count,
      add
    }
  }
}

setup语法

js
import { ref, computed } from "vue";

let count = ref(1)
function add(){
    count.value++
}

let { title, todos, addTodo, clear, active, all, allDone } = useTodos();

vue style标签使用js中的变量

html
<style scoped>
h1 { 
  color:v-bind(color);
}
</style>

Composition API 和 <script setup> 虽然能提高开发效率,但是带来的一些新的语法 比如 ref 返回的数据就需要修改 value 属性 响应式和生命周期也需要 import 后才能使用等等

对于value,官方是这样解释的: 将值封装在一个对象中,看似没有必要 但为了保持 JavaScript 中不同数据类型的行为统一,这是必须的 这是因为在 JavaScript 中,Number 或 String 等基本类型是通过值而非引用传递的 在任何值周围都有一个封装对象,这样我们就可以在整个应用中安全地传递它,而不必担心在某个地方失去它的响应性

关于导入问题,antfu大神有一个插件unplugin-auto-import,可以自动注入依赖项,不用import

https://github.com/antfu/unplugin-auto-import

我们可以在 loading 状态下,去修改浏览器的小图标 favicon

js
import {ref,watch} from 'vue'
export default function useFavicon( newIcon ) {
  const favicon = ref(newIcon)

  const updateIcon = (icon) => {
    document.head
      .querySelectorAll(`link[rel*="icon"]`)
      .forEach(el => el.href = `${icon}`)
  }
  const reset = ()=>favicon.value = '/favicon.ico'

  watch( favicon,
    (i) => {
      updateIcon(i)
    }
  )
  return {favicon,reset}
}

React 主要构建 App 还是用 Class Component,当然也有 functional component 这俩区别就在于 class 有状态,functional 比较“纯粹”没有状态

现在 React 都用 “functional” 组件,但是有“状态”的,状态哪里来的 “Hook” 过来的,钩过来的。 组件“不负责”维护状态,useXXX 去管理了。

5星评星组件 从中间往两边切割

vue
<template>
    <div>
        {{rate}}
    </div>
</template>

<script setup>
import { defineProps,computed } from 'vue';
let props = defineProps({ // 定义组件接受参数,使用方式是props.xxx,而不是this.xxx
    value: Number
})
let rate = computed(()=>"★★★★★☆☆☆☆☆".slice(5 - props.value, 10 - props.value))
</script>
vue
<template>
<div :style="fontstyle">
    <div class='rate' @mouseout="mouseOut">
      <span @mouseover="mouseOver(num)"  v-for='num in 5' :key="num">☆</span>
      <span class='hollow' :style="fontwidth">
        <span @mouseover="mouseOver(num)" v-for='num in 5' :key="num">★</span>
      </span>
    </div> 
</div>
</template>
<script setup>
// ...其他代码
// 评分宽度
let width = ref(props.value)
function mouseOver(i){
    width.value = i 
}
function mouseOut(){
    width.value = props.value
}
const fontwidth = computed(()=>`width:${width.value}em;`)

</script>

<style scoped>
.rate{
  position:relative;
  display: inline-block;
}
.rate > span.hollow {
  position:absolute;
  display: inline-block;
  top:0;
  left:0;
  width:0;
  overflow:hidden;
}
</style>

手写在vue3中的响应式store

window._store 并不是响应式的,如果在 Vue 项目中直接使用,那么就无法自动更新页面 所以我们需要用 ref 和 reactive 去把数据包裹成响应式数据 并且提供统一的操作方法,这其实就是数据管理框架 Vuex 的雏形了

js

import { inject, reactive } from 'vue'
const STORE_KEY = '__store__'
function useStore() {
  return inject(STORE_KEY)
}
function createStore(options) {
  return new Store(options)
}
class Store {
  constructor(options) {
    this.$options = options
    this._state = reactive({
      data: options.state()
    })
    this._mutations = options.mutations
  }
  get state() {
    return this._state.data
  }
  commit = (type, payload) => {
    const entry = this._mutations[type]
    entry && entry(this.state, payload)
  }
  install(app) {
    app.provide(STORE_KEY, this)
  }
}
export { createStore, useStore }

手写在vue3中的vue-router

myRouter.js

js
import {ref,inject} from 'vue'
const ROUTER_KEY = '__router__'

function createRouter(options){
    return new Router(options)
}

function useRouter(){
    return inject(ROUTER_KEY)
}
function createWebHashHistory(){
    function bindEvents(fn){
        window.addEventListener('hashchange',fn)
    }
    return {
        bindEvents,
        url:window.location.hash.slice(1) || '/'
    }
}
class Router{
    constructor(options){
        this.history = options.history
        this.routes = options.routes
        this.current = ref(this.history.url)

        this.history.bindEvents(()=>{
            this.current.value = window.location.hash.slice(1)
        })
    }
    install(app){
        app.provide(ROUTER_KEY,this)
    }
}

export {createRouter,createWebHashHistory,useRouter}

main.js

js
import {
    createRouter,
    createWebHashHistory,
} from './grouter/index'
const router = createRouter({
  history: createWebHashHistory(),
  routes // 页面组件SFC组件在对象数据里
})

手写router-view组件

current 返回当前的路由地址,并且使用 ref 包裹成响应式的数据。router-view 组件的功能,就是 current 发生变化的时候,去匹配 current 地址对应的组件,然后动态渲染到 router-view 就可以了

vue
<template>
    <component :is="comp"></component>
</template>
<script setup>

import {computed } from 'vue'
import { useRouter } from '../myRouter/index'

let router = useRouter()

const comp = computed(()=>{
    const route = router.routes.find(
        (route) => route.path === router.current.value
    )
    return route?route.component : null
})
</script>

手写router-link组件

把 a 标签的 href 属性前面加了个一个 #, 就实现了 hash 的修改

vue
<template>
    <a :href="'#'+props.to">
        <slot />
    </a>
</template>

<script setup>
import {defineProps} from 'vue'
let props = defineProps({
    to:{type:String,required:true}
})

</script>

全局挂载组件router-view router-link

js
const app = createApp()
app.component("router-link",RouterLink)
app.component("router-view",RouterView)

因为这个两个全局组件只有挂载router插件才需要 因此可以放在router插件的install里

js


import {ref,inject} from 'vue'
import RouterLink from './RouterLink.vue'
import RouterView from './RouterView.vue'
class Router{
    ....
    install(app){
        app.provide(ROUTER_KEY,this)
        app.component("router-link",RouterLink)
        app.component("router-view",RouterView)
    }
}

vue3 ts限制

ts
import { computed, reactive, ref } from '@vue/runtime-core';
interface typeObj {
  name:string,
  price:number
}

const msg = ref('') //  根据输入参数推导字符串类型
const msg1 = ref<string>('') //  可以通过范型显示约束

const obj = reactive({})
const course = reactive<typeObj>({name: '玩转Vue3全家桶', price: 129})

const msg2 = computed(() => '') // 默认参数推导
const course2 = computed<typeObj>(() => {
  return {name: '玩转Vue3全家桶', price: 129}
})

👆 ref reactive computed 都具有类型隐式推导,根据传入和return 自动推导 并且都可以通过泛型(ts用于类型推导的变量)覆盖隐式推导结果,即显式约束

开发过程使用到的响应式数据,对象类型数据最好都做好泛型指定(基础类型靠隐式推导即可

ts
import {ref, Ref} from 'vue'

interface Todo{
  title: string,
  done: boolean
}

// 定义响应式对象数据
const item = reactive<Todo>({title: '玩转Vue3全家桶', done: false})

// 定义响应式数组数据
let todos:Ref<Todo[]> = ref([{title:'学习Vue',done:false}])

// 定义响应式

组件开发

组件除了要导出组件自身还要导出类型 在vue的时候是直接引用.vue文件,而.vue文件的js部分就是 export default

在vue3中如果不使用setup script 而是使用 setup function 的话,还是要 export default defineComponent

ts
// 导出组件
export default defineComponent({
  name: 'MyComponent',
  props: {
    list: {
      type: Array as PropType<MyComponentProps[]>,
      required: true
    }
  }
})

// 导出类型定义(数组的对象属性类型
export interface MyComponentProps {
  id: string,
  title: string
}

使用方

ts
// 引入组件及类型
import MyComponent {MyComponentProps} from './MyComponent.vue'

const list: MyComponentProps[] = [
  {id:'', title:''}
]