vue3

吴龙淼 写于 2021-12-10

Composition API

Vue2 模板随着组件功能越来越多,代码量越来越大,每个功能模块的代码会散落分布在各个位置,让整个项目的内容难以阅读和维护

Vue3 根据逻辑功能来进行组织,提高可维护性,可读性,重用代码,vue2 用 mixins 重用逻辑代码,容易产生冲突,所以 Composition API 又被称为基于函数组合的 API

全局 API

2.x 全局 API(Vue) 3.x 实例 API (app)
Vue.config.xxxx app.config.xxxx
Vue.config.productionTip 移除
Vue.component app.component
Vue.directive app.directive
Vue.mixin app.mixin
Vue.use app.use
Vue.prototype app.config.globalProperties

hooks 函数

Vue3 的 hook 函数 相当于 vue2 的 mixin, 不同在与 hooks 是函数

Vue3 的 hook 函数 可以帮助我们提高代码的复用性, 让我们能在不同的组件中都利用 hooks 函数

//一般都是建一个hooks文件夹,都写在里面
import {reactive,onMounted,onBeforeUnmount} from 'vue'
export default function (){
   //鼠标点击坐标
   let point = reactive({
      x:0,
      y:0
   })

   //实现鼠标点击获取坐标的方法
   function savePoint(event){
      point.x = event.pageX
      point.y = event.pageY
      console.log(event.pageX,event.pageY)
   }

   //实现鼠标点击获取坐标的方法的生命周期钩子
   onMounted(()=>{
      window.addEventListener('click',savePoint)
   })

   onBeforeUnmount(()=>{
      window.removeEventListener('click',savePoint)
   })

   return point
}
//在其他地方调用
import useMousePosition from './hooks/useMousePosition'
let point = useMousePosition()

setup

组合式 api 的入口,在 beforeCreate 前调用,setup 不能使用 this,

setup(props,context){}

props 父组件传递给子组件的所有数据,不能使用解构

context:(attrs,slots,emit)

props: ["parent_data"],
emits:['subClick'],
setup (props, context) {
    let { attrs, slots, emit } = context;

    // vue3.x 获取组件上的属性
    console.log(attrs.subData);  // 'some other data'

    // vue3.x 获取slot插槽的节点
    console.log(slots.default());

    // vue3.x emit方法(子组件向父组件传递数据)
    function handleClick () {
        emit('subClick', '发送给父组件的值');
    }

    return { handleClick }
}

setup语法糖

//Father.vue
<template>
  <div >
    <h2 >我是父组件!</h2>
    <Child :msg="hello" @child-click="childCtx" />
  </div>
</template>

<script setup>
//无需注册components
import Child from './Child.vue';

const childCtx = (ctx) => {
  console.log(ctx);
}
</script>


//Child.vue
<template>
  <span @click="handleClick">我是子组件! -- msg: props.msg </span>
</template>



<script setup>
// 模板中可以通过 $slots 和 $attrs
import { useSlots,useAttrs } from 'vue'

const emit = defineEmit(['child-click', 'update:changeProp'])
const props = defineProps({
  msg: String
})

const handleClick = () => emit('child-click', 'x')

// 自定义指令
const vMyDirective = {beforeMount,mounted}

<!-- vue3.3语法糖 -->
根据接受 defineModel 返回值的变量名,这里是 newName,会自动定义 props 名为 newName,emit 事件为 update:newName
const newName = defineModel()

defineOptions()
defineExpose({a,b})
defineSlots()
</script>

TSX&h

import { h } from 'vue'
// 除了类型必填以外,其他的参数都是可选的
h('div')
h('div', { id: 'foo' })

// attribute 和 property 都能在 prop 中书写
// Vue 会自动将它们分配到正确的位置
h('div', { class: 'bar', innerHTML: 'hello' })

// 像 `.prop` 和 `.attr` 这样的的属性修饰符
// 可以分别通过 `.` 和 `^` 前缀来添加
h('div', { '.name': 'some-name', '^width': '100' })

// 类与样式可以像在模板中一样
// 用数组或对象的形式书写
h('div', { class: [foo, { bar }], style: { color: 'red' } })

// 事件监听器应以 onXxx 的形式书写
h('div', { onClick: () => {} })

// children 可以是一个字符串
h('div', { id: 'foo' }, 'hello')

// 没有 props 时可以省略不写
h('div', 'hello')
h('div', [h('span', 'hello')])

// children 数组可以同时包含 vnodes 与字符串
h('div', ['hello', h('span', 'hello')])

// 重复的 vnodes 是无效的
const p = h('p', 'hi')
return h('div', [
  p,
  p])
{
  "compilerOptions": {
    "jsx": "preserve",
    "jsxImportSource": "vue"
    // ...
  }
}

// 事件修饰符
<input
  onClickCapture=
  onKeyupOnce=
  onMouseoverOnceCapture=
/>


// 渲染插槽(子组件)
export default {
  props: ['message'],
  setup(props, { slots }) {
    return () => [
      // 默认插槽:
      // <div><slot /></div>
      h('div', slots.default()),

      // 具名插槽:
      // <div><slot name="footer" :text="message" /></div>
      h(
        'div',
        slots.footer({
          text: props.message
        })
      ),
    ]
  }
}
// 作用域插槽
export default {
  setup(props, { slots }) {
    const text = ref('hi')
    return () => h('div', null, slots.default({ text: text.value }))
  }
}
// 默认插槽
<div>{slots.default()}</div>
// 具名插槽
<div>{slots.footer({ text: props.message })}</div>


