导读
注:为行文方便,本文将对 Vue 3 的 composition api 也称为 hooks,也是目前业内比较通俗的说法。
Vue 团队于 2020 年 9 月 18 日晚 11 点半发布了 Vue 3.0 版本。大家最关注的点就是 Vue 也引入了类似 React Hooks 的概念和用法。很多人说二者越来越像了,那么它们到底有多像?哪里像?能像到什么程度? 我想没有比列出代码对比更直观的方式了,毕竟 “Talk is cheap, show me the code.” 才是程序员的风格。
本文将以 表单提交 和 列表展示 两个典型场景,来对比 Vue 3 和 React16.8+ 的代码实现。React 的代码将只使用 hooks + functional component 的形式展现;Vue 由于支持的形式更丰富,所以会有 hooks + template、hooks + JSX 两个版本,后者将是体现本文主题的关键点。
场景说明
场景一 – Form
如上图,一个最简单的表单提交场景,主要展示数据响应式的实现,具体说明如下:
- 表单有初始值,进入页面后从服务端异步获取,API 为
fetchUserInfo
- input 和 radio 实时响应用户输入
- 点击 Submit 按钮,会 alert 出当前表单展示的用户信息
场景二 – Table
如上图,一个最简单的表格展示场景,主要展示对数组的处理以及表达式的实现,具体说明如下:
- 数据从后端异步获取,API 为
fetchUserList
- sex 字段返回值为 0 或 1,列表要分别展示为 Female 或 Male
代码实现
全文的代码将全部采用 TS 实现
API & Types
此部分代码为必要的 TS 声明和模拟的后端 API,是所有例子的前置依赖。
// services.ts
export enum Sex {
female,
male,
}
export interface UserInfo {
name: string;
sex: Sex;
}
export const fetchUserInfo = (userId: string): Promise<UserInfo> =>
new Promise((rev) => {
setTimeout(() => rev({ name: "Zhang san", sex: Sex.male }), 500);
});
export const updateUserInfo = (params: UserInfo): Promise<boolean> =>
Promise.resolve(true);
export const fetchUserList = (): Promise<UserInfo[]> =>
new Promise((rev) => {
setTimeout(() => {
rev([
{ name: "Zhang san", sex: Sex.male },
{ name: "Li si", sex: Sex.female },
{ name: "Wang wu", sex: Sex.male },
]);
}, 500);
});
Vue 3 Hooks + template
我们先来看下 .vue
单文件组件的经典写法,也就是 template + script + style 的形式。本例只是做了 Vue 3 hooks 的改造,显然这根 React 还不够像,我们只是做个铺垫。
Form.vue
<template>
<div>
<div>
<div>Name</div>
<input type="text" v-model="name" />
</div>
<div>
<div>Sex</div>
<input type="radio" name="sex" :checked="maleChecked" @click="setSex(Sex.male)" />Male
<input type="radio" name="sex" :checked="femaleChecked" @click="setSex(Sex.female)" />Female
</div>
<p>
<button @click="handleSubmit">Submit</button>
</p>
</div>
</template>
<script lang="ts"> import { onMounted, ref, computed } from "vue"; import { Sex, fetchUserInfo, updateUserInfo } from "../services"; export default { setup() { const nameRef = ref(""); const sexRef = ref(Sex.male); onMounted(() => { fetchUserInfo("id-xxx").then((res) => { nameRef.value = res.name; sexRef.value = res.sex; }); }); const handleSubmit = () => { const params = { name: nameRef.value, sex: sexRef.value }; updateUserInfo(params).then((res) => { if (res) alert(JSON.stringify(params)); }); }; const setSex = (sex: Sex) => { sexRef.value = sex; }; const maleChecked = computed(() => sexRef.value === Sex.male); const femaleChecked = computed(() => sexRef.value === Sex.female); return { name: nameRef, sex: sexRef, handleSubmit, setSex, maleChecked, femaleChecked, }; }, }; </script>
从以上代码可以看出,理论上在 Vue 3 里,data、methods、computed、生命周期等这些代码,都可以不再出现在实例对象的第一层,而是全部聚合到了 setup 里。正因如此,setup 内的每一行代码都可以自由的调整位置,以达到“根据功能聚合代码”的目的,甚至可以再进一步的抽离出去,形成独立的 hooks。这也是 Vue 团队引入此功能的目的所在。详见官方文档 Why Composition API?。
另外,请注意 setSex
、maleChecked
、femaleChecked
的实现,下文会有所优化。回过头来看的话,这部分实际上有些冗余了。
Table.vue
<template>
<table :cellPadding="5" :cellSpacing="5">
<tr>
<th>Name</th>
<th>Sex</th>
</tr>
<tr v-for="user in userList" :key="user.name">
<td>{{ user.name }}</td>
<td>{{ user.sex === 0 ? "Male" : "Female" }}</td>
</tr>
</table>
</template>
<script lang="ts"> import { onMounted, ref } from "vue"; import { fetchUserList, UserInfo } from "../services"; export default { setup() { const userListRef = ref<UserInfo[]>([]); onMounted(() => { fetchUserList().then((res) => { userListRef.value = res; }); }); return { userList: userListRef, }; }, }; </script>
表格场景的代码实现非常简单,但是请注意 sex 转换成 Male/Female 部分的逻辑(template 部分)。我们看到,代码里出现了 Magic Number。这是因为 template 中无法结合 TS 导致的,否则完全可以用 Sex.male
这样的代码代替(见下文代码)。
当然,我们也可以把这个表达式的逻辑放到 setup 中,用一个 function 实现(类似 Form 例中的 maleChecked
方式),这样就可以充分利用 TS 的特性让代码变得更可读,但是会比较重,也不太优雅。这个是目前笔者认为 Vue 结合 TS 最尴尬的地方,template 与 TS 几乎无法结合。
Vue 3 + JSX vs. React 16.8+
重头戏来了,经过上面的铺垫,我们来看下 Vue3 和 JSX 在一起会起什么样的化学反应。为展示方便,下面的代码进行了格式处理并用图片展示(文末会有代码的文本版),尽量将相同功能对齐,左侧 Vue 右侧 React。下面开始“大家来找茬”:
Form
Table
像,太像了,真的太像了。起码表面上看起来,它们的相似度能达到 80%。
本文的例子非常简单,当加入 props、slot、其他生命周期等特性的时候,二者的代码实现还是会有较大差异的。笔者会另起一篇文章写,本文还是聚焦在二者像的部分。
思考
细心的朋友会注意到,文章的标题用的是“能多像”而不是“有多像”。这是因为要想让二者代码很像,Vue 必须使用 JSX 的方式,所以是需要一些主观控制才能达到“像”的目的,不过这已足够说明二者在深层次的设计理念上有绝对的相似之处。笔者无意纠结于谁抄袭谁,谁更优秀的问题,只是想学习和理解其设计理念和思想。不过人毕竟是感情动物,还是会有所偏向,下面就简单说说笔者这几年的思想变化,供大家参考。
不得不说,从 VDOM 到 Hooks,React 团队对整个行业起到了颠覆性的影响。前者催生了前端跨平台的解决方案,后者让前端项目的可维护性提高了一大截,真真让人拍案叫绝。这些理念也很快被整个行业所接受并借鉴。笔者在 React 16.8 之前,更欣赏 Vue 多一些,因为其文档与渐进式的设计理念实在做的太棒了,Vue 的学习曲线要明显比 React 平滑的多,而且全家桶使用也更简单,管理较集中(都是 Vue 团队维护),.vue
单文件写法也对于熟悉模板语法的前端同学比较友好。当时都流行说,React 适合做大型复杂项目,Vue 更适合做一些中小型项目,个人认为这种断言的说服力很有限,根本不疼不痒。
但是 React Hooks 的出现,让 React 的世界只剩下了 functional component 和 hooks 的概念,二者与 TS 的结合毫无阻碍,完美配合。
与之相对的,老版本的 class component 的方式,实际上会让 TS 的使用在 Class 这里产生断层。比如 constructor 的参数 props,不能够自动继承 class 泛型中的声明,必须手动再次声明(见下图代码注释)。虽然问题不大,但是总归不算完美。
这就造成了 hooks + jsx 的收益,明显大于放弃使用 template 所造成的不适,毕竟 template 与 TS 基本算得上是格格不入了。尤大也说过,“对 TS 的支持更友好”,是 Vue 3 的一个重大提升,相信大牛对于这点的认识肯定比笔者要深(补充:实际已经有了解决方案,详见 Vue3 + TS 最佳实践)。就像上文展示的,现在 Vue 可以有多种写法,而且它们是那么的不同,选择多同样意味着容易造成混乱,也许过段时间关于 Vue 3 的最佳实践之类的讨论会成为热点。就笔者而言,还是更倾向于拥抱 JSX + TS,因为这个组合真的能够大大提升项目的可维护性。
总结
TS 的出现,让前端代码的可读性、稳定性与可维护性有了显著的提高,可以说是提升前端生产效率的重要工具。以此为前提,前端项目如果能够充分利用 TS,将能获得可观的收益。
React Hooks 的出现,让 React 与 TS 形成了完美的配合。目前来看,Vue3 可以做到类似于 React Class Component + Hooks + TS 的效果,但是还未达到完美契合的程度,单从这点来说,Vue 算是落后于 React 一点。不过 template 作为渐进式理念的重要组成部分,有自己的优势,也很难被舍弃,个人臆测,未来 Vue 会面临 template 与 TS 的取舍抉择。当然,大牛或许有两全其美的解决方案,目前的趋势还不够明朗,我们且拭目以待。
最后,作为程序员,务实、好学是必要的品质。希望大家不要受限于“门派之见”,仰望星空,集百家之所长,努力提升自己,才是最明智的选择。
“得其大者可以兼其小,未有学其小而能至其大者也” —— 欧阳修
引用
附录
代码文本
React Hooks – Form.tsx
// Form.tsx
import React, { useState, useEffect } from "react";
import { Sex, fetchUserInfo, updateUserInfo } from "../services";
const Form = () => {
const [name, setName] = useState("");
const [sex, setSex] = useState(Sex.male);
useEffect(() => {
fetchUserInfo("id-xxx").then((res) => {
setName(res.name);
setSex(res.sex);
});
}, []);
const handleSubmit = () => {
const params = { name, sex };
updateUserInfo(params).then((res) => {
if (res) alert(JSON.stringify(params));
});
};
return (
<div> <p> <div>Name</div> <input type="text" value={name} onChange={(e) => setName(e.target.value)} /> </p> <p> <div>Sex</div> <input type="radio" name="sex" checked={sex === Sex.male} onClick={() => setSex(Sex.male)} />Male <input type="radio" name="sex" checked={sex === Sex.female} onClick={() => setSex(Sex.female)} />Female </p> <p> <button onClick={handleSubmit}>Submit</button> </p> </div>
);
};
export default Form;
React Hooks – Table.tsx
// Table.tsx
import React, { useState, useEffect } from "react";
import { Sex, UserInfo, fetchUserList } from "../services";
const Table = () => {
const [userList, setUserList] = useState<UserInfo[]>([]);
useEffect(() => {
fetchUserList().then((res) => setUserList(res));
}, []);
return (
<table cellPadding={5} cellSpacing={5}> <tr> <th>Name</th> <th>Sex</th> </tr> {userList.map(({ name, sex }) => ( <tr key={name}> <td>{name}</td> <td>{sex === Sex.male ? "Male" : "Female"}</td> </tr> ))} </table>
);
};
export default Table;
Vue 3 + JSX – Form.tsx
// Form.tsx
import { ref, onMounted, defineComponent } from "vue";
import { Sex, fetchUserInfo, updateUserInfo } from "../services";
const Form = defineComponent({
setup() {
const nameRef = ref("");
const sexRef = ref(Sex.male);
onMounted(() => {
fetchUserInfo("id-xxx").then((res) => {
nameRef.value = res.name;
sexRef.value = res.sex;
});
});
const handleSubmit = () => {
const params = { name: nameRef.value, sex: sexRef.value };
updateUserInfo(params).then((res) => {
if (res) alert(JSON.stringify(params));
});
};
return () => (
<div> <p> <div>Name</div> <input type="text" value={nameRef.value} onChange={(e) => { nameRef.value = e.target.value; }} /> </p> <p> <div>Sex</div> <input type="radio" name="sex" checked={sexRef.value === Sex.male} onClick={() => { sexRef.value = Sex.male; }} /> Male <input type="radio" name="sex" checked={sexRef.value === Sex.female} onClick={() => { sexRef.value = Sex.female; }} /> Female </p> <p> <button onClick={handleSubmit}>Submit</button> </p> </div>
);
},
});
export default Form;
Vue 3 + JSX – Table.tsx
// Table.tsx
import { ref, onMounted, defineComponent } from "vue";
import { Sex, UserInfo, fetchUserList } from "../services";
const Table = defineComponent({
setup() {
const userListRef = ref<UserInfo[]>([]);
onMounted(() => {
fetchUserList().then((res) => {
userListRef.value = res;
});
});
return () => (
<table cellpadding={5} cellspacing={5}> <tr> <th>Name</th> <th>Sex</th> </tr> {userListRef.value.map(({ name, sex }) => ( <tr key={name}> <td>{name}</td> <td>{sex === Sex.male ? "Male" : "Female"}</td> </tr> ))} </table>
);
},
});
export default Table;
今天的文章Vue 3 和 React 16.8 到底能多像分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/19051.html