0


Vue3 之 Composition API

1. Options API

在Vue2中,编写组件的方式是Options API

  • Options API的一大特点就是在对应的属性中编写对应的功能模块
  • 比如data定义数据、methods中定义方法、computed中定义计算属性、watch中监听属性改变,也包括生命周期钩子

但是这种代码有一个很大的弊端

  • 当实现某一个功能时,这个功能对应的代码逻辑会被拆分到各个属性中
  • 当组件变得更大、更复杂时,逻辑关注点的列表就会增长,那么同一个功能的逻辑就会被拆分的很分散
  • 尤其对于那些一开始没有编写这些组件的人来说,这个组件的代码是难以阅读和理解的(阅读组件的其他人)

2. Composition API

为了开始使用Composition API,我们需要有一个可以实际使用它(编写代码)的地方在Vue组件中,这个位置就是 setup 函数

**setup函数 : **

  • setup其实就是组件的另外一个选项
  • 只不过这个选项强大到我们可以用它来替代之前所编写的大部分其他选项
  • 比如methods、computed、watch、data、生命周期等等

二、setup函数

1. setup函数的参数

setup函数有两个主要的参数 : props 、context

props

props非常好理解,它其实就是父组件传递过来的属性会被放到props对象中,我们在setup中如果需要使用,那么就可以直接通过props参数获取

  • 对于定义props的类型,还是和Vue2的规则是一样的,在props选项中定义
  • 并且在template中依然是可以正常去使用props中的属性
  • 如果在setup函数中想要使用props,那么不可以通过 this 去获取 ( 没有this )- 内部调用setup函数的时候是直接调用的,没有绑定this,所以没有- props有直接作为参数传递到setup函数中,所以可以直接通过参数来使用即可

context

context,也称之为是一个SetupContext,它里面包含三个属性

  • attrs:所有的非prop的attribute ( 传了属性过来,但是没用props接受的,会在这里 )
  • slots:父组件传递过来的插槽(这个在以渲染函数返回时会有作用)
  • emit:当我们组件内部需要发出事件时会用到emit

栗子 🌰

** 父组件**

<template>
  <HelloWorld msg="Welcome to Your Vue.js App" class="app-attr" />
</template>
<script>
import HelloWorld from './components/HelloWorld.vue';
export default {
  name: 'App',
  components: {
    HelloWorld
  }
};
</script>
<style></style>

** 子组件 **

<template>
  <div class="hello">
    <!-- 使用接受过来的参数 -->
    {{ msg }}
  </div>
</template>
<script>
export default {
  name: 'HelloWorld',
  // 组件接受的参数
  props: {
    msg: String
  },
  // 发射的事件这里可以标注一下
  emits:['change'],
  setup(props, context) {
    // 这样可以拿到传递过来的msg的值
    console.log(props.msg);
    // attrs
    console.log(context.attrs);
    // 发射事件
    context.emit('change');
  }
};
</script>
<style scoped></style>

2. setup函数的返回值

  • setup的返回值可以在模板template中被使用
  • 也就是说可以通过setup的返回值来替代data选项

最后导出的一定要是个对象

代码

<template>
  <div class="hello">
    <!-- 使用导出的变量 -->
    <h1>{{ count }}</h1>
    <!-- 使用导出的方法 -->
    <button @click="increment">+ 1</button>
  </div>
</template>
<script>
export default {
  name: 'HelloWorld',
  setup() {
    // 定义普通的变量,可以被正常使用
    // 缺点 : 数据不是响应式的
    let count = 100;
    // 定义方法
    const increment = () => {
      count++;
      console.log(count);
    };
    // 导出
    return {
      count,
      increment
    };
  }
};
</script>
<style scoped></style>

效果

因为只是定义了个变量,然后导出了,并没有使它响应式

三、定义响应式数据的两种方式

1. Reactive API

如果想为在setup中定义的数据提供响应式的特性,那么可以使用reactive的函数

ps : 如果传入一个基本数据类型(String、Number、Boolean)会报一个警告


应用场景 : reactive API对传入的类型是有限制的,它要求我们必须传入的是一个对象或者数组类型,最好相互有关联的数据时使用

