1

watchEffect初始化时会调用吗?

会,就算没监听东西,初始化也会主动调用一次。

defineModel的用途

用于简化组件使用v-model的双向绑定逻辑.

我么以一个包含input的组件演示defineModel的用途

版本1:不使用defineModel

# App.vue

<script setup lang="ts">
import { ref } from 'vue';
import CustomInput from './CustomInput.vue';

const msg = ref('hi');
</script>

<template>
  <div>App:{{ msg }}</div>
  <CustomInput v-model="msg" />
</template>
# CustomInput.vue

<script setup lang="ts">
defineProps(['modelValue']);
defineEmits(['update:modelValue']);
</script>

<template>
  CustomInput:
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

版本2:使用defineModel

# App.vue

<script setup lang="ts">
import { ref } from 'vue';
import CustomInput from './CustomInput.vue';

const msg = ref('hi');
</script>

<template>
  <div>App:{{ msg }}</div>
  <CustomInput v-model="msg" />
</template>
# CustomInput.vue

<script setup lang="ts">
import { defineModel } from 'vue';

const modelValue = defineModel();
</script>

<template>
  CustomInput:
  <input v-model="modelValue" />
</template>

根据上面的代码,我们发现defineModel的版本简化了CustomInput接收传入的v-model,帮我们实现了双向绑定。也就是modelValueupdate:modelValue的语法糖。

读取通过defineExpose暴露的ref变量读取时还会有响应能力吗?

# Test.vue

const path = ref('')

defineExpose({
  path,
})
# App.vue

<Test ref="testRef" />

const testRef = ref<InstanceType<typeof Test> | null>(null)

// 这里的path已经不需要.value了
console.log('testRef', testRef.value.path)

ref的适用场景

<AddDirForm ref="addDirFormRef" />

可以通过addDirFormRef读取表单里面的值,这样可以隔离逻辑

封装弹层时,不要使用ref来做关闭和打开逻辑

这样会使情况变的复杂,常规的做法是用props做打开关闭控制

# App.vue

<Modal :visible="visible">
    // Content里面的逻辑不会初始化加载,他只会在visible为true时,才会加载
    <Content />
</Modal>

const visible = ref(false)
# Modal.vue

<div class="modal" v-if="visible">
    <slot />
</div>

interface Props {
  visible: boolean
}
defineProps<Props>()

如果一个函数的参数是ref创建的类型,需要用Ref类型标记

import type { Ref } from "vue";
import { ref } from 'vue'

const count = ref(0)

// Ref<number>
const update = (count: Ref<number>) => {
    count.value = count.value + 1
}

pinia创建的store不能被解构,解构了就失去了响应式的能力

import { defineStore } from 'pinia';

const useCounterStore = defineStore('counter', () => {
  const count = ref(0);

  const increment = () => {
    count.value++;
  };

  return { count, increment };
});

// 错误
// count已经失去了响应式的能力
const { count, increment } = useCounterStore();

// 正确
const store = useCounterStore();
// store.count or store.increment

vue3的reactive是为了解决什么问题?

下面两种写法有什么区别?
难道只有一个写.value和不写的差异?

reactive能做的,ref都能做,所以目前我还找不到必须使用reactive的场景。

<script setup lang="ts">
import { ref, reactive } from 'vue'

const user1 = ref({
    name: ''
})
const user2 = reactive({
    name: ''
})
</script>

表单弹层的最佳实践

核心目标:
运用最少知识原则,将表单的逻辑封装在独立的组件中,避免和弹层逻辑交织。

实现思路:
表单自闭环,暴露一个handleSubmit方法给外部调用。

表单逻辑

<template>
  <div>
    username:
    <input v-model="username" />
  </div>
  <div>
    password:
    <input v-model="password" />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const randomBoolean = () => Math.random() < 0.5;

const api = {
  login: (value) => {
    return new Promise((resolve, reject) => {
      console.log(`发起登录请求:${value.username}-${value.password}`);
      setTimeout(() => {
        randomBoolean() ? resolve() : reject();
      }, 2000);
    });
  },
};

const username = ref<string>('');
const password = ref<string>('');

const handleSubmit = async () => {
  // 这里需要进行用户输入校验,校验通过才执行接口调用
  // 如果校验不通过,要避免弹窗关闭
  if (!username.value) {
      return;
  }

  if (!password.value) {
      return;
  }

  await api.login({
    username: username.value,
    password: password.value,
  });
};

defineExpose({
  handleSubmit,
});
</script>

弹层逻辑

<template>
  <el-button plain @click="dialogVisible = true">
    Click to open the Dialog
  </el-button>
  <el-dialog v-model="dialogVisible" title="Tips" width="500">
    <LoginForm ref="myLoginFormRef" />
    <template #footer>
      <div class="dialog-footer">
        <el-button @click="handleClose">Cancel</el-button>
        <el-button type="primary" @click="handleConfirm" :loading="loading">
          Confirm
        </el-button>
      </div>
    </template>
  </el-dialog>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { ElMessageBox } from 'element-plus';

import LoginForm from './Form.vue';

const myLoginFormRef = ref();
const dialogVisible = ref(false);
const loading = ref(false);

const handleClose = () => {
  dialogVisible.value = false;
};

const handleConfirm = async () => {
  loading.value = true;
  try {
    await myLoginFormRef.value.handleSubmit();
    handleClose();
  } catch (err) {
    console.error(err);
  }
  loading.value = false;
};
</script>

将一个ref数据传递给子组件,子组件在watchEffect中能监听ref数据的变化吗?

结论是依然可以触发,不管多少层,他们都可以监听到上层的props变化

// App.vue

const count = ref(0)

// 更新
count.value = 1

<TestLevel-1 :count="count" />
// TestLevel-1.vue

interface Props {
  count: number;
}
defineProps<Props>();

watchEffect(() => {
  // 能够触发
  console.log('watchEffect', props.count);
});

热饭班长
3.7k 声望434 粉丝

先去做,做出一坨狗屎,再改进。