double定义的0要写成0.0吗_又是一夜,这篇CompositionAPI实操还觉得短吗

村长学前端

又是一夜,这篇Composition-API实操还觉得短吗 

a48b336131847d5a3697660935ff783d.png原创:

这两天大帅*、大圣和然叔几位老湿畅谈了composition api的设计动机和理念,相关链接:

那个忙了一夜的Vue3动画很好,就是太短了

忙了一夜用CompositionAPI征服产品妹子花里胡哨的需求

做了一夜动画,就为让大家更好的理解Vue3的Composition Api

我的另一篇composition原理深度剖析

闪电五连鞭:Composition API原理深度剖析

把大家撩的着实痒痒,很多人就开始动手写东西了,随之而来的便是一些应用上的疑问:

  • 没有this了,我要怎么获取组件实例?

  • 没有this了,怎么派发自定义事件?

  • 我该如何在reactiveref之间做选择?

  • setup函数太长了怎么办?

  • 我的属性怎么就不响应了

  • watchEffectwatch啥不同?

  • 生命周期钩子能不能写多个?顺序是怎样的?

e9ab0ec3671af1e2501a8bdc95cb4450.png

我要怎么获取组件实例?

我们都知道composition api是可以和options api一起使用、友好相处的,比如下面的示例:

  const { createApp } = Vue

  createApp({

    data() {

      return {

        foo: 'foo'

      }

    },

    setup() {

      // 没有this,我该如何获取data中的foo和methods中的bar哪?

      return { }

    },

    methods: {

      bar() {

        console.log('我是bar方法');

      }

    },

  }).mount('#app')

但是setup里面this指向window,composition的文档中也没有提到怎么获取组件实例呀,这着实难住了不少小伙伴,方法自然是有的:咱们可以通过getCurrentInstance()这个接口获取组件实例:

setup() {

  // getCurrentInstance()可以获取组件实例

  const instance = getCurrentInstance()

  console.log(instance);

  onMounted(()=>{

    // 组件实例的上下文才是我们熟悉的this

    console.log(instance.ctx.foo); // 'foo'

    console.log(instance.ctx.bar()); // '我是bar方法'

  })

  return {}

},

但是很快我们又蒙圈了,这个组件实例和我们以前熟悉的this不一样,直接访问this.foo还是找不到数据。

bb156deea9eba22ff6d3e5daacffcea9.png

vue3中组件实例结构如下,各个选项中的this实际上是ctxproxy

37debaa071e8d977372b5559a44a5911.png

当然坑还是有的,你仔细观察这个ctx,发现它不是一个Proxy对象,也就是这位兄台只有值却没有响应性,所以如果要利用响应特性,还得用proxy这个属性返回上下文对象,如果只是想要数据,上图中不是有个data也是Proxy类型的嘛。

setup() {

  const { proxy, data } = getCurrentInstance()

  // 想要利用响应能力,就要使用proxy这个上下文

  watchEffect(() => {

    console.log(proxy.foo) // foo变化会有输出

    console.log(data.foo) // foo变化会有输出

  })

},

最后大家还要注意,setup()执行的时间点是很早的,甚至早于created,因此foo和bar的访问如果没有特意放到onMounted里面还真没有。

setup() {

  const instance = getCurrentInstance()

  console.log(instance.ctx.foo); // undefined

  console.log(instance.ctx.bar()); // undefined

},

ae88c665ff81cc7049b55a20ad9235f5.png

怎么派发自定义事件?

突然之间没有this了之后,好像突然生活不能自理了,再也不能用this.$emit()派发事件了。

其实通过组件实例是可以派发事件的,比如:

setup() {

   getCurrentInstance().emit('ooxx') 

}

但是这样比较麻烦,所以我们要用到setup函数的第二个参数:

setup(props, ctx) {

              ctx.emit('ooxx')

}

当然,还能把emit直接解构出来使用更方便:

setup(props, { emit }) {

              emit('ooxx')

}

我该如何在reactiveref之间做选择?

composition-api引入了独立的数据响应式方法reactive,它可以将传入的对象做响应式处理:

const state = reactive({

  foo: 'foo',

})

watchEffect(() => {

  console.log(state.foo)

})

state.foo = 'foooooo' // 输出 'foooooo'

这个方式类似于我们设置的data选项,能够解决我们大部分需求。但是也有以下问题:

  • 当我们直接导出这个state时,我们在模板中就要带上state这个前缀

setup() {

const state = reactive({})

return { state }

}

{{ state.foo }}

为了解决这个问题又要引入toRefs

setup() {

const state = reactive({})

return { ...toRefs(state) }

}

{{ foo }}
  • 小伙伴们又懵了,toRefs是个啥?为啥不直接展开?

    00667656fce1065433983a74c4fcd52d.png

  • 单个值时用reactive()显得比较多余

于是就有了Ref的概念,通过包装单值为Ref对象,这样就可以对其做响应式代理

setup() {

const foo = ref('foo')

return { foo } 

}

模板中使用还可以省掉前缀,toRefs就是利用这一点将reactive()返回代理对象的每个key对应的值都转换为Ref

{{ foo }}

但是Ref对象也有副作用:

  • 在JS中修改这个值要额外加上value

setup() {

const foo = ref('foo')

setTimeout(() => {

    // 额外的value让人恼火

foo.value++

}, 1000)

return { foo }

}

额外增加的心智负担:一个值到底是不是Ref,我要不要加.value

setup(props) {

const foo = props.foo

// foo是Proxy还是Ref?  

  // 编写`watch`方法的时候写法完全不同

  // Ref可以被直接watch

  watch(foo, () => {})

  // Proxy则需要写成函数形式

  watch(() => foo.bar, () => {})

}

1e2af13973980723613603ab126e4d9a.png

对比之后发现都有一些问题,我们来讨论一下两者选择问题:

  • 如果是单值,建议ref,哪怕是个单值的对象也可以

const counterRef = ref(1)

const usersRef = ref(['tom', 'jerry'])

  • 一个业务关注点有多个值,建议reactive

const mouse = reactive({

x: 0,

y: 0

})

  • 降低Ref心智负担的方法:利用unref、isRef、isProxy等工具方法,利用一些命名约定。

setup(props) {

  const foo = unref(props.foo) // foo是我们要的值

  // 等效于

  const foo = isRef(props.foo) ? props.foo.value : props.foo

}

setup函数太长了怎么办?

虽然很好的将关注点集中起来,就像下面这样:

3ed7b9d0aa38dd91faa31ffd6cdebd1d.gif

但是难免还是太长了(怎么太长还成了困扰?),此时就可以开始函数拆分

38ffa4fd2eddb33e0018098c70e78ede.gif

setup(){

  let { val, todos, addTodo } = useTodo()

  let {count,double,add} = useCounter() 

  let {x, double:doubleX} = useMouse()

  return {

    val, todos, addTodo,

    count,double,add,

    x,doubleX

  }

}

return的上下文太长了,我们可以使用vue3的setup script功能,把setup这个配置也优化掉,一个功能export一次

import useCounter from './counter'

import useMouse from './mouse'

import useTodo from './todos'

let { val, todos, addTodo } = useTodo()

export {val, todos, addTodo}

let {count,double,add} = useCounter()

export {count,double,add}

let {x, double:doubleX} = useMouse()

export {x,doubleX}

我的属性怎么就不响应了

下面的代码是小伙伴们可能会写出来的:

setup({ foo, bar }) {

  watchEffect(() => {

    console.log(foo, bar) // foo,bar发生变化,也不会有输出

  })

}

props是一个Proxy对象,直接解构就失去了响应能力,所以对待props要温柔,不能动不动就劈开了

setup(props) {

  watchEffect(() => {

    console.log(props.foo, props.bar) // foo,bar发生变化,会有输出

  })

}

真想劈开也行,看你喜欢什么姿势了

setup(props) {

  const { foo, bar } = toRefs(props)

  watchEffect(() => {

    console.log(foo.value, bar.value) // foo,bar发生变化,会有输出

  })

}

watchEffectwatch有啥不同?

这俩方法很相似,都是观察响应式数据,变化执行副作用函数,但有如下不同:

  • watch需要明确指定监视目标

watch(() => state.counter, (val, prevVal) => {})

watchEffect不需要

watchEffect(() => {

console.log(state.counter)

})

  • watch可以获取变化前后的值

  • watch是懒执行的,它等效于vue2中的this.$watch(),watchEffect为了收集依赖,要立即执行一次

  • 现在知道怎么选择它们了吧?

生命周期钩子能不能写多个?

当然可以写多个,最后它们会按注册顺序依次执行:

setup() {

  onMounted(() => {})

  onMounted(() => {})

  onMounted(() => {})

}

甚至还能拆分出多个相同生命周期钩子到独立函数中呢,相当帅气

function fun1() {

  // 这里可以用onMounted执行代码

  onMounted(() => {})

}

function fun2() {

  // 这里还可以用onMounted执行代码

  onMounted(() => {})

}

setup() {

  fun1()

  fun2()

  onMounted(() => {})

}

关于composition的实践分享就先说到这里吧,还有问题没有涉及到?可以在评论区给我留言讨论。

如果你喜欢看视频学习,欢迎来羊村逛逛。

最后点赞还是要求一波的,没准屏幕前的大帅比大漂亮就关注了呢!!!

f50e36df9b2f98991e4007e75538acc1.png


3a81346e88c79fe74ed72cbe54889a85.png

  • 这是我们团队的开源项目 element3

  • 一个支持 vue3 的前端组件库


作者:杨村长
链接:https://juejin.im/post/6892017198450081800
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

封面和摘要

选择封面

23/120

原创声明

已声明原创

  • 作者

    村长

  • 赞赏

    未开启

文章设置

原文链接 icon https://juejin.im/post/6892017198450081800

留言 icon

话题标签 icon

正文字数2892 疑似错别字2 隐藏 文章设置

保存并群发预览保存

遇到问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值