背景
这篇文章应该非常实用,目标群体就是vue技术栈转react hook的开发人员。其实最关键的就是一个开发思路的转变,快速上手的方法就是原来我们在vue里的用到的生命周期、watch、computed等等功能对应到react hook中应该怎么写。
React Hook介绍
毕竟读这篇文章的是vue的开发人员,可能都没有了解过react hook,所以说几点概括性的总结方便理解。
- Hook 是 React 16.8 的新增特性。
- Hook写组件的方式和原来的Class写法是完全兼容的,只是多了一种写组件的方式。
- Hook给函数组件带来的状态管理的能力。
- Hook的出现和vue3的组合式API动机一样,都是能更好的抽象逻辑,方便复用和维护。
看一下Hook的所有API 列表:
不要慌,掌握以下4个就能应付大部分常规场景
Hook实现vue中的常用选项
data
vue中的data是响应式的数据,也就是data变化的时候组件可以重新渲染。 一个加减计数器的功能.
<template>
<h1>count: {{count}}</h1>
<button @click="add">add</button>
<button @click="reduce">reduce</button>
</template>
<script>
export default {
name: "App",
data: function() {
return {
count: 0
}
},
methods: {
add() {
this.count++
},
reduce() {
this.count--
}
}
};
</script>
对应到Hook中用useState实现。 如果不用useState 可能会这么写:
export default function App() {
let count = 0
return (
<div className="App">
<h1>count: {count}</h1>
<button onClick={()=>count++}>add</button>
<button onClick={()=>count--}>reduce</button>
</div>
);
}
问题来了,count就是普通变量,变化不会让这个函数组件重新渲染,就算重新渲染了作为普通的函数,函数重新执行了count也会重新变成0,不记录上一次的结果。useState就是解决这个问题的。
import {useState} from 'react'
export default function App() {
const [count, setCount] = useState(0)
return (
<div className="App">
<h1>count: {count}</h1>
<button onClick={()=>setCount(count+1)}>add</button>
<button onClick={()=>setCount(count-1)}>reduce</button>
</div>
);
}
useState接收状态的初始值,返回当前值和设置该值的方法。state的值变化时函数会重新渲染。函数在重新渲染时最新的值也能保留,理解成在该函数的外侧为这个组件记录了变量,所以变量的生命周期能不在该函数范围内。
computed
在data例子的基础上增加一个computed属性doubleCount展示count的2倍。
<template>
<h1>count: {{count}}</h1>
<h1>double count: {{doubleCount}}</h1>
<button @click="add">add</button>
<button @click="reduce">reduce</button>
</template>
<script>
export default {
name: "App",
data: function() {
return {
count: 0
}
},
computed: {
doubleCount() {
return this.count*2
}
},
methods: {
add() {
this.count++
},
reduce() {
this.count--
}
}
};
</script>
对应到Hook中可以用普通变量或者useMemo实现, 普通变量就是每次渲染都会重新执行,用useMemo的意义是可以缓存计算结果,第二个参数传入依赖项,只有第二个参数传入的依赖项变化时,才会重新计算,这样避免每次渲染都重新计算,和vue中的computed的作用是相似的,缓存计算结果,只不过vue中可以自动分析依赖,useMemo需要手动指定。
import {useState, useMemo} from 'react'
export default function App() {
const [count, setCount] = useState(0)
const doubleCount = useMemo(()=>count*2,[count])
// const doubleCount = count*2
return (
<div className="App">
<h1>count: {count}</h1>
<h1>doubleCount: {doubleCount}</h1>
<button onClick={()=>setCount(count+1)}>add</button>
<button onClick={()=>setCount(count-1)}>reduce</button>
</div>
);
}
methods
vue中的methods就是声明能给模版中使用的的函数,对应到Hook中可以使用普通函数和useCallBack实现,和useMemo类似,普通函数就是每次渲染都创建新的函数,useCallBack的意义就是能够指定在依赖项的值变化时才会重新创建新的函数。这对react中一些根据引用值判断子组件是否重新渲染比较有用,也属于性能优化的方法。
import {useState, useCallback} from 'react'
export default function App() {
const [count, setCount] = useState(0)
function add() {
setCount(count+1)
}
const reduce = useCallback(()=>{
setCount(count-1)
},[count])
return (
<div className="App">
<h1>count: {count}</h1>
<button onClick={add}>add</button>
<button onClick={reduce}>reduce</button>
</div>
);
}
生命周期
vue中几个常用的就三个生命周期 mounted、updated、unmounted, 。对应使用的Hook是useEffect。
useEffect有2个参数,第一个参数是执行的函数,第二个参数是执行的依赖。执行的时机是组件渲染到屏幕之后执行,如果第二个参数没有,那就每次渲染都执行,如果第二个参数指定了依赖,那就依赖变化的时候执行,如果第二个依赖是空数组,那就只有第一次渲染的之后执行。 useEffect的返回值是当第一个参数重新执行之前会执行上一次的返回值函数,用于清除上一次执行的副作用。
对应生命周期的实现如下,beforeCreated和created本身用的场景也不多,mounted就够了。 这个里边还用了一个Hook 是 useRef, 这个Hook可以理解为就是在this上创建了一个变量,记录状态。和useState的区别就是它不是响应式的。
import { useState, useEffect, useRef, memo } from "react";
export default function Com(props){
const {count} = props
// beforeCreated 拿不到任何状态,基本不用
const beforeCreated = useRef(false);
if (!beforeCreated.current) {
console.log("beforeCreated");
beforeCreated.current = true;
}
const [stateA] = useState(0);
// created可以拿到初始状态
const created = useRef(false);
if (!created.current) {
console.log("created", stateA);
created.current = true;
}
const mounted = useRef(false);
useEffect(() => {
if (mounted.current) {
console.log("mounted之后的 effect 是updated");
}
});
//mounted在第一次渲染之后
useEffect(() => {
mounted.current = true;
console.log("mounted");
return () => {
// 卸载组件会执行useEffect的返回函数
console.log("unmounted");
};
}, []);
return <h1>sub component{stateA} {count}</h1>;
}
还有一个问题是vue对自动识别组件依赖的属性而出发updated,而react默认是父组件渲染子组件也会渲染,不会判断子组件是否没变化, 所以就会额外的出发updated, 我们可以使用React.memo方法可以做优化, 默认只有当属性的值变化时才会触发子组件的渲染。
import { memo } from "react";
export default memo(props=>{
})
watch
vue中默认的immediate是false,也就是第一次不会执行,只有当值变化时才会执行。
watch: {
// count:function(oldValue, newValue) {
// console.log(oldValue, newValue)
// },
count: {
immediate: true,
handler: function (newValue, oldValue) {
console.log("watch count", newValue, oldValue);
},
},
},
实现watch功能还是使用useEffect,使用useEffect的第二个参数,指定依赖执行函数。
const [count, setCount] = useState(0);
useEffect(() => {
console.log("watch count", count);
}, [count]);
通过判断是否执行过一次来控制让其在第一次不执行:
const [count, setCount] = useState(0);
const mounted = useRef(false);
useEffect(() => {
if (!mounted) return;
console.log("watch count", count);
setOldCount(count);
}, [count]);
useEffect(() => {
mounted.current = true;
}, []);
通过手动存储来获取改变前的值:
const mounted = useRef(false);
const [oldCount, setOldCount] = useState(count);
useEffect(() => {
if (!mounted) return;
if (count === oldCount) return;
console.log("watch count", count, oldCount);
setOldCount(count);
}, [count, oldCount, setOldCount]);
//mounted在第一次渲染之后
useEffect(() => {
mounted.current = true;
}, []);
总结
本文先介绍了下Hook是什么,然后详细讲解了如果在Hook中如何实现vue中的data、computed、methods、生命周期、watch,这也是vue开发中最常用的几个选项,能覆盖大部分常规开发的场景,当然关于Hook的更多学习和理解还是需要去官网看详细文档。
- 如果觉得有用请帮忙点个赞。
- 我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿。