最近打算使用React Hook +TypeScript 打造一个前端的组件库,所以在此之前先对React Hook进行了相关总结,方便复习和巩固,后续也会更新TS篇。
React 在16.8中推出了Hook的概念,开始使用function 组件来替代class 组件。
Hook是一个特殊的函数,它提供了相关的钩子函数,用来钩入react的特性。
Hook 是一种复用状态逻辑的方式,它不复用 state 本身。
它实质上解决了以下问题:
以下介绍hook中提供的几个常用的钩子函数,用来帮助函数组件实现管理内部的state。
- 1. useState useState函数接受一个参数,作为当前state的默认值。它的返回值为当前 state 以及更新 state 的函数(理解为class中定义的一个state和更新这个state时调用的setState,但是它不会把新的state和旧的state进行合并 ),state可以是任何简单类型,也可以是复杂类型对象。
首先创建class组件实现以下功能:在button组件上显示点赞次数,且每点击一次都会执行+1:
class LikeButton extends React.Component { constructor(props) { super(props); this.state = { like: 0 }; } render() { return <button onClick={() => this.setState({ like: this.state.like + 1 })}> {this.state.like} </button>; } }
使用函数组件编写如下。直观的区别首先代码量就减少了近一半…
// 栗子① const LikeButton: React.FC = () => { const [like, setLike] = useState(0); return <button onClick={() => setLike(like + 1)}>{like}</button>; }
可以多次调用useState,创建并管理多个state:
// 栗子② const LikeButton: React.FC = () => { const [like, setLike] = useState(0); const [on, setOn] = useState(true); return <> <button onClick={() => setLike(like + 1)}>{like}</button> <button onClick={() => setOn(!on)}> {on ? "ON" : "PFF"} </button> </>; }
也可以在一个useState中定义一个对象,来包含多个属性。按个人需求选择任意方式
// 栗子③ const LikeButton: React.FC = () => { const [obj, setObj] = useState({ like: 0, on: true }); return <> <button onClick={() => setObj({ like: obj.like + 1, on: obj.on })}> {obj.like} </button> <button onClick={() => setObj({ like: obj.like, on: !obj.on })}> {obj.on ? "ON" : "PFF"} </button> </>; }
- 2. useEffect 官网介绍,这个钩子是用来处理组件副作用的。(类似class组件中生命周期函数)
React将按照effect声明的顺序依次调用组件中的每一个effect。使用Hook可以把组件内相关的副作用组织在一起,而不再把它们拆分到不同的生命周期函数里。
(1)首先一个无需清除副作用的栗子,例如:网络请求,手动变更DOM,记录日志等;
class组件实现如下,可以看到在两个周期函数中是完全重复的代码,但这是我们的需求,我们需要在组件挂载完成和更新完成后都执行这个操作:
componentDidMount() { document.title = `You clicked ${this.state.like} times`; } componentDidUpdate() { document.title = `You clicked ${this.state.like} times`; }
使用useEffect实现如下,这个钩子的第一个参数是一个函数,用来执行相关操作,无论是挂载还是更新,在重新渲染后都会执行useEffect:
// 栗子④ const LikeButton: React.FC = () => { const [like, setLike] = useState(0); const [on, setOn] = useState(true); useEffect(() => { document.title = `You clicked ${like} times`; }); return <> <button onClick={() => setLike(like + 1)}>{like}</button> <button onClick={() => setOn(!on)}>{on ? "ON" : "PFF"}</button> </>; }
(2)添加监听事件,卸载组件时需要移除监听的栗子:
class组件实现如下,我们需要在挂载阶段添加监听,将要卸载组件时移除:
componentDidMount() { document.addEventListener("click", this.updateMouse) } componentWillUnmount() { document.removeEventListener("click", this.updateMouse) }
使用useEffect实现如下,只需要在一个useEffect中,第一个参数的函数里执行添加监听,并且return一个函数,这个函数用来执行卸载时的移除:
// 栗子⑤ const MouseTracker: React.FC = () => { const [positions, setPositions] = useState({ x: 0, y: 0 }); useEffect(() => { const updateMouse = (e: MouseEvent) => { setPositions({ x: e.clientX, y: e.clientY }) } document.addEventListener("click", updateMouse); return () => { document.removeEventListener("click", updateMouse); } }); return <p>X: {positions.x},Y: {positions.y}</p>; }
(3)注意:useEffect会在每次渲染后都执行 !这就会导致,其他state的更新也执行这个与当前state不相关的副作用。
这时useEffect的第二个参数就发挥作用了,它是一个数组:
当设置为空数组时,useEffect只会执行一次(可以用来执行数据请求等操作,控制只加载一次)
当设置为一个值时,只有这个值改变才会执行当前effect
也可以设置多个值,这样在每个值改变导致重渲染之后都会执行useEffect
// 栗子⑥ useEffect(() => { document.title = `You clicked ${count} times`; }, [count]); // 仅在 count 更改时更新
- 3. 自定义hook 在必要的时候我们可以把公共的业务逻辑写成一个自定义的Hook,例如调用api获取数据,当加载过程中需要loading状态,加载完成后loading结束。相关代码如下:
// 栗子⑦ import { useState, useEffect } from "react"; import axios from "axios"; const useURLLoader = (url: string, deps: any[] = []) => { const [data, setData] = useState<any>(null); const [loading, setLoading] = useState(false); useEffect(() => { setLoading(true); axios.get(url).then(result => { setData(result.data); setLoading(false); }) }, deps); return [data, loading]; } export { useURLLoader };
这个自定义的Hook: useURLLoader就可以在任何地方引入调用,传入url和保证钩子执行的依赖后,可以获取到包含加载结果和loading状态的操作。
- 4. useContext 组件之间共享数据(该数据对于组件树来说是全局的数据)
以下栗子,父组件中创建一个context,定义主题相关的对象key:value,引用react提供的createContext创建一个组件ThemeContext,这个组件提供属性Provider,将其他组件包裹在provider内,其他组件就可以拿到全局的样式设置。
// 栗子⑧ import React from "react"; interface IThemeProps { [key: string]: { color: string; background: string } } const themes: IThemeProps = { "light": { color: "#000", background: "#eee", }, "dark": { color: "#ff", background: "#222", } } export const ThemeContext = React.createContext(themes.light); const App: React.FC = () => { return <ThemeContext.Provider value={themes.light}> <button>hello</button> </ThemeContext.Provider>; } export { App };
在包裹的任意子组件中引用:
import React, { useContext } from "react"; import { ThemeContext } from "./App"; const Page: React.FC = () => { const theme = useContext(ThemeContext); const style = { background: theme.background, color: theme.color }; return <button style={style}>click</button>; }; export { Page };
使用hook要注意的两个重点: ● 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。 ● 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。
还有一些其他的钩子函数这里不逐个介绍,以后用到会继续总结,react钩子函数官网 可查看~