Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

组合式API最佳实践 #44

Open
maicFir opened this issue Aug 11, 2022 · 0 comments
Open

组合式API最佳实践 #44

maicFir opened this issue Aug 11, 2022 · 0 comments

Comments

@maicFir
Copy link
Owner

maicFir commented Aug 11, 2022

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-api,更多 vue3 参考官网

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant