前言
什么是React Hook
官方说法:Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性
为什么要使用Hook?
- 类组件组件逻辑难以复用,Hook 使你在无需修改组件结构的情况下复用状态逻辑
- 组件将会更简洁,更利于拆分。
- 更符合模块化开发的理念
React.FC (函数组件)
React.FC是函数式组件,是在TypeScript使用的一个泛型。FC是FunctionComponent的缩写,React.FC可以写成React.FunctionComponent
React.FC 类型格式要求必须具有一个返回值
useState
useState 作为我们最常用的一个Hook,具有一个极其重要的特性React 会在重复渲染时保留这个 state,因为这个特性,函数组件拥有了自身的状态。可以实现类组件的一些逻辑
useState唯一的参数就是初始值,这个初始值接受任何类型,只需要拥有返回值。
定义useState的类型
对于不使用Ts的同学,可以选择跳过这里。
我们通常使用如下方式去定义useState的类型。
正确类型:
错误类型:
但是通常情况下,TS会帮我们推断出useState的类型。
合并useState
相信很多和我一样刚开始使用React Hook的同学,会遇到一个变量去重复的声明一个useState。但是细想一下,其实很多的变量是可以合并起来,接受一个统一的类型。
未合并前:
这样书写React Hook,如果在一个变量极其多的页面中,我们会发现我们会使用大量的useState,即使我们在每个useState上层都很详细的写出这个变量的含义,但是依然会给人一种很不舒服的感觉。
例如:我之前写的业务代码:
所以我们需要对可以合并的useState 进行合并。
合并后:
是不是看着顺眼了很多。其实这么去写。如果我们要根据一个事件去改变用户的信息。就会发现一个问题
我们先创建一个按钮,给按钮绑定一个事件,并且通过这个事件,去将用户的姓名更改
可以思考一下。这样去改变是否可以更改成功?按照useState的官方说法,每次useState改变都会刷新视图,是否会刷新视图?
效果图:
触发更改事件。发现页面并没有变化。但是我们打印的userInfo却改变了。按照官方的说法,useState可以更改初始值,但是异步的形式,我们是不可能拿到更改后的值。为什么这里会出现这么一个情况?
深拷贝,浅拷贝?????
是的没错,就是因为深浅拷贝的原因,我们是通过浅拷贝,去给params赋予与useInfo的值,我们对params进行改变,其实一并也将userInfo给更改了。所以我们再去setUserInfo的视图没有刷新的根本原因就是:对于useState来说,其实userInfo并没有更新,所以不会刷新视图
解决方式:
我们可以通过ES6的扩展运算符来解决这个问题。将代码更改为:
效果图:
我们发现视图更新了,我们也没有拿到最新的值,一切又符合了useState的特性
useEffect
useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API。
React Hook的出现,才使函数组件具有了生命周期,可以实现类组件的一切功能
componentDidMount生命周期
组件挂载阶段, 我们需要将useEffect的第二个参数传递为一个空数组。这样useEffect只会在页面挂载组件的阶段执行一次。
效果图:
我们可以看到,在组件的挂载阶段可以访问到变量了,说明componentDidMount阶段,其实页面已经加载结束,这个时候我们通常会需要数据来渲染页面,所以我们通常会在第二个参数为空数组的useEffect中去执行获取数据的方法
componentDidUpdate生命周期
这个方式,通常用于我们需要监听一个变量的变化,根据这个变量的变化去做出不同的变化。第二个数组需要传递我们需要监听的变量,也就是依赖项,可以是多个,也可以是单个。
效果图:
我们发现,其实更新阶段执行了2次。一次是在挂载阶段之后,一次是在变量更新时。试想一下,如果我们是根据这个值的变化去向服务端请求数据,重新渲染页面。那么,我们刚进入页面的时候,是不是就会发起2次相同的请求。这个是一个坑点,所以涉及到依赖变化需要发送请求的useEffect请慎重使用。
componentWillUnmount 组件卸载
我们如果在页面中使用定时器,闭包,或者循环调用等类似的方法,我们需要在组件卸载的阶段去将这些清楚,避免内存泄漏的问题。
说到内存泄漏,俗称GA算法,是JS的一套垃圾回收机制,通过可达性分析去清楚无用的变量,释放内存。内存泄漏会导致浏览器的负荷增加,直到浏览器崩溃。最近在面试。。。。顺便复习下。嘿嘿嘿嘿
useRef
useRef(initialValue) 是一个内置的 React 钩子,它接受一个参数作为初始值并返回一个引用。引用是具有单个属性“current”的对象,可以访问和更改。
useRef 在我看来,主要是解决不同作用域共享一个状态的解决方案。
基础用法网上有很多,我在这里用我最近遇到的一个问题作为例子说明下。
下面是我写的一个有问题demo的代码:
Home.tsx文件,具体参数这里就不额外声明了,暂时用any替代
import React, { useEffect, useRef, useState } from 'react'
import { Table, Button } from 'antd'
import { Columns } from './Columns'
const data = [
{
id:1,
},
{
id:2
}
]
const Home:React.FC = () => {
// 动态表头
const [dynamicColumns, setDynamicColumns] = useState<any>([])
// 用户信息
const [userInfo, setUserInfo] = useState({
name:'张三'
})
// 更改用户信息
const changeUserInfo = () => {
const data = {...userInfo}
data.name = '李四'
setUserInfo(data)
}
// 删除事件
const delBtn = () => {
console.log(userInfo,'userInfo');
}
// 处理表头
const manageColumns = () => {
const columns = Columns.map((i) => {
if(i.dataIndex === 'option'){
i.render = () => {
return <span onClick={delBtn}>删除</span>
}
return i
}
return i
})
setDynamicColumns(columns)
}
useEffect(() => {
manageColumns()
},[])
return (
<div>
<h1>用户姓名:{userInfo.name}</h1>
<Button
onClick={changeUserInfo}
>更改用户信息</Button>
<Table
rowKey={(i) => i.id}
bordered
columns={dynamicColumns}
dataSource={data}
/>
</div>
)
}
export default Home
Columns.tsx文件
export const Columns = [
{
title: '序号',
dataIndex: 'index',
align: 'center',
key: 'index',
render: (data, row, index) => {
return index + 1
},
},
{
title: '操作',
dataIndex: 'option',
align: 'center',
key: 'option',
}
]
有兴趣的可以自行,运行下代码。
我们先点击按钮,更改userInfo的值,在点击表格中的删除按钮,打印userInfo的值。
效果图
我们可以看到视图已经发生了变化,说明userInfo的值其实已经被修改了。但是我们点击删除,却打印的是初始值,这里不是因为useState异步的原因造成的。有疑问的可以多点击几次看下效果。
我理解的是因为作用域的不同,其实暴露出表头的文件其实是并没有共享到userInfo的变化。所以一直读取的是userInfo的初始值。
如果将表头放入到Home.tsx文件中,共享同一个作用域。并不会出现这个情况。
解决方案:
通过useRef去记录最新的值。
效果图:
不要纠结为什么要把表头拿到另外一个文件,这只是一个场景。或者不是表头呢?如果有更好的理解,可以在评论区下发表一下自己的看法。我并不认为我的理解是最正确的。
还剩下React 优化的Hook, React 局部状态管理的Hook, React 自定义Api的Hook。等明天再说吧。
下雨了,各位大佬加班愉快。。。。。。。。