We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
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
vue组合式是vue2项目过渡vue3的一种友好方案,在历史项目逐步迁移到vue3中,有项目历史包袱原因,一下子升级带来的问题可能比较多,composition-api天然兼容vue2,在vue2中使用组合式API让你提前感受vue3的各种姿势,vue3已经出来 3 年了,都2022了,vue祖师爷赏饭吃,相信你跟笔者一样早已跃跃欲试。
vue组合式
vue2
vue3
composition-api
组合式API
2022
vue
前段时间,笔者项目已经完成升级ts、组合式api,毕竟去年第 4 季度首要KPI便是升级项目业务引入ts和组合式API。在一边搬砖迭代业务,一边升级项目技术栈的挑战,过程虽曲折,好在组内有大佬及时解围,常常遇到奇葩问题,算是少走弯路。本篇不做组合式API语法过渡解读,因最近一个页面需求优化,以最简单的注册业务为例,在vue2与组合式API的选择中,希望给你的项目升级的过程中带来一点点帮助和思考。
ts
组合式api
KPI
组合式API语法
正文开始...
这是一个非常普通的一个注册流程,可以看下具体页面,第一步账号注册
第一步账号注册
这是完成注册后,需要登记信息第二步信息登记
第二步信息登记
在第二步完成信息登记成功后,便等待管理员审核便可成功登陆控制台
// 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,模板和逻辑是常用的options和template方式,在vue2中看起来似乎没毛病。
index.vue
options
template
我们在继续关注第一步Register.vue,具体代码如下:
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切换两种不同方式注册,但实际上发现手机注册、邮箱注册最后调用接口都是一样的,所以冗余代码有些多,代码虽长了些,好在能改得动。
tab
手机注册
邮箱
用jsx与composition-api重构了这个页面,减少了很多不必要的代码
jsx
新重构Index.vue模板代码
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来做判断标识。
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能力,让你组织你的代码,更强壮,可维护性更强,业务逻辑能进一步复用并减少耦合。
rule
formParams
useRegister
api
hooks
react
render
接下来我们来看下useRegister这个引入的hook,我们通常把这个方法有个更优雅的名字来定义它useXXXX,也就是类比 react 中的hook
hook
useXXXX
// 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中,这样每个人只需要维护自己那份代码就行。
index
userRegistInfo.ts
useRegister.ts
useStepConfig.ts
看到这里你是否感受到composition-api的思想呢,在vue3中,所有的api用法几乎与composition-api用法一样,在官方有这么一段话,当迁移到 Vue 3 时,只需简单的将 @vue/composition-api 替换成 vue 即可。你现有的代码几乎无需进行额外的改动。。看到这里,你情不自禁的发出尖叫,vue3向下兼容了vue2,并且当你用composition-api过渡vue3时,我只需要全局替换一下@vue/composition-api这个就可以全量升级到vue3了。
当迁移到 Vue 3 时,只需简单的将 @vue/composition-api 替换成 vue 即可。你现有的代码几乎无需进行额外的改动。
@vue/composition-api
此时你心中有没有被震惊到,赶紧升级你项目的vue2,让你自己在vue2的项目中也能畅游vue3的各种姿势,哈哈。
1.在 vue2 中使用options面条方式编码,业务页面有冗余代码,当我们发现字段设计与交互有差别时,可以与产品设计沟通,用你的理由说服他
2.在 vue2 中用composition-api方式组织你的业务代码时,明显感受到业务逻辑比以前更清晰,并且天然支持ts,让你的代码更安全,更强壮 💪
3.类似 react 的hook思想,高度解耦业务视图层的数据逻辑,让你更专注解决疑难杂症,或者有更多的时间轻松聊天喝茶摸鱼。
4.更多关于composition-api,更多 vue3 参考官网
The text was updated successfully, but these errors were encountered:
No branches or pull requests
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
,模板和逻辑是常用的options
和template
方式,在vue2
中看起来似乎没毛病。我们在继续关注第一步
Register.vue
,具体代码如下:模板代码
js代码
这个页面代码如此冗余,我们发现有非常多重复的东西,用了两个
tab
切换两种不同方式注册,但实际上发现手机注册
、邮箱
注册最后调用接口都是一样的,所以冗余代码有些多,代码虽长了些,好在能改得动。升级后代码(组合式 API)
用
jsx
与composition-api
重构了这个页面,减少了很多不必要的代码新重构
Index.vue模板代码
由页面结构来看,其实与未升级前并没有发生多大变化,就是第一步注册操作、第二步信息登记,第三部等待审核。不过注意页面上还有一个状态
currentStep=3
的条件,这是一个等待审核被拒绝的页面状态。页面每个步骤的阶段显示都是通过路由的currentStep
来做判断标识。重构后后新注册页面
模板代码
我们发现模板页面少了邮箱与手机号的区别,这是因为把手机号与邮箱统称为账号了,笔者认为在需求阶段应该就能考虑到,由于接口设计原因,邮箱和手机号为一个字段,但是前端表现形式不一样,譬如涉及邮箱和手机号的正则校验,因此,在需求与编码阶段,你是否会走第一种方案?还是说以后端一个字段设计为准,在视图层里,你不要那么明确的给用户两种方式选择。有更好的选择,如果设计如此,我们可以与产品设计沟通,因为只要你有理由说服了他们,那么就会增加代码的可复用度,降低冗余代码的堆积,从而减少维护成本。
重构后新注册
js代码
不知道你注意一段代码没有,以前的表单校验
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
这个
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
,让你的代码更安全,更强壮 💪3.类似 react 的
hook
思想,高度解耦业务视图层的数据逻辑,让你更专注解决疑难杂症,或者有更多的时间轻松聊天喝茶摸鱼。4.更多关于composition-api,更多 vue3 参考官网
The text was updated successfully, but these errors were encountered: