vue 3.0新特性之reactive与ref

vue 3.0新特性

参考:  https://www.cnblogs.com/Highdoudou/p/9993870.html

https://www.cnblogs.com/ljx20180807/p/9987822.html

性能优化

  • 观察者机制的变化:Proxy 替代 object.defineProperty  

    •  Vue 2.x使用 Object.defineProperty 的 getter 和 setter。 但是,Vue 3 将使用 ES2015 Proxy 作为其观察者机制。 这消除了以前存在的警告,使速度加倍,并节省了一半的内存开销。
  • virtual DOM重构(比2.5快一倍) 【和模版大小有关 --> 和模版内的动态内容数量相关】

    1.   传统:组件 update时,整个vdom树需要重新创建,然后遍历进行diff, update

    2.   新的更新策略: block tree

      1.  区分动态节点和静态节点

      2.  基于动态节点指令(v-if, v-for,  { { name }}等)更新

  • 编译时优化

  1. slot默认编译为函数

  2. vnode的创建函数保持参数一致化

  3. 编译时生成vnode的类型标记

新增composition-api,

https://composition-api.vuejs.org/

可以在vue2.x的项目中通过安装@vue/composition-api包来使用composition-api.

  • reactive:reactive的作用是将对象包装成响应式对象——通过 Proxy代理后的对象。
  • ref:由传入值返回一个响应式的、可变的且只有value一个属性的ref对象。

  当ref被作为render context被返回,在template中使用该ref对象时,自动获取内部的值,不需要使用.value属性。

复制代码



复制代码

  当ref对象被作为属性,传入响应式对象reactive时,会自动获取其内部的值(表现得像普通的属性值,而非对象)。

复制代码

const count = ref(0)
const state = reactive({
  count
})

console.log(state.count) // 0

state.count = 1
console.log(count.value) // 1

复制代码

  reactive属性绑定新的ref对象后,原来ref的value不变(断开了)。

const otherCount = ref(2)

state.count = otherCount
console.log(state.count) // 2
console.log(count.value) // 1

  其他情况获取ref时需要带.value

复制代码

const arr = reactive([ref(0)])
// need .value here
console.log(arr[0].value)

const map = reactive(new Map([['foo', ref(0)]]))
// need .value here
console.log(map.get('foo').value)

复制代码

    •   reactive对象与ref对象的区别

    可以通过如何撰写标准的 JavaScript 逻辑来比较:

复制代码

// 风格 1: 将变量分离
let x = 0
let y = 0

function updatePosition(e) {
  x = e.pageX
  y = e.pageY
}


// 风格 2: 单个对象
const pos = {
  x: 0,
  y: 0,
}

function updatePosition(e) {
  pos.x = e.pageX
  pos.y = e.pageY
}

复制代码

  • 可以将风格 (1) 转换为使用 ref写法 (为了让基础类型值具有响应性) 。

  • 将风格 (2) 转换为使用 reactive 对象的写法。

      如果只使用 reactive 的问题是,使用组合函数时必须始终保持对这个所返回对象的引用以保持响应性。这个对象不能被解构或展开

复制代码

// 组合函数:
function useMousePosition() {
  const pos = reactive({
    x: 0,
    y: 0,
  })

  // ...
  return pos
}

// 消费组件
export default {
  setup() {
    // 这里会丢失响应性!
    const { x, y } = useMousePosition()
    return {
      x,
      y,
    }

    // 这里会丢失响应性!
    return {
      ...useMousePosition(),
    }

    // 这是保持响应性的唯一办法!
    // 你必须返回 `pos` 本身,并按 `pos.x` 和 `pos.y` 的方式在模板中引用 x 和 y。
    return {
      pos: useMousePosition(),
    }
  },
}

复制代码

toRefs API 用来提供解决此约束的办法——它将响应式对象的每个 property 都转成了相应的 ref。

复制代码

function useMousePosition() {
  const pos = reactive({
    x: 0,
    y: 0,
  })

  // ...
  return toRefs(pos)
}

// x & y 现在是 ref 形式,可以i解构了!
const { x, y } = useMousePosition()

复制代码

 

  • computed:传入一个getter()函数,返回一个值不可变的响应式ref对象。