**reactive : **

  • 这是因为当我们使用reactive函数处理我们的数据之后,数据再次被使用时就会进行依赖收集
  • 当数据发生改变时,所有收集到的依赖都是进行对应的响应式操作(比如更新界面)
  • 事实上,我们编写的data选项,也是在内部交给了reactive函数将其变成响应式对象的

01 - 栗子 🌰

** 代码**

<template>
  <div class="hello">
    <!-- 这样使用即可 -->
    <h1>{{ state.count }}</h1>
    <button @click="increment">+ 1</button>
  </div>
</template>
<script>
// 从vue中导入reactive
import { reactive } from 'vue';
export default {
  name: 'HelloWorld',
  setup() {
    // 使用reactive,会返回一个响应式对象
    const state = reactive({
      // 在对象中编写自己所需要的属性
      count: 100
    });
    const increment = () => {
      // 这样使用
      state.count++;
      console.log(state.count);
    };
    return {
      // 导出响应式state对象
      state,
      increment
    };
  }
};
</script>
<style scoped></style>

** 效果 **

02 - Reactive判断的API

  • isProxy : 检查对象是否是由 reactive 或 readonly创建的 proxy
  • isReactive : 检查对象是否是由 reactive创建的响应式代理,如果该代理是 readonly 建的,但包裹了由 reactive 创建的另一个代理,它也会返回 true
  • isReadonly : 检查对象是否是由 readonly 创建的只读代理
  • toRaw : 返回 reactive 或 readonly 代理的原始对象(不建议保留对原始对象的持久引用。请谨慎使用)
  • shallowReactive : 建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (深层还是原生对象),只响应第一层
  • shallowReadonly : 创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换(深层还是可读、可写的)只检查第一层

2. Ref API ( 常用 )

Ref函数 : 定义简单类型的数据,也可以定义复杂类型的数据


应用场景 : 定义一些简单的数据,或者从接口中获得的数据

**Ref : **

  • ref 会返回一个可变的响应式对象,该对象作为一个 响应式的引用 维护着它内部的值,这就是ref名称的来源
  • 它内部的值是在ref的 value 属性中被维护的
  • 不管传入的是基本类型还是引用类型,都放在.value中

使用的时候是用 .value,但是有两个注意事项

  • 在模板中引入ref的值时,Vue会自动帮助我们进行解包操作,所以并不需要在模板中通过 ref.value 的方式,直接使用即可
  • 在 setup 函数内部,它依然是一个 ref引用, 所以对其进行操作时,依然需要使用 ref.value的方式

01 - 基本使用

** **** 代码**

<template>
  <div class="hello">
    <!-- 这样使用即可,不需要使用count.value,会自动解包,取出其中的value -->
    <h1>{{ count }}</h1>
    <button @click="increment">+ 1</button>
  </div>
</template>
<script>
// 从vue中导入ref
import { ref } from 'vue';
export default {
  name: 'HelloWorld',
  setup() {
    // 使用Ref,会返回一个响应式对象
    let count = ref(100);
    const increment = () => {
      // 这样使用,需要使用 .value
      count.value++;
      console.log(count.value);
    };
    return {
      // 直接导出count即可
      count,
      increment
    };
  }
};
</script>
<style scoped></style>

效果

02 - Ref自动解包

就是不用在template中使用.value

** 注意事项**

1. 模板中的解包是浅层的解包,如果放到了对象中,则接包不了

2. 如果把ref返回的对象又放入到reactive中,那么也会自动解包

03 - Ref判断的API

  • isRef : 判断值是否是一个ref对象
  • unref : 如果我们想要获取一个ref引用中的value,那么也可以通过unref方法- 如果参数是一个 ref,则返回内部值,否则返回参数本身- 这是 val = isRef(val) ? val.value : val 的语法糖函数
  • shallowRef : 创建一个浅层的ref对象
  • triggerRef : 手动触发和 shallowRef 相关联的副作用

04 - 自定义Ref => customRef

创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显示控制

  • 它需要一个工厂函数,该函数接受 track 和 trigger 函数作为参数
  • 并且应该返回一个带有 get 和 set 的对象
  • **简单写一个带防抖的ref **

** 新建customDebounceRef.js文件**

// 导入customRef
import { customRef } from 'vue';

