vue3-Composition-API实操

  • 没有this了,我要怎么获取组件实例?
  • 没有this了,怎么派发自定义事件?
  • 我该如何在reactiveref之间做选择?
  • setup函数太长了怎么办?
  • 我的属性怎么就不响应了
  • watchEffectwatch有啥不同?
  • 生命周期钩子能不能写多个?顺序是怎样的?

我要怎么获取组件实例?

我们都知道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还是找不到数据。

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

当然坑还是有的,你仔细观察这个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
},

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

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

setup() {
  getCurrentInstance().emit('ooxx') 
}

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

setup(props, ctx) {
    ctx.emit('ooxx')
}

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

setup(props, { emit }) {
    emit('ooxx')
}

我该如何在reactive和ref之间做选择?

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 }
}
<div>{{ state.foo }}</div>

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

setup() {
    const state = reactive({})
    return { ...toRefs(state) }
}

<div>{{ foo }}</div>

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

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

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

setup() {
    const foo = ref('foo')
    return { foo } 
}

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

<div>{{ foo }}</div>

但是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, () => {})
}

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

  • 如果是单值,建议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函数太长了怎么办?

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

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

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一次

<script setup>
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}
</script>

我的属性怎么就不响应了
下面的代码是小伙伴们可能会写出来的:

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, bar) // foo,bar发生变化,会有输出
  })
}

watchEffect和watch有啥不同?

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

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(() => {})
}

抄袭自掘金羊村杨老师

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351