React Hooks
一、什么是 Hooks
1.Hooks 是 React 16.8 新增的特性,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性
2.React 一直都提倡使用函数组件,但是有时候需要使用 state 或者其他一些功能时,只能使用类组件,因为函数组件没有实例,没有生命周期函数,只有类组件才有
3.如果你在编写函数组件并意识到需要向其添加一些 state,以前的做法是必须将其它转化为 class。现在你可以直接在现有的函数组件中使用 Hooks
二、Hooks 解决的问题(类组件的缺点)
1.生命周期复杂,容易写出bug
2.this 指向问题:父组件给子组件传递函数时,必须绑定 this
3.逻辑组件相对难以复用
React 团队希望,组件不要变成复杂的容器,最好只是数据流的管道。开发者根据需要,组合管道即可。 组件的最佳写法应该是函数,而不是类。
三、Hooks 的优点
1、代码更加简洁
2、不容易产生bug
这个是类组件的生命周期
这个是hooks的渲染过程
四、Hooks 的原理
Hook 这个单词的意思是"钩子"。
React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。 React Hooks 就是那些钩子。
你需要什么功能,就使用什么钩子。React 默认提供了一些常用钩子,你也可以封装自己的钩子。
所有的钩子都是为函数引入外部功能,所以 React 约定,钩子一律使用use前缀命名,便于识别。你要使用 xxx 功能,钩子就命名为 usexxx。
1.函数组件需要解决的问题
(1)内部状态无法持久化
(2)没有生命周期
2.内部状态持久化
hooks采用闭包的方式存储内部状态。
hook的数据结构
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
type Hook = {
memoizedState: any,
baseState: any,
baseQueue: Update<any, any> | null,
queue: UpdateQueue<any, any> | null,
next: Hook | null,
};
type Update<S, A> = {
expirationTime: ExpirationTime,
suspenseConfig: null | SuspenseConfig,
action: A,
eagerReducer: ((S, A) => S) | null,
eagerState: S | null,
next: Update<S, A>,
priority?: ReactPriorityLevel
};
type UpdateQueue<S, A> = {
pending: Update<S, A> | null,
dispatch: (A => mixed) | null,
lastRenderedReducer: ((S, A) => S) | null,
lastRenderedState: S | null,
};
3.hooks渲染过程
React采用自上而下单向数据流的方式,管理自身的数据与状态。在单向数据流中,数据只能由父组件触发,向下传递到子组件。
在React中,state与props的改变,都会引发组件重新渲染。如果是父组件的变化,则父组件下所有子组件都会重新渲染。而在函数式组件中,是整个函数重新执行。
每次渲染时都会将hooks组件从头到尾执行一遍。
五、Hooks 的 API
1、useState
每次渲染,函数都会重新执行。我们知道,每当函数执行完毕,所有的内存都会被释放掉。useState就是帮助我们持久化组件内部状态的。
useState利用闭包,在函数内部创建一个当前函数组件的状态。并提供一个修改该状态的方法。
- 1
- 2
- 3
- 4
- 5
- 6
import { useState } from 'react';
// 利用数组解构的方式接收状态名及其设置方法
// 传入0作为状态 counter的初始值
const [counter, setCounter] = useState(0);
每当setCounter执行,就会改变counter的值。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
import React, { useState } from 'react';
export default function Counter() {
const [counter, setCounter] = useState(0);
return [
<div key="a">{counter}</div>,
<button key="b" onClick={() => setCounter(counter + 1)}>
点击+1
</button>
]
}
利用useState声明状态,每当点击时,setCounter执行,counter递增。
需要注意的是,setCounter接收的值可以是任意类型,无论是什么类型,每次赋值,counter得到的,都是新传入setCounter中的值。
举个例子,如果counter是一个引用类型。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
// counter默认值为 { a: 1, b: 2 }
const [counter, setCounter] = useState({ a: 1, b: 2 });
// 此时counter的值被改为了 { b: 4 }, 而不是 { a: 1, b: 4 }
setCounter({ b: 4 });
// 如果想要得到 { a: 1, b: 4 }的结果,就必须这样
setCounter({ ...counter, b: 4 });
useState接收一个值作为当前定义的state的初始值。并且初始操作只有组件首次渲染才会执行。
- 1
- 2
- 3
- 4
// 首次执行,counter初始值为10
// 再次执行,因为在后面因为某种操作改变了counter,则获取到的便不再是初始值,而是闭包中的缓存值
const [counter, setCounter] = useState(10);
setCounter(20);
如果初始值需要通过较为复杂的计算得出,则可以传入一个函数作为参数,函数返回值为初始值。该函数也只会在组件首次渲染时执行一次。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
const a = 10;
const b = 20
// 初始值为a、b计算之和
const [counter, setCounter] = useState(() => {
return a + b;
})
无论是在class中,还是hooks中,state的改变,都是异步的。
状态异步,也就意味着,当你想要在setCounter之后立即去使用它时,你无法拿到状态最新的值,而之后到下一个事件循环周期执行时,状态才是最新的值。
- 1
- 2
- 3
const [counter, setCounter] = useState(10);
setCounter(20);
console.log(counter); // 此时counter的值,并不是20,而是10
实践中有许多的错误使用,因为异步问题而出现bug。
例如我们想要用一个接口,去请求一堆数据,而这个接口接收多个参数。
当改变各种过滤条件,那么就势必会改变传入的参数,并在参数改变时,立即重新去请求一次数据。
利用hooks,会很自然的想到使用如下的方式。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
import React, { useState } from 'react';
interface ListItem {
name: string,
id: number,
thumb: string
}
// 一堆各种参数
interface Param {
current?: number,
pageSize?: number,
name?: string,
id?: number,
time?: Date
}
export default function AsyncDemo() {
const [listData, setListData] = useState<ListItem[]>([]);
// 定义一个状态缓存参数,确保每次改变后都能缓存完整的参数
const [param, setParam] = useState<Param>({});
function fetchListData() {
// @ts-ignore
listApi(param).then(res => {
setListData(res.data);
})
}
function searchByName(name: string) {
setParam({ ...param, name });
// 改变param之后立即执行请求数据的代码
// 这里的问题是,因为异步的原因,param并不会马上发生变化,
// 此时直接发送请求无法拿到最新的参数
fetchListData();
}
return [
<div>data list</div>,
<button onClick={() => searchByName('Jone')}>search by name</button>
]
}
正确的做法
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
export default function AsyncDemo() {
const [param] = useState<Param>({});
const [listData, setListData] = useState<ListItem[]>([]);
function fetchListData() {
// @ts-ignore
listApi(param).then(res => {
setListData(res.data);
})
}
function searchByName(name: string) {
param.name = name;
fetchListData();
}
return [
<div>data list</div>,
<button onClick={() => searchByName('Jone')}>search by name</button>
]
}
2、useEffect
在函数组件中,每当DOM完成一次渲染,都会有对应的副作用执行,useEffect用于提供自定义的执行内容,它的第一个参数(作为函数传入)就是自定义的执行内容。为了避免反复执行,传入第二个参数(由监听值组成的数组)作为比较(浅比较)变化的依赖,比较之后值都保持不变时,副作用逻辑就不再执行。
简单的例子,我们如果想使用类组件做一个能够刷新的列表页面,我们通常是这么做的
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
import React from 'react';
import './style.scss';
export default class effectDemo extends React.Component {
constructor(props){
super(props);
this.state={
list:[]
}
this.refresh=this.refresh.bind(this)
}
componentDidMount(){
this.refresh();
}
refresh(){
recordListApi().then(res => {
this.setState({
list:res.data
});
})
}
render(){
return(
<div className="container">
<button onClick={this.refresh}>点击刷新
</button>
<FlatList data={list} />
</div>
)
}
}
我们使用useEffect我们可以做到和生命周期一样的功能,但是使用useEffect我们必须抛开之前使用类组件生命周期的思维方式,我们可以利用state改变,函数组件重新渲染,然后副作用执行这一特点,定义一个loading的state,然后改变这个状态,随之相对应的副作用执行从而达到我们的目的。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
import React, { useState, useEffect } from 'react';
import './style.scss';
export default function effectDemo() {
const [list, setList] = useState(0);
const [loading, setLoading] = useState(true);
// DOM渲染完成之后副作用执行
useEffect(() => {
if (loading) {
recordListApi().then(res => {
setList(res.data);
setLoading(false)
})
}
}, [loading]);
return (
<div className="container">
<button onClick={() => setLoading(true)}>点击刷新</button>
<FlatList data={list} />
</div>
)
}
从上面的例子中可以看到我们使用useEffect代替了类组件,实现了我们的目的,并且更加简洁。
这种博客系统要怎么搭建的
回复 #54 @影视热点 :
买个阿里云服务器,自己搭一下就好了啊