vue3.x之初始setup

Vue 3.0中的setup函数是一个全新的选项,它是在组件创建时执行的一个函数,用于替代Vue2.x中的beforeCreatecreated钩子函数。setup函数的作用是将组件的状态和行为进行分离,使得组件更加清晰和易于维护。

一、初识setup

1. 概念

setup函数是Vue 3.0新增的一个关键字,它是在组件创建时执行的函数。该函数接收两个参数:propscontext。其中,props是一个对象,包含了组件接收到的所有props属性;context是一个对象,包含了一些与组件实例相关的属性和方法。在setup函数中,我们可以使用这两个参数来访问并设置组件的状态和行为。

import { ref, onMounted } from 'vue'

export default {
  name: 'App',
  //setup函数是组合式API的入口函数,调用在beforeCreated之前
  setup(props, context) {
    //1.定义普通变量
    let count_normal = 0;
    //2.定义响应式变量
    let count_reactive = ref(0)
    //3.定义方法
    function myFn() {
      alert(123)
    }
    //4.定义生命周期钩子函数
    onMounted(() => {

    });
    //5.等等...

    //👀注意点:返回值类型要么是对象,要么是渲染函数。
    //在组合式API中定义的变量/方法,要想在外界使用,必须通过return {xxx,xxx},以返回对象的形式暴露出去
    return { count_reactive, myFn, count_normal }
  }
}

参数

⏰ props

setup 函数中的第一个参数是 props。props是一个响应对象,包含父组件传递给子组件的所有数据。

在子组件中使用props进行接收。 包含配置声明并传入的所有的属性的对象。也就是说:如果你想通过props的方式输出父组件传递给子组件的值。 你需要使用props进行接收配置。即props:{......} 如果你未通过Props进行接受配置,则输出的值是undefined

  • 父组件
    <template>
      <header>
        <div> 父组件 </div>
        <HelloWorld :mytitle="msg" otherTitle="别人的标题"></HelloWorld>
      </header>
    </template>
    
    <script lang="ts">
    import HelloWorld from './components/HelloWorld.vue';
    
    export default {
      name: 'App',
      components:{HelloWorld},
      //setup函数是组合式API的入口函数,调用在beforeCreated之前
      setup(props, context) {
        //1.定义普通变量
        let msg = {
          title:'父组件给子组件的数据'
        }
        return {msg}
      }
    }
    </script>
  • 子组件
    <template>
      <div @click="sonHander">我是子组件中的数据</div>
    </template>
    
    <script lang="ts">
    export default {
      name:'HelloWorld',
      props:{//👀 用于声明我要接受那些父组件中传入的属性,只有在这里声明,才能在下面setup函数中的props参数中获取到相关属性值;在这里不声明,下面获取不到
        mytitle:{ //这里的配置项还是给vue2.x一致。
          type:Object
        }
      }, 
      setup(props,contenxt){
        //自定义函数
        function sonHander(){
          console.log('打印props的值:',props);
        }
        return {sonHander}
      }
    }
    </script>
  • 效果如下

  • 改成同时也包含“otherTitle”,效果如下

⏰ context

setup 函数中的第一个参数是 context。context是一个普通对象,包括attrs,emit,slots三个属性。详情如下图

🐝 attrs:值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs。可以理解为就是props不要的那些,attrs给拿走了!

  • 父组件

    <template>
      <header>
        <div> 父组件 </div>
        <HelloWorld :mytitle="msg" otherTitle="别人的标题"></HelloWorld>
      </header>
    </template>
    
    <script lang="ts">
    import HelloWorld from './components/HelloWorld.vue';
    
    export default {
      name: 'App',
      components:{HelloWorld},
      //setup函数是组合式API的入口函数,调用在beforeCreated之前
      setup(props, context) {
        //1.定义普通变量
        let msg = {
          title:'父组件给子组件的数据'
        }
        return { msg}
      }
    }
    </script>
  • 子组件

    <template>
      <div @click="sonHander">我是子组件中的数据</div>
    </template>
    
    <script lang="ts">
    export default {
      name:'HelloWorld',
      props:{//👀用于声明我要接受那些传入的属性,只有在这里声明,才能在下面setup函数中的props参数中获取到相关属性值
        mytitle:{ //这里的配置项还是给vue2.x一致。
          type:Object
        },
      }, 
      setup(props,contenxt){
        //自定义函数
        function sonHander(){
          console.log('打印props的值:',props);
          console.log('打印context.attrs的值:',contenxt.attrs)
        }
        return {sonHander}
      }
    }
    </script>

    效果如下

