vue3之setup的使用理解

作者: Nelson_sylar | 来源:发表于2021-01-13 16:02 被阅读0次
    1. vue3中的setup有什么用?
      setup的设计是为了使用组合式api
    2. 为什么不用之前的组件的选项
      data、computed、methods、watch 组织逻辑在大多数情况下都有效。然而,当我们的组件变得更大时,逻辑关注点的列表也会增长。这可能会导致组件难以阅读和理解,尤其是对于那些一开始就没有编写这些组件的人来说。而通过setup可以将该部分抽离成函数,让其他开发者就不用关心该部分逻辑了.
    3. setup的在vue生命周期的位置
      setup位于created 和beforeCreated只前,用于代替created 和beforeCreated,但是在setup函数里不能访问到this,另外setup内可以通过以下hook操作整个生命周期
      onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted,onErrorCaptured,onRenderTracked,onRenderTriggered
    4. setup可以接收哪些参数?
      setup可接受props,context,其中props由于是响应式数据,不能直接解构赋值,context不是响应式数据,可以直接解构赋值;setup必须返回一个对象,一旦return,就可以像vue2.x的方式使用该属性
    props:['test']
    setup(props,context){
    //const {test} = props //错
    const {test} = toRefs(props) //对
    const { attrs, slots, emit }= context //对
      return {
        test
      }
    }
    
    1. 优先级,如果data,props,setup都有一个同名属性,setup返回的该属性优先级最高,以执行以下代码为例,将显示:test from son's setup
    //father.vue
    ...
    <custField :test="test" />
    setup(){
      const test = ref('test from father')
      return{
        test
      }
    }
    ...
    
    //son.vue
    <template>
      <div class="custField">
        优先级测试
        <h1>{{ test }}</h1>
      </div>
    </template>
    
    <script>
    import { toRefs } from "vue";
    export default {
      props: ["test"],
      data() {
        return {
          test: "test from son's data",
        };
      },
      setup(props) {
        let test = toRefs(props);
        test = "test from son's setup";
        return { test };
      },
    };
    </script>
    
    

    执行结果如下


    结果
    1. 如上代码所示,若要在setup内执行ref,toRefs,toRef,computed,watch,watchEffect等函数,需要通过import的方式从vue中引入后才能使用,eg:import { toRefs, ref, onMounted, nextTick } from "vue";
    2. 如何在setup中拿到ref对应的子组件,并执行其的函数,场景如下:使用antd的form表单的验证,在vue2.x方案时可以在methods中通过this时需要使用this.$refs.ruleForm.validate(),而在setup中拿不到this,应该从{ref}入手,看下面代码
    //...
     <a-form
        ref="ruleForm"
        :model="form"
        :rules="rules"
      >
        <a-form-item ref="name" label="Activity name" name="name">
          <a-input v-model:value="form.name" />
        </a-form-item>
        <a-form-item :wrapper-col="{ span: 14, offset: 4 }">
          <a-button type="primary" @click="onSubmit"> 验证</a-button>
          <a-button style="margin-left: 10px" @click="resetForm"> 重置</a-button>
        </a-form-item>
    </a-form>
    //...vue2.x
     methods: {
        onSubmit() {
          this.$refs.ruleForm
            .validate()
            .then(() => {
              console.log('values', this.form);
            })
            .catch(error => {
              console.log('error', error);
            });
        },
        resetForm() {
          this.$refs.ruleForm.resetFields();
        },
      },
    
    //..vue3
    setup(){
      //1.设置一个 <a-form
      // ref="ruleForm"
      //  :model="form"
      // :rules="rules"
      //> ref同名属性,并使用ref(null)包装
      const ruleForm=ref(null)//通过ref或reactive包裹起来让其成为响应式数据
      //2.一旦后面return {ruleForm},vue3会自动绑定ref="ruleForm"的组件
      //设定方法,但是要通过ruleForm.value才能拿到组件
      const onSubmit=()=>{
        ruleForm.value//通过ref包裹的数据需要使用.value来取得相应的值
            .validate()//,而reactive包裹的数据不需要通过.value来取得相应的值
            .then(() => {
              console.log("values", form);
            })
            .catch((error) => {
              console.log("error", error);
            });
      }
      const resetForm = () => {
          console.log("resetForm");
          ruleForm.value.resetFields();
      };
      //3.setup必须返回一个对象,把vue在生命周期需要调用的方法,属性暴露出去
      return  {
        ruleForm,//Q:为什么上面要用.value的形式,A:这里会自动解绑
        onSubmit,
        resetForm 
      }
    }
    
    1. 目前"ant-design-vue": "^2.0.0-rc.8",与"vue": "^3.0.0",在使用a-form里的submit事件时,若需要校验,会使得校验无论如何都不能走then,只能走catch,需要将a-form的submit事件改为按钮执行方法
    //以下代码上述版本会导致点击提交表单时数据验证通不过
    <a-form
        ref="ruleFormRef"
        :model="formData"
        :rules="rules"
        :label-col="labelCol"
        :wrapper-col="wrapperCol"
        @submit='onSubmit'
      >
      //....
      <a-form-item :wrapper-col="{ span: 14, offset: 4 }">
          <a-button type="primary" html-type='submit'> Create </a-button>
      </a-form-item>
    </a-form>
    //...
    setup(){
      const ruleFormRef = ref(null)
      const onSubmit= () => {
            ruleFormRef.value
              .validate()
              .then(() => {
                console.log('sucess') //上述版本无论如何都不会执行then,只会走catch
              })
              .catch((error) => {
                console.log("error", error);
              });
          }
      return{
        onSubmit
      }
    }
    
    1. 截止2021/1/14,最新版vite与最新版antd有冲突,得改成vuecli;
      冲突版本:vite"vite": "^1.0.0-rc.1"与"ant-design-vue": "^2.0.0-rc.8",
    2. 如何调用子组件内setup内的方法?
      i. 子组件在setup写好方法method,并通过return暴露出去
      ii. 父组件调用子组件时为其添加ref属性
      iii. 父组件setup内拿到ii添加的ref属性property,再通过property.value.method()调用
      子组件
    <template>
      // 渲染从父级接受到的值
      <div>Son: {{ valueRef }}</div>
    </template>
    <script lang="ts">
    import { defineComponent, ref } from 'vue'
    export default defineComponent({
      name: 'Son',
      setup() {
        const valueRef = ref('')    
        // 该函数可以接受父级传递一个参数,并修改valueRef的值
        const acceptValue = (value: string) => (valueRef.value = value)
        return {
          acceptValue,
          valueRef
        }
      }
    })
    </script>
    

    父组件

    <template>
      <div>sonRef</div>
      <button @click="sendValue">send</button>
      // 这里ref接受的字符串,要setup返回的ref类型的变量同名
      <Son ref="sonRef" />
    </template>
    <script lang="ts">
    import { defineComponent, ref } from 'vue'
    import Son from '@/components/Son.vue'
    export default defineComponent({
      name: 'Demo',
      components: {
        Son
      },
      setup() {
        // 如果ref初始值是一个空,可以用于接受一个实例
        // vue3中获取实例的方式和vue2略有不同
        const sonRef = ref()
        const sendValue = () => {
          // 可以拿到son组件实例,并调用其setup返回的所有信息
          console.log(sonRef.value)      
          // 通过调用son组件实例的方法,向其传递数据
          sonRef.value.acceptValue('123456')
        }
        return {
          sonRef,
          sendValue
        }
      }
    })
    </script>
    
    1. defineComponent是便于typescript推断类型的组件构造函数,可以传入name,data,setup,methods等参数,如果只有setup,则可以直接传入setup方法;但是注意:若要在setup中使用props属性,props为必输,所以方法二访问不到props属性
    //方法一,传入其他属性
    export default defineComponent({
      name:'xxx',
      props:['aaa']//若要在setup中使用props,必输props
      setup(props,context){
        const aaa = ref(aaa)
        return {aaa}
      }
    })
    //方法二,直接传入setup函数
    export default defineComponent((props,context)=>{
      //...你的代码 ,但是注意这里props拿不到实际数据
    })
    
    1. vue3如何setup函数如何实现多属性监听,如何实现深度监听?
      i. 引入watch,watch最后返回unwatch方法,在调用该方法将停止监听
      ii. watch传入数组,注意,监听的是普通类型可直接输入,若是引用类型,则需要输入函数返回的值,例如要想同时监听data.form.c.c1属性和ddd属性
      iii. 对于watch第三个传参deepimmediate都不陌生,而flush的作用是决定callback的执行时机,有三个选项,pre(默认),post,sync,分别对应watch在组件更新前,后,时执行callback.
    const ddd = ref("wwww");
    const data = reactive({
          form: {
            a: 1,
            b: 2,
            c: {
              c1: "c1",
              c2: "c2",
            },
          },
          haha: "haha",
        });
    const unwatch = watch(
        [ddd, () => data.form.c.c1],//传入数组
        (newValue, oldValue) => {//结构的也是数组,
        //也可以写成([nowddd,nowC1],[preddd,preC1])=>{...}
          console.log(`new--->${newValue}`);
          console.log(`old--->${oldValue}`);
          console.log(newValue[0]);
          console.log(newValue);
        },
        { deep: true }//第三个参数传入deep,immediate,flush属性
    );
        setTimeout(() => {
          ddd.value = "eee";
        }, 1000);
        setTimeout(() => {
          data.form.c.c1 = "2222";
          setTimeout(() => {
            unwatch();//这里异步使用unwatch方法,后面的ddd.value = "ffff"将不被监听
          });
        }, 2000);
        setTimeout(() => {
          ddd.value = "ffff";
        }, 3000);
    
    1. vue3的watchEffect有什么用?
      i. 它是一个与侦听器,作用和watch差不多,但是不能拿到newValueoldValue,下面是它的定义,传参effect函数option对象,effect函数又可传入onInvalidate函数,option对象可传入flush,onTrack,onTrigger,flush与watch的flush相同,onTrack,onTrigger又可传入DebuggerEvent 函数用于开发调试,返回与watch相同返回一个停止侦听的函数
    function watchEffect(
      effect: (onInvalidate: InvalidateCbRegistrator) => void,
      options?: WatchEffectOptions
    ): StopHandle;
    interface WatchEffectOptions {
      flush?: "pre" | "post" | "sync";
      onTrack?: (event: DebuggerEvent) => void;
      onTrigger?: (event: DebuggerEvent) => void;
    }
    interface DebuggerEvent {
      effect: ReactiveEffect;
      target: any;
      type: OperationTypes;
      key: string | symbol | undefined;
    }
    type InvalidateCbRegistrator = (invalidate: () => void) => void;
    type StopHandle = () => void;
    

    ii. 传参的effect函数会在组件beforeCreate之前就执行一次,若该函数里使用到了某些数据,将监听该数据,当监听的数据发生变化时就会(若watchEffect传入了onInvalidate函数,则会先执行onInvalidate函数后)再次执行effect函数.

    <template>
      <div class="about">
        <h1>This is an about page</h1>
        <h4>{{ count }}</h4>
        <h4>{{ test }}</h4>
        <button @click="jump">jump</button>
      </div>
    </template>
    <script lang='ts'>
    import { onMounted, ref, watchEffect, onBeforeMount } from "vue";
    import { useRoute, useRouter } from "vue-router";
    const fetchData = () => {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve("success");
        }, 1000);
      });
    };
    export default {
      setup() {
        const test = ref("test");
        const route = useRoute();
        const router = useRouter();
        const count = ref(0);
        const effect = async (onInvalidate) => {
          console.log('监听route'+route.query);
          onInvalidate(() => {
            console.log("执行onInvalidate");
          });
          const res = await fetchData();
          console.log(res);
          test.value = res;
        };
        onBeforeMount(() => {
          console.log("onBeforeMount");
        });
        onMounted(() => {
          console.log("onmounted");
        });
        const unWachEffect = watchEffect(effect);
        useRoute();
        setTimeout(() => {
          console.log("5秒时间后注销WachEffect");
          unWachEffect();
        }, 5000);
        setInterval(() => count.value++, 1000);//每一秒count自加1,因为watchEffect带有该参数,所以改变时会自动触发
        const jump = () => {
          router.push(`?time=${new Date().getTime()}`);
        };
        return { count, jump, test };
      },
      beforeCreate() {
        console.log("beforeCreate");
      },
    };
    </script>
    
    

    iii. onInvalidate函数的执行时机,
    (1). effect里的值改变时,会先于内部函数执行
    (2). 侦听器被停止(组件unMounted也会关闭侦听器)

    1. 如何使用向vue2那样读取route和使用router?
      使用import { useRoute, useRouter } from "vue-router";如上面示例代码所示.

    相关文章

      网友评论

        本文标题:vue3之setup的使用理解

        本文链接:https://www.haomeiwen.com/subject/buxluktx.html