export default function (value) {
  let timer = null;
  // track收集依赖    trigger更新数据
  return customRef((track, trigger) => {
    return {
      get() {
        // 先收集依赖
        track();
        // 把数据返回
        return value;
      },
      set(newValue) {
        // 防抖
        clearTimeout(timer);
        timer = setTimeout(() => {
          // 设置数据
          value = newValue;
          // 更新数据
          trigger();
        }, 1000);
      }
    };
  });
}

** 使用**

<template>
  <div>
    <input type="text" v-model="message" />
    <h1>{{ message }}</h1>
  </div>
</template>

<script>
import customDebounceRef from '../hook/customDebounceRef';
export default {
  setup() {
    // 使用自定义的customRef
    let message = customDebounceRef('hello');
    return {
      message
    };
  }
};
</script>
<style lang="scss" scoped></style>

** 效果 **

四、readonly

1. 概念

通过reactive或者ref可以获取到一个响应式的对象,但是某些情况下,传入给其他地方(组件)的这个 响应式对象希望在另外一个地方(组件)被使用

**但是不能被修改 **

  • readonly的方法
  • readonly会返回原始对象的只读代理(也就是它依然是一个Proxy,这是一个proxy的set方法被劫持,并且不能对其进行修改)

**在开发中常见的readonly方法会传入三个类型的参数: **

  • 类型一:普通对象
  • 类型二:reactive返回的对象
  • 类型三:ref的对象

2. 使用

**在readonly的使用过程中,有如下规则 : **

  • readonly返回的对象都是不允许修改
  • 但是经过readonly处理的原来的对象是允许被修改的- 比如 const info = readonly(obj),****info对象是不允许被修改的- 当****obj被修改时,readonly返回的info对象也会被修改- 但是****不能去修改readonly返回的对象info

本质上就是readonly返回的对象的setter方法被劫持了

五、toRefs && toRef

1. toRefs

如果使用ES6的解构语法,对reactive返回的对象进行解构获取值,那么之后无论是修改结构后的变量,还是修改reactive 返回的state对象,数据都不再是响应式的

如何改成响应式呢,Vue提供了一个toRefs的函数

可以将reactive返回的对象中的属性都转成ref,这样解构出来的就是响应式的了

代码

<template>
  <div class="hello">
    <h1>{{ age }}</h1>
    <button @click="increment">+age</button>
  </div>
</template>
<script>
// 从vue中导入ref
import { reactive, ref, readonly, toRefs } from 'vue';
export default {
  name: 'HelloWorld',
  setup() {
    const info = reactive({ name: 'star', age: 18 });
    // 使用toRefs包裹需要结构的reactive对象,这样解构出来的值也是响应式的
    let { name, age } = toRefs(info);
    const increment = () => {
      info.age++;
      // 👆这样都可以修改age,都是响应式的👇
      // 相当于已经建立了链接,任何一个修改都会引起另外一个变化
      age.value++;
      console.log(age, info.age);
    };
    return {
      name,
      age,
      increment
    };
  }
};
</script>
<style scoped></style>

**效果 **

2. **toRef **

如果只希望转换reactive对象中的其中某个属性为ref, 那么可以使用toRef的方法


ps : 这个效率会更高点

六、computed

Vue2

使用的是computed属性

Vue3

需要在setup函数中使用computed方法来编写一个计算属性

方式一

接收一个getter函数,并为 getter 函数返回的值,返回一个不变的 ref 对象

<template>
  <!-- coderstar -->
  {{ fullName }}
  <!-- 一般 -->
  {{ scoreState }}
</template>

<script>
import { computed, reactive, ref } from 'vue';
export default {
  name: 'App',
  setup() {
    const names = reactive({
      firstName: 'coder',
      lastName: 'star'
    });
    // 直接使用getter函数,正常来说都这么使用
    const fullName = computed(() => names.firstName + names.lastName);

    const score = ref(88);
    const scoreState = computed(() => (score.value > 90 ? '优秀' : '一般'));
    
    return {
      fullName,
      scoreState
    };
  }
};
</script>

方式二

接收一个具有 get 和 set 的对象,返回一个可变的(可读写)ref 对象

01 - 代码

<template>
  {{ fullName }}
  <button @click="changeName">change</button>
