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
  }
}

5、優先級,如果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>

6、如上代碼所示,若要在setup內執行ref,toRefs,toRef,computed,watch,watchEffect等函數,需先引入

import { toRefs, ref, onMounted, nextTick } from "vue";

7、如何在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 
  }
}

8、如何調用子組件內setup內的方法?

  1. 子組件在setup寫好方法method,並通過return暴露出去
  2. 父組件調用子組件時為其添加ref屬性
  3. 父組件setup內拿到子組件添加的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>

9、vue3如何setup函數如何實現多屬性監聽,如何實現深度監聽?

  1. 引入watch,watch最後返回unwatch方法,在調用該方法將停止監聽
  2. watch傳入數組,注意,監聽的是普通類型可直接輸入,若是引用類型,則需要輸入函數返回的值,例如要想同時監聽data.form.c.c1屬性和ddd屬性
  3. 對於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);

10、vue3的watchEffect有什麼用?

  1. 它是一個與偵聽器,作用和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;
  1. 傳參的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>
  1. onInvalidate函數的執行時機

(1). effect裏的值改變時,會先於內部函數執行

(2). 偵聽器被停止(組件unMounted也會關閉偵聽器)

注意點:

setup函數是Vue3中新增的函數,它是我們在編寫組件時,使用Composition API的入口。同時它也是Vue3中新增的一個生命週期函數,會在beforeCreate之前調用。因為此時組件的data和methods還沒有初始化,因此在setup中是不能使用this的。所以Vue為了避免我們錯誤的使用,它直接將setup函數中的this修改成了undefined。並且,我們只能同步使用setup函數,不能用async將其設為異步


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
  }
}

5、優先級,如果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>

6、如上代碼所示,若要在setup內執行ref,toRefs,toRef,computed,watch,watchEffect等函數,需先引入

import { toRefs, ref, onMounted, nextTick } from "vue";

7、如何在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 
  }
}

8、如何調用子組件內setup內的方法?

  1. 子組件在setup寫好方法method,並通過return暴露出去
  2. 父組件調用子組件時為其添加ref屬性
  3. 父組件setup內拿到子組件添加的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>

9、vue3如何setup函數如何實現多屬性監聽,如何實現深度監聽?

  1. 引入watch,watch最後返回unwatch方法,在調用該方法將停止監聽
  2. watch傳入數組,注意,監聽的是普通類型可直接輸入,若是引用類型,則需要輸入函數返回的值,例如要想同時監聽data.form.c.c1屬性和ddd屬性
  3. 對於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);

10、vue3的watchEffect有什麼用?

  1. 它是一個與偵聽器,作用和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;
  1. 傳參的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>
  1. onInvalidate函數的執行時機

(1). effect裏的值改變時,會先於內部函數執行

(2). 偵聽器被停止(組件unMounted也會關閉偵聽器)