Hook 这个单词的意思是"钩子"。React Hooks 是加强版函数组件,提倡组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。React 官方默认提供了一些常用钩子,当然也可以按照需要封装自己的钩子进行使用。
1. useState():用于为函数组件引入状态(state),并进行状态数据的读写操作。
语法: const [xxx, setXxx] = React.useState(initValue)
返回值 : 包含2个元素, 第1个(xxx)为内部当前状态值;
第2个(setXxx)为更新状态值的函数,有2种写法::
setXxx(newValue): 参数非函数值, 直接指定新的状态值, 用其覆盖原来的状态值
setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值
import React, { useState } from "react";
export default function Button() {
const [buttonText, setButtonText] = useState("请点击");
function handleClick() {
return setButtonText("点过了");
}
return
2. useEffect():用于引入副作用操作(控制组件的生命周期)。常见操作:发送ajax异步请求数据、
设置订阅 / 启动定时器、手动更改真实DOM。
语法: useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 组件卸载前做收尾工作, 比如清除定时器/取消订阅等}
}, [stateValue])
useEffect()接受两个参数:第一个参数是一个函数,副作用操作代码放在里面, 如果函数体有return 则相当于 componentWillUnmount();
第二个参数是一个数组传递依赖项,第一次以及当依赖stateValue发生变化时,回调才执行(其他状态变化时不管), 相当于 componentDidUpdate();
第二个参数如果指定的是[ ], 仅第一次 render 后执行, 相当于 componentDidMount()。
第二个参数可以省略,表示全局监测,任何状态变化都会执行useEffect()的回调。
import React, { useState,useEffect} from 'react'
export default function Count(){
const [count, setCount] = useState(0)
useEffect(()=>{console.log(`useEffect=>点了${count}次`)},[])
return (
点了{count}次
)
}
useEffect 的执行时机 默认情况下, effect 在第一次渲染之后和每次更新之后都会执行,也可以是只有某些值发生变化之后执行,重点在于是 每轮渲染结束后延迟调用( 异步执行 ) ,这是 useEffect 的好处,保证执行 effect 的时候,DOM 都已经更新完毕,不会阻碍 DOM 渲染造成视觉阻塞。
此外,还有一个钩子useLayoutEffect()
对比如下:
注意:
除非要修改DOM并且不让用户看到修改DOM的过程,才考虑使用useLayoutEffect ,否则应当使用useEffect 。
如果只是为了获取DOM属性(或其它get操作),则没必要使用useLayoutEffect(它会在所有的 DOM 变更之后同步调用 effect)
3. useCallback():缓存回调函数
语法:useCallback ( fn, dep )
返回一个函数,只有在依赖项发生变化的时候才会更新(返回一个新的函数)。
要知道:函数式组件在每次渲染时,内部函数都会重新定义。场景:很多时候父组件渲染时子组件会跟着渲染即内部函数重新定义,这显然是没有必要的。useCallback可以设置依赖,使得子组件只在依赖发生变化时才进行渲染。
import React, { useState, useCallback } from 'react';
export default function App() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const handleClick1 = () => {
setCount1(count1 + 1)
};
const handleClick2 = useCallback(() => {
setCount2(count2 + 1);
}, [count2]);
return (
点了{count1}次
点了{count2}次
)
}
上述代码,handleClick2方法使用 useCallback 包装了一层,并且后面还传入了一个 [count2]
,这里 useCallback 就只会根据 count2
是否发生变化,从而决定是否返回一个新的函数。即使点击 Button1 也不重新渲染 Button2 的内容。
4. useMemo() :缓存函数返回值
语法: useMemo( fn, dep )
传递一个创建函数 fn 和依赖项 dep,依赖项发生改变的时候,才会重新调用此函数,返回一个新的值缓存起来,避免重复计算,相当于记忆化。
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(1);
const userInfo1 = {
age: count1,
name: 'Jerry'
}
const userInfo2 = useMemo(() => {
return {
name: "Tom",
age: count2
};
}, [count2]);
return (
userInfo:{ count1 }
userInfo:{ count2 }
)
小结:useMemo 的场景远比 useCallback 要广泛的很多,我们可以将 useMemo 的返回值定义为返回一个函数这样就可以变通的实现了 useCallback。在开发中当我们有部分变量改变时会影响到多个地方的更新那我们就可以返回一个对象或者数组,通过解构赋值的方式来实现同时对多个数据的缓存。
5.useContext():定义全局状态
由于props只能在父给子组件进行通信,如果需要在组件之间共享状态,可以使用useContext()。
语法:
1) 创建Context容器对象:
const XxxContext = React.createContext()
2) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
子组件
3) 后代组件读取数据:
{ value => ( // value就是context中的value数据要显示的内容 ) }
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
// 创建一个 Theme 的 Context
const ThemeContext = React.createContext(themes.light);
function App() {
// 整个应用使用 ThemeContext.Provider 作为根组件
return (
// 使用 themes.dark 作为当前 Context
);
}
// 在 Toolbar 组件中使用一个会使用 Theme 的 Button
function Toolbar(props) {
return (
);
}
// 在 Theme Button 中使用 useContext 来获取当前的主题
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
);
}
6. useRef():
(1) 绑定 DOM 节点,模拟类组件的ref,但 useRef()
比 ref
属性更有用。它可以很方便地保存任何可变值
(2) 保存数据,存储跨生命周期渲染的数据。在多组件渲染间共享数据,传入.current生成一个ref对象,让其在组件每个生命周期内都能访问到,useRef 保存的数据变化不会触发 UI 的渲染(区别于 useState ),如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。
import React, { useState, useMemo,useRef } from 'react';
export default function App() {
const inputEl = useRef(null);
const onButtonClick = () => {
// current 属性指向了真实的 input 这个 DOM 节点,从而可以调用 focus 方法
inputEl.current.focus();
};
return (
<>
>
);
}
const [time, setTime] = useState(0);
// 定义 timer 容器用于在跨组件渲染之间保存一个变量
const timer = useRef(null);
const handleStart = useCallback(() => {
// 使用 current 属性设置 ref 的值
timer.current = window.setInterval(() => { setTime((time) => time + 1); }, 100);
}, []);
7. useImperativeHandle()
可以让你在使用 ref
时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle
应当与 forwardRef 一起使用!
import React, { useRef, useImperativeHandle } from 'react';
import ReactDOM from 'react-dom';
const FancyInput = React.forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return
});
const App = props => {
const fancyInputRef = useRef();
return (
)
}
ReactDOM.render( , root);
在本例中,渲染
的父组件可以调用 inputRef.current.focus()
。
相对于官方的例子下面的意图更明显一些,通过 useImperativeHandle 将子组件的实例属性输出到父组件,而子组件内部通过 ref 更改 current 对象后,组件不会重新渲染,需要改变 useState 设置的状态才能更改。
import React, {
useState,
useRef,
useImperativeHandle,
useCallback
} from 'react';
import ReactDOM from 'react-dom';
const FancyInput = React.forwardRef((props, ref) => {
const [ fresh, setFresh ] = useState(false)
const attRef = useRef(0);
useImperativeHandle(ref, () => ({
attRef,
fresh
}), [ fresh ]);
const handleClick = useCallback(() => {
attRef.current++;
}, []);
return (
{attRef.current}
)
});
const App = props => {
const fancyInputRef = useRef();
return (
)
}
ReactDOM.render( , root);