</template>

<script>
import { computed, reactive } from 'vue';
export default {
  name: 'App',
  setup() {
    const names = reactive({
      firstName: '冲啊',
      lastName: '迪迦奥特曼'
    });
    // 会返回一个ref对象
    const fullName = computed({
      set(newValue) {
        const tempNames = newValue.split(' ');
        names.firstName = tempNames[0];
        names.lastName = tempNames[1];
      },
      get() {
        return names.firstName + names.lastName;
      }
    });
    // 设置值
    const changeName = () => {
      fullName.value = fullName.value === '冲啊迪迦奥特曼' ? '神秘的 宇宙人' : '冲啊 迪迦奥特曼';
    };

    return {
      fullName,
      changeName
    };
  }
};
</script>

02 - 效果

七、setup中使用ref获取元素或组件

要定义一个ref对象,绑定到元素或者组件的ref属性上即可

1. 获取元素

<template>
  <!-- 1. 指定ref -->
  <h2 ref="titleRef">我是迪迦</h2>
</template>

<script>
import { onMounted, ref } from 'vue';
export default {
  name: 'App',
  setup() {
    // 2. 生成ref对象
    const titleRef = ref();

    // 4. 可以在生命周期中获取到值
    onMounted(() => {
      console.log(titleRef.value); // <h2>我是迪迦</h2>
    });

    return {
      // 3. 返回出去,会自动匹配到对应的ref的
      titleRef
    };
  }
};
</script>

2. 获取组件

01 - 子组件

<template>
  <div>我是子组件</div>
</template>

<script>
export default {
  name: 'home-layout',
  setup() {
    const showMessage = () => {
      console.log('home-layout function exection');
    };
    return { showMessage };
  }
};
</script>

02 - 父组件

<template>
  <!-- 1. 指定ref -->
  <home ref="homeCompRef" />
</template>

<script>
import { onMounted, ref } from 'vue';
import home from './home.vue';
export default {
  name: 'App',
  components: { home },
  setup() {
    // 2. 生成ref对象
    const homeCompRef = ref();

    // 4. 可以在生命周期中获取到值
    onMounted(() => {
      console.log(homeCompRef.value); // proxy对象
      console.log(homeCompRef.value.$el); // <div>我是子组件</div>
      homeCompRef.value.showMessage(); // 调用子组件方法
    });

    return {
      // 3. 返回出去,会自动匹配到对应的ref的
      homeCompRef
    };
  }
};
</script>

八、生命周期函数

setup中可以直接使用导入的onX函数注册生命周期,并且同一个生命周期可以使用多次


ps : 之前在created和beforeCreated生命周期中编写的代码,直接在setup函数中编写即可

九、provide && inject

**provide可以传入两个参数 : **

  • name:提供的属性名称
  • value:提供的属性值

**inject可以传入两个参数 : **

  • 对应provide传过来的name值
  • 默认值

1. 父组件代码

<template>
  <h1>APP count: {{ count }}</h1>
  <button @click="change">APP button</button>
  <demo />
</template>

<script>
import { ref, provide, readonly } from 'vue'
import demo from './components/demo.vue'

export default {
  name: 'App',
  components: {
    demo
  },
  setup() {
    let count = ref(100)
    // 第一个参数key  第二个参数值,不让子组件随便修改,用readonly包裹一下
    provide('count', readonly(count))
    const change = () => count.value++
    return {
      count,
      change
    }
  }
}
</script>

<style></style>

2. 子组件代码

<template>
  <h2>demo count:{{ count }}</h2>
  <button @click="change">demo change</button>
</template>
<script>
import { ref, inject } from 'vue'
export default {
  setup() {
    // 接收   第二个参数可以给一个默认值
    let count = inject('count', '')
    // 因为设置了readOnly 所以更改不了
    const change = () => count.value++
    return {
      count,
      change
    }
  }
}
</script>

<style lang="scss" scoped></style>

3. 效果

十、 侦听数据的变化之watch

**watch : **

  • watch需要侦听特定的数据源,并在回调函数中执行副作用
  • 默认情况下它是惰性的,只有当被侦听的源发生变化时才会执行回调
  • 与watchEffect的比较,watch允许我们懒执行副作用(第一次不会直接执行)
  • 更具体的说明当哪些状态发生变化时,触发侦听器的执行
  • 访问侦听状态变化前后的值

1. 侦听单个数据源

**watch侦听函数的数据源有两种类型: **

  • 一个getter函数:但是该getter函数必须引用可响应式的对象(比如reactive或者ref)
  • 直接写入一个可响应式的对象,reactive或者ref(比较常用的是ref)

01 - 监听具体的某个值

用来监听ref 或者 reactive对象中的具体属性可使用

02 - 传入响应式对象

** ref对象**

<template>
  <div>
    <h1>
      {{ star }}
    </h1>
    <button @click="change">change</button>
  </div>
</template>
<script>
import { ref, reactive, watch } from 'vue';
export default {
  setup() {
    /**
     * 一、如果ref响应式对象中的是基本数据类型
     */
    const star = ref('123');
    // const star = ref('123');
    // 改变
    const change = () => (star.value = star.value === 'coder' ? '123' : 'coder');
    // 那么直接这样传入即可,拿到的是value值本身
    watch(star, (newValue, oldValue) => {
      console.log('newValue:', newValue, 'oldValue:', oldValue);
    });

    /**
     * 二、如果ref对象中的是对象数据,那么也需要结构 () => ({...star.value}),如果不结构,拿到的是ref对象
     */
    const star = ref({ name: '123', age: 14 });
    // 改变
    const change = () => (star.value.name = star.value.name === 'coder' ? 'pppp' : 'coder');
    // 那么需要结构,如果不结构,拿到的是ref对象
    watch(
      () => ({ ...star.value }),
      (newValue, oldValue) => {
        console.log('newValue:', newValue, 'oldValue:', oldValue);
      }
    );

    return {
      star,
      change
    };
  }
};
</script>

<style lang="scss" scoped></style>

** reactive对象**

<template>
  <div>
    <h1>
      {{ star.name }}
    </h1>
    <button @click="change">change</button>
  </div>
</template>
<script>
import { ref, reactive, watch } from 'vue';
export default {
  setup() {
    const star = reactive({ name: '123', age: 15 });
    // 改变
    const change = () => (star.name = star.name === 'coder' ? '123' : 'coder');
    /**
     * 一、如果直接传入reactive对象,那么打印出的也是两个reactive对象
     */
    watch(star, (newValue, oldValue) => {
      console.log('newValue:', newValue, 'oldValue:', oldValue);
    });
    /**
     * 二、如果希望获得普通对象,结构一下
     */
    watch(
      () => ({ ...star }),
      (newValue, oldValue) => {
        console.log('newValue:', newValue, 'oldValue:', oldValue);
      }
    );
    return {
      star,
      change
    };
  }
};
</script>

<style lang="scss" scoped></style>

2. 侦听多个数据源

01 - 代码

<template>
  <div>
    <h1>
      {{ star.name }}
    </h1>
    <button @click="change">change</button>
  </div>
</template>
<script>
import { ref, reactive, watch } from 'vue';
export default {
  setup() {
    // ref对象
    const coder = ref('know');
    // reactive对象
    const star = reactive({ name: '123', age: 15 });
    // 改变
    const change = () => {
      star.name = 'coder';
      coder.value = 'unKnow';
    };
    // 传入数组即可                      // 对应结构
    watch([coder, () => ({ ...star })], ([newCoder, newStar], [oldCoder, oldStar]) => {
      console.log(newCoder, newStar, '----', oldCoder, oldStar);
    });

    return {
      star,
      change
    };
  }
};
</script>

<style lang="scss" scoped></style>

02 - 效果

3. watch的选项

**配置第三个参数 : **

  • deep : 是否深度监听
  • immediate : 是否立即执行

代码

<template>
  <div>
    <h1>
      {{ info.name }}
    </h1>
  </div>
</template>
<script>
import { ref, reactive, watch } from 'vue'
export default {
  setup() {
    // reactive对象
    const info = reactive({
      name: '123',
      age: 15,
      friend: { name: '456' }
    })
    watch(
      () => {
        const obj = { ...info }
        // 因为有两层,需要自己结构下,否则返回的是proxy
        obj.friend = { ...obj.friend }
        return obj
      },
      (newValue, oldValue) => {
        console.log(newValue, oldValue)
      },
      {
        // 如果有多层,需要加上deep
        deep: true,
        // 立即执行
        immediate: true
      }
    )

    return {
      info
    }
  }
}
</script>

<style lang="scss" scoped></style>

十一、侦听数据的变化之watchEffect

1. watchEffect的基本使用

**watchEffect : **

  • 自动收集响应式数据的依赖
  • watchEffect传入的函数会被立即执行一次,并且在执行的过程中会收集依赖
  • 只有收集的依赖发生变化时,watchEffect传入的函数才会再次执行

01 - 代码

<template>
  <div>
    <h1>{{ name }} - {{ age }}</h1>
    <button @click="changeName">changeName</button>
    <button @click="changeAge">changeAge</button>
  </div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
  setup() {
    let name = ref('star');
    let age = ref(18);

    const changeName = () => (name.value === 'star' ? (name.value = 'xuanyu') : (name.value = 'star'));
    const changeAge = () => age.value++;

    watchEffect(() => {
      // 因为这里只使用了name,所以只会监听name,如果把age也写进来,那么两个都会监听
      console.log('name:', name.value);
    });

    return { name, age, changeName, changeAge };
  }
};
</script>

<style lang="scss" scoped></style>

02 - 效果

2. watchEffect的停止监听

假如,某些情况想要停止监听,那么可以获取watchEffect的返回值函数,调用该函数即可

01 - 代码

<template>
  <div>
    <h1>{{ name }} - {{ age }}</h1>
    <button @click="changeName">changeName</button>
    <button @click="changeAge">changeAge</button>
  </div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
  setup() {
    let name = ref('star');
    let age = ref(18);

    const changeName = () => (name.value === 'star' ? (name.value = 'xuanyu') : (name.value = 'star'));
    // 获取返回值
    const stopWatchEffect = watchEffect(() => {
      // 自动监听age
      console.log('age:', age.value);
    });
    const changeAge = () => {
      age.value++;
      if (age.value > 22) {
        // 停止监听
        stopWatchEffect();
      }
    };

    return { name, age, changeName, changeAge };
  }
};
</script>

<style lang="scss" scoped></style>

02 - 效果

3. watchEffect清除副作用

01 - 什么是清除副作用

  • 比如在开发中可能需要在侦听函数中执行网络请求,但是在网络请求还没有达到的时候,停止了侦听器,或者侦听器侦听函数被再次执行了
  • 那么上一次的网络请求应该被取消掉,这个时候就可以清除上一次的副作用

02 - 如何使用

  • 在给watchEffect传入的函数被回调时,其实可以获取到一个参数:onInvalidate
  • 当副作用即将重新执行 或者 侦听器被停止 时会执行该函数传入的回调函数
  • 可以在传入的回调函数中,执行一些清除工作

03 - 代码

<template>
  <div>
    <h1>{{ name }} - {{ age }}</h1>
    <button @click="changeName">changeName</button>
    <button @click="changeAge">changeAge</button>
  </div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
  setup() {
    let name = ref('star');
    let age = ref(18);

    const changeName = () => (name.value === 'star' ? (name.value = 'xuanyu') : (name.value = 'star'));
    // 获取返回值
    const stopWatchEffect = watchEffect((onInvalidate) => {
      // 自动监听age
      console.log('age:', age.value);
      const timer = setTimeout(() => {
        console.log('1s后执行');
      }, 1000);
      // 取消副作用,清除上一次的定时器
      onInvalidate(() => {
        clearTimeout(timer);
      });
    });
    const changeAge = () => {
      age.value++;
      if (age.value > 22) {
        // 停止监听
        stopWatchEffect();
      }
    };

    return { name, age, changeName, changeAge };
  }
};
</script>

<style lang="scss" scoped></style>

**04 - 效果 **

4. watchEffect的执行时机

  • pre : 默认值,它会在元素 挂载 或者 更新 之前执行
  • post : 元素 挂载 或者 更新 之后执行
  • sync : 强制同步一起执行,效率很低,不推荐

一般来说,如果需要等dom挂载完后操作dom元素的时候,传post即可

代码

