⑥ composition API

Composition API

1 Setup 函数的使用

Composition api 代码编写要建立在 setup 函数上

  • 执行时间:created 实例被完全初始化之前

    • setup 函数内部 获取不到 this

1.1 return 的内容可以在 template 中直接使用

const app = Vue.createApp({
  template: `
   <div @click="handleClick">name: {{name}}, age: {{age}}</div>
 `,
 // created 实例被完全初始化之前
 setup(props, context) {
   return {
     name: 'dell',
     age: 18,
     handleClick() {
       alert(1223)
     }
   }
 },
})
const vm = app.mount('#root')

1.2 setup 函数内部不能使用外部的方法;生命周期函数、方法中可以使用 setup 函数

const app = Vue.createApp({
  template: `
    <div @click="handleClick">name: {{name}}, age: {{age}}</div>
  `,
  methods: {
    test() {
      console.log(this.$options.setup());
    }
  },
  mounted() {
    this.test()
  },
  // created 实例被完全初始化之前
  setup(props, context) {
    return {
      name: 'dell',
      age: 18,
      handleClick() {
        alert(1223)
      }
    }
  },
})
const vm = app.mount('#root')

2 ref,reactive 响应式引用的用法和原理

  • 原理:通过 proxy 对数据进行封装,当数据变化时,触发模板等内容的更新

2.1 ref:处理基础类型的数据

const app = Vue.createApp({
  template: `
    <div>name: {{ name }}</div>
  `,
  setup(props, context) {
    const { ref } = Vue;
    // ref, 'dell' 变成 proxy({ value: 'dell' }) 的响应式引用
    let name = ref('dell');
    setTimeout(() => {
      name.value = 'lee'
    }, 2000);
    return { name }
  },
})
const vm = app.mount('#root')

2.2 reactive:处理非基础类型的数据

const app = Vue.createApp({
  template: `
    <div>name: {{ nameObj.name }}</div>
  `,
  setup(props, context) {
    const { reactive } = Vue;
    // reactive, { name: 'dell' } 变成 proxy({ name: 'dell' }) 的响应式引用
    const nameObj = reactive({ name: 'dell' });
    setTimeout(() => {
      nameObj.name = 'lee'
    }, 1000);
    return { nameObj }
  },
})
const vm = app.mount('#root')

2.3 readonly:限制响应式引用不可被修改

const app = Vue.createApp({
  template: `
    <div>name: {{ nameObj.name }}</div>
  `,
  setup(props, context) {
    const { reactive, readonly } = Vue;
    const nameObj = reactive({ name: 'dell' });
    const copyNameObj = readonly(nameObj);
    setTimeout(() => {
      nameObj.name = 'lee'
      copyNameObj.name = 'lee' // target is readonly.
    }, 1000);
    return { nameObj, copyNameObj}
  },
})
const vm = app.mount('#root')

2.4 toRefs:将响应式属性的属性转换为响应式的

const app = Vue.createApp({
  template: `
    <div>name: {{ name }}</div>
  `,
  setup(props, context) {
    const { reactive, toRefs } = Vue;
    // reactive, { name: 'dell' } 变成 proxy({ name: 'dell' }) 的响应式引用
    const nameObj = reactive({ name: 'dell', age: 18 });
    setTimeout(() => {
      nameObj.name = 'lee'
      nameObj.age = 20
    }, 1000);
    // toRefs, ({ name: 'dell', age: 18 }) 变成 { name: proxy({ value: 'dell' }), age: proxy({ value: 18 })}
    const { name } = toRefs(nameObj)
    return { name }
  },
})
const vm = app.mount('#root')

3 toRef 以及 context 参数

3.1 toRef

const app = Vue.createApp({
  template: `
    <div>name: {{ age }}</div>
  `,
  setup(props, context) {
    const { reactive, toRef } = Vue;
    const data = reactive({ name: 'dell' });
    const age = toRef(data, 'age')
    setTimeout(() => {
      age.value = 'lee'
    }, 1000);
    return { age }
  },
})
const vm = app.mount('#root')

3.2 context

1. attrs

  • attrs -> Non-Props 属性
const app = Vue.createApp({
  template: `
    <child app='app' />
  `,
})
app.component('child', {
  template: `
    <div>child</div>
  `,
  setup(props, context) {
    const { attrs, slots, emit } = context
    console.log(attrs); // Non-Props属性
    return {}
  },
})
const vm = app.mount('#root')

2. slots

  • slots.default() -> slot的内容
const app = Vue.createApp({
  template: `
    <child>parent</child>
  `,
})
app.component('child', {
  setup(props, context) {
    const { h } = Vue
    const { attrs, slots, emit } = context
    console.log(slots.default()); // slot 的内容
    return () => h('div', {}, slots.default())
  },
})
const vm = app.mount('#root')

