vue组合式vue2项目过渡vue3的一种友好方案,在历史项目逐步迁移到vue3中,有项目历史包袱原因,一下子升级带来的问题可能比较多,composition-api天然兼容vue2,在vue2中使用组合式API让你提前感受vue3的各种姿势,vue3已经出来 3 年了,都2022了,vue祖师爷赏饭吃,相信你跟笔者一样早已跃跃欲试。

前段时间,笔者项目已经完成升级ts组合式api,毕竟去年第 4 季度首要KPI便是升级项目业务引入ts组合式API。在一边搬砖迭代业务,一边升级项目技术栈的挑战,过程虽曲折,好在组内有大佬及时解围,常常遇到奇葩问题,算是少走弯路。本篇不做组合式API语法过渡解读,因最近一个页面需求优化,以最简单的注册业务为例,在vue2组合式API的选择中,希望给你的项目升级的过程中带来一点点帮助和思考。

正文开始...

注册页面

这是一个非常普通的一个注册流程,可以看下具体页面,第一步账号注册

信息登记

这是完成注册后,需要登记信息第二步信息登记

等待审核

在第二步完成信息登记成功后,便等待管理员审核便可成功登陆控制台

看下未升级之前的代码(vue2)版本

// Index.vue
<template>
  <a-base-page-layout>
    <i-card class="page-content-card">
      <i-steps :current="currentStep">
        <i-step v-for="step of steps" :key="step.value" :title="$t(step.label)"></i-step>
      </i-steps>
      <i-divider></i-divider>
      <!--注册-->
      <a-register v-if="currentStep === 0" class="step-one" @finish="finishRegister"></a-register>
      <!--信息登记-->
      <a-info v-if="currentStep === 1" class="step-two" @finish="finishRegisterInfo"></a-info>
      <!--等待审核-->
      <a-moderation v-if="currentStep === 2" class="step-three"></a-moderation>
    </i-card>
  </a-base-page-layout>
</template>
// Index.vue
<script>
import auth from '@/service/auth';
import { BasePageLayout } from '@/components';
import Register from './Register';
import Info from './Info';
import Moderation from './Moderation';

export default {
  components: {
    ABasePageLayout: BasePageLayout,
    ARegister: Register,
    AInfo: Info,
    AModeration: Moderation,
  },
  data() {
    return {
      currentStep: 0,
      steps: [
        {
          value: 1,
          label: 'register.steps.one',
        },
        {
          value: 2,
          label: 'register.steps.two',
        },
        {
          value: 3,
          label: 'register.steps.three',
        },
      ],
    };
  },
  methods: {
    finishRegister(result) {
      if (result.developerInfo) {
        if (result.developerInfo.enabled) {
          this.$Message.info(this.$t('register.message.registerd'));
          auth.goLogin('/');
        } else {
          this.currentStep = 2;
        }
      } else {
        this.currentStep = 1;
      }
    },
    finishRegisterInfo() {
      this.currentStep = 2;
    },
  },
  created() {
    const step = +this.$route.query.step;
    if (step) {
      this.currentStep = step;
    } else {
      this.currentStep = 0;
    }
  },
};
</script>

以上是index.vue,模板和逻辑是常用的optionstemplate方式,在vue2中看起来似乎没毛病。

我们在继续关注第一步Register.vue,具体代码如下:

模板代码