const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2  count值改变,plusOne的值相应的改变

plusOne.value++ // error

  当传入的是一个具有get和set函数的对象时,返回的是一个可写的ref对象。

复制代码

const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: val => {
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value) // 0   count值改变,plusOne的值随之改变;改变plusOne的值, count的值也相应的改变

复制代码

  • readonly:将接受到的对象(无论是响应式对象、ref对象或普通对象),转成通过Proxy代理的只读对象。

复制代码

const original = reactive({ count: 0 })

const copy = readonly(original)

watchEffect(() => {
  // works for reactivity tracking
  console.log(copy.count)
})

// mutating original will trigger watchers relying on the copy
original.count++

// mutating the copy will fail and result in a warning
copy.count++ // warning!

复制代码

  • watchEffect:接受一个函数,当依赖改变时,重新调用该函数。

复制代码

const count = ref(0)

watchEffect(() => console.log(count.value))

setTimeout(() => {
  count.value++
}, 100)

复制代码

  当watchEffect()在setup()或生命周期钩子中被调用时,监听就始终存在该组件的生命周期中,直到组件unmount.

  另一种卸载监听的情况是,watchEffect()返回一个stop handler,调用该handler即可停止监听。

const stop = watchEffect(() => {
  /* ... */
})

// later
stop()

  当向后台获取数据时,watchEffect()接受async回调函数。

const data = ref(null)
watchEffect(async () => {
  data.value = await fetchData(props.id)
})

  组件的update函数也有watch effect。用户定义的watchEffect会在组件update之后再去调用。

复制代码



复制代码

上述代码,第一轮会同步打印count.value(在onmount生命周期前); 当count发生改变时,先执行组件更新,然后再去log.

  如果想将watchEffect中的回调函数第一次执行,放在onmount后,

onMounted(() => {
  watchEffect(() => {
    // access the DOM or template refs
  })
})

  如果想让watchEffect()调用发生在组件update前,或re-run同步,需要传递一个带有flush属性(默认值为post)的option对象。

watchEffect(()=> {
//...
}, {
 flush: 'sync'  // 在更新前触发 flush: "pre"
})

此外,option对象还有ontrack和ontrigger两个函数属性,用于调试watcher的行为。

  • onTrack will be called when a reactive property or ref is tracked as a dependency
  • onTrigger will be called when the watcher callback is triggered by the mutation of a dependency

复制代码

watchEffect(
  () => {
    /* side effect */
  },
  {
    onTrigger(e) {
      debugger   // 进行交互式调试
    }
  }
)

复制代码

  • watch:等价于vue 2.x中的this.$watch.

相比于watchEffect(), watch()可帮我们实现:

  1. Perform the side effect lazily;
  2. Be more specific about what state should trigger the watcher to re-run;
  3. Access both the previous and current value of the watched state.

  watch()的数据源可以是一个返回值的getter函数,或者是一个ref对象。

复制代码

// watching a getter
const state = reactive({ count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
    /* ... */
  }
)

// directly watching a ref
const count = ref(0)
watch(count, (count, prevCount) => {
  /* ... */
})

复制代码

  对于多数据源的监听,可借助数组。

watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  /* ... */
})

 生命周期api调整

vue 3.0新特性之reactive与ref_第1张图片

复制代码


复制代码

setup()接受的第一个参数是props。props是响应式的。注意:传参时不能对其解构。上面的代码将props传入setup()后,并通过watchEffect()进行监听。

setup()还可接受第二个参数context(相当于vue2.x中的this, setup()不允许使用this),context作为参数时,可以进行解构。常用的有:

context.attrs, context.slots, context.emit

setup(props, {attrs}) {
    function onClick() {
      console.log(attrs.foo) // 可以保证是最新的值
    }
 }

其他生命周期函数可以在setup()中被同步注册。当同步执行生命周期hooks时,组件实例的renderContext,watcher和computed properties也同步建立。当组件卸载时,内部的生命周期hooks也会同步去掉。

复制代码

setup() {
    onMounted(() => {
      console.log('mounted!')
    })
    onUpdated(() => {
      console.log('updated!')
    })
    onUnmounted(() => {
      console.log('unmounted!')
    })
  }

复制代码