🍛

解决开发时跨域问题

// vue.config.js
module.exports = {
  devServer: {
    proxy: {
      '/dev-api': {
        target: 'http://47.97.154.202:1091',
        changeOrigin: true,
        pathRewrite: { '^/dev-api': '' }
      }
    }
  }
}

静态文件转为 Base64 格式引入

下面是将字体文件,转为 base64 的配置
问题来自 element-ui IE11 部分版本图标不显示
但是转为 base64 就好了

// vue.config.js
module.exports = {
  chainWebpack: config => {
    const fontsRule = config.module.rule('fonts')
    fontsRule.uses.clear()
    fontsRule.test(/\.(woff|eot|ttf|otf)(\?.*)?$/i)
    fontsRule.use('file-loader')
      .loader('url-loader')
      .options({
        fallback: {
          loader: 'file-loader',
          options: {
            name: 'fonts/[name].[hash:8].[ext]'
          }
        }
      })
  }
}

Vue3 学习

setup 方法

触发时机位于 beforeCreated 和 created 之间
无法获取 this,也没有必要获取 this

Vue3 相比于 Vue2 新增了 setup 方法,作为初始化方法
将 data、computed、methods、watch、生命周期函数全都整合到这一个方法中定义
但这不是必须的,继续使用 Vue2 的定义方式也是可以的。

个人更喜欢 setup 的方式定义,代码都放在一起,关联性强,可以快速阅读书写,更方便一些 😛
import { ref, toRefs } from 'vue';

export default {
  props: {
    title: {
      type: String,
      default: '默认值'
    }
  },
  
  /**
   *  props 是 "响应式数据",不能使用 ES6 解构,它会消除 prop 的响应性。
   *  若非要解构,则需要使用 toRefs 方法将 props 转为普通对象(对象内部的值为 ref 对象)
   **/
  setup (props, { attrs, slots, emit }) {
    const { title } = toRefs(props)
    const num = ref(10)
    
    /**
     *  只有通过 return 暴露出去的属性和方法才可以在 template 中使用
     *  所以 Vue3 开发时感觉最常见的错误应该就是 "忘记写 return 了"
     *  不过应该可以通过统一定义到一个对象中,然后在 return 中已解构的方式,一劳永逸
     *  待测试开发友好度~~
     **/
    return { num }
  }
}

响应式数据定义

通过 ref 方法或 reactive 方法定义 “响应式数据”

import { ref, reactive } from 'vue';

export default {
  setup (props, { attrs, slots, emit }) {
    const num = ref(0)
    const state = reactive({ num: 0 })
    
    return { num, state }
  }
}
/**
 *  ref 方法用来定义 "简单" 的响应式数据,参数不限制类型
 *  ref 会将传入的数据,套一层对象,并将数据作为对象的 value 属性
 *  ref 方法会将内部的引用类型全部转为 "响应式数据"
 *  ref 内部其实也是用的 reactive
 *
 *  使用 attr.value 的方式获取值
 *  使用 attr.value = any 的方式改变值
 *
 *  shallowRef 与 ref 类似
 *  区别是不会将值内部的引用类型的值转为 "响应式数据"
 *    const data = ref({ obj: { count: 0 } })
 *    data.value.obj.count = 10
 *    // => data.value.obj 是响应式数据,obj 中的值改了,页面就会自动刷新
 *
 *    const data = shallowRef({ obj: { count: 0 } })
 *    data.value.obj.count = 10
 *    // => data.value.obj 不是响应式数据,obj 中的值虽然改了,但是页面不会自动刷新
 **/
const num = ref(0)
/**
 *  reactive 方法用来定义 "复杂" 的响应式数据
 *  参数必须是对象或者数组,通常为对象(对象中再定义数组)
 *  比较好的思想是 "抽象" 一个功能,将相关的数据都封装在一起。
 *  一旦定义之后,无法改变自身,可以改变内部的属性值。
 *
 *  reactive 不会像 ref 一样套一层 value
 *  因为 reactive 的参数只可传入引用类型
 *  直接使用 attr.key 的方式获取值
 *  使用 attr.key = any 的方式改变值
 *
 *  reactive 和 shallowReactive 的区别
 *  区别是不会将值内部的引用类型的值转为 "响应式数据"
 *    const data = reactive({ obj: { count: 0 } })
 *    data.value.obj.count = 10
 *    // => data.value.obj 是响应式数据,obj 中的值改了,页面就会自动刷新
 *
 *    const data = shallowReactive({ obj: { count: 0 } })
 *    data.value.obj.count = 10
 *    // => data.value.obj 不是响应式数据,obj 中的值虽然改了,但是页面不会自动刷新
 **/
const state = reactive({ num: 0 })
export default {
  data () {
    return {
      num: 0,
      state: { num: 0 }
    }
  }
}

计算属性

概念上没啥大变化。

import { computed } from 'vue';

export default {
  setup (props, { attrs, slots, emit }) {
    const name = ref('jason')
    const fullName = computed(() => {
      return `------ ${ name.value } ------`
    })
    
    // 可以将计算属性写在 ref 或者 reactive 定义的对象类型 "响应式数据" 中
    const state = reactive({ 
      fullName: computed(() => {
        return `====== ${ name.value } ======`
      })
    })
    
    console.log(name.value)
    // => jason
    console.log(fullName.value)
    // => ------ jason ------
    console.log(state.fullName)
    // => ====== jason ======
    
    return { name, fullName, state }
  }
}
/**
 *  computed 用来定义计算属性
 *  和 vue2 的没有任何的不同,都是基于 "响应式数据" 来实时计算结果。
 *  也可以传入一个对象,同时设置 get 和 set。
 *  注意的是 computed 将返回一个 ref 对象
 *  也可以将 computed 写在 ref 和 reactive 定义的中 "响应式数据" 对象中
 **/
export default {
  data () {
    return {
      name: 'jason'
    }
  },
  computed: {
    fullName () {
      return `------ ${ this.name } ------`
    }
  }
}

方法定义

没啥大变化

import { computed } from 'vue';

export default {
  setup (props, { attrs, slots, emit }) {
    const submit = () => {
      console.log('submit')
    }
    
    return { submit }
  }
}
/**
 *  methods 应该是最容易理解的了
 *  写在 setup 中,内部的话直接调用即可
 *  return 出去,就可以在 template 中调用了
 **/
export default {
  methods: {
    submit () {
      console.log('submit')
    }
  }
}

监听器

  1. 可以监听到数组直接赋值子项的情况了(vue2 中 this.arr[0] = 10 将不会触发 watch)
  2. 可以同时监听多个 “响应式数据” 了。
  3. 新增了 onInvalidate 方法,用来在函数即将重新执行时、侦听器被停止时清除异步代码带来的影响。
  4. 增加了 watchEffect 方法,不用指定需要监听的 “响应式数据”,通过定义的函数内部自动依赖监听项。
  5. watch 方法的第一个参数,需要理解后使用,否则总会出问题。
import { watch, watchEffect } from 'vue';

export default {
  setup (props, { attrs, slots, emit }) {
    const num = ref(0)
    const state = { num: 0 }
    const arr = ref([ 1, 2 ])
    
    // 若监听某 "响应式数据" 的替换型改变(ref.value = any)时,直接传入 ref 就行。
    watch(num, (newValue, oldValue, onInvalidate) => {})
    
    // 若 "响应式数据" 为对象,并且想监听对象内部值变化时
    // 需要传入一个函数,返回这个对象,并且配置 deep: true
    // 注意:此时 oldValue = newValue
    watch(() => state, (newValue, oldValue, onInvalidate) => {
      // ...
    }, { deep: true })
    
    // 若 "响应式数据" 为对象,并且想监听固定某内部值的变化时
    // 则需要传入一个函数,返回指定的字段
    // 注意:必须深拷贝对象,才能保证 oldValue 值正确
    watch(() => _.cloneDeep(state), (newValue, oldValue, onInvalidate) => {})
    
    // 若 "响应式数据" 为对象,并且想监听固定某内部值的变化时
    // 则需要传入一个函数,返回指定的字段
    watch(() => state.num, (newValue, oldValue, onInvalidate) => {})
  
    // 若想监听的值为数组,则需要传入一个函数,函数中返回数组的克隆副本
    // 若数组中含有引用类型,则还是需要深拷贝,否则 oldValue 值错误
    watch(() => [ ...arr ], (newValue, oldValue, onInvalidate) => {})
    watch(() => _.cloneDeep(arr), (newValue, oldValue, onInvalidate) => {})
    
    // 不需要指定监听的 "响应式数据",内部自动依赖
    // 值改变后,重新执行函数
    watchEffect((onInvalidate) => {})
  }
}
/**
 *  watch 方法用来监听 "响应式数据" 的变化
 *  当指定的 "响应式数据" 变化时,触发回调
 *
 *  需要传入两个必要参数和一个可选参数
 *    1. 指定需要监听的 "响应式数据",可以为数组,代表同时监听多个 "响应式数据"
 *    2. 回调函数,调用时将传入 newValue、oldValue、onInvalidate
 *    3. 配置项,{ deep: false, immediate: true }
 *
 *  复杂点在第一个参数上
 *    - 若监听某 "响应式数据" 的替换型改变(ref.value = any)时,直接传入 ref 就行。
 *    - 若 "响应式数据" 为对象,并且想监听对象内部值变化时,需要传入一个函数,返回这个对象,并且配置 deep: true
 *    - 若 "响应式数据" 为对象,并且想监听固定某内部值的变化时,则需要传入一个函数,返回指定的字段
 *    - 若想监听的值为数组,则需要传入一个函数,函数中返回数组的克隆副本
 *
 *  oldValue
 *    若监听值为对象或者数组中包含引用类型,想正确获取 oldValue 值
 *    需要在传入的函数中返回深拷贝的副本
 *    lodash _.cloneDeep 方法可以帮助做到
 *    若使用了深拷贝,则不需要 deep: true
 *
 *  onInvalidate
 *  函数执行时,将接收 onInvalidate 函数作为入参,
 *  需要自行实现
 *  在函数即将重新执行时、侦听器被停止时调用
 *  主要用来清除异步代码带来的 "副作用"
 **/