// Register.vue
<template>
  <div>
    <i-alert class="warm-prompt-alert" show-icon closable>
      <span class="warm-prompt-tips">{{ $t('register.warmPrompt.title') }}</span>
      <span slot="desc">{{ $t('register.warmPrompt.content') }}</span>
    </i-alert>
    <i-row>
      <i-col span="12" offset="6">
        <i-tabs>
          <!-- 手机号注册 -->
          <i-tab-pane :label="$t('register.tabs.phone')" icon="ios-phone-portrait">
            <i-form
              :model="registerPhoneForm"
              ref="registerPhoneForm"
              :rules="phoneRuleValidate"
              :label-width="140"
            >
              <a-fixed-autofill-password></a-fixed-autofill-password>
              <i-form-item :label="$t('register.registerForm.phone.label')" prop="phone">
                <i-input
                  v-model="registerPhoneForm.phone"
                  :placeholder="$t('register.registerForm.phone.placeholder')"
                ></i-input>
              </i-form-item>
              <i-form-item :label="$t('register.registerForm.password.label')" prop="password">
                <i-input
                  type="password"
                  v-model="registerPhoneForm.password"
                  :placeholder="$t('register.registerForm.password.placeholder')"
                ></i-input>
              </i-form-item>
              <i-form-item :label="$t('register.registerForm.rpassword.label')" prop="rpassword">
                <i-input
                  type="password"
                  v-model="registerPhoneForm.rpassword"
                  :placeholder="$t('register.registerForm.rpassword.placeholder')"
                ></i-input>
              </i-form-item>
              <i-form-item :label="$t('register.registerForm.authCode.label')" prop="authCode">
                <i-row type="flex" class="authcode-row">
                  <i-col>
                    <i-input
                      v-model="registerPhoneForm.authCode"
                      :placeholder="$t('register.registerForm.authCode.placeholder')"
                    ></i-input>
                  </i-col>
                  <i-col class="padding-left-16">
                    <a-vcode-button
                      @send-code="sendCode('phone')"
                      :disabled="!registerPhoneForm.phone"
                    ></a-vcode-button>
                  </i-col>
                </i-row>
              </i-form-item>
              <i-form-item>
                <i-checkbox v-model="registerPhoneForm.agreeAndComply">
                  {{ $t('login.agreeAndComply') }}
                  <a @click="handleProtal('/user-agreement')" class="link">
                    {{ $t('login.userAgreement') }}
                  </a><a @click="handleProtal('/privacy-policy')" class="link">
                    {{ $t('login.privacyPolicy') }}
                  </a>
                </i-checkbox>
              </i-form-item>
              <i-form-item>
                <i-button
                  type="primary"
                  @click="submitPhoneForm"
                  :disabled="!registerPhoneForm.agreeAndComply"
                >
                  {{ $t('register.registerForm.submitButtonLabel') }}
                </i-button>
              </i-form-item>
            </i-form>
          </i-tab-pane>
          <!-- 邮箱注册 -->
          <i-tab-pane :label="$t('register.tabs.email')" icon="ios-mail">
            <i-form
              :model="registerEmailForm"
              ref="registerEmailForm"
              :rules="emailRuleValidate"
              :label-width="140"
            >
              <a-fixed-autofill-password></a-fixed-autofill-password>
              <i-form-item :label="$t('register.registerForm.email.label')" prop="email">
                <i-input
                  v-model="registerEmailForm.email"
                  :placeholder="$t('register.registerForm.email.placeholder')"
                ></i-input>
              </i-form-item>
              <i-form-item :label="$t('register.registerForm.password.label')" prop="password">
                <i-input
                  type="password"
                  v-model="registerEmailForm.password"
                  :placeholder="$t('register.registerForm.password.placeholder')"
                ></i-input>
              </i-form-item>
              <i-form-item :label="$t('register.registerForm.rpassword.label')" prop="rpassword">
                <i-input
                  type="password"
                  v-model="registerEmailForm.rpassword"
                  :placeholder="$t('register.registerForm.rpassword.placeholder')"
                ></i-input>
              </i-form-item>
              <i-form-item :label="$t('register.registerForm.authCode.label')" prop="authCode">
                <i-row type="flex" class="authcode-row">
                  <i-col>
                    <i-input
                      v-model="registerEmailForm.authCode"
                      :placeholder="$t('register.registerForm.authCode.placeholder')"
                    ></i-input>
                  </i-col>
                  <i-col class="padding-left-16">
                    <a-vcode-button
                      @send-code="sendCode('email')"
                      :disabled="!registerEmailForm.email"
                    ></a-vcode-button>
                  </i-col>
                </i-row>
              </i-form-item>
              <i-form-item>
                <i-checkbox v-model="registerEmailForm.agreeAndComply">
                  {{ $t('login.agreeAndComply') }}
                  <a @click="handleProtal('/user-agreement')" class="link">
                    {{ $t('login.userAgreement') }}
                  </a><a @click="handleProtal('/privacy-policy')" class="link">
                    {{ $t('login.privacyPolicy') }}
                  </a>
                </i-checkbox>
              </i-form-item>
              <i-form-item>
                <i-button
                  type="primary"
                  @click="submitEmailForm"
                  :disabled="!registerEmailForm.agreeAndComply"
                >
                  {{ $t('register.registerForm.submitButtonLabel') }}
                </i-button>
              </i-form-item>
            </i-form>
          </i-tab-pane>
        </i-tabs>
      </i-col>
    </i-row>
  </div>
