万众期待的Vue3在9月18号发布了正式版本,代号为One Piece,看过海贼的都知道One Piece是大秘宝,也就意味着这次从2.x到3.x的升级有很多的亮点和惊喜等着去发现。比如说全新的 Proxy
架构, Composition API
,对 TypeScript
的支持。而且这次升级学习了 react
时下最热的 hooks
特性,让用户在选择框架的时候也少了更多的心智负担。我们就来通过一个 TodoList
组件案例来看看 React Hooks
与 Composition API
之间的对比。
首先贴上 Hooks
和 Composition API
的文档,还没具体看过API的可以去了解一下:
开发体验
开始先来创建项目,这里我们试了下尤大之前提到的一个工具, vite
,这是一个由原生ESM驱动的Web开发构建工具,在开发环境下基于浏览器的原生 ES imports
进行开发,生成环境基于 Rollup
打包。
vite: github.com/vitejs/vite
具有以下特点:
- 轻量级快速冷启动
- 模块热更新
- 真正的按需加载
不得不说,这个工具的特点真的很 vue
,轻量,快读,易上手。
React
方面,我们选用了蚂蚁金服团队提供的 UmiJS
,一个插件化的 React
前端应用框架。
UmiJs: umijs.org/zh-CN/docs/…
Umi
内置了路由、构建、部署、测试等一系列的开发生产模块,仅需要一个依赖即可上手开发,结合 antd
使用起来也是相当优雅,可以说是一站式的 react
应用框架。
这边为了方便对比实际开发的体验,我们针对同一个业务场景(TodoList)做两套项目(Vue和React),并分别使用了 Composition API
和 React Hooks
。
从UI上来看其实是没有差别的,毕竟使用了相同的CSS,Vue使用的是 Template
方式,React使用的是 JSX
引入css的方式。我个人可能会更倾向于使用额外引入css的方式,管理起来会更加清晰,vue内也是支持的。
功能代码
Hooks风格
function TodoList({
dataSource,
onChange
}: TProps) {
const [itemName, setItemName] = useState('')
const handleChangeItemName = (e: any) => {
setItemName(e.target.value);
}
const handleAddItem = () => {
const newList = [
...dataSource,
{
name: itemName,
hasDo: false
}
]
setItemName('')
onChange(newList)
}
const handleItemStatus = (value: boolean, index: number) => {
const newList = dataSource.map((item, tIndex) => {
if (tIndex === index) {
return {
...item,
hasDo: value
}
}
return item
})
onChange(newList)
}
const handleRemove = (index: number) => {
const newList = dataSource.filter((item, tIndex) => tIndex !== index)
onChange(newList)
}
return (
<div>
<div className="input-line">
...内容输入
</div>
<div className="todo-list">
...列表渲染
</div>
</div>
)
}
首先我们来看下 React
内function组件 Hooks
的写法,react组件的特点就是每次状态发生改变都会更新虚拟DOM,并重新渲染一遍,函数式组件内因为this指向的问题,所以在之前的版本里无法做到状态管理,这一次的 hooks
加入了 useState
进行状态管理,本质上就是利用了JS闭包的特性,将状态进行了持久化的保存。另外,改变状态也不能用之前的 this.setState
,只能使用useState结果中解构的第二个参数,进行状态更新。
Hooks
也是 react
未来发展的一个方向,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码给“钩”进去。同时,也建议把一些常用的 hooks
业务逻辑封装成自定义 hooks
,方便项目庞大后的统一业务管理,社区里也有很多 hooks
方面的沉淀,比如说 ahooks
就提供了常用的高质量的 hooks
库, react-table7.x
的使用就全面拥抱了 hooks
。
自定义Hooks:reactjs.org/docs/hooks-…
Composition API风格
export default {
name: 'TodoList',
props: {
dataSource: Array,
// onChange: Function
},
setup() {
const itemName = ref('')
const handleAddItem = (dataSource) => {
dataSource.push({
name: itemName.value,
hasDo: false
})
itemName.value = ''
}
const handleItemStatus = (index, dataSource) => {
dataSource[index].hasDo = !dataSource[index].hasDo
}
const handleRemove = (index, dataSource) => {
dataSource.splice(index, 1)
}
return {
itemName,
handleAddItem,
handleItemStatus,
handleRemove
}
}
}
我们来看看Vue3.0提供的 Composition API
,用起来就是定义一个 setup
函数,这个函数相当于在 beforeCreated
和 created
这两个生命周期,然后将一些属性赋予成响应式监听的能力,从而返回输出至视图层,这样数据就有了双向绑定的能力。
其实对比 React
, vue
在以下几个方面看起来会方便:
- 使用
v-model
双向绑定了以后,就不需要关心Input的onChange
事件处理 - 在改变状态的时候,react是setState然后进行重新渲染,性能上要做很多的
useMemo
的优化,vue可以直接修改属性值:ref.value = ''
,而且并不会触发重新渲染。 - react为了保证
diff
算法的比较,修改一些数组的时候会比较麻烦,要用到rest风格,甚至用一些immutable
,immer
库来辅助,vue因为proxy
的原因,可以直接做修改。
Composition API
针对之前的 Option API
风格上很大程度上借鉴了 Hooks
的设计理念,那就是业务驱动代码,把每一个小的业务模块进行集中化管理,并且可以使用自定义 hooks
的方式,进行业务逻辑的复用。
生命周期
function Todo () {
const [todo, setTodo] = useState<Todo[]>([])
useEffect(() => {
async function fetchData() {
const info = await request('/api/todolist')
setTodo(info)
}
fetchData()
return () => {
...// 做一些清理工作
}
}, [])
return (
<TodoList
dataSource={todo}
onChange={(val: Todo[]) => {
setTodo(val)
}}
/>
)
}
我们在 React
的父组件里增加了一个初始化数据的逻辑,调用了 useEffect
方法,第二个参数传入[]就等同于class组件内的 componentDidMount
生命周期,当return一个函数出来的时候,这个函数会在组件销毁的时候执行。不过需要注意的一点是:hooks不能加载if判断中,这样会导致每次组件渲染的混乱。
export default {
name: 'App',
components: {
TodoList
},
setup() {
const dataSource = ref([])
onMounted(() => {
async function fetchData() {
const info = await request('/api/todolist')
dataSource.value = info
}
fetchData()
})
return {
dataSource
}
}
}
同样的业务逻辑,我们看到 Composition API
就是在 onMounted
函数里传入一个包含处理逻辑的函数,进行生命周期的挂载,如果从语义化的角度来说, Composition API
会更加简单明了,从 Options API
迁移到Composition API
也更加的平滑,风格上, hooks
和Composition API
也是比较接近的。
Vue2 | Vue3 |
---|---|
beforeCreated | setup |
created | setup |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroy | onBeforeUnmount |
destroyed | onUnmounted |
errorCaptured | onErrorCaptured |
这边我们也列出了Vue2和Vue3的生命周期函数对比。
性能比较
那么Vue3的性能怎么样呢,我这里做了一个小实验,创建了一个长度为10000的列表数据来进行初始化,然后再添加新的数据。
const list = []
for(let i = 0; i < 10000; i++) {
list.push({
name: `${i}--test plan`,
hasDo: false
})
}
左边这个是用 Vue Composition API
的检测结果,右边是 React hooks
的结果,两边的代码其实都是没有进行优化过的,从指标上来看, Vue
占用的内存更少,CPU执行的占用也会比 React
更低,说明Vue全新的 Proxy
底层带来的性能提升还是比较显著的,当然 react
也能通过一些手段( Immutable
)进行优化。
最后
react hooks
和 Composition API
各有特色,都是很好的前端解决方案,也说明了未来的前端发展更加规范化,通用化。(文中有任何错误,欢迎大家指正)