组合式API
是vue2
项目过渡vue3
的一种友好方案,在历史项目逐步迁移到vue3
中,有历史包袱原因,一下子升级带来的问题可能比较多,composition-api
天然兼容vue2
,在vue2
中使用组合式API
让你提前感受vue3
的各种姿势,vue3
已经出来3年了,都2022
了,vue
祖师爷赏饭吃,相信你跟笔者一样早已跃跃欲试。
前段时间,笔者项目已经完成升级ts
、组合式API
,毕竟去年第4季度首要KPI
便是升级项目业务引入ts
和组合式API
。在一边搬砖迭代业务,一边升级项目技术栈的挑战,过程虽曲折,好在组内有大佬及时解围,常常遇到奇葩问题,算是少走弯路。本篇不做组合式API语法
过渡解读,因最近一个页面需求优化,以最简单的注册业务为例,在vue2
与组合式API
的选择中,希望能给你项目升级的过程中,带来一点点帮助和思考。
正文开始...
注册页面
这是一个非常普通的一个注册流程,可以看下具体页面,第一步账号注册
信息登记
这是完成注册后,需要登记信息第二步信息登记
等待审核
在第二步完成信息登记成功后,等待管理员审核便可成功登陆控制台
看下未升级之前的代码(vue2)版本
代码语言:javascript复制// 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>
代码语言:javascript复制// 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
,模板和逻辑是常用的options
和template
方式,在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)
用jsx
与composition-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>
不知道你注意一段代码没有,以前的表单校验rule
与formParams
全部从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.ts
、useRegister.ts
、useStepConfig.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
,让你的代码更安全,更强壮