</template>

js代码

// Register.vue
<script>
import md5 from 'blueimp-md5';
import { commonRegexp } from '@/utils/index';
import { VcodeButton, FixedAutofillPassword } from '@/components';
import { handlegetRegistVCodeProxy, handleRegisterProxy } from '@/service/proxy/index';

export default {
  components: {
    AVcodeButton: VcodeButton,
    AFixedAutofillPassword: FixedAutofillPassword,
  },
  data() {
    return {
      registerPhoneForm: {
        phone: '',
        password: '',
        rpassword: '',
        authCode: '',
        agreeAndComply: true,
      },
      phoneRuleValidate: {
        phone: [
          {
            required: true,
            validator: (rule, value, callback) => {
              if (value === '') {
                callback(new Error(this.$t('register.registerForm.phone.emptyMessage')));
              } else if (!commonRegexp.MOBILE_REG_EXP.test(value)) {
                callback(new Error(this.$t('register.registerForm.phone.regCheckMessage')));
              } else {
                callback();
              }
            },
            trigger: 'blur',
          },
        ],
        password: [
          {
            required: true,
            validator: (rule, value, callback) => {
              if (value === '') {
                callback(new Error(this.$t('register.registerForm.password.emptyMessage')));
              } else if (!commonRegexp.PASSWORD_REG_EXP.test(value)) {
                callback(new Error(this.$t('register.registerForm.password.regCheckMessage')));
              } else {
                callback();
              }
            },
            trigger: 'blur',
          },
        ],
        rpassword: [
          {
            required: true,
            validator: (rule, value, callback) => {
              if (value === '') {
                callback(new Error(this.$t('register.registerForm.rpassword.emptyMessage')));
              } else if (value !== this.registerPhoneForm.password) {
                callback(new Error(this.$t('register.registerForm.rpassword.notMatchMessage')));
              } else {
                callback();
              }
            },
            trigger: 'blur',
          },
        ],
        authCode: [
          {
            required: true,
            validator: (rule, value, callback) => {
              if (value === '') {
                callback(new Error(this.$t('register.registerForm.authCode.emptyMessage')));
              } else {
                callback();
              }
            },
            trigger: 'blur',
          },
        ],
      },
      registerEmailForm: {
        email: '',
        password: '',
        rpassword: '',
        authCode: '',
        agreeAndComply: true,
      },
      emailRuleValidate: {
        email: [
          {
            required: true,
            validator: (rule, value, callback) => {
              if (value === '') {
                callback(new Error(this.$t('register.registerForm.email.emptyMessage')));
              } else if (!commonRegexp.EMAIL_REG_EXP.test(value)) {
                callback(new Error(this.$t('register.registerForm.email.regCheckMessage')));
              } else {
                callback();
              }
            },
            trigger: 'blur',
          },
        ],
        password: [
          {
            required: true,
            validator: (rule, value, callback) => {
              if (value === '') {
                callback(new Error(this.$t('register.registerForm.password.emptyMessage')));
              } else if (!commonRegexp.PASSWORD_REG_EXP.test(value)) {
                callback(new Error(this.$t('register.registerForm.password.regCheckMessage')));
              } else {
                callback();
              }
            },
            trigger: 'blur',
          },
        ],
        rpassword: [
          {
            required: true,
            validator: (rule, value, callback) => {
              if (value === '') {
                callback(new Error(this.$t('register.registerForm.rpassword.emptyMessage')));
              } else if (value !== this.registerEmailForm.password) {
                callback(new Error(this.$t('register.registerForm.rpassword.notMatchMessage')));
              } else {
                callback();
              }
            },
            trigger: 'blur',
          },
        ],
        authCode: [
          {
            required: true,
            validator: (rule, value, callback) => {
              if (value === '') {
                callback(new Error(this.$t('register.registerForm.authCode.emptyMessage')));
              } else {
                callback();
              }
            },
            trigger: 'blur',
          },
        ],
      },
    };
  },
  methods: {
    handleProtal(path) {
      this.$router.push(path);
    },
    async sendCode(type) {
      const params = {};
      if (type === 'phone') {
        params.phoneNum = this.registerPhoneForm.phone;
        params.countryCode = '+86';
      } else if (type === 'email') {
        params.email = this.registerEmailForm.email;
      }
      params.authCodeType = 0; // 0 注册 1验证码登陆 2 找回密码
      try {
        await handlegetRegistVCodeProxy(params);
        this.$Message.success({
          content: this.$t('common.message.success'),
        });
      } catch (err) {
        throw err;
      }
    },
    async _doRegister(params) {
      const res = await handleRegisterProxy(params);
      console.log(res, 'register');
      this.$emit('finish', res);
    },
    submitPhoneForm() {
      const {
        registerPhoneForm: { phone: account, password, authCode },
      } = this;
      this.$refs.registerPhoneForm.validate(valid => {
        if (valid) {
          const params = {
            account,
            password: md5(password),
            authCode,
          };
          this._doRegister(params);
        }
      });
    },
    submitEmailForm() {
      const {
        registerEmailForm: { email: account, password, authCode },
      } = this;
      this.$refs.registerEmailForm.validate(valid => {
        if (valid) {
          const params = {
            account,
            password: md5(password),
            authCode,
          };
          this._doRegister(params);
        }
      });
    },
  },
};
</script>

这个页面代码如此冗余,我们发现有非常多重复的东西,用了两个tab切换两种不同方式注册,但实际上发现手机注册邮箱注册最后调用接口都是一样的,所以冗余代码有些多,代码虽长了些,好在能改得动。

升级后代码(组合式 API)

jsxcomposition-api重构了这个页面,减少了很多不必要的代码

新重构Index.vue模板代码

// Index.vue
<script lang="tsx">
import { defineComponent, SetupContext, watch, onMounted } from '@vue/composition-api';
import Qs from 'qs';
import auth from '@/service/auth';

import { BasePageLayout } from '@/components';
import { useStepConfig } from './hooks';
import Register from './Register.vue';
import Info from './Info.vue';
import Moderation from './Moderation.vue';
import AuditFail from './AuditFail.vue';

export default defineComponent({
  components: {
    ABasePageLayout: BasePageLayout,
    ARegister: Register,
    AInfo: Info,
    AModeration: Moderation,
    AuditFail,
  },
  setup(props, ctx: SetupContext) {
    const { currentStep, steps } = useStepConfig();
    const { root } = ctx;
    // 完成注册
    const finish = (result: PlainObj, setType: string) => {
      if (setType === 'regiter') {
        if (result.developerInfo) {
          if (result.developerInfo.enabled) {
            root.$Message.info(root.$t('register.message.registerd'));
            auth.goLogin('/');
          } else {
            root.$router.replace('/register?step=2');
          }
        } else {
          root.$router.replace('/register?step=1');
        }
      } else {
        // const { step, status } = result;
        const params = Qs.stringify(result, { addQueryPrefix: true });
        root.$router.push(`/register${params}`);
      }
    };

    watch(
      () => root.$route,
      (val): void => {
        const {
          query: { step = 0 },
        } = val;
        currentStep.value = step ? Number(step) : 0;
      }
    );
    onMounted(() => {
      const step = +root.$route.query.step;
      currentStep.value = step || 0;
    });
    return {
      currentStep,
      steps,
      finish,
    };
  },
  render() {
    const { currentStep, steps, finish } = this;
    const CurentComponent: any = (): JSX.Element => {
      const ret: {
        [key: number]: JSX.Element;
      } = {
        0: <a-register class="step-one" onFinish={finish}></a-register>,
        1: <a-info class="step-two" onFinish={finish}></a-info>,
        2: <a-moderation class="step-three"></a-moderation>,
      };
      return ret[currentStep];
    };
    return (
      <div class="register">
        <a-base-page-layout>
          <a-card class="page-content-card">
            {currentStep === 3 ? (
              <audit-fail onFinish={finish}></audit-fail>
            ) : (
              <div>
                <i-steps current={currentStep}>
                  {steps.map(v => (
                    <i-step key={v.value} title={v.label.value}></i-step>
                  ))}
                </i-steps>
                {[1, 2].includes(currentStep) ? (
                  <i-divider style="width:auto;min-width:80%;margin:30px 60px;"></i-divider>
                ) : null}
                <CurentComponent />
              </div>
            )}
          </a-card>
        </a-base-page-layout>
      </div>
    );
  },
});
</script>