🐝 emit: 分发自定义事件的函数, 相当于 this.$emit

  • 父组件
    <template>
      <header>
        <div> 父组件 </div>
        <HelloWorld @sonclick='handleSonClick'></HelloWorld>
      </header>
    </template>
    
    <script lang="ts">
    import HelloWorld from './components/HelloWorld.vue';
    
    export default {
      name: 'App',
      components:{HelloWorld},
      //setup函数是组合式API的入口函数,调用在beforeCreated之前
      setup() {
        //1.自定义函数
        function handleSonClick(value:string){
          console.log(value);
        }
        return {handleSonClick}
      }
    }
    </script>
  • 子组件
    <template>
      <div @click="sonHander">我是子组件中的数据</div>
    </template>
    
    <script lang="ts">
    export default {
      name:'HelloWorld',
      setup(props,contenxt){
        //自定义函数
        function sonHander(){
          contenxt.emit('sonclick','子组件传递给父组件')
        }
        return {sonHander}
      }
    }
    </script>
  • 效果如下

🐝 slots: 收到的插槽内容, 相当于 this.$slots

  • 父组件
    <template>
      <header>
        <div> 父组件 </div>
        <HelloWorld>
          <!-- 具名插槽1 -->
          <template v-slot:header>
            <span>头部信息</span>
          </template>
    
          <!-- 具名插槽2 -->
          <template v-slot:footer>
            <span>尾部信息</span>
          </template>
    
        </HelloWorld>
      </header>
    </template>
    
    <script lang="ts">
    import HelloWorld from './components/HelloWorld.vue';
    
    export default {
      name: 'App',
      components: { HelloWorld },
      //setup函数是组合式API的入口函数,调用在beforeCreated之前
      setup() {
    
        return {}
      }
    }
    </script>
  • 子组件
    <template>
      <div @click="sonHander">我是子组件中的数据</div>
      <slot name="header"></slot>
      <slot name="footer"></slot>
    </template>
    
    <script lang="ts">
    export default {
      name:'HelloWorld',
      setup(props,contenxt){
        //自定义函数
        function sonHander(){
          console.log("打印context下面的slots属性",contenxt.slots);
        }
        return {sonHander}
      }
    }
    </script>
  • 效果如下

 返回值

⏰ 返回一个对象

对象中的属性、方法,在模板中均可使用。

<script lang="ts">
import { onMounted } from 'vue';
import HelloWorld from './components/HelloWorld.vue';

export default {
  name: 'App',
  components: { HelloWorld },
  //setup函数是组合式API的入口函数,调用在beforeCreated之前
  setup() {
    //1.定义普通变量
    let value_normal = '普通变量';
    //2.定义响应式变量
    let value_reactive = '响应式变量'
    //3。定义方法
    function myFn(){
      alert(123)
    }
    //4.定义生命周期钩子函数
    onMounted(()=>{

    })
    //5.等等

    //👀注意点:
    //在组合式API中定义的变量/方法,要想在外界使用,必须通过return {xxx,xxx},以返回对象的形式暴露出去
    return {value_normal, value_reactive, myFn, onMounted}
  }
}
</script>

⏰ 返回一个渲染函数

setup 还可以返回一个渲染函数,该函数可以直接使用在同一作用域中声明的响应式状态:

import { h, ref, reactive } from 'vue'

export default {
  setup () {
    const readersNumber = ref(0)
    const book = reactive({ title: 'Vue 3 Guide' })
    // 请注意这里我们需要显式使用 ref 的 value
    return () => h('div', [readersNumber.value, book.title])
  },
}

返回一个渲染函数将阻止我们返回任何其它的东西。从内部来说这不应该成为一个问题,但当我们想要将这个组件的方法通过模板 ref 暴露给父组件时就不一样了。

我们可以通过调用 expose 来解决这个问题,给它传递一个对象,其中定义的 property 将可以被外部组件实例访问:

import { h, ref } from 'vue'
export default {
  setup (props, { expose }) {
    const count = ref(0)
    const increment = () => ++count.value

    // expost 用于暴露一个对象,这个对象的所有属性都将挂载到组件实例上
    expose({
      increment,
    })

    return () => h('div', count.value)
  },
}

这个 increment 方法现在将可以通过父组件的模板 ref 访问。

⚠️返回一个渲染函数时,渲染函数渲染的内容将取代模板里面内容。

2.setup中如何使用this

在Vue3中,setup函数中的this指向的是undefined,因为setup函数是在组件实例化之前执行的(beforeCreate时还没有创建组件实例),此时还没有this对象。因此,在setup函数中不能使用this关键字。

🔔 如果想要在setup函数中访问组件实例的属性或方法,可以使用Vue3提供的两个新的函数:getCurrentInstancectx。getCurrentInstance函数可以获取当前组件实例对象,而ctx则是一个包含了props、attrs、emit等属性和方法的上下文对象。

🌰示例
如果有一个组件,其中有一个data属性和一个方法:
  • vue2.x
    <template>
      <div>{{ message }}</div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          message: 'hello world'
        }
      },
      methods: {
        sayHello() {
          console.log('hello')
        }
      }
    }
    </script>
  • v3.0x

    import { getCurrentInstance } from 'vue'
    
    export default {
      setup() {
        const instance = getCurrentInstance()
        const message = instance.data.message
        const sayHello = instance.ctx.sayHello
    
        return {
          message,
          sayHello
        }
      }
    }
    在上面的例子中,我们通过getCurrentInstance函数获取了当前组件实例对象,并通过instance.data.message和instance.ctx.sayHello访问了组件实例的数据和方法。最终,我们将这些数据和方法作为对象返回,以供模板使用。

二、如何使用setup函数

1.setup 函数内声明响应式的数据和计算属性

setup函数中,我们可以像Vue 2.x中一样声明响应式的数据和计算属性。不过在Vue 3.0中,我们需要使用refcomputed函数来完成这些操作。

<template>
  <header>
    <div> 父组件 </div>
    <div>{{ count }}, {{ doubleCount }}</div>
    <button @click="increase">increase</button>
  </header>
</template>

<script lang="ts">

//👀:需要什么导入什么
import { ref,computed} from 'vue'

export default {
  name: 'App',
  //setup函数是组合式API的入口函数,调用在beforeCreated之前
  setup() {
    //1.定义响应式变量
    const count = ref(0);
    //2.定义计算属性,通过computed
    const doubleCount = computed(()=>count.value * 2)
    //3.定义函数
    function increase(){
      count.value++
    }

    //3.将相关属性,函数,暴露给外界方便使用
    return{count,doubleCount,increase}
  }
}
</script>

在上面的代码中,我们使用ref函数来声明一个响应式的count变量,并使用computed函数来创建一个计算属性doubleCount,该计算属性依赖于count变量。

2.setup 函数内注册 watch 监听

除了旧版的 Options API 可以在组件选项上使用 watch 以外, Vue 3 中还可以在 setup 函数中注册 watch 监听。

可以从 Vue 中导入 watch 函数,它接受 3 个参数:

  • 一个想要侦听的响应式引用或 getter 函数
  • 一个回调
  • 可选的配置选项
import { ref, watch } from 'vue'
export default {
  setup () {
    const count = ref(0)
    const AddCount = () => {
      // 在 js 中,访问响应式变量要访问变量的 value 属性
      count.value++
      console.log(count.value)
    }
    // 可以监听到 count 的变化
    watch(count, (newValue, oldValue) => {
      console.log(newValue, oldValue)
    })
    return {count,AddCount}
  },
}

监听组件 props 变化:

import { toRefs, watch } from 'vue'
export default {
  props: {
    propName: {
      type: String,
      default: 'defaultValue',
    },
  },
  setup (props) {
    const { propName } = toRefs(props)
    watch(propName, (newValue, oldValue) => {
      console.log(newValue, oldValue)
    })
  },
}

3.setup 函数内注册事件处理函数

在Vue 2.x中,我们可以在methods选项中定义一个事件处理函数。而在Vue 3.0中,我们可以在setup函数中使用普通的JavaScript函数来实现相同的功能。

<template>
  <header>
    <div> 父组件 </div>
    <div>{{ count }}, {{ doubleCount }}</div>
    <button @click="increase">increase</button>
  </header>
</template>

<script lang="ts">

//👀:需要什么导入什么
import { ref,computed} from 'vue'

export default {
  name: 'App',
  //setup函数是组合式API的入口函数,调用在beforeCreated之前
  setup() {
    //1.定义响应式变量
    const count = ref(0);
    //2.定义计算属性,通过computed
    const doubleCount = computed(()=>count.value * 2)
    //3.定义函数
    function increase(){
      count.value++
    }

    //3.将相关属性,函数,暴露给外界方便使用
    return{count,doubleCount,increase}
  }
}
</script>

在上面的代码中,我们使用ref声明了一个响应式的count变量,并定义了一个名为increment的函数,在点击按钮时会将count变量的值加1。

4.setup 函数内定义生命周期钩子函数

