React Hooks 是 React v16.8 中新添加的特性,这个特性主要是对函数型组件的功能进行增强,让函数型组件可以做类组件做的事情。
这个篇文章主要包含三个部分:
- 什么是react hooks
- react hooks 主要包含哪些功能,要解决哪些问题。
- react hooks 的使用
- react hooks 到底是什么
- 这些函数要在什么场景下使用
- 【React Hooks】自定义 react hooks
- 通过自己定义函数,如何将组件内部的公共逻辑提取出来
对函数组件进行增强,让函数组件可以储存状态,可以拥有处理副作用的能力,让开发者在不使用类组件的情况下,实现相同的功能。
副作用:在组件中不是将数据转换为视图的代码被称为副作用。2.类组件的不足
比如:获取DOM元素、为DOM元素添加事件、设置定时器以及发送Ajax请求等,这都属于副作用。
- 缺少逻辑复用机制
- 为了复用逻辑增加无实际渲染效果的组件,增加了组件层级显示十分臃肿,增加了调试的难度以及运行效率的降低。
- 类组件经常会变得很复杂难以维护
- 将一组相干的业务逻辑拆分到多个生命周期函数中,例如:1.在组件挂载时要做些什么 2.更新完成之后要做些什么
- 在一个生命周期函数内存在多个不相干的业务逻辑
- 类成员的方法不能保证this指向的正确性,例如:在声明一些函数要使用bind进行绑定,或者嵌套函数来解决this指向的问题
react hooks 提供的钩子函数有:
- useState()
- useEffect()
- useReducer()
- useRef()
- useCallback()
- useContext()
- useMemo()
用于为函数组件引入状态,在现有的认知里面,在函数执行完毕之后该变量就会被释放掉,所以函数型组件原本是不可以保存组件数据的,useState 用来解决这个问题
import React, {useState} from 'react';
function App () {
// useState 返回一个数组,count: 状态 setCount: 设置状态的方法
const [count, setCount] = useState(0);
return
{count}
}
使用 setCount 方法设置 count 的值后,组件会被重新渲染,而渲染后count的状态还在。
- useState 使用细节
- 接收唯一的参数即状态初始值,初始值可以是任意数据类型。
- 返回为数组,数组中存储状态值和更新状态值的方法,方法名称约定以set开头,后面加上状态的名称。
- useState 方法可以被调用多次,用以保存不同的状态值。
- 参数可以是一个函数,函数返回什么初始状态值就是什么,函数只会被调用一次,用在初始值是动态值的情况下。
import React, {useState} from 'react';
function UseState (props) {
console.log('渲染即执行');
// 传入一个方法
const [count, setCount] = useState(() => {
console.log('这个位置只会被执行一次');
return props.count || 0;
});
// 可以接收任意数据类型为状态值
const [person, setPerson] = useState({name: 'howie', age: 20});
return
{count} {person.name} {person.age}
}export default UseState;
- 设置状态的细节
- 设置状态值的方法的参数,可以是一个值也可以是一个函数
- 设置状态值的方法本身是异步的
import React, {useState} from 'react';
function UseState (props) {
const [count, setCount] = useState(() => {
return props.count || 0;
});
function handleSetCount () {
// 传递回调函数设置状态,setCount 是异步的
setCount(count => {
return count + 1;
// 这样设置title就会变成同步的
// const newCount = count + 1;
// document.title = newCount;
// 1
// return newCount;
});
document.title = count;
// 0
}return
{count}
}export default UseState;
2.useReducer
useReducer是另一种让函数组件保存状态的方式。
import React, {useReducer} from 'react';
function UseReducer () {function reducer(state, action) {
switch (action.type) {
case 'increment':
return state + 1;
case 'decrement':
return state - 1;
default:
return state;
}
}const [count, dispatch] = useReducer(reducer, 0);
return useReducer
{count}
}export default UseReducer;
相较于 useState 的优势在于,如果子组件要修改父组件中的数据时,可以直接将父组件中的dispatch方法传递给子组件,
然后根据不同的类型,做不同的处理。
3.useContext
在跨组件层级获取数据时简化获取数据的代码。
- 跨层级获取数据
import React, {createContext} from 'react'; const countContext = createContext(); function UseContext () { return UseContext
{ value => { return {value} } } }export default UseContext;
- 简化代码
import React, {createContext, useContext} from 'react'; const countContext = createContext(); function UseContext () { return UseContext
让函数组件拥有处理副作用的能力,类似生命周期函数。
1. useEffect 执行时机
- useEffect(() => {}): componentDidMount、componentDidUpdate
- useEffect(() => {}, []): componentDidMount
- useEffect(() => () => {}): componentWillUnMount
import React, {useEffect, useState} from "react";
function UseEffect() {const [count, setCount] = useState(0);
// 组件挂载和组件更新时执行
// useEffect(() => {
//console.log('run')
// });
// 组件挂载时执行
// useEffect(() => {
//console.log('run')
// }, []);
// 清除上一个 effect
useEffect(() => {
console.log('1')
return () => {
console.log('run');
}
});
return
useEffect
{count}
}export default UseEffect;
2. useEffect 使用方式
- 为window对象添加滚动事件
- 设置定时器让count数值每隔一秒增加1
import React, {useEffect, useState} from "react";
import {root} from "../index";
function UseEffect() {function onScroll() {
console.log('页面滚动了');
}// 挂载之后绑定事件
useEffect(() => {
window.addEventListener('scroll', onScroll)
return () => {
console.log('run')
window.removeEventListener('scroll', onScroll);
}
}, []);
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(count => count + 1);
}, 1000)
return () => {
clearInterval(timer);
}
}, [])useEffect(() => () => {
console.log('卸载组件');
})return (useEffect
{count}
)
}export default UseEffect;
3. useEffect 解决的问题
- 按照用途将代码进行分类(将一组相干的业务逻辑归置到了同一个副作用函数中)
- 简化重复代码,是组件内部代码更加清晰(在类组件中通常componentDidMount和componentDidUpdate中的代码是一样的)
const [count, setCount] = useState(0);
const [num, setNum] = useState(0);
// 只有count改变的时候才会执行
useEffect(() => {
document.title = count;
}, [count]);
return (useEffect
{count} {num}
)
5.useEffect 结合异步函数 useEffect中的参数函数不能是异步函数,因为useEffect函数要返回清理资源的函数,如果是异步函数就变成了返回Promise
useEffect(() => {
// 编写一个立即执行函数
(async () => {
await axios.get()
})();
})
6.useMemo
useMemo 的行为类似 Vue 中的计算属性,可以监测某一个值的变化,根据变化值计算新值。
useMemo 会缓存计算结果,如果监测值没有发生变化,即使组件重新渲染也不会重新计算,此行为可以有助于避免在每个渲染上进行昂贵的计算。
import {useMemo} from 'react';
// 挂载完成后默认执行一次,如果监听的count发生了变化则再次执行
const result = useMemo(() => {
return result;
}, [count]);
7.使用memo方法提高组件性能
性能优化,如果本组件中的数据没有发生任何变化,就阻止组件进行更新。类似类组件中的 PureComponent 和 shouldComponentUpdate
import React, {memo, useState} from "react";
function Memo () {
const [count, setCount] = useState(0);
return
{count}
}// 每当 count + 1 Foo即使没有改变也会被重新渲染
// function Foo() {
//console.log('重新渲染');
//return Foo组件
// }// 这样 Memo 组件更新就不会连带 Foo 更新
const Foo = memo(() => {
console.log('重新渲染');
return Foo组件
})// 当Memo没有发生数据变化且父组件发生了变化,Memo组件不会被更新
export default memo(Memo);
8.useCallback
性能优化,缓存函数,使组件重新渲染时得到相同的函数实例。
Foo 子组件被重新渲染
import React, {useCallback, useState, memo} from 'react';
function UseCallback() {
const [count, setCount] = useState(0);
function resetCount () {
setCount(0);
}return
{count}
}const Foo = memo((props) => {
/**
* 当 count 发生改变时会更新父组件,父组件中的 resetCount 函数被重新定义
* 这时的 resetCount 与 Foo props中的 resetCount 实例已经发生了改变,所以 Foo 会被重新渲染
*/
console.log('重新渲染');
return Foo组件
})export default UseCallback;
// 使用 useCallback 优化 resetCount
const resetCount = useCallback(() => setCount(0), [setCount]);
9.useRef
1.获取DOM对象
import React, {useRef} from 'react';
function UseRef () {
const userName = useRef();
const handler = () => console.log(userName.current);
return
UseRef
}export default UseRef;
2.useRef保存数据 即使组件重新渲染,保存的数据仍然还在,保存的数据被更改不会触发组件渲染(跨组件周期)。
// 保存数据
const [count, setCount] = useState(0);
// 即使组件更新并不会重置,所以 stopCount 可以拿到 timer
let timer = useRef();
useEffect(() => {
timer.current = setInterval(() => {
setCount(count => count + 1);
}, 1000);
}, [])const stopCount = () => {
clearInterval(timer.current);
}return
useRef
{count}
10.自定义hook
- 自定义hook是标准的封装和共享逻辑的方式
- 自定义hook是一个函数,其名称以use开头
- 自定义hook其实就是逻辑和内置hook的组合
import React, {useEffect, useState} from 'react';
import axios from 'axios';
/**
* 当前组件需要再组件挂载完成之后,获取文章数据
* 假设获取文章数据的请求是共享逻辑,尝试把它写到自定义hook当中
*/// 创建自定义hook
function useGetEssay () {const [essay, setEssay] = useState({});
useEffect(() => {
axios.get('https://www.fastmock.site/mock/eefbb8ce7302645510629510865adb64/api/essay')
.then(res => setEssay(res.data));
}, []);
return [essay, setEssay];
}function CustomizationHook () {const [essay, setEssay] = useGetEssay(null);
return
CustomizationHook{essay.title}
{essay.content}}export default CustomizationHook;
- 提取表单中的常用逻辑
function useUpdateInput(initialValue) { const [value, setValue] = useState(initialValue); return { value, onChange: e => setValue(value => value = https://www.it610.com/article/e.target.value) } }function CustomizationHook() { const userNameInput = useUpdateInput(''); const passwordInput = useUpdateInput(''); const submitForm = e => { e.preventDefault(); console.log(userNameInput.value, passwordInput.value) }return }