由页面结构来看,其实与未升级前并没有发生多大变化,就是第一步注册操作、第二步信息登记,第三部等待审核。不过注意页面上还有一个状态currentStep=3的条件,这是一个等待审核被拒绝的页面状态。页面每个步骤的阶段显示都是通过路由的currentStep来做判断标识。

重构后后新注册页面模板代码

// Register.vue
<template>
  <div>
    <i-alert class="warm-prompt-alert">
      <span slot="desc">
        {{ $t('register.warmPrompt.title') }}{{ $t('register.warmPrompt.content') }}
        <a href="javascript:void(0)" style="color: #4754ff" @click="handleLogin">
          {{ $t('register.warmPrompt.accountLogin') }}
        </a>
      </span>
    </i-alert>
    <i-row>
      <i-col span="12" offset="6">
        <i-form :model="formParams" :rules="rulesConfig" ref="form" :label-width="140">
          <a-fixed-autofill-password></a-fixed-autofill-password>
          <i-form-item :label="$t('register.registerForm.useServer.label')" prop="useServer">
            <i-select
              style="width: 320px"
              v-model="formParams.useServer"
              :placeholder="$t('register.registerForm.useServer.placeholder')"
            >
              <i-option
                v-for="item in useServerListOption"
                :value="item.value"
                :key="item.value"
                :label="$t(item.label)"
              ></i-option>
            </i-select>
          </i-form-item>
          <i-form-item :label="$t('register.registerForm.account.label')" prop="account">
            <i-input
              v-model.trim="formParams.account"
              clearable
              :placeholder="$t('register.registerForm.account.placeholder')"
            ></i-input>
          </i-form-item>
          <i-form-item :label="$t('register.registerForm.password.label')" prop="password">
            <i-input
              type="password"
              clearable
              v-model.trim="formParams.password"
              :placeholder="$t('register.registerForm.password.placeholder')"
            ></i-input>
          </i-form-item>
          <i-form-item :label="$t('register.registerForm.rpassword.label')" prop="rpassword">
            <i-input
              type="password"
              clearable
              v-model="formParams.rpassword"
              :placeholder="$t('register.registerForm.rpassword.placeholder')"
            ></i-input>
          </i-form-item>
          <i-form-item :label="$t('register.registerForm.authCode.label')" prop="authCode">
            <i-row type="flex" class="authcode-row">
              <i-col>
                <i-input
                  v-model="formParams.authCode"
                  clearable
                  :placeholder="$t('register.registerForm.authCode.placeholder')"
                ></i-input>
              </i-col>
              <i-col class="padding-left-16">
                <a-vcode-button
                  :sendCode="sendCode"
                  :disabled="!formParams.account"
                ></a-vcode-button>
              </i-col>
            </i-row>
          </i-form-item>
          <i-form-item>
            <i-checkbox v-model="formParams.agreeAndComply">
              {{ $t('login.agreeAndComply') }}
              <a @click="handleProtal('/user-agreement')" class="link">
                {{ $t('login.userAgreement') }}
              </a><a @click="handleProtal('/privacy-policy')" class="link">
                {{ $t('login.privacyPolicy') }}
              </a>
            </i-checkbox>
          </i-form-item>
          <i-form-item>
            <i-button
              style="width: 100%"
              type="primary"
              @click="handleSubmit"
              :disabled="!formParams.agreeAndComply"
            >
              {{ $t('register.registerForm.submitButtonLabel') }}
            </i-button>
          </i-form-item>
        </i-form>
      </i-col>
    </i-row>
  </div>
</template>

我们发现模板页面少了邮箱与手机号的区别,这是因为把手机号与邮箱统称为账号了,笔者认为在需求阶段应该就能考虑到,由于接口设计原因,邮箱和手机号为一个字段,但是前端表现形式不一样,譬如涉及邮箱和手机号的正则校验,因此,在需求与编码阶段,你是否会走第一种方案?还是说以后端一个字段设计为准,在视图层里,你不要那么明确的给用户两种方式选择。有更好的选择,如果设计如此,我们可以与产品设计沟通,因为只要你有理由说服了他们,那么就会增加代码的可复用度,降低冗余代码的堆积,从而减少维护成本。

重构后新注册js代码

// Register.vue
<script lang="tsx">
import { defineComponent, SetupContext, computed } from '@vue/composition-api';
import { Form } from 'view-design';
import md5 from 'blueimp-md5';
import { commonRegexp } from '@/utils/index';
import { VcodeButton, FixedAutofillPassword } from '@/components';
import useServerListOptionMinx from '@/mixins/useServerListOptionMinx';
import { handlegetRegistVCodeProxy, handleRegisterProxy } from '@/service/proxy/index';
import { useRegister } from './hooks';
interface formParamsType {
  account: string;
  password: string;
  authCode: string | number;
  useServer: number | string;
}
export default defineComponent({
  components: {
    AVcodeButton: VcodeButton,
    AFixedAutofillPassword: FixedAutofillPassword,
  },
  mixins: [useServerListOptionMinx],
  setup(props: any, ctx: SetupContext) {
    const { formParams, rules } = useRegister();
    const { refs, emit, root } = ctx;
    const rulesConfig = computed(() => rules.value);
    const doRegister = async (params: formParamsType) => {
      const res = await handleRegisterProxy(params);
      emit('finish', res, 'regiter');
    };
    const handleProtal = (path: string) => {
      root.$router.push(path);
    };
    const handleSubmit = () => {
      const { account, password, authCode, useServer } = formParams.value;
      (refs.form as InstanceType<typeof Form>).validate((valid: Boolean) => {
        if (valid) {
          const params: formParamsType = {
            account,
            password: md5(password),
            authCode,
            useServer,
          };
          doRegister(params);
        }
      });
    };
    // 发送验证码
    const sendCode = async (callback: Function) => {
      const { account } = formParams.value;
      const params: {
        phoneNum?: string | number;
        email?: string;
        authCodeType: number;
      } = {
        authCodeType: 0, // 0 注册 1验证码登陆 2 找回密码
      };
      if (commonRegexp.PHONE_REG_EXP.test(account)) {
        params.phoneNum = account;
        //    params.countryCode = '+86';
      } else if (commonRegexp.EMAIL_REG_EXP.test(account)) {
        params.email = account;
      }
      try {
        await handlegetRegistVCodeProxy(params);
        root.$Message.success({
          content: root.$t('common.message.success'),
        } as any);
        callback(true);
      } catch (err) {
        callback(false);
        throw err;
      }
    };
    const handleLogin = () => {
      root.$router.push('/login');
    };
    return {
      formParams,
      rulesConfig,
      handleSubmit,
      handleProtal,
      sendCode,
      handleLogin,
    };
  },
});
</script>

不知道你注意一段代码没有,以前的表单校验ruleformParams全部从useRegister解构了出来,在vue3大量的api都是用hooks的思想写的,与react越来越相似,在react中,函数式组件,hooks极大的解耦了业务组件,React 构建的页面思想就是像搭积木一样,每个视图模块就是一个组件,在vue2之前虽然提供了render渲染组件,但是对于像react一样天然支持jsx的能力还是非常欠缺,虽然在vue也可以申明函数组件,也提供的template模板的方式。但是composition-api除了支持jsx,有更大的ts能力,让你组织你的代码,更强壮,可维护性更强,业务逻辑能进一步复用并减少耦合。

接下来我们来看下useRegister这个引入的hook,我们通常把这个方法有个更优雅的名字来定义它useXXXX,也就是类比 react 中的hook

// hooks/index.ts
import { reactive, toRefs, computed } from '@vue/composition-api';
import i18n from '@/i18n/index';
import { commonRegexp } from '@/utils/index';