在Vue 2.x中,我们可以在createdmounted钩子函数中执行一些初始化操作。而在Vue 3.0中,我们可以在setup函数中使用onMountedonUnmounted函数来实现相同的功能。

<template>
  <header>
    <div> 父组件 </div>
    <div>{{ message }}</div>
  </header>
</template>

<script lang="ts">

//👀:需要什么导入什么
import { log } from 'console';
import { ref, onMounted, onUnmounted } from 'vue'

export default {
  name: 'App',
  //setup函数是组合式API的入口函数,调用在beforeCreated之前
  setup() {
    //1.定义响应式变量
    const message = ref('');

    //2.声明钩子
    onMounted(() => {
      console.log('mounted');
    })
    onUnmounted(() => {
      console.log('unmounted');
      clearInterval(intervalId)
    });

    //3.定义函数
    //每一秒拼接一个'hello'
    const intervalId = setInterval(() => {
      message.value += 'hello'
    }, 1000)

    //4.将相关属性,函数,暴露给外界方便使用
    return { message }
  }
}
</script>

在上面的代码中,我们使用ref声明了一个响应式的message变量,并使用setInterval函数定时向该变量中添加字符串。在setup函数中,我们使用onMounted函数注册一个函数,在组件挂载时执行;使用onUnmounted函数注册一个函数,在组件卸载时执行。在本例中,我们在组件卸载时清除了定时器。

⏰ setup 内所有可用生命周期

可以通过在 Options API 的生命周期钩子前面加上 on 来访问组件的生命周期钩子。

可以看到,beforeCreatecreated 两个生命周期是不需要在 setup 函数中使用的,因为 setup 就能代表这两个生命周期,这两个生命周期编写的任何代码都应该直接在 setup 函数中编写。

4.setup 函数注册子组件

在Vue 2.x中,我们可以使用components选项将子组件注册到父组件中。而在Vue 3.0中,我们可以在setup函数中使用普通的JavaScript函数来注册子组件。

<template>
  <div>
    <Child />
  </div>
</template>

<script>
import { defineComponent } from 'vue';
import Child from './Child.vue';

export default defineComponent({
  name: 'Example',
  components: {
    Child
  },
  setup() {
    return {};
  }
});
</script>

在上面的代码中,我们使用defineComponent函数定义了一个具有注册子组件能力的组件。在setup函数中,我们返回了一个空对象。

6.setup 函数访问父级组件的属性和方法

在Vue 2.x中,我们可以通过$parent$emit等属性和方法来访问父级组件的属性和方法。而在Vue 3.0中,我们可以在setup函数中使用injectprovide函数来获取和提供属性。

  • 父组件
    <!-- parent -->
    <template>
      <div>
        <Child :increment="increment" />
      </div>
    </template>
    
    <script>
    import { defineComponent, ref } from 'vue';
    import Child from './Child.vue';
    
    export default defineComponent({
      name: 'Example',
      components: {
        Child
      },
      setup() {
        const count = ref(0);
        const increment = () => {
          count.value++;
        };
        provide('increment', increment);
        return {count};
      }
    });
    </script>
  • 子组件
    <!-- child -->
    <template>
      <button @click="increment">{{ count }}</button>
    </template>
    
    <script>
    import { defineComponent, inject } from 'vue';
    
    export default defineComponent({
      name: 'Child',
      props: ['increment'],
      setup() {
        const count = inject('count');
        return {count};
      }
    });
    </script>

在上面的代码中,我们使用provide函数提供了一个名为increment的函数,并在子组件中使用inject函数获取该函数。注意,在调用provide函数时,我们需要传递一个键值对,表示提供的属性和值的关系。

7.setup 函数访问路由和状态管理器等全局对象

除了访问父级组件的属性和方法之外,我们还可以在setup函数中访问其他全局对象,比如路由和状态管理器等。

<template>
  <div>{{ count }}</div>
</template>

<script>
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';

export default {
  name: 'Example',
  setup() {
    const count = ref(0);
    const route = useRoute();
    const router = useRouter();
    const navigate = () => {
      router.push('/');
    };
    return {
      count,
      route,
      navigate
    };
  }
};
</script>

在上面的代码中,我们使用useRouteuseRouter函数来访问路由相关的属性和方法。其中,useRoute函数返回了当前路由对象,包含了当前路由的路径、参数、查询参数等信息;useRouter函数返回了一个路由管理器对象,包含了一些常用的路由操作方法,比如pushreplace等。在setup函数中,我们为组件提供了一个名为navigate的函数,用于跳转到主页。

posted on 2024-07-11 16:24  梁飞宇  阅读(43)  评论(0)    收藏  举报