3. emit

const app = Vue.createApp({
  methods: {
    handleChange() {
      alert('change')
    }
  },
  template: `
    <child @change="handleChange">parent</child>
  `,
})
app.component('child', {
  template: `<div @click="handleClick">123123</div>`,
  setup(props, context) {
    const { h } = Vue
    const { attrs, slots, emit } = context
    function handleClick() { emit('change') } // 向外触发事件
    return { handleClick }
  },
})
const vm = app.mount('#root')

4 使用 Composition API 开发 TodoList

// 关于 list 操作的代码做封装
const listRelativeEffect = () => {
  const { reactive } = Vue;
  const list = reactive([]);
  const addItemToList = (item) => {
    list.push(item)
  }
  return { list, addItemToList }
}
// 关于 input 操作的代码做封装
const inputRelativeEffect = () => {
  const { ref } = Vue;
  const inputValue = ref('');
  const handleInpulValueChange = e => {
    inputValue.value = e.target.value
  }
  return { inputValue, handleInpulValueChange }
}
const app = Vue.createApp({
  setup() {
    // 流程调度中转
    const { list, addItemToList } = listRelativeEffect()
    const { inputValue, handleInpulValueChange } = inputRelativeEffect()

    return {
      list,
      addItemToList,
      inputValue,
      handleInpulValueChange,
    }
  },
  template: `
    <div>
      <input :value="inputValue" @input="handleInpulValueChange" />
      <div>{{ inputValue }}</div>
      <button @click="()=>addItemToList(inputValue)">提交</button>
      <ul>
        <li v-for="(item, index) in list" :key=index>{{ item }}</li>
      </ul>
    </div>
  `,
})
const vm = app.mount('#root')

5 computed方法生成计算属性

  • computed 一般操作 get
const app = Vue.createApp({
  setup() {
    const { ref, computed } = Vue;
    const count = ref(0);
    const handleClick = () => count.value += 1
    const countAddFive = computed(() => count.value + 5 )
    return { count, handleClick, countAddFive }
  },
  template: `
    <div>
      <span @click="handleClick">{{count}}</span> -- {{countAddFive}}
    </div>
  `,
})
const vm = app.mount('#root')
  • computed 一般操作 get + set
const app = Vue.createApp({
  setup() {
    const { ref, computed } = Vue;
    const count = ref(0);
    const handleClick = () => count.value += 1
    let countAddFive = computed({
      get: () => {
        return count.value + 5
      },
      set: (param) => {
        return count.value = param - 5
      }
    })
    setTimeout(() => {
      countAddFive.value = 100
    }, 1000);
    return { count, handleClick, countAddFive }
  },
  template: `
    <div>
      <span @click="handleClick">{{count}}</span> -- {{countAddFive}}
    </div>
  `,
})
const vm = app.mount('#root')

6 watch 和 watchEffect 的使用和差异性

6.1 watch 侦听器

  • 具备一定的惰性 lazy

  • 参数可以拿到原始和当前值

1. 一般用法

const app = Vue.createApp({
  setup() {
    const { ref, watch } = Vue
    const name = ref('dell')
    watch(name, (currentVal, prevVal) => {
      console.log(currentVal, prevVal);
    })

    return { name }
  },
  template: `
    <div>
      Name: <input v-model="name" />
    </div>
    <div>
      Name is {{ name }}
    </div>
  `,
})
const vm = app.mount('#root')

2. 侦听 reactive 方法下的数据需要用箭头函数

const app = Vue.createApp({
  setup() {
    const { reactive, watch, toRefs } = Vue
    const nameObj = reactive({ name: 'dell' })
    watch(() => nameObj.name, (currentVal, prevVal) => {
      console.log(currentVal, prevVal);
    })

    const { name } = toRefs(nameObj)

    return { name }
  },
  template: `
    <div>
      Name: <input v-model="name" />
    </div>
    <div>
      Name is {{ name }}
    </div>
  `,
})
const vm = app.mount('#root')

3. 用一个侦听器可以侦听多个数据的变化

const app = Vue.createApp({
  setup() {
    const { reactive, watch, toRefs} = Vue
    const nameObj = reactive({ name: 'dell', englishName: 'lee' })

    watch([() => nameObj.name, () => nameObj.englishName], ([curName, curEng], [prevName, prevEng]) => {
      console.log(curName, prevName, '------------', curEng, prevEng);
    })

    const { name, englishName } = toRefs(nameObj)

    return { name, englishName }
  },
  template: `
    <div>
      Name: <input v-model="name" />
    </div>
    <div>
      Name is {{ name }}
    </div>
    <div>
      englishName: <input v-model="englishName" />
    </div>
    <div>
      englishName is {{ englishName }}
    </div>
  `,
})
const vm = app.mount('#root')