// 传递插槽(父组件)
export default defineComponent({
  name: 'SideMenu',
  props: {},
  async setup() {
    // 插槽,并按组件名称引入
    return () => <>
              icon: () => h(resolveComponent(item.icon)),
              default: (val) => <>{val}</>
    </>
  }
})


h(MyComponent, null, {
    default: () => 'default slot',
    foo: () => h('div', 'foo'),
    bar: () => [h('span', 'one'), h('span', 'two')]
})
// 作用域插槽
h(MyComp, null, {
  default: ({ text }) => h('p', text)
})

ref、reactive

ref:任意类型(建议基本类型)数据的响应式引用(设置、获取值时需要加.value)

reactive:只能是复杂类型数据的响应式引用,浅拷贝

import { ref, reactive } from 'vue'
let name=1
let a=ref(name)
a.value
let nameObj=reactive({a:1})
let readonlyObj = readonly(nameObj);
nameObj.a


template中引用 ref:attr     reactive:state.attr

toRef 与 toRefs

toRef:用来给抽离响应式对象中的某一个属性,并把该属性包裹成 ref 对象,使其和原对象产生链接。

toRef 的本质是引用,修改响应式数据会影响原始数据。

toRefs:用来把响应式对象转换成普通对象,把对象中的每一个属性,包裹成 ref 对象。

import { toRef, toRefs, reactive } from 'vue'
let names=reactive({
    name:'老谭',
    age:23,
    job:{
    salary:10
    }
})
return {
name:toRef(names,'name'),
age:toRef(names,'age'),
salary:toRef(names.job,'salary')
...toRefs(names)
}



template中引用 reactive:attr

computed、watch、watchEffect

//computed
const addCount = computed(() => {
    return num.value * 2;
})

// 设置计算属性
const plusOne = computed({
  get: () => count.value + 1,
  set: (val) => {
    count.value = val - 1
  }
})

//watch
watch(name, (newVal, oldVal) => {
    console.log(newVal, oldVal);
}, { immediate: true }) // 立即执行

//监听复杂数据
watch(() => nameObj, (newVal, oldVal) => {
    console.log(newVal, oldVal); // newVal、oldVal具有响应式
}, { deep: true,immediate: true})

// 同时监听多个对象
watch([() => nameObj.name, () => nameObj.lastName], ([newName, newLastName], [oldName, oldLastName]) => {
    console.log(newName, oldName, newLastName, oldLastName);
})

//1.立即执行、立即监听(immediate)
//2.自动会感知代码依赖(自动收集依赖),不需要传递监听的内容(不需要像 watch 一样手动传入依赖)
//3.无法获得变化前的值(oldVal)
const stop = watchEffect(() => {
    console.log(name);
    console.log(nameObj.name);
})
stop()//停止监听

获得 dom 节点

子组件需要将数据暴露出去,父组件才能用ref获得数据defineExpose

//子组件
import { reactive, toRefs } from 'vue'
  defineExpose({
    // 解构state
    ...toRefs(state)
  })

//父组件
<template>
  <div ref="myRef">获取单个DOM元素</xdiv>
</template>

<script>
import { ref, onMounted } from 'vue';

export default {
  setup() {
    const myRef = ref(null);//单个
    const myRefs = ref([null]);//多个

    onMounted(() => {
      console.log(myRef.value);
    });
    return {
      myRef
    };
  }
};
</script>

provide 与 inject

==react context

父组件
setup () {
    const name = ref('张三');

    // provide(别名, 要传递的数据和方法)
    provide('myName', name)
    provide('handleClick', () => {
        name.value = 'zhangsan';
    })

},
子组件
setup () {
    //调用 inject 方法,通过指定的别名,接收到父级共享的数据和方法
    const name = inject('myName', defaultValue);
    const handleClick = inject('handleClick');

    return { name, handleClick }
}

router

定义 vue-router 然后引入的 useRoute,useRouter == vue2 的 this.$route,this.$router

import {useRouter,useRoute} from "vue-router";
setup(){
  const router= useRouter()
  const route= useRoute()
  function fn(){
    router.push('/about')
  }
  onMounted(()=>{
    console.log(route.query.code)
  })
  return{
    fn
  }
}

生命周期改变

beforeCreate ===> Not needed*
created ===> Not needed*
beforeMount ===> onBeforeMount
mounted ===> onMounted
beforeUpdate ===> onBeforeUpdate
updated ===> onUpdated
beforeDestroy ===> onBeforeUnmount
destroyed ===> onUnmounted

vue2局限

this.$set({} || [],key,value)

不能检测数组变化
this.arr[i] = newValue

解决:
Vue.set(example1.items, indexOfItem, newValue)
//vue2重写方法
example1.items.splice(indexOfItem, 1, newValue)

this.arr.length = newlength

Vue 不能检测对象属性的添加或删除
let obj = {a:213,b:322}
obj.c = 3242 //非响应

解决:
this.$delete(obj,key)

this.obj = Object.assign({}, this.obj, {
 age: 27,
 favoriteColor: 'Vue Green'
})

this.$set(this.obj,age,27)
this.$set(this.obj,favoriteColor,'Vue Green')