<template>
  <div>
    <h1 ref="hRef">star</h1>
  </div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
  setup() {
    // 先声明一个null的ref,等到元素挂载后,会自动赋值过来
    const hRef = ref(null);

    watchEffect(
      () => {
        console.log(hRef.value);
      },
      {
        // 设定元素挂载再执行这个函数
        flush: 'post'
      }
    );
    return {
      hRef
    };
  }
};
</script>

<style lang="scss" scoped></style>

5. 和watch的区别

  • wathc必须指定数据源,watchEffect自动收集依赖
  • watch监听到改变,可以拿到改变前后的值,watchEffect只能拿到最新的值
  • watch第一次默认不执行,watchEffect默认直接执行一次

十二、自定义指令

1. 自定义指令的简单使用

Vue中自带的指令例如v-show、v-for、v-model等等,****除了使用这些指令之外,Vue 也允许我们来自定义自己的指令

ps : 一般需要对dom元素进行底层操作时使用

自定义指令 :

  • 局部指令 : 组件中通过directives选项设定,只能在当前组件中使用
  • 全局指令 : app的directive方法,可以在任意组件中使用

栗子 : 实现input元素挂在完成后自动获得焦点

01 - 默认实现方式

<template>
  <input type="text" ref="inputRef" />
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
  setup() {
    // 这样设定,元素挂载后会自动绑定过来
    const inputRef = ref(null)

    onMounted(() => {
      // 聚焦
      inputRef.value.focus()
    })
    return {
      inputRef
    }
  }
}
</script>

<style></style>

02 - 局部指令

<template>
  <!-- 使用自定义指令 -->
  <input type="text" v-focus />
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
  // 定义自定义指令
  directives: {
    // 指令名称,注 : 不需要加 v-
    focus: {
      // 使用自定义指令的生命周期,挂载后访问
      mounted(el, bindings, vnode, preVnode) {
        el.focus()
      }
    }
  },
  setup() {}
}
</script>

<style></style>

03 - 全局指令

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

// 指令名称
app.directive('focus', {
  // 使用自定义指令的生命周期,挂载后访问
  mounted(el, bindings, vnode, preVnode) {
    el.focus()
  }
})

app.mount('#app')

04 - 效果

2. 指令的生命周期

01 - Vue3

  • created:在绑定元素的 attribute 或事件监听器被应用之前调用
  • beforeMount:当指令第一次绑定到元素并且在挂载父组件之前调用
  • mounted:在绑定元素的父组件被挂载后调用 ( 常用 )
  • beforeUpdate:在更新包含组件的 VNode 之前调用
  • updated:在包含组件的 VNode 及其子组件的 VNode 更新后调用
  • beforeUnmount:在卸载绑定元素的父组件之前调用
  • unmounted:当指令与元素解除绑定且父组件已卸载时,只调用一次

02 - Vue2 与 Vue3对比

3. 指令的参数和修饰符

<template>
  <!-- 使用自定义指令  right为修饰符   'text' 为参数 -->
  <input type="text" v-star.right="'text'" />
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
  directives: {
    star: {
      // 传递的参数和修饰符都在bindings对象中
      mounted(el, bindings) {
        console.log(bindings)
      }
    }
  },
  setup() {}
}
</script>

<style></style>

4. 时间显示栗子

01 - 代码

这里使用了dayjs,可以 npm install dayjs 安装一下

<template>
  <h1 v-fomat-time="timeFormatType">{{ timeStamp }}</h1>
</template>
<script>
import { ref } from 'vue'
import dayJs from 'dayjs'
export default {
  directives: {
    'fomat-time': {
      mounted(el, bindings) {
        // 默认显示时间类型
        let formatType = bindings.value
        console.log(formatType)
        // 转换成number类型
        let time = el.innerHTML * 1
        // 格式化
        el.innerHTML = dayJs(time).format(formatType)
        setInterval(() => {
          // 定时器
          time = dayJs(new Date().valueOf()).format(formatType)
          el.innerHTML = time
        }, 1000)
      }
    }
  },
  setup() {
    // 设置初始时间戳
    const timeStamp = ref(new Date().valueOf())

    const timeFormatType = ref('YYYY-MM-DD HH:mm:ss')

    return {
      timeStamp,
      timeFormatType
    }
  }
}
</script>

<style>
h1 {
  display: inline-block;
  color: transparent;
  background-clip: text;
  background-image: linear-gradient(to right, red, blue);
}
</style>

02 - 效果

十三、script setup语法糖 🍬

1. 概念

<script setup> 是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖,当同时使用 SFC 与组合式 API 时则推荐该语法

  • 更少的样板内容,更简洁的代码
  • 能够使用纯 Typescript 声明 prop 和抛出事件
  • 更好的运行时性能
  • 更好的 IDE 类型推断性能

需要将 setup attribute 添加到 <script> 代码块上

<script setup>
console.log('哈哈哈');
</script>

**里面的代码会被编译成组件 setup() 函数的内容 : **

  • 这意味着与普通的 <script> 只在组件被首次引入的时候执行一次不同
  • <script setup> 中的代码会在每次组件实例被创建的时候执行

2. 顶层的绑定会被暴露给模板

当使用 <script setup> 的时候,****任何在 <script setup> 声明的

**顶层的绑定 (包括变量,函数声明,以及 import 引入的内容) **
都能在模板中直接使用

<template>
  <div>{{ mes }}</div>
  <button @click="addClick">按钮</button>
</template>

<!-- 1. 这里加上setup属性 -->
<script setup>
import { ref } from 'vue';

// 定义数据后,template中可以直接使用,无需返回
const mes = ref(0);
// 定义的方法也是,直接可被使用
const addClick = () => {
  console.log('hahah');
};
</script>

3. 导入的组件直接使用

<template>
  <!-- 2. 直接使用,不用通过compoents注册 -->
  <my-home></my-home>
</template>

<script setup>
// 1. 这是导入的组件
import myHome from './myHome.vue';
</script>

4. defineProps()

defineProps => 用来接收从父组件传递过来的数据

01 - 父组件

<template>
  <my-home name="hello" :age="18"></my-home>
</template>

<script setup>
import myHome from './myHome.vue';
</script>

02 - 子组件

<template>
  <div>{{ name }} - {{ age }}</div>
</template>

<script setup>
// defineProps是内置组件,可以直接使用,不用导入
// 可以接收一下返回的props对象,也可以不用
const props = defineProps({
  name: {
    type: String,
    default: ''
  },
  age: {
    type: Number,
    default: 0
  }
});
console.log(props); // Proxy {name: 'hello', age: 18}
</script>

5. defineEmits()

defineProps => 用来发射事件给父组件

01 - 子组件

<template>
  <button @click="btnClick">发送</button>
</template>

<script setup>
// 1. 注册一下发射的事件
const emits = defineEmits(['btnClick']);
// 2. 监听按钮的点击
const btnClick = () => {
  // 3. 发射
  emits('btnClick', '我发射了');
};
</script>

02 - 父组件

<template>
  <!-- 1. 监听子组件发射来的事件 -->
  <my-home @btnClick="handleClick"></my-home>
</template>

<script setup>
import myHome from './myHome.vue';

// 2. 获取子组件传递过来的值
const handleClick = (message) => {
  console.log(message); // 我发射了
};
</script>

6. defineExpose()

defineExpose => 用来暴露数据

ps : 使用 <script setup> 的组件是默认关闭的

01 - 子组件

<script setup>
const foo = () => {
  console.log('foo');
};
// 暴露出去,才可以被访问到
defineExpose({
  foo
});
</script>

02 - 父组件

<template>
  <!-- 1. 定义ref -->
  <my-home ref="myHomeRef"></my-home>
</template>

<script setup>
import { onMounted, ref } from 'vue';
import myHome from '../../../Vue3/06_阶段六-Vue3全家桶实战/code/04_learn_composition/src/11_script_setup语法/myHome.vue';
// 2. 定义名称一样
const myHomeRef = ref();
onMounted(() => {
  // 3. 在生命周期中访问
  console.log(myHomeRef.value);
});
</script>
标签: vue3 vue.js 前端

本文转载自: https://blog.csdn.net/a15297701931/article/details/125086216
版权归原作者 玄鱼殇 所有, 如有侵权,请联系我们删除。

“Vue3 之 Composition API”的评论:

还没有评论