export default {
  data () {
    return {
      num: 0,
      arr: [ 1, 2, 3 ],
      state: { num: 0 }
    }
  },
  watch: {
    num (newValue, oldValue) {},
    state: {
      deep: true,
      handler (newValue, oldValue) {}
    },
    'state.num' (newValue, oldValue) {},
    
    // 无法监听 this.arr[0] = 10 的这种情况
    arr (newValue, oldValue) {},
  }
}

template ref

<template>
  <!--
    注意:
      - ref 不需要动态绑定的写法
      - 若元素/组件未渲染,btn = null
  -->
  <comp-button ref="btn"/>
</template>
export default {
  setup (props, { attrs, slots, emit }) {
    const btn = ref(null)
    
    return { btn }
  }
}

若为数组,动态循环渲染的情况暂时未找到方案…

<template>
  <comp-button ref="btn"/>
</template>
export default {
  mounted () {
    console.log(this.$refs.btn)
  }
}

emits

这是 Vue3 新增的 “声明属性”,
类似于 props, 在子组件中提前声明内部向父组件发出的事件列表,
并且还可以校验 emit 时参数是否正确,若不正确 Vue 将发出警告,
不一定非要定义,向 Vue2 一样,不定义,也可以 emit。

学习到了,Vue2 虽然没有这个特性,但是也可以仿照着定义在 emits 上,预览组件内部可以发出的事件。
export default {
  emits: {
    // emit('submit', { name: 'jason' })
    submit: function (payload) {
      return typeof payload.name === 'string'
    },
    
    // 当然可以不指定校验
    quit: null
  }
}

v-model

可以定义多个 v-model 了,弃用了 sync 修饰符语法糖

<template>
  <div>
    <!--
      在 comp-dialog 组件内部
      使用 emit('update:title', newValue) 的方式传递事件
      其中,默认的 v-model prop 名字为 value
    -->
    <comp-dialog
      v-model="show"
      v-model:title="text"
      v-model:content="content"/>
  </div>
</template>
export default {
  setup (props, { attrs, slots, emit }) {
    const show = ref(false)
    const text = ref('我是标题')
    const content = ref('我是内容')
    
    return { show, text, content }
  }
}

太简单了,就是 sync 修饰符换成了统一的 v-model 指令写法,
并且好像之前的 sync 串用不了向 trim 这种修饰符,而 v-model 可以,
算是增强功能了。

<template>
  <div>
    <!--
      在 comp-dialog 组件内部
      使用 this.$emit('update:title', newValue) 的方式传递事件
      其中,默认的 v-model prop 名字为 value
    -->
    <comp-dialog
      v-model="show"
      :title.sync="text"
      :content.sync="content"/>
  </div>
</template>
export default {
  data () {
    return {
      show: false,
      text: '我是标题',
      content: '我是内容'
    }
  }
}

v-slot

统一使用 v-slot 指令来定义插槽,统一了 slot 和 slot-scope
同时提供了 v-slot 的简写形式(使用了 # 符),就像 v-bind 和 v-on

<!-- comp-dialog -->
<template>
  <div>
    <slot name="header" text="header"/>
    <slot name="default" text="default"/>
    <slot name="footer" text="footer"/>
  </div>
</template>
<template>
  <!-- 简写 v-slot -->
  <comp-dialog>
    <template #header="{ text }"></template>
    <template #default="scope"></template>
    <template #footer="{ text }"></template>
  </comp-dialog>
  
  <!-- 正常写 v-slot -->
  <comp-dialog>
    <template v-slot:header="{ text }"></template>
    <template v-slot:default="scope"></template>
    <template v-slot:footer="{ text }"></template>
  </comp-dialog>
</template>  

v-slot 指令在 Vue 2.6 就已经有了
但是在 Vue 2.6 之后还可以继续使用 slot 和 slot-scope()
但是在 Vue3 中 slot 和 slot-scope 已经被移除了

<!-- comp-dialog -->
<template>
  <div>
    <slot name="header" text="header"/>
    <slot name="default" text="default"/>
    <slot name="footer" text="footer"/>
  </div>
</template>
<template>
  <comp-dialog>
    <template slot="header" slot-scope="{ name }"></template>
    <template slot="default" slot-scope="scope"></template>
    <template slot="footer" slot-scope="{ name }"></template>
  </comp-dialog>
</template>