4. 设置非惰性

watch([() => nameObj.name, () => nameObj.englishName], ([curName, curEng], [prevName, prevEng]) => {
  console.log(curName, prevName, '------------', curEng, prevEng);
}, { immediate: true })

6.2 watchEffect 侦听器,偏向于 effect

  • 立即执行,没有惰性 immediate

  • 不需要传递你要侦听的内容,会自动感知代码依赖,不需要传递很多参数,只要传递一个回调函数

  • 不能获取之前数据的值

const app = Vue.createApp({
    setup() {
      const { reactive, watch, toRefs, watchEffect } = Vue
      const nameObj = reactive({ name: 'dell', englishName: 'lee' })
      watchEffect(() => {
        console.log(nameObj.name);
      })
      const { name, englishName } = toRefs(nameObj)

      return { name, englishName }
    },
    template: `
      <div>
        Name: <input v-model="name" />
      </div>
      <div>
        Name is {{ name }}
      </div>
      <div>
        englishName: <input v-model="englishName" />
      </div>
      <div>
        englishName is {{ englishName }}
      </div>
    `,
  })
  const vm = app.mount('#root')

6.3 关闭侦听

const stop = watch(name, (currentVal, prevVal) => {
  console.log(currentVal, prevVal);
  setTimeout(() => {
    stop()
  }, 5000)
})
const stop = watchEffect(() => {
  console.log(nameObj.name);
  setTimeout(() => {
    stop()
  }, 5000);
})

7 生命周期函数的新写法

  • beforeCreate, created 不存在

  • beforeMount => onBeforeMount

  • mounbted => onMounted

  • beforeUpdate => onBeforeUpdate

  • updated => onUpdated

  • beforeUnmount => onBeforeUnmount

  • unmounted => onUnmounted

  • onRenderTracked 每次渲染后重新收集响应式依赖时执行

  • onRenderTriggered 每次触发页面重新渲染时执行

const app = Vue.createApp({
  setup() {
    const { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onRenderTracked, onRenderTriggered } = Vue;
    const name = ref('dell')
    const handleClick = () => {
      name.value = 'lee'
    }
    onBeforeMount(() => {
      console.log('onBeforeMount');
    })
    onMounted(() => {
      console.log('onMounted');
    })
    onBeforeUpdate(() => {
      console.log('onBeforeUpdate');
    })
    onUpdated(() => {
      console.log('onUpdated');
    })
    onRenderTracked(() => {
      console.log('onRenderTracked');
    })
    onRenderTriggered(() => {
      console.log('onRenderTriggered');
    }) 
    return { name, handleClick }
  },
  template: `
    <div @click="handleClick">
      {{ name }}
    </div>
  `,
})
const vm = app.mount('#root')

8 Provide, Inject, 模版 Ref 的用法

8.1 provide inject

1. 父子传值

const app = Vue.createApp({
  setup() {
    const { provide } = Vue;
    provide('name', 'dell');
    return { }
  },
  template: `
    <div>
      <child />
    </div>
  `,
})
app.component('child', {
  setup() {
    const { inject } = Vue;
    const name = inject('name', 'hello'); // 'hello' 为设置的默认值
    return { name }
  },
  template: `<div>{{name}}</div>`
})
const vm = app.mount('#root')

2. 改变传递的数据

  • readonly 单向数据流,限制父组件传递的数据只能由父组件修改
const app = Vue.createApp({
  setup() {
    const { ref, provide, readonly } = Vue;
    const name = ref('dell');
    provide('name', readonly(name));
    provide('changeName', value => {
      name.value = value
    })
    return { }
  },
  template: `
    <div>
      <child />
    </div>
  `,
})
app.component('child', {
  setup() {
    const { inject } = Vue;
    const name = inject('name');
    const changeName = inject('changeName')
    const handleClick = () => {
      changeName('lee')
    }
    return { name, handleClick }
  },
  template: `<div @click="handleClick">{{name}}</div>`
})
const vm = app.mount('#root')

8.2 dom ref

  • CompositionApi 语法下获取真实的 DOM 元素节点
const app = Vue.createApp({
  setup() {
    const { ref, onMounted } = Vue
    // ②
    const hello = ref(null);
    onMounted(() => {
      console.log(hello, hello.value);
    })
    // ③
    return { hello }
  },
  // ①
  template: `
    <div>
      <div ref="hello">hello</div>
    </div>
  `,
})
const vm = app.mount('#root')
posted on 2022-03-16 11:42  pleaseAnswer  阅读(26)  评论(0编辑  收藏  举报