// step进度条
export const useStepConfig = () => {
  const setUpConfig = reactive({
    currentStep: 0,
    steps: [
      {
        value: 1,
        label: computed(() => i18n.t('register.steps.one')),
      },
      {
        value: 2,
        label: computed(() => i18n.t('register.steps.two')),
      },
      {
        value: 3,
        label: computed(() => i18n.t('register.steps.three')),
      },
    ],
  });
  return {
    ...toRefs(setUpConfig),
  };
};
// 账号注册
export const useRegister = () => {
  const registeConfig = reactive({
    formParams: {
      account: '',
      password: '',
      rpassword: '',
      authCode: '',
      agreeAndComply: true,
      useServer: '',
    },
    rules: {
      useServer: [
        {
          required: true,
          trigger: 'change',
          validator: (_rule: any, value: string, callback: Function) => {
            if (value === '') {
              callback(new Error(i18n.t('register.registerForm.useServer.message')));
            } else {
              callback();
            }
          },
        },
      ],
      account: [
        {
          required: true,
          trigger: 'blur',
          validator: (_rule: any, value: string, callback: Function) => {
            if (value === '') {
              callback(new Error(i18n.t('register.registerForm.account.emptyMessage')));
            } else if (
              !commonRegexp.MOBILE_REG_EXP.test(value) &&
              !commonRegexp.EMAIL_REG_EXP.test(value)
            ) {
              callback(new Error(i18n.t('register.registerForm.account.message')));
            } else {
              callback();
            }
          },
        },
      ],
      password: [
        {
          required: true,
          validator: (_rule: any, value: string, callback: Function) => {
            if (value === '') {
              callback(new Error(i18n.t('register.registerForm.password.emptyMessage')));
            } else if (!commonRegexp.PASSWORD_REG_EXP.test(value)) {
              callback(new Error(i18n.t('register.registerForm.password.regCheckMessage')));
            } else {
              callback();
            }
          },
          trigger: 'blur',
        },
      ],
      rpassword: [
        {
          required: true,
          validator: (_rule: any, value: string, callback: Function) => {
            if (value === '') {
              callback(new Error(i18n.t('register.registerForm.rpassword.emptyMessage')));
            } else if (value !== registeConfig.formParams.password) {
              callback(new Error(i18n.t('register.registerForm.rpassword.notMatchMessage')));
            } else {
              callback();
            }
          },
          trigger: 'blur',
        },
      ],
      authCode: [
        {
          required: true,
          validator: (_rule: any, value: string, callback: Function) => {
            if (value === '') {
              callback(new Error(i18n.t('register.registerForm.authCode.emptyMessage')));
            } else {
              callback();
            }
          },
          trigger: 'blur',
        },
      ],
    },
  });
  return {
    ...toRefs(registeConfig),
  };
};
// 信息填写
export const userRegistInfo = () => {
   ...
   return {

   }
}

这个hook文件已经把三个步奏用到的数据层已经高度的分离了出去,在实际业务中,你并不一定需要写在一个文件中,如果涉及多人合作,那么你可以把index里面拆分得更细些,比如这里你差分成三个不同ts文件userRegistInfo.tsuseRegister.tsuseStepConfig.ts,我们把每一块自己需要数据写入自己相关的hooks中,这样每个人只需要维护自己那份代码就行。

看到这里你是否感受到composition-api的思想呢,在vue3中,所有的api用法几乎与composition-api用法一样,在官方有这么一段话,当迁移到 Vue 3 时,只需简单的将 @vue/composition-api 替换成 vue 即可。你现有的代码几乎无需进行额外的改动。。看到这里,你情不自禁的发出尖叫,vue3向下兼容了vue2,并且当你用composition-api过渡vue3时,我只需要全局替换一下@vue/composition-api这个就可以全量升级到vue3了。

此时你心中有没有被震惊到,赶紧升级你项目的vue2,让你自己在vue2的项目中也能畅游vue3的各种姿势,哈哈。

总结

1.在 vue2 中使用options面条方式编码,业务页面有冗余代码,当我们发现字段设计与交互有差别时,可以与产品设计沟通,用你的理由说服他

2.在 vue2 中用composition-api方式组织你的业务代码时,明显感受到业务逻辑比以前更清晰,并且天然支持ts,让你的代码更安全,更强壮 💪

3.类似 react 的hook思想,高度解耦业务视图层的数据逻辑,让你更专注解决疑难杂症,或者有更多的时间轻松聊天喝茶摸鱼。

4.更多关于composition-apiopen in new window,更多 vue3 参考官网open in new window

扫二维码,关注公众号
专注前端技术,分享web技术
加作者微信
扫二维码 备注 【加群】
教你聊天恋爱,助力脱单
微信扫小程序二维码,助你寻他/她
专注前端技术,分享Web技术
微信扫小程序二维码,一起学习,一起进步
前端面试大全
海量前端面试经